Skip to content

server : do not re-verify replayed draft tokens after a checkpoint restore - #28061

Closed
JayToltTech wants to merge 1 commit into
ggml-org:masterfrom
JayToltTech:fix/spec-replay-livelock
Closed

server : do not re-verify replayed draft tokens after a checkpoint restore#28061
JayToltTech wants to merge 1 commit into
ggml-org:masterfrom
JayToltTech:fix/spec-replay-livelock

Conversation

@JayToltTech

Copy link
Copy Markdown

Fixes #28060.

What

With full-checkpoint rollback, a partial draft acceptance restores the pre-round state and re-decodes the accepted tokens to rebuild it. That replay currently runs through the same verification as a fresh draft. On backends where logits depend on batch shape or memory layout (Vulkan), the re-verification can reject a token the original verification accepted; the rejection restores the same checkpoint and replays again, and the slot loops on one position without emitting anything.

This accepts the replayed tokens without re-verifying, and samples only the continuation from the final position. The replayed prefix was already accepted by the verification that triggered the restore — the replay exists to rebuild state, not to re-decide.

Credit

The diagnosis and the patch are @Nathanw1014's, from strix-halo-vulkan commit 9c5d899ff, preserved here with authorship intact. I am submitting it because I hit the surrounding area while porting MTP onto current master and thought the fix should not stay on a fork. @Nathanw1014 — happy to close this if you would rather submit it yourself.

Symptom independently reported by @flobob45 in #27836.

Conflict resolution

The commit does not apply to current master unchanged: master has since added the synth_probs sampling path, and the original also carried a spec_dists branch that master does not have (spec_dists has no references in tools/server/; spec_is_replay has 6). I kept master's synth_probs path, grafted on only the replay short-circuit, and dropped the spec_dists branch. That resolution is mine and is the part most worth reviewing.

Testing

  • Builds clean on 9723942 (Vulkan, -DGGML_VULKAN=ON).
  • On an AMD Strix Halo (gfx1151) / RADV, running MTP speculative decoding on a tree carrying this fix: a 400-token generation at temp 1.0 / top-p 0.95 / top-k 20 completes in 26 s, with flat decode throughput across generation lengths of 50/100/200/400 tokens (18.32 / 15.01 / 15.92 / 15.61 tok/s) and 68-81% draft acceptance. No collapse.

Not verified by me: I have not reproduced the livelock on unmodified master — see the issue for why. The original author reports the change is bit-identical on CPU (800-token greedy pair, 118 restore rounds), which matches the expectation that only batch-shape-sensitive backends are affected. Reviewers with a CUDA or Metal setup that exercises checkpoint rollback may want to confirm no behaviour change there.

…store

With full-checkpoint rollback, a partial draft acceptance restores the
pre-round state and re-decodes the accepted tokens to rebuild it. The replay
went through the same verification as a fresh draft. On backends where logits
change with batch shape or memory layout (Vulkan), that re-verification can
reject a token the original verification accepted; the rejection restores the
same checkpoint and replays again, and the slot loops on one position without
emitting anything. qwen35moe with --spec-type draft-mtp stalled this way a few
hundred tokens into long generations (the v0.6.8 MTP hang): the loop repeated
"accepted 2/3, restore at pos 995" every 27 ms with the GPU at 90 percent.

Accept the replayed tokens without re-verifying and sample only the
continuation from the final position. The replayed prefix was accepted by the
verification that triggered the restore; the replay exists to rebuild state.
On backends with batch-shape invariant logits the re-verification always
agreed, so behavior there is unchanged (verified bit-identical on CPU with
and without this change, 800-token greedy pair, 118 restore rounds).

Assisted-by: Claude Fable 5
(cherry picked from commit 9c5d899)
@JayToltTech
JayToltTech requested a review from a team as a code owner August 31, 2026 02:23
Copilot AI lite review requested due to automatic review settings August 31, 2026 02:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@ggml-gh-bot

ggml-gh-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown

Hi @JayToltTech, thanks for your contribution!

Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:

  • PR Template not respected: Please respect the template when creating a new pull request. Make sure to fill out all required sections.

  • AI-generated content: While code is allowed to be generated by AI, please write the PR description and commit messages on your own without the help of AI.


Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below.

@ggml-gh-bot ggml-gh-bot Bot added the draft PR will be changed to draft by github-actions bot label Aug 31, 2026
@github-actions
github-actions Bot marked this pull request as draft August 31, 2026 02:28
@github-actions github-actions Bot removed the draft PR will be changed to draft by github-actions bot label Aug 31, 2026
@0cc4m

0cc4m commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Don't submit other people's work, unless they asked you to or explicitely allowed it.

@JayToltTech

Copy link
Copy Markdown
Author

Understood, and you are right — I should have asked first. Crediting is not consent, and that was my mistake, not a grey area. Apologies for the noise.

@Nathanw1014 — the fix is yours (9c5d899ff). If you would like it upstream, would you consider submitting it? I am happy to hand over anything useful: it applies to master with one conflict (master has since added the synth_probs path, and the original also carried a spec_dists branch master does not have), and I have a resolution plus a Vulkan build if that saves you time. Entirely your call, and no obligation.

Leaving this closed. The bug report at #28060 stays open on its own merits, and I have converted #28104 to a draft pending your permission.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

server: Vulkan speculative decoding can livelock on checkpoint-restore replay (no tokens emitted, GPU pegged)

4 participants