feat(v4): prefix checkpoints (memory + disk), resumable 4096-token prefill segments, cancellable prefill, gateway prefix hint - #1053
Conversation
…efill segments, cancellable prefill, batched indexer selection
- Prefix checkpoints (V4_PREFIX_CKPT, on): snapshot the attention
transaction (window KV + compressed slots + compressor/indexer state) at a
shared boundary and restore it for any later request whose prompt starts
with the same bytes. Capture rules: the gateway's prefix hint (prefix_bytes
in the session options), the longest common prefix of two successive fresh
prompts, and a prompt-end capture after every prefill (agent clients
re-render the assistant reply). 4 LRU slots (V4_PREFIX_CKPT_SLOTS),
prompt-end evicted first.
- Disk persistence (V4_PREFIX_CKPT_DISK, on): <model>/.coli_ckpt/, config
fingerprinted, temp+rename, lazily loaded after a restart. New
coli_v4_{attention,compressor,indexer}_snapshot_write/read; restore grows
the live buffers and prepares the per-layer window attention state.
- Segmented prefill: atomic 4096-token segments (V4_PREFILL_SEGMENT), a
should_abort poll between them (ColiV4SessionAbortFn in the session
options), completed segments recorded so an identical retry resumes;
progress lines per segment.
- 128-token chunks (V4_PREFILL_CHUNK clamp 64 -> 128; sums[128] in the fp8
batch reference).
- Batched indexer selection in prefill (V4_IDX_BATCH, on): advance per
token, score/select once per chunk past index_topk, per-token numerics
unchanged; V4_IDX_IDENTITY (off) documents the identity short-circuit.
- MTP: clamp the noise token to the vocabulary.
CPU parity vs dev: 826-token prompt, 48 greedy tokens, COLI_V4_ROWS16=0
COLI_V4_AUTOPIN=0 COLI_V4_SAVE_USAGE=0 V4_PREFIX_CKPT=0 -> byte-identical
text (md5 9cf553633292 on both).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e acks a CANCEL - Emit the byte offset of the first user/assistant turn marker as an optional 8th SUBMIT header field (older engines ignore it) so the very first request seeds the shared-prefix checkpoint. - Do not release the scheduler admission before the engine confirms a client cancel: releasing early let the next request SUBMIT into a pipe the busy engine was not reading (every later request hung). Wait for the ERROR CANCELLED / DONE frame; raise ClientCancelled on a DONE that arrives after a CANCEL. Wire client_disconnected as the abort poll. - Exit cleanly on Ctrl-C. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…U-path output identity means Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ts.M) coli plan counted zero routed experts for V4 checkpoints because the expert regex only knew model.layers.N.mlp.experts.M. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7badd9c to
d44b096
Compare
|
Reviewed this properly, and I want it — the problem it solves is real and the measurement makes it undeniable: 677 s to re-prefill 8,548 tokens on turn 2, past opencode's entire budget before decode even starts. An engine that re-prefills the whole transcript every turn isn't usable with an agent client, and this is the fix. The design is right too. Snapshotting the attention transaction at a shared boundary and restoring it for a matching byte prefix is the only approach that works given V4's window/compressor/indexer state can't rewind, and the three capture rules (gateway I checked the thing I was most worried about, and you got it rightThe Raising a bound and growing the fixed-size buffer it protects, in the same change, is exactly what that needed — a guard raised without the array is a stack smash. Thank you for doing both. And +3% for Also noted and appreciated: One change before I merge: please make the disk half opt-in
I'd like:
If it stays on by default, please at least make an unwritable or full model directory fail soft and silent-ish — a warning, not an error, and certainly not a failed request. But I'd rather have the default flipped: this is the same reasoning I applied to #1058 this week, where LTO-by-default changes something the user didn't ask to change. Everything else here is ready. Separately: #1055 can rebase now too — both #988 and #1054 (@ZacharyZcR's kernels, with his test and oracle suite) are in |
|
Small catch from reading the merged tree: the review asked for Happy to send the one-line flip ( |
Stacked on #988 (dual-SSD mirror); the diff against
devincludes it until #988 merges.Problem
An agent client (opencode, aider…) sends an 8k-token system prompt and then a new
request per turn/session. V4 keeps one KV slot and its window/compressor/indexer
state cannot rewind, so every request re-prefilled the whole conversation
(measured: turn 2 re-prefilled 8548 tokens = 677 s on the CPU path, well past
opencode's 30-minute budget once decode is added; an aborted client also left
the engine finishing the orphaned generation, wedging the pipe for the next one).
What this adds (all CPU-side, no CUDA)
V4_PREFIX_CKPT, on): a snapshot of the attentiontransaction (the existing
ColiV4AttentionSnapshot: window KV + compressedslots + compressor/indexer recurrent state) is captured at a shared boundary
and restored for any later request whose prompt starts with the same bytes.
Three capture rules: (1) the gateway passes the byte offset where the rendered
system turn ends (optional 8th
SUBMITheader field /prefix_bytesin thesession options — older engines ignore it), so the first request already
seeds the checkpoint; (2) fallback: longest common prefix of two successive
fresh prompts; (3) prompt-end capture after every prefill, because agents
re-render the assistant reply so "extends everything fed" fails at the reply
boundary. 4 in-memory slots, LRU, prompt-end evicted first.
V4_PREFIX_CKPT_DISK, on): prefix captures go to<model>/.coli_ckpt/(config-fingerprinted, temp+rename, ~140 MB for an 8.3kprefix) and are loaded lazily after a serve restart. New
coli_v4_{attention,compressor,indexer}_snapshot_write/read.(
V4_PREFILL_SEGMENT); a newshould_abortpoll in the session options runsbetween segments (the gateway wires it to client disconnect), completed
segments are recorded so an identical retry resumes; the gateway keeps the
scheduler admission until the engine acks the CANCEL (this is the wedge fix).
V4_PREFILL_CHUNKclamp raised 64→128, +3 % CPU prefill;the fp8 batch reference's
sums[]grows accordingly).V4_IDX_BATCH, on): advance pertoken, score/select once per chunk past
index_topk; per-token numericsunchanged.
V4_IDX_IDENTITY(off) documents the faster identityshort-circuit that changes rounding.
V4_PREFIX_LOGdiagnostics, docs section"Prefill: segments, chunks, checkpoints", env table.
Numbers (CPU path, 8.3k-token opencode system prompt)
re-prefill
Validation
dev: 826-token prompt, 48 greedy tokens,--memory-gb 22,COLI_V4_ROWS16=0 COLI_V4_AUTOPIN=0 COLI_V4_SAVE_USAGE=0 V4_PREFIX_CKPT=0→ byte-identical text (md5 9cf553633292 on this branch and on upstream dev,
2026-08-16). Why those variables — and why greedy text is not a stable
identity check across kernel/cache configurations — is written up in
docs/deepseek-v4.md → Validation.
make deepseek-v4-tiny-check) token-exact; two-requestSUBMIT/DATA/DONE flow; cancel-during-prefill exercised through the gateway.
Files: c/deepseek_v4.c (+~1.3k), c/deepseek_v4_internal.h, c/deepseek_v4.h
(
should_abort,prefix_bytes), c/openai_server.py (prefix hint, cancel ack,KeyboardInterrupt), docs.
🤖 Generated with Claude Code