feat(server): add scoped disk prefix cache policy#364
Conversation
The scoped disk prefix cache (auto/fixed policy) prefills exactly to the selected boundary and requests a snapshot at snap_pos == prompt end. The qwen35 chunked prefill only snapshots when snap_pos falls strictly inside a chunk, so end-of-prefill snapshots never fired: scoped saves silently no-opped and every auto-mode request paid the scoped prefill plus a full re-prefill (~2x prefill cost, zero cache entries). Take the snapshot after the chunk loop when snap_pos == committed. This does not touch the prefill computation (no chunk reshaping, cur_pos is already at committed), so cache-hit bit-exactness is preserved. Verified on RTX 3090 (sm_86), Qwen3.6-27B Q4_K_M, auto:8 policy: - scoped save fires at the chat boundary ([snap] end-of-prefill) - double prefill gone: save request 8.35s vs 13.4s before - cold-start disk hits after server restart: 0.36-0.50s vs 9.2s miss - with DFlash draft: greedy output bit-identical across miss/save/hit/ restart, accept rate unchanged - 1905 server unit assertions pass Also fixes qwen35moe in the fully GPU-resident case, which delegates to this prefill path. Co-Authored-By: WOZCODE <contact@withwoz.com>
|
Validated end-to-end on lucebox2 (RTX 3090 sm_86, branch merged with current main), and pushed one fix commit (70cd8bf) to this branch. Validation resultsqwen35moe hybrid (Qwen3.6-35B-A3B, forced hot/cold split),
qwen35 dense (Qwen3.6-27B) — was broken, fixed by 70cd8bf: the chunked prefill only snapshotted when laguna / gemma4 single-device — scoped snapshot fires and the staged restore works in-run (laguna 1.68s vs 3.2s, gemma4 0.26s vs 2.45s), but Other checks: 1905 unit assertions pass (incl. the 4 new policy tests); LGTM with 70cd8bf included. |
…rg#364 scoped cache - Port 354e7b6 message-count freeze (aged[1..n-hot) compressed once, cached) - Remove mutual-exclusion: FlowKV active → disk clamps to system_end (verbatim system anchor, stable cross-session key); Luce-Org#364 unchanged when compress=false - WS1: non-continuation turns skip compression (cold-poison fix preserved) - Inert-guard: aged band < 512 tokens → FlowKV-OFF - Config: DiskPrefixCachePolicy::compress + --disk-prefix-cache-compress CLI - Tests T1-T7: 1908 assertions, 0 failures
… vs Luce-Org#364 FlowKV ran whenever disk_cache_policy.compress was set, with no size gate, so every multi-turn agentic turn paid the full pFlash drafter-forward (~400s/session at 59K) and re-expanded the prompt — making COMPOSE ~1.9x slower than the plain Luce-Org#364 scoped disk cache it should improve on. - Gate FlowKV on the original prompt size (same threshold as the pFlash gate), and skip it once pFlash has already compressed. - Below threshold COMPOSE is byte-identical to Luce-Org#364 (full prefix-cache hits, no drafter tax); compression fires only when the conversation can't fit the KV. - Keep the scoped-disk-re-prefill skip under compression (avoids turn-2 hang). Validated on abc_cache_harness COMPOSE arm (auto, threshold=65000): goldgate_fix total wall 846s -> 480s (~Luce-Org#364's 443s), zero compression on sub-threshold turns. Activate via --prefill-compression auto --prefill-threshold ~max_ctx.
…g#364 scoped save 47081e67 demoted FlowKV to a downstream else-if after whole-prompt pFlash, gated on the same threshold — making FlowKV structurally unreachable (any threshold that let it run made pFlash fire first; PFLASH_FREEZE_HISTORY went dead). Replace with the unified gate (compute should_compress once; route continuations to FlowKV-freeze with should_compress=false; whole-prompt pFlash only for cold non-continuations), mirroring the working flowkv-standalone structure. Re-enable Luce-Org#364's scoped disk save under compression (drop the band-aid guard; the disk-clamp already pins the save to the stable system_end prefix). Paired A/B, same binary (cb458145), full 7-turn goldgate_fix, single-session: COMPOSE_FLOWKV 615.9s vs pure-Luce-Org#364 713.7s (1.16x), decode 13.6 vs 6.7 tps, tool-valid 85.7% vs 71.4%. FlowKV engages on continuations; ee7 keeps the drafter forward cheap. Turn-4 transition cost (park/unpark + uncached compressed-prefill) is the remaining lever, not the gate.
Summary
This PR adds a configurable prefix-selection policy for disk prefix cache to improve hit rate on real agent-style workloads.
The existing disk prefix restore behavior, introduced by PR #227 and extended for target split / mixed-backend restore by PR #325 and PR #352, was oriented around saving/restoring the full prompt. That works for repeated identical requests, but agent workloads usually have a large stable context followed by a dynamic tail: tool results, recent turns, and task state can change between requests. When the full prompt is used as the cache scope, small tail changes can prevent reuse of an otherwise valuable cached prefix.
This PR adds a controllable prefix scope for disk cache. By caching a slightly shorter but more stable prefix, the server can trade a small number of cached tokens for a higher cross-request hit rate. The scope can be a fixed token count provided by the user, or
autocan infer a stable boundary from recent similar requests.CLI:
Requests can also override the policy through
prefix_cache.scope:{ "prefix_cache": { "scope": "auto:30" } }Semantics:
off: disables disk prefix cache restore/save.full: keeps the existing full-prompt disk cache behavior, including cold-prefix and continued checkpoints.auto: defaults toauto:30; searches the most similar token prefix in the most recent 30 requests, then aligns down to a safe chat boundary.auto:N: sets the lookback window to N requests. N is the candidate window size; it does not require all N requests to share the same prefix.N: caches/restores the first N prompt tokens, for example1000.The server only compares token prefixes. It does not make semantic decisions about system prompts, tools, AGENTS.md, RAG, or dynamic tails. Disk keys remain token-prefix hashes, so semantically similar but token-different prompts do not collide.
Window / Hit-rate Tradeoff
auto:Nuses N as the recent-request lookback window. Smaller windows usually cache more recent-context tokens, but the key is more likely to drift with the dynamic tail; larger windows usually cache fewer tokens but produce a more stable hit rate.Tokenizer-level simulation results:
auto:2auto:8auto:301432tokens,28/30hits (93%)1432tokens,28/30hits (93%)1432tokens,28/30hits (93%)1988tokens,1/30hits (3%)1783tokens,7/30hits (23%)1452tokens,28/30hits (93%)1776tokens,8/20hits (40%)1389tokens,10/20hits (50%)1049tokens,18/20hits (90%)2204tokens,7/20hits (35%)1489tokens,10/20hits (50%)869tokens,18/20hits (90%)The default
auto:30is therefore intentionally conservative: it caches fewer high-variance tail tokens to get a higher disk prefix hit rate. Users that want a longer recent-context cache can still useauto:2,auto:8, or a fixed token count.Changes
DiskPrefixCachePolicywithoff,full,auto[:window], and fixed-token modes.--disk-prefix-cache off|full|auto|auto:N|N.prefix_cache.scope;prefix_cache.windowcan override the auto window.autouse the best-match longest common token prefix from the recent request window, aligned down to a safe chat boundary.fixed/autoprefill exactly to the selected token boundary before saving the KV snapshot, keeping the disk key and snapshot position aligned.fullon the existing full-prompt, cold-prefix, and continued-checkpoint behavior.--kv-cache-min-tokensas the global minimum save threshold.--kv-cache-cold-maxtofullmode cold-prefix selection; it does not capautoor fixed-number boundaries.auto/fixedwhen PFlash rewrites the effective prompt; full-prompt restore keeps the existing behavior./props.full_cache.disk_policy.[disk-cache] auto scope: ... selected=...diagnostics for boundary selection.End-to-end Restore Checks
End-to-end restore closure was validated on both a 27B dense backend and an OpenClaw-style MoE request. The dense backend check showed that a scoped prefix selected by
auto:30can be persisted, found again after server restart as a cold-start disk hit, and reduce prefill from a77.6scold run to1.6safter restore. The MoE check showed that auto boundary selection maps to a stable scoped prefix on a real agent prompt shape. qwen35moe hybrid restore itself is covered by companion bugfix PR #362: #362. This PR only covers disk prefix policy and boundary selection; the hit-rate behavior is covered by the window tradeoff simulation above.