Repository navigation
Build exact-prefix SSD restores with the caller's cache factory - #668
Open
CocoaBeans wants to merge 2 commits into
Open
CocoaBeans wants to merge 2 commits into
CocoaBeans wants to merge 2 commits into
Conversation
SessionBank.restore() falls back to _restore_cold when RAM misses. For an exact-prefix SSD hit (session_restore_mode "ssd_clone"), _restore_cold built its cache with runtime.make_cache() and dropped the cache_factory its caller passed. generation passes the contiguous target-prefill layout and repages after the suffix prefill; make_cache() installs the tail-owned decode layout (vLLM-Metal paged attention under turbo). So every ssd_clone turn ran its suffix prefill and its whole decode paged, with compiled verify falling back on every call (context_above_threshold). The RAM lanes and the near-prefix lane already build through the factory, which is why ssd_block_prefix_boundary_clone restores were never slow. Forward cache_factory and mtp_cache_factory into _restore_cold and build through them, mirroring the RAM clone lane (restore_meta_state=cache_factory is None). Without a factory, behavior is unchanged. generation passes only cache_factory today; forwarding mtp_cache_factory keeps the cold lane consistent with the RAM lane. Measured 2026-10-24 on an M5 Pro with 64 GB, Qwen3.8-27B Optimized Speed, the app's launch flags (turbo, depth 3, smart fans, nominal thermal), one synthetic multi-turn conversation per build on port 1240 with a private SSD bank, the server restarted before each SSD turn: - ssd_clone at 62k context, 6,047 new tokens: prefill 50.1 -> 303.2 tok/s, decode 15.0 -> 23.7 tok/s, TTFT 124.7 -> 23.4 s, paged blocks 3,953 -> 0, compiled verify 0/276 -> 231/278. - ssd_clone at 76k context: prefill 41.6 -> 277.6 tok/s, decode 14.3 -> 22.3 tok/s, TTFT 150.3 -> 24.8 s. - Cold turns (387.7 vs 393.3 tok/s) and RAM restores (282.7 and 266.3 vs 287.8 and 265.8 tok/s prefill) are unchanged, and the SSD read took 0.85 to 1.82 s either way. MTP acceptance by depth on the fixed ssd_clone turns stays in the band of the RAM restores. On the same Mac before the fix, all 8 ssd_clone restores in about 50 hours of agent use ran paged, at 17 to 46 tok/s prefill and 0.47 to 0.67x the decode of same-context peers, about 25 minutes in all. Test: test_session_bank_ssd_exact_restore_builds_caches_with_caller_factories fails on v2.12.2 and passes here. The 43 session-bank and cold-tier test files: 1,198 passed, 2 skipped, 1 xfailed. Rollback: revert this commit.
benchmarks/repro_ssd_clone_restore_layout.py reproduces the ssd_clone layout bug fixed in the previous commit on demand instead of waiting for a session to fall out of the RAM bank. It runs two arms with the MTPLX app's serve flags: the MTPLX installed in the environment, and the same environment with PYTHONPATH at this checkout. Each arm gets a private port and an empty private SSD session-cache folder, so ~/.mtplx/session-bank is never touched, and drives one synthetic multi-turn conversation built from MTPLX's own source files: a 56k cold turn saved to SSD, a restart, an ssd_clone turn (A2), a RAM-restore control (A3), another restart, ssd_clone at 76k (A4) and a second control (A5). With thinking off, each turn's rendered prompt is an exact prefix of the next, so the near-prefix lane declines the saved entry and the exact-prefix SSD lane serves it. Receipts come from request-log-<port>.jsonl. The JSON output records hardware, OS, model and revision, quantization, sampler, profile, fan mode, reasoning effort, token counts, date, both arms' mtplx and the checkout commit, with the home folder redacted. --dry-run sizes the prompts and checks which mtplx each arm imports; the run refuses while the MTPLX app's port is busy, or when both arms import the same session_bank. benchmarks/results/ssd-clone-restore-layout-m5pro-64gb.json is the 2026-10-24 run behind the previous commit's numbers: Apple M5 Pro, 64 GB, macOS 26.4.1, Youssofal/Qwen3.8-27B-MTPLX-Optimized-Speed at 1d5087d (4-bit dynamic, group 32), temperature 1.0, top-p 0.95, top-k 20, thinking off, profile turbo, fan mode smart (thermal pressure nominal on every turn), reasoning effort medium, turns of 56,012 and about 6,035 tokens, installed 2.12.2 against v2.12.2 plus the fix. The harness defaults to fan mode default, per CONTRIBUTING. Rollback: revert this commit.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
An exact-prefix SSD session restore (
session_restore_mode: "ssd_clone") ran its suffix prefill and its whole decode on the paged decode layout. Prefill was about 6× slower and decode about 1.55× slower than a RAM restore at the same context.SessionBank.restore()reaches the SSD tier throughcold_fallback()→_restore_cold()._restore_coldbuilt its cache withruntime.make_cache()and dropped thecache_factorythe caller passed. generation passes the contiguous target-prefill layout (_session_restore_cache_factory→_make_target_prefill_cache) and repages after the suffix prefill.make_cache()installs the tail-owned decode layout instead, which is vLLM-Metal paged attention under turbo, and compiled verify then falls back on every call withcontext_above_threshold.cache_factoryandmtp_cache_factoryinto_restore_coldand build through them, mirroring the RAM clone lane, includingrestore_meta_state=cache_factory is None. With no factory, behavior is unchanged.restore_entry_prefix_cache, reported asssd_block_prefix_boundary_clone) already build through the factory. generation passes onlycache_factorytoday. Forwardingmtp_cache_factorykeeps the cold lane consistent with the RAM lane.benchmarks/repro_ssd_clone_restore_layout.pytriggers the bug on demand and compares the installed MTPLX against this checkout.benchmarks/results/ssd-clone-restore-layout-m5pro-64gb.jsonholds the run below.session_bank.py.Verification
tests/test_cache_bank.py::test_session_bank_ssd_exact_restore_builds_caches_with_caller_factoriesfails on v2.12.2 withassert [] is ['prefill-layout-trunk']and passes here.cache_factoryorssd_clone: 1,198 passed, 2 skipped, 1 xfailed, 0 failed.test_no_mlx_imports,test_public_cli,test_runtime_kpis,test_server_openai,test_server_responses,test_endpoint_probe_hygiene,test_openai_bridge,test_thermal,test_cache_stateandtest_aria2_downloader, plustest_cache_bank: 1,085 passed, 4 skipped, 0 failed.python -m compileall -q mtplx tests,python -m build,twine check dist/*(both artifacts passed),scripts/fresh_venv_smoke.shandscripts/hygiene_scan.shall pass.ssd_cloneturns, 0.80/0.60/0.41 and 0.78/0.59/0.43, stays near the RAM controls, which ranged 0.82–0.87, 0.57–0.71 and 0.37–0.50. The model still copies from the restored context, so there is no sign of a corrupted restore. This is indirect evidence. A temperature-0 token-equality check against a RAM restore would be the strict version.Benchmark Evidence
Youssofal/Qwen3.8-27B-MTPLX-Optimized-Speedat1d5087dexpected_value,ar_batch, batching presetagent, context 131,0729882703f); fixed: v2.12.2 with this PR'ssession_bank.pychangeSame prompts and serve flags on both arms. Each arm used a private, initially empty SSD bank. The server restarted before A2 and A4, so those turns had to restore from SSD.
The SSD read itself took 0.85–1.82 s on both arms.
Without fan control. Production logs on the same Mac show the bug under default fans. In about 50 hours of Hermes Agent use before the fix, every one of the 8
ssd_clonerestores ran paged, with 5,071–7,510 blocks. Of the 587 other restores at 60k+ tokens in that window, 583 ran dense. The other four were 171k–177k-token prompts above the 131,072-token dense-decode ceiling, where paged is expected. Five of the eight ran with fan mode default:To reproduce on your own machine, stop any running MTPLX server first, then run the repro with the Python environment that has the MTPLX release installed:
The repro defaults to fan mode
default. It takes about 25 minutes for both arms on an M5 Pro.