kv-cache : index (seq,pos) cells to make ngram prev-token lookups O(log n) for qwen4exp decode speedup - #27992
kv-cache : index (seq,pos) cells to make ngram prev-token lookups O(log n) for qwen4exp decode speedup#27992tvanderka wants to merge 2 commits into
Conversation
get_prev_tokens() scans all used cells once per ubatch to resolve the n-gram predecessor tokens (qwen4exp PLE). the scan is O(used) per call and dominates decode at long context: measured ~46 ms per token at ~170k ctx on 2xL40S (out of ~89 ms/token total, ~65% of wall time). llama_kv_cells now maintains a per-seq index of cell rows per position (seq_pos: pos -> set<rows>), updated by the existing seq_pos_inc/dec funnels, so add/remove/defrag/copy (memory_seq_cp) paths update it by construction. prev_token(p) resolves to the token of the largest existing position <= p, with the 'last cell wins' tie-break of the general scan preserved. ubatches with shared temporal positions (multimodal) are detected as not-applicable and keep using the general scan: within a shared position, cells resolve by ubatch order, not by row. LLAMA_KV_PREV_TOKENS env: fast (default) | verify | off. verify runs both paths for every call and logs mismatches + a cost heartbeat; used to validate the index against production traffic (455k lookups across prompt-cache loads and checkpoint restores, 0 mismatches; scan 45.9 ms vs index 6.9 us avg). measured (qwen4exp, 2xL40S, unified KV, 256k ctx): tg @155k ctx: ~11.7 -> ~31 t/s tg @200k ctx: ~11.5 -> ~27 t/s related upstream work: ggml-org#27941 (qsa correctness; likely fixes the 65535 gridDim.y abort at n_kv 262144), ggml-org#27977 (shrinks the general scan constants + qsa gather windows). complementary layers; can be combined.
coverage: sequential, holes, duplicate positions (checkpoint copies), restore-into-low-rows (the ordering trap that a newest==highest-row assumption falls for), multi-seq isolation, and a fuzzed model checked against the brute-force scan semantics (9.5k lookups per run).
20738fa to
211e29d
Compare
|
Second data point, different hardware class and a real workload rather than Setup
A/B at 32k
+11.7 % decode, no overlap between the ranges. Prefill +0.6 %. Three rounds interleaved against a same session control, one boot per run, one Per-token saving 3.59 ms against the 4.45 ms the verify path reports for the Correctness
Sustained loadOne agent run, 16 milestones, driven to completion by the model itself.
25.01 tok/s is a cross-section over every depth from 10k to 183k and is not Fitted over all 407 requests, decode ms/token against the context depth reported
Raw rows, one per request: Conditions and the fit: A depth term of 0.0898 ms per 1,000 survives this PR. At 180k that is Single arm, no control at these depths, this is not a second speedup Found during testing1. 2. The verify heartbeat cannot fire on a normal turn. ArchitecturesThe PR notes it charges every other model a little. One measurement on this Not measured
RequirementsI have read and agree with the contributing guidelines AI Disclosure : Claude helped me Coding, build tables (like the csv file) + I reviewed, wrote code, wrote this text (ai helped translating some parts since no native english speaker). |
|
Better fix in #28040 and performance within margin of error. |
Overview
While testing qwen4exp I noticed decode is cpu limited. Found get_prev_tokens() scans all used cells to resolve the n-gram predecessor tokens. This implements a TODO by @ngxson from llama_kv_cache::get_prev_tokens, mostly just to show the impact.
On my test HW with 2xL40s this PR achieves 2.7x speedup at 240k ctx.
Additional information
llama_kv_cells now maintains a per-seq index of cell rows per position (seq_pos: pos -> set) instead of refcount. Updated by the existing seq_pos_inc/dec, so add/remove/defrag/copy (memory_seq_cp) paths update it. Multimodal falls back to original scan.
There is a bit of debug/verification plumbing, using LLAMA_KV_PREV_TOKENS env: fast (default) | verify | off.
Verify plumbing would be removed if this is the way. Test suite passed.
This adds a small memory/cpu cost for every other model, while only qwen4exp is using it.
measured (qwen4exp, 2xL40S, unified KV, 256k ctx):
numbers from: llama-bench -hf "unsloth/Qwen3.8-Flash-Next-GGUF:UD-Q4_K_XL" -ngl 999 -fa 1 -p 0 -n 32 -fitt 128 -d 0,4096,16384,65536,131072,240000
Possibly related upstream work: #27941 (qsa correctness; likely fixes the 65535 gridDim.y abort at n_kv 262144), #27977 (shrinks the general scan constants + qsa gather windows). complementary; can be combined.
Requirements