Skip to content

server : make 2 checkpoints near the end of the prompt - #20288

Merged
ggerganov merged 2 commits into
masterfrom
gg/server-ckpt-near-end
Mar 10, 2026
Merged

server : make 2 checkpoints near the end of the prompt#20288
ggerganov merged 2 commits into
masterfrom
gg/server-ckpt-near-end

Conversation

@ggerganov

Copy link
Copy Markdown
Member

fix #20239 (comment)

In some cases, reprocessing the last 512 tokens of the prompt could be too slow. In other cases it is necessary in order to allow mutating the last user message.

Make 2 checkpoints to satisfy all needs.

@alex2robotic

alex2robotic commented Mar 9, 2026

Copy link
Copy Markdown

fix #20239 (comment)

In some cases, reprocessing the last 512 tokens of the prompt could be too slow. In other cases it is necessary in order to allow mutating the last user message.

Make 2 checkpoints to satisfy all needs.

Thanks, this helps a lot.

From reading the patch, my understanding is that the new logic intentionally keeps two near-end checkpoints:

  • one around n_ubatch before the end of the prompt
  • one around 64 tokens before the end

My guess is that this is a tradeoff between:

  1. reducing TTFT for replay/regenerate of the same request, and
  2. still allowing the last user message to be edited without falling back too far.

Is that the right way to think about it?

If so, what is the main practical benefit of still keeping a small tail (for example ~64 tokens) uncheckpointed, instead of trying to checkpoint all the way to the end of the prompt? Is that mainly for robustness when the last user message changes slightly or prompt boundaries shift?

Also, in my testing, this behavior does not show up with Qwen3-30B-A3B: replay/regenerate there can have near-zero prompt re-eval, while Qwen3.5-35B-A3B re-evaluates the tail much more noticeably. Is that difference mainly because Qwen3-30B-A3B can reuse the live KV/cache path more directly, while Qwen3.5-35B-A3B relies more on this checkpoint heuristic due to its hybrid/recurrent-like architecture?

Finally, do you think near-zero or zero prompt re-eval is achievable in principle for Qwen3.5 regenerate/replay, or is some small amount of tail reprocessing still expected by design?

@ggerganov

Copy link
Copy Markdown
Member Author

Is that the right way to think about it?

Yes, that's correct.

If so, what is the main practical benefit of still keeping a small tail (for example ~64 tokens) uncheckpointed, instead of trying to checkpoint all the way to the end of the prompt? Is that mainly for robustness when the last user message changes slightly or prompt boundaries shift?

The main restriction is to guarantee that no "reasoning" tokens will get included in the checkpoint because they will be removed for the next user message. I guess 64 tokens is quite big, considering that typically only one "reasoning" token gets added (f.ex <think>). We should probably reduce this to 4?

@schynce

schynce commented Mar 9, 2026

Copy link
Copy Markdown

Thanks a lot for this! I just tested this branch and It is working well and reduces the time to first token quite a lot :)

@ggerganov

Copy link
Copy Markdown
Member Author

Thanks, could you confirm that changing the secondary checkpoint from 64 -> 4 tokens works OK?

@schynce

schynce commented Mar 9, 2026

Copy link
Copy Markdown

Thanks, could you confirm that changing the secondary checkpoint from 64 -> 4 tokens works OK?

It seems to be working okay with 4 as well for Qwen3.5 with the default template in both instruct and reasoning modes.

@ggerganov

ggerganov commented Mar 9, 2026

Copy link
Copy Markdown
Member Author

@pwilkin @aldehir Is my assumption correct that "begin thinking" tokens that get appended after a new user message and before the generation starts, cannot be more than 4 (typically a single token)? Or do we know about models with longer "begin thinking" incantations?

@aldehir

aldehir commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

The longest I know is gpt-oss at 3 tokens: <|channel|> analysis, <|message|>.

Seems like a safe assumption.

@ggerganov
ggerganov marked this pull request as ready for review March 10, 2026 06:02
@ggerganov
ggerganov requested a review from ngxson as a code owner March 10, 2026 06:02
@ggerganov
ggerganov merged commit a7b3dee into master Mar 10, 2026
16 of 75 checks passed
@ggerganov
ggerganov deleted the gg/server-ckpt-near-end branch March 10, 2026 12:28
ProgenyAlpha pushed a commit to ProgenyAlpha/llama.cpp that referenced this pull request Mar 12, 2026
* server : make 2 checkpoints near the end of the prompt

* cont : adjust checkpoints
Ethan-a2 pushed a commit to Ethan-a2/llama.cpp that referenced this pull request Mar 20, 2026
* server : make 2 checkpoints near the end of the prompt

* cont : adjust checkpoints
Seunghhon pushed a commit to Seunghhon/llama.cpp that referenced this pull request Apr 26, 2026
* server : make 2 checkpoints near the end of the prompt

* cont : adjust checkpoints
ljubomirj pushed a commit to ljubomirj/llama.cpp that referenced this pull request May 6, 2026
* server : make 2 checkpoints near the end of the prompt

* cont : adjust checkpoints
dandm1 pushed a commit to dandm1/llama.cpp that referenced this pull request May 13, 2026
* server : make 2 checkpoints near the end of the prompt

* cont : adjust checkpoints
my-other-github-account pushed a commit to my-other-github-account/llama.cpp that referenced this pull request May 15, 2026
* server : make 2 checkpoints near the end of the prompt

* cont : adjust checkpoints
my-other-github-account pushed a commit to my-other-github-account/llama.cpp that referenced this pull request May 15, 2026
* server : make 2 checkpoints near the end of the prompt

* cont : adjust checkpoints
fewtarius pushed a commit to fewtarius/CachyLLama that referenced this pull request May 30, 2026
* server : make 2 checkpoints near the end of the prompt

* cont : adjust checkpoints
fukuro-kun pushed a commit to fukuro-kun/fukuro-llama-cpp-turboquant that referenced this pull request Jul 5, 2026
* server : make 2 checkpoints near the end of the prompt

* cont : adjust checkpoints
MrLordCat referenced this pull request in MrLordCat/llama.cpp-rdna-lab Jul 16, 2026
* server : make 2 checkpoints near the end of the prompt

* cont : adjust checkpoints
MarkShark2 added a commit to MarkShark2/llama.cpp that referenced this pull request Jul 29, 2026
Upstream breaks the prompt batch twice near the end so checkpoints can be
taken (PR ggml-org#20288, offsets {4 + n_ubatch, 4}). Each break is a separate
llama_decode, and a decode boundary drains the whole pipeline -- a cost
proportional to stage count on a multi-stage RPC split.

Measured on DeepSeek-V4-Flash across 10 stages (8 BC-250s + shredder x2),
15,360-token real-text prompts: the two breaks showed up in the daemon
graph traces as gaps of 15.0 s and 10.3 s in a 60 s board span, against a
median inter-graph gap of 0.11 s.

The deep break only buys a fallback checkpoint n_ubatch tokens further
back. Per-turn restores come from the user-message-boundary rule above it,
and end-of-prompt continuation from the shallow break, so drop it by
default and keep upstream's pair behind LLAMA_CKPT_DEEP_BREAK=1.

Prefill 180.3 -> 205.4 t/s warm (+13.9%), 158.1 -> 182.0 cold; the board
trace now shows exactly one drain and duty rises 50% -> 66%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MarkShark2 added a commit to MarkShark2/llama.cpp that referenced this pull request Jul 30, 2026
…a one

Upstream cuts the prompt batch 4 + n_ubatch and 4 tokens before the end so a
checkpoint can be snapshotted before a decode, when the memory holds
everything except the batch about to run (PR ggml-org#20288).

Each cut is a separate llama_decode, and a decode boundary drains the whole
pipeline. That cost is proportional to backend count and, crucially,
independent of how many tokens the extra decode carries: on a 10-stage RPC
split the surviving 4-token break still cost 11.6 s of an 85 s request.

Snapshot in post_decode() instead, right after the decode that completes the
prompt. That decode has already drained the pipeline so the caller can
sample, so the state read rides a synchronization that is paid anyway, and
the checkpoint covers the whole prompt instead of stopping 4 tokens short --
a strictly later recovery point.

LLAMA_CKPT_PROMPT_BREAKS = 0 (default, deferred) | 1 (shallow break) |
2 (upstream's pair), replacing LLAMA_CKPT_DEEP_BREAK from the previous
commit.

DeepSeek-V4-Flash, 8 BC-250s + shredder x2, 15,360-token real-text prompts:

  breaks                     cold    warm
  2 (upstream)              158.1   180.3
  1 (shallow only)          182.0   205.4
  0 (deferred)              180.7   239.8
  0 + checkpoints disabled  184.0   243.0   <- ceiling

+33% over upstream and within 1.3% of the ceiling; the residual is the 17 MiB
state read itself.

Restore verified end to end: a request diverging from the cached sequence
re-processes 10 tokens of 1210 --
  checking checkpoint with [1199, 1199] against 1200...
  restored context checkpoint (pos_min = 1199, n_past = 1200)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MarkShark2 added a commit to MarkShark2/llama.cpp that referenced this pull request Jul 30, 2026
…a one

Upstream cuts the prompt batch 4 + n_ubatch and 4 tokens before the end so a
checkpoint can be snapshotted before a decode, when the memory holds
everything except the batch about to run (PR ggml-org#20288).

Each cut is a separate llama_decode, and a decode boundary drains the whole
pipeline. That cost is proportional to backend count and, crucially,
independent of how many tokens the extra decode carries: on a 10-stage RPC
split the surviving 4-token break still cost 11.6 s of an 85 s request.

Snapshot in post_decode() instead, right after the decode that completes the
prompt. That decode has already drained the pipeline so the caller can
sample, so the state read rides a synchronization that is paid anyway, and
the checkpoint covers the whole prompt instead of stopping 4 tokens short --
a strictly later recovery point.

LLAMA_CKPT_PROMPT_BREAKS = 0 (default, deferred) | 1 (shallow break) |
2 (upstream's pair), replacing LLAMA_CKPT_DEEP_BREAK from the previous
commit.

DeepSeek-V4-Flash, 8 BC-250s + shredder x2, 15,360-token real-text prompts:

  breaks                     cold    warm
  2 (upstream)              158.1   180.3
  1 (shallow only)          182.0   205.4
  0 (deferred)              180.7   239.8
  0 + checkpoints disabled  184.0   243.0   <- ceiling

+33% over upstream and within 1.3% of the ceiling; the residual is the 17 MiB
state read itself.

Restore verified end to end: a request diverging from the cached sequence
re-processes 10 tokens of 1210 --
  checking checkpoint with [1199, 1199] against 1200...
  restored context checkpoint (pos_min = 1199, n_past = 1200)

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Qwen3.5 35B in llama-server keeps re-evaluating ~512 tail tokens on every turn

4 participants