Repository navigation
fix(server): DECODE_APPLY KV restore check was inverted — success looked like failure - #93
Merged
Merged
Conversation
…ked like failure llama_state_seq_set_data returns the number of bytes read on success (0 means failed to load), per its own doc comment in include/llama.h and confirmed identical in upstream ggml-org/llama.cpp. Every real call site upstream (tools/server/server-task.cpp, tests/test-save-load-state.cpp) checks the full expected byte count, not "nonzero == error". The DECODE_APPLY handler had it backwards: `if (status != 0)` treated every successful restore (which returns the byte count) as a failure, aborted the decode, and logged a bogus "KV restore failed (N)" where N was actually the correct number of bytes restored. The sibling STATE_PUT call site 20 lines above (server-context.cpp:3394-3397) already had the correct check. Fixes the symptom in #78 — 157626320 in that issue's log line is ~150.3 MiB, matching "the 150 MB KV blob" the issue itself describes as the input; the restore was succeeding the whole time. Closes #78 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…h, and never injected trailing logits Correction to the previous commit on this branch: `status != kv_len` was still a false-failure. llama_state_seq_set_data's return value only counts bytes the KV-cache reader itself consumed (llama-context.cpp state_seq_read_data -> io.n_bytes(), a running counter of actual reads) -- it has no knowledge of the trailing logits block PREFILL_DONE appends after the KV state (~line 4098, unconditional whenever logits are available, which is the normal merged-decode case). So status < kv_len whenever logits are present, and `status != kv_len` fails exactly the case it needed to pass. Corrected to `status == 0`, matching the STATE_PUT sibling check exactly (server-context.cpp ~line 3395). Also: DECODE_APPLY was missing the trailing-logits extraction entirely. STATE_PUT computes `remaining = state_len - n_read` and, when it matches n_vocab*sizeof(float), injects those floats into the per-slot restored_logits buffer so the first sample after restore doesn't read garbage. DECODE_APPLY restored the KV cache but silently dropped this step. Ported the same logic (kv_len - status / expected_logits / slot->restored_logits / slot->logits_valid) into DECODE_APPLY. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…b checkpoint, align n_prompt_tokens_cache (llama#94) - Failure path now clears tokens/checkpoints/cache and removes KV cells, mirroring STATE_PUT so the slot is not left poisoned (n_past > 0 with no KV cells -> pos_min == -1 abort on next decode). - v2 header checkpoint is now captured (not skipped) and registered after restore, with a fabricated (0, n_past-1) fallback — fixes hybrid/ recurrent models whose KV was restored without its memory checkpoint. - n_prompt_tokens_cache/processed now read hdr_n_tok (blob_n_tok), the same header field STATE_PUT uses, instead of blob_n_past. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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
llama_state_seq_set_datareturns the number of bytes read on success and0on failure — per its own doc comment ininclude/llama.h:server-context.cpp:4534's original check wasif (status != 0), backwards — it treated every successful restore (nonzero = bytes read) as a failure. Confirmed against upstreamggml-org/llama.cppdirectly (not just this fork's copy of the doc comment):src/llama-context.cpp's implementation is untouched by the fork, and every real call site upstream (tools/server/server-task.cpp,tests/test-save-load-state.cpp) checks the expected byte count, never!= 0as failure.Correction during review: the first fix pushed here changed the check to
if (status != kv_len). That is also wrong, for a subtler reason:status(llama_state_seq_set_data's return value) only counts bytes the KV-cache reader itself consumed (llama_context::state_seq_read_datareturnsio.n_bytes(), a running counter of actualread()/read_tensor()calls — it has no knowledge of the buffer size passed in beyond an overrun guard).kv_lenhere is KV-state bytes plus trailing logits:PREFILL_DONEunconditionally appendsn_vocab * sizeof(float)logit bytes after the KV state whenever logits are available (~line 4098) — the normal case for merged-decode, not an edge case — and the coordinator forwards that full blob verbatim as the DECODE 0x43 payload. Sostatus < kv_lenis the expected, correct outcome whenever logits are present, and comparing againstkv_lenreintroduces a false failure on essentially every real restore.Fixed to
if (status == 0), matching the STATE_PUT sibling check exactly (server-context.cpp~line 3395).Also fixed: DECODE_APPLY was missing the trailing-logits extraction entirely. The STATE_PUT handler computes
remaining = state_len - n_readand, when it equalsn_vocab * sizeof(float), injects those floats into the per-slotrestored_logitsbuffer so the first sample after restore doesn't read garbage (server-context.cpp~line 3405-3417, comment: "without these, common_sampler_sample reads garbage after StatePut"). DECODE_APPLY restored the KV cache but silently dropped this step. Ported the same logic in.Evidence this is the real bug behind #78
157626320 in #78's reported log line (
KV restore failed (157626320)) is ≈150.3 MiB — matching almost exactly "the 150 MB KV blob" the issue itself describes as the input being restored. The restore was succeeding the whole time; the check just reported it as failed. Same shape independently observed in an earlier debug session (KV restore failed (36656)).Provenance
The inverted check was introduced fresh in PR #64 ("feat(rpc): ggml-org#470 Phase 2 — framed DECODE (0x43) rewrite", merged 2026-07-24) and survived two subsequent PRs that touched adjacent lines for unrelated reasons — #66 (2026-07-28) and #69 (2026-07-29, Closes ggml-org#506) — without the polarity being caught in any of those review rounds. The missing logits-injection step also dates to the same PR #64 (DECODE_APPLY was written without porting the full STATE_PUT success path).
Verification
Not yet build- or live-rig-verified — no local build directory was available in this worktree. The changes are localized to the DECODE_APPLY success/failure branch, use only existing fields (
slot->restored_logits,slot->logits_valid,vocab) already used identically in the STATE_PUT sibling a few hundred lines above, and introduce no new symbols. Live-rig E2E verification (the project's own required step before merge) is still outstanding.Closes #78