Skip to content

kv-cache: scan only the predecessor window in get_prev_tokens - #153

Closed
danielhanchen wants to merge 1 commit into
base/upstream-9723942adfrom
qwen4exp/ple-scan-window
Closed

kv-cache: scan only the predecessor window in get_prev_tokens#153
danielhanchen wants to merge 1 commit into
base/upstream-9723942adfrom
qwen4exp/ple-scan-window

Conversation

@danielhanchen

Copy link
Copy Markdown
Member

get_prev_tokens rebuilds the n-gram predecessor history for the PLE input by walking every cell of the cache, at every decode step. Only positions in [w0, p_max] are ever read back out of the map it builds. The cells below w0 exist for one reason: to fill below[], which is the fallback for a lookup that lands in an M-RoPE position gap.

For text that fallback is never used, because text positions are unique and every lookup resolves inside the window. So at 64k context the scan visits 65536 cells to return two tokens.

This scans the window first and falls back to the full scan only when a lookup actually misses.

The M-RoPE path is why this is not a one line change

An earlier version simply narrowed the range and dropped below[]. It was byte identical on text, which made the fallback look like dead weight. It is not: a multimodal ubatch repeats one position across a whole image, so a lookup can legitimately land in a gap, and that version produced a different, still plausible caption.

lookup therefore reports a miss instead of silently returning below[], the missed (index, seq_id) pairs are collected, and the [0, w0) scan runs only if that list is non empty, through the same collect lambda.

Measurement

Note that 62acc89c2 (ggml-org#28011) landed first and already stops the inner sequence loop once every sequence in a cell has been seen. That removed most of this cost. Any number measured before it, including the ones I had from earlier work, no longer describes this patch.

Re-measured against current master, llama-bench -d 65536 -n 32, arms alternated inside one job, 8 reps each:

arm mean t/s sd vs base
base 59.229 0.499
this patch 60.708 1.723 +2.5%

7 of 8 reps beat every base rep. This is a couple of percent at 64k context, not the large win it was before ggml-org#28011, and it is worth stating plainly.

The two changes attack different loops, this one the number of cells visited and ggml-org#28011 the work per cell, but they land on the same total because what remains after ggml-org#28011 is small.

Testing

Built at 9723942ad, CUDA, B200.

  • Byte identical greedy output against base on Qwen3.8-Flash-Next UD-IQ1_S, Qwen3.8-27B Q4_K_M and Qwen3-0.6B Q4_K_M, with a base against base self control run first.
  • Byte identical image captioning through llama-mtmd-cli, which is the gate the earlier version failed.
  • test-llama-archs for qwen4exp, llama, qwen3next, gemma3n and qwen3moe.
  • No ggml change.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-31T04:08:30.501254Z dbac7c4 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c49b634dce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/llama-kv-cache.cpp
// a lookup landed in an M-RoPE gap, so now scan below the window
if (p_scan0 > 0) {
for (uint32_t s = 0; s < n_stream; ++s) {
v_cells[s].for_each_token_in(seqs, 0, p_scan0, collect);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid repeating the full cache traversal on fallback

When an M-RoPE lookup misses, this fallback makes a second complete traversal of the cache: llama_kv_cells::for_each_token_in() iterates every entry in used and only then filters by position (src/llama-kv-cells.h:317-319). The previous implementation traversed used once, whereas the new miss path traverses it twice while invoking collect for the same total set of cells, so multimodal batches with position gaps regress at large contexts; use a position-indexed range traversal or preserve the below-window result from the first traversal.

Useful? React with 👍 / 👎.

Comment thread src/llama-kv-cache.cpp Outdated
Comment on lines +1877 to +1878
// only [w0, p_max] is read back from hist. cells below w0 only fill below[], which is used
// when a lookup finds nothing in the window. so scan the window first, the rest only if needed

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep each comment sentence on one line

This new comment hard-wraps one sentence across two source lines, and the same pattern recurs in the comments below. AGENTS.md explicitly forbids hard-wrapping comments and splitting a line mid-sentence, so keep each sentence on one line.

AGENTS.md reference: AGENTS.md:L77-L81

Useful? React with 👍 / 👎.

get_prev_tokens rebuilds the n-gram predecessor history by walking every
cell of the cache, but only positions in [w0, p_max] are ever read back
out of it. The cells below w0 exist solely to fill below[], which is the
fallback for a lookup that lands in an M-RoPE position gap.

Scan the window first and fall back to the full scan only when a lookup
actually misses, which for text never happens: positions are unique, so
every lookup resolves inside the window.

Assisted-by: Claude
@danielhanchen
danielhanchen force-pushed the qwen4exp/ple-scan-window branch from c49b634 to dbac7c4 Compare August 31, 2026 04:06
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.

1 participant