Skip to content

server: extend live-prefix rewind to Flash on Metal, bounded by raw-cache budget - #710

Open
Flor1an-B wants to merge 1 commit into
antirez:mainfrom
Flor1an-B:extend-flash-live-prefix-rewind
Open

server: extend live-prefix rewind to Flash on Metal, bounded by raw-cache budget#710
Flor1an-B wants to merge 1 commit into
antirez:mainfrom
Flor1an-B:extend-flash-live-prefix-rewind

Conversation

@Flor1an-B

Copy link
Copy Markdown

Fixes #698.

Background

#668 added live-KV rewind for repeated exact-prefix prompts (retry/regenerate workflows), but gated it to GLM only via ds4_engine_is_glm_dsa(). #698 reports that DeepSeek V4 Flash on Metal still does a full cold prefill in the same scenario, even when the incoming prompt is an exact token-prefix of the live session (e.g. a 175K-token prompt re-prefilled from scratch after a client-side stream abort).

Why the gate exists (this isn't a small oversight)

Read metal_graph_encode_token_raw_swa() rather than guessing: Metal's raw SWA cache is a genuine ring buffer, raw_row = pos % raw_cap. ds4_session_rewind() only truncates checkpoint.len — it does not shift or revalidate that ring. GLM's dense KV cache has no such ring (ds4_session_glm_cap_dense_cache() keeps it consistent unconditionally on rewind), but naively enabling rewind for Flash risks reusing a raw row that the discarded continuation has already overwritten — silently conditioning the resumed generation on stale/wrong tokens. That's a correctness bug, not just a missed cache hit, so I didn't want to just flip the gate.

Fix

New ds4_session_raw_rewind_budget():

  • Returns 0 for GLM (unaffected, still gated the existing way).
  • For Flash, returns raw_cap - raw_window — the margin the graph already reserves specifically so chunked prefill doesn't evict window-relevant rows mid-chunk. That existing margin turns out to be exactly the safe reuse budget for this too.

The rewind is only allowed when the discarded tail (old_pos - prompt_len) stays under that budget, which guarantees no raw row the rewound tail depends on has been overwritten.

Empirical validation, not just the theoretical argument

M5 Max, Metal, production Flash q2-q4 quant:

  1. Positive control: real ctx=8192 session, ~1000-token prompt, resend the exact same prompt after a short generation → log shows rewound Flash live prefix from N to M, continuation is byte-identical to the first generation.
  2. Negative control: forced raw_cap=300 via DS4_METAL_GRAPH_RAW_CAP (budget becomes an easy-to-exceed 44 tokens), with the budget check temporarily removed to confirm the failure mode is real and not just theoretical — generated 400 tokens (well past budget), resent the same prompt: continuation corrupted into a degenerate 140,140,140,... repetition loop instead of the correct count, while the unmodified first generation was correct. Same forced raw_cap, same over-budget scenario, with the guard in place: correctly falls through to a full re-prefill (reason=token-mismatch in the log) instead of rewinding, and stays correct.
  3. A short within-budget retry under that same forced raw_cap=300 still rewinds and matches, confirming the guard isn't just conservative to the point of never firing.

Happy to re-run any of this against a different quant/ctx if useful — the scratch negative-control build was throwaway and not included here, only the guarded version in this diff.

🤖 Generated with Claude Code

…ache budget

antirez#668 added live-KV rewind for repeated exact-prefix prompts, but gated it to
GLM only (ds4_engine_is_glm_dsa()) -- DeepSeek V4 Flash on Metal still does a
full cold prefill on a retry/regenerate, even when the incoming prompt is an
exact token-prefix of the live session, per antirez#698.

Root cause of the GLM-only gate (confirmed by reading the encode path, not
guessed): Metal's raw SWA cache is a ring buffer, `raw_row = pos % raw_cap`
(metal_graph_encode_token_raw_swa()). ds4_session_rewind() only truncates
checkpoint.len; it does not shift or revalidate that ring. GLM's dense KV
cache has no such ring -- ds4_session_glm_cap_dense_cache() keeps it
consistent unconditionally -- but naively enabling rewind for Flash risks
reusing a raw row that the discarded continuation has since overwritten,
silently conditioning the resumed generation on the wrong tokens.

Fix: new ds4_session_raw_rewind_budget() returns 0 for GLM (unaffected,
gated separately as before) and, for Flash, (raw_cap - raw_window) -- the
margin the graph already reserves so chunked prefill doesn't evict
window-relevant rows mid-chunk. The rewind is only allowed when the
discarded tail (old_pos - prompt_len) stays under that budget, which
guarantees no raw row the rewound tail depends on has wrapped.

## Empirical validation (not just the theoretical argument above)

M5 Max, Metal, production Flash q2-q4 quant:

1. Positive control: real ctx=8192 session, ~1000-token prompt, resend the
   exact same prompt after a short generation -> log shows
   "rewound Flash live prefix from N to M", continuation is byte-identical
   to the first generation.
2. Negative control: forced raw_cap=300 via DS4_METAL_GRAPH_RAW_CAP (so the
   budget is a easy-to-exceed 44 tokens), *with the budget check removed* to
   confirm the failure this guards against is real, not hypothetical --
   generated 400 tokens (>> budget), resent the same prompt: continuation
   corrupted into a degenerate "140,140,140,..." repetition loop instead of
   the correct count, while the unmodified first generation was correct.
3. Same forced raw_cap=300, *with* the guard: the identical over-budget
   scenario correctly falls through to a full re-prefill
   (`reason=token-mismatch`) instead of rewinding, and the output stays
   correct -- confirming the guard degrades to today's existing (slower but
   correct) behavior exactly when it should, and a short within-budget
   retry under the same forced raw_cap still rewinds and matches.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extend live-prefix rewind (#668) beyond GLM: DeepSeek V4 Flash on Metal still cold-prefills exact-prefix retries

1 participant