Repository navigation
Conversation
…ompt With no exact prefix in RAM, the near-prefix lane ran first and asked the SSD tier for its best candidate. For a session the bank had spilled, that was the session's own entry, a whole prefix of the prompt: the lane hydrated it, refused it (it serves only matched < prefix_len; whole prefixes belong to the exact lane), and served another conversation's checkpoint at the shared system prompt. session_bank.restore(), which reads the spilled entry back (ssd_clone), never ran. Measured 2026-10-09 (Qwen3.8-27B, 7 GB bank, two conversations sharing a 4,019-token system prompt): the turn after the spill restored 4,019 of 58,529 tokens and re-read the rest in 143 s, while 57,989 tokens sat on disk; the hydrated 5.4 GB copy was held through that prefill. After a restart, with RAM empty, the same turn restored from SSD. The cold tier now reports the longest whole prefix it holds from the manifest alone (no tensor read), and the near lane must beat it when RAM has no exact prefix. Below that floor the cold lookup skips hydration through the existing min_useful_matched_tokens gate, and the exact lane serves the turn. If the exact lane declines, the second near-lane call keeps its floor of 0, as before.
The near-prefix lane iterated over the bank's candidate list and ran the served candidate's suffix prefill inside that loop, so the list kept every refused and untried candidate alive through the prefill. A cold-tier candidate is decoded before the lane can refuse it: on 2026-10-09 a spilled conversation's whole prefix (5.4 GB) was decoded, refused as out of range, and held while a neighbour's checkpoint at the shared system prompt was served and 54K tokens were re-read; the prefill guard then stopped that prefill under its floor. The lane now takes each candidate off its list as it tries it and drops the untried rest before the served candidate's suffix prefill. Once unreferenced, the decoded buffers go back to the allocator pool, which the prefill guard already counts as reclaimable.
A near-prefix candidate decoded from the SSD tier is private to the request: the bank does not keep it. Its restore clones the decoded state into the new cache up to the restore point, yet the lane kept the candidate itself until the suffix prefill returned, so a long suffix was read with the restored prefix in memory twice (or more, when the decode covered the whole entry). The lane now drops the candidate once it is served, just before the suffix prefill. A RAM entry stays with the bank and still gets its hit and access time after the prefill.
The Gemma 4 near-prefix lane iterated over the bank's candidate list and ran the served candidate's tail forward inside that loop, so the list kept every refused and untried candidate alive through it. When nothing was served, the last candidate tried stayed bound through the cold prefill. A candidate from the SSD tier is decoded before the lane can refuse it, so either way a whole decoded entry could sit next to the prefill. The lane now takes candidates off its list as it tries them, drops the untried rest and a served SSD candidate (already cloned into the new cache) before the tail forward, and lets go of the last candidate before the cold prefill. Same change as the Qwen lane.
A hybrid entry restores its prefix only at its newest recurrent boundary at or below the match. The near-prefix lane refuses a candidate whose boundary does not beat its floor (its exact prefix in RAM, or the spilled SSD entry the exact lane serves), but the cold tier only received the floor folded into min_useful_matched_tokens, which compares the match. A row matching above the floor with its boundary below it was decoded and then refused. The boundary is in the row's payload.json, which _restore_row already reads before any tensor. lookup_prefix_boundary takes min_restore_point, and a row whose boundary lies below it is refused undecoded; the next ranked row is tried, as for a row without a boundary. SessionBank passes the near lane's floor when the tier advertises SUPPORTS_MIN_RESTORE_POINT. A boundary at the floor itself still decodes: the batched lane serves it. The app explains the new miss reason with the sentence it shows when the SSD copy is not better than the one in memory.
…#608), eviction restore erratic (VONDSTEN 126, youssofal#609), segmented KV round 2 on the production candidate, sustained-profile fix and Mistral (VONDSTEN 122) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tion, segments under any profile); segmented KV round 3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
A data point for #608 and #609 together, on Flash-Next. Short version: no errors, the same cache hits as 2.12.2, and fully repeatable outputs. Build
Setup
The restore counts are taken from Repeatability (byte-identical reasoning, content and tool calls)
So at T=0 the build changes outputs, but it's deterministic for a given request history. About speed: decode followed run order more than build (the two stock runs were first and last), so I read it as no regression rather than a gain. I didn't isolate #608 from #609, and with one-copy off this doesn't exercise #619's path. I can run a narrower A/B if that's useful. |
|
Thanks for running this. On #607 not installing on Flash-Next: that's by design. |
Why
When a conversation has been evicted from memory to the SSD cache and comes back, MTPLX often does not restore it. It reuses only the system prompt it shares with another conversation still in memory and computes the rest again: about 145 s for a 50K conversation instead of a few seconds. Sometimes the request fails with a 507 instead, because a conversation read from SSD and then refused stays in memory during that long prefill.
How it works, in plain terms
Before the normal restore, a near-prefix lane looks for the best partial match. It asked the SSD tier, read and decoded the conversation's own full state, then refused it, because a full prefix belongs to the exact restore. The next candidate, a neighbour's system prompt in RAM, was accepted and the lane stopped, so the exact restore never ran. This PR changes three things:
payload.json, also before reading tensors.What changes
Five commits:
ssd_prefix_boundary_below_floor, with a line inCacheExplanation.swift).Results
Qwen3.8-27B, a 50K and an 80K conversation, eviction to SSD forced by the bank cap, two runs. The turn after eviction restored the 50K conversation from SSD in about 4 s in both runs, where it took 143 to 146 s (or failed) before. Each SSD restore now decodes the state once instead of twice. The release commits are checked by unit tests: refused, untried and served SSD candidates are gone during the prefill.
Tests
tests/test_near_lane_spilled_exact_prefix.py,tests/test_near_lane_releases_refused.py(including the served SSD candidate, checked with weak references),tests/test_gemma4_near_lane_releases.pyandtests/test_cold_tier_restore_point_floor.py; each fails without its commit. Full suite passes.🤖 Generated with Claude Code