Repository navigation
fix(server): restore prompt tokens from v2 blob in DECODE_APPLY - #69
Conversation
DECODE_APPLY calls prompt_clear(false) before loading KV state, which wipes slot->prompt.tokens. The subsequent COMPLETION task's n_common decision then finds 0 stored tokens, computes n_past=0, and seq_rm(slot, 0, -1) destroys the KV that llama_state_seq_set_data just loaded — causing the decode slot to stall at n_past=0 for the full timeout window. Parse the v2 blob header (if present) to restore the token list, skip past the header before calling llama_state_seq_set_data, and set just_restored so the CHECKPOINT_SEARCH path uses the cached n_past. Closes ggml-org#506
ddvnguyen
left a comment
There was a problem hiding this comment.
Reviewed the diff and root-cause explanation — this checks out. DECODE_APPLY's prompt_clear(false) wiping slot->prompt.tokens before the v2-blob-aware KV restore, leaving n_common's decision logic with nothing to match against, is a precise explanation for the exact symptom in ggml-org#506 (restore RPC succeeds, n_past stuck at 0 for the full timeout, no crash). The fix correctly detects the v2 blob (same 0x02 magic byte check already used in the STATE_PUT handler at server-context.cpp:3276), restores the token list, skips the optional checkpoint sub-blob before advancing past the header, and sets just_restored so the downstream CHECKPOINT_SEARCH path uses the cached n_past instead of recomputing from an empty token list.
One non-blocking note: this duplicates the v2-header-parsing logic that already exists inline in the STATE_PUT handler (~line 3273-3300) rather than factoring it into a shared helper. Not a regression — STATE_PUT's version is also inline, so this follows existing style rather than introducing a new pattern — but worth a follow-up cleanup given there are now two independent inline copies of this parsing logic that could drift out of sync.
Build passes per the PR. No blocking concerns — good catch and a well-targeted fix.
Closes ggml-org#506
Root Cause
In the DECODE_APPLY handler (async phase of the DECODE 0x43 path),
slot->prompt_clear(false)clearsslot->prompt.tokensbefore loading KV state viallama_state_seq_set_data. After the KV restore, the token list is never restored.When the subsequent COMPLETION task runs
update_slots():slot.prompt.tokensis empty (cleared, never restored)n_common = 0n_past = 0(else branch of n_common decision)pos_next = slot.prompt.tokens.pos_next(0) = 0seq_rm(ctx_tgt, slot.id, 0, -1)— wipes the entire KV cache that was just loadedn_past=0,n_decoded=0for the full timeout windowThe STATE_PUT handler (old HTTP-completion path) correctly parses the v2 blob header and restores tokens (lines 3357-3362). The DECODE_APPLY handler did not.
Fix
Parse the v2 blob header in DECODE_APPLY to:
slot->prompt.tokensllama_state_seq_set_dataslot->just_restored = trueso the CHECKPOINT_SEARCH path uses the cachedn_pastThe fix is backward-compatible: if the coordinator sends raw KV data (no v2 header), the code falls through to the existing behavior.
Evidence
Clean re-test (2026-07-29, build db1654c):
restore_kv_ms=571— STATE_PUT/DECODE RPC completed successfullyn_past=0,n_decoded=0for 291 seconds until timeoutseq_rmBuild
CUDA 13.2 sm_86+sm_120 build: 480/480 targets passed.