Pull requests / #2843
#2843 Slot checkpoints for clients that rewrite the end of their history (3/3, depends on #2527 and #2528): STRATA_SLOT_CHECKPOINTS, STRATA_BRANCH_CHECKPOINT, BCHECKS
open · draft · @andyhrc · 0 コメント · GitHub で見る
BenchmarksServer & APIAMD / HIPNVIDIA / CUDAModels & quantsDocumentationLinux
本文
## Summary A client that rewrites the end of its history every time never finds a checkpoint to continue from. Claude Code's safety classifier is one: each query is the agent's transcript with the new tool calls inserted before the closing text (`... </transcript>` and the instruction), so the next query leaves this one a few dozen tokens before its end. The turn checkpoint is past that point, the periodic ones are up to 16K tokens before it, and a slot that receives the conversation (park=, an admission continuing in its slot) keeps only its deepest checkpoint. On our server (3 x RTX A4000 layer split, --batch 6) every classifier query of 2026-10-16 read its whole 42-74K-token transcript again: 15.6-27.4 s per call, several per agent step. **Part 3, depends on #2527 and #2528**: the branch includes their commits; this PR's own are the last three (`batch: STRATA_SLOT_CHECKPOINTS=N`, `generate: STRATA_BRANCH_CHECKPOINT=<margin>`, `batch, serve: BCHECKS`). The placement side of the same problem - a slot holding an older version of the conversation is the one to replace (`STRATA_SLOT_SIBLING_SHARE`) - is in #2528. ## Change (all opt-in) - `STRATA_SLOT_CHECKPOINTS=N` (batch): a slot keeps the main session's N deepest checkpoints of its conversation (one per length) instead of one; a restore from a slot checkpoint brings the slot's shallower ones back with it. The log: `slot 2 keeps 2 checkpoints (at 32768 43372; 227.5 MiB)`. Pure helpers in `include/strata/program/slot_checkpoints.hpp`, unit test `slot_checkpoints_test` (20 checks). - `STRATA_BRANCH_CHECKPOINT=<margin>` (serve, batch or not): before reading, the engine measures how far the prompt runs along each conversation it holds (the main session's, every idle slot's) before leaving it; when that point is `<margin>` tokens or more past where the read starts and before the turn boundary, the read stops there for one more checkpoint, like the root and turn cuts. The next query - which leaves this one later - resumes there. - `BCHECKS <slot> <tokens> <lengths>` (with N > 1): after `PARKED`, `BADM 1` and `BYIELD` the engine says which checkpoints a slot keeps; the server keeps them per slot (forgotten before it sends anything that overwrites the slot), shows them in `/metrics` (`checkpoint_tokens`), and `place()` uses the deepest one that starts the prompt as the hint where no slot's history continues it (it was 0). Information only: the engine still picks what it reuses. Unset, every path is the one before. ## Results 3 x RTX A4000, Qwen3.8-Flash-Next IQ3_S, 0.1.44.1 + #2527 / #2528, --batch 6 in 3 groups, 200K context. A 43-45K-token classifier transcript growing by about 250 tokens per query, an agent conversation taking the main session between the queries; without / with `STRATA_SLOT_CHECKPOINTS=2 STRATA_BRANCH_CHECKPOINT=1024`: | Query | without: reused + read, ms | with: reused + read, ms | | --- | ---: | ---: | | 1 | 0 + 43,379, 14,146 | 0 + 43,379, 14,255 | | 2 | 0 + 43,624, 14,112 | 32,768 + 10,856, 7,577 (checkpoint at 43,340) | | 3 | 0 + 43,874, 14,277 | 43,340 + 534, 1,253 | | 4 | 0 + 44,134, 14,399 | 43,340 + 794, 1,430 | | 5 | 0 + 44,400, 14,535 | 43,340 + 1,060, 1,652 | | 6 | 0 + 44,669, 14,596 | 43,340 + 1,329, 1,818 | The same chain with a request kept decoding beside it (batch mode on throughout): 14.5-14.8 s per query without, 0.97-1.83 s with. On the solo path (no --batch, 262K): the third query 6,564 ms (32,768 + 11,110) without, 1,096 ms (43,340 + 538) with. Costs: a branch cut adds 0.8-0.9 s to the read it is in (the same 11,110 tokens from 32,768: 5.9 s straight, 6.8 s with the cut; solo 6.5 / 7.3 s); for a transcript growing by g tokens per query a margin M costs about 0.9 s x g / M in cuts and M / 2 more tokens per read (0.7 ms each), so g = 250-500 puts the best M at 800-1,100. Each checkpoint is about 114 MiB here; with N = 2 a slot kept at most 227.6 MiB. Single user 63.7 / 63.9 tok/s, 6 concurrent x 2048 tokens 178.7 / 179.3 tok/s (`tools/serve_load.py`). ## How it was checked Exactness, with docs/BATCHING.md's settings (STRATA_IQ_MT_MIN=1, --pcie-frac 0, --adapt-every 1000000, --no-prefill-borrow), greedy: the 6 queries' short answers are the same tokens without and with; of 6 160-token answers, 5 are the same and the third differs from its first token. That one depends only on whether the read had a cut at 43,340: read at once, or resumed from 32,768 without the cut -> answer A; read through the cut in one request (no restore), resumed from the branch checkpoint in the main session, or from it in a slot -> answer B. So the saved, copied and restored state is the one the read had there; the cut changes the chunks of the read after it, as `pin=` or a resume from an earlier turn checkpoint does. Tests: `slot_checkpoints_test` (deepest-N selection, shared prefix, the branch boundary's conditions); `serve/test_slot_alloc.py` 72 (the hint from reported checkpoints; BCHECKS lines kept per slot and forgotten before an overwrite); test_parallel + test_slots + test_cache_trace + test_slot_alloc 140; the serve suite. Built natively on CUDA (sm_86, Linux). The engine change is in `src/program/generate.cpp` only (shared by every backend's serve loop); not built here: HIP, SYCL. ## Extra Notes Both variables are in our production profiles on the machine above since 2026-10-17. The claim of the classifier's prompt layout comes from our server's logs of Claude Code (a query's transcript grows inside `<transcript>...</transcript>`; each new query inserts its tool calls before the closing tag). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
関連リンク
インストール・モデル・リリースへの站内リンク。