Repository navigation
Description
Area
[Engine]
What do you want to do?
User problem. With KV streaming (--kv-resident) the main session and every --batch slot pin the K/V of the whole --max-context in RAM, however short the conversation: 3.09 GiB each at 262,144 cells with --kv int8. A conversation that moves between the main session and a slot is copied, and at 256k that takes 3.5-5.2 s. On a RAM-limited box with two long lanes the extra pinned RAM also brings the host close to its memory guard. Workaround today: a smaller --max-context or --parallel, or K/V in VRAM (fewer cached experts).
Proposed behaviour. --kv-pool-tokens N, opt-in (absent means nothing changes). N cells per layer are pinned once, in 4,096-cell chunks, and a session holds the chunks its conversation has reached. A conversation moves between the main session and a slot by swapping chunk tables. When the pool is full an idle slot's cached conversation gives way, then a new request gets a retryable 503 (kv_pool_full), and a decoding lane that cannot grow ends with length and "truncated": true. It works on one GPU and across a layer split. The idea and the first four commits are xdbxdbx's #1011 (his authorship is kept); #1614 carried it on v0.1.44.1 and was closed by the history rewrite. It is now rebased on 0.2.0-beta (e88ee39d), branch pr/kv-pool-144 on noon-at-cgn/Strata, 19 commits.
Tradeoffs.
- Memory and long-context lane handling only. I make no speed claim; short-context speed did not change.
- N is chosen by the operator. Too small a pool means refused requests or shortened answers.
- A new layer for K/V host addressing: every reader and writer of the host copy has to go through the chunk table. On this rebase two sites that were added to main after the old base (
conversation_kv_copy,conversation_kv_restore_prefix) did not translate and are fixed, with a test that runs them through a shared pool. Later changes to those sites need the same care. - The MTP drafter ring is not pooled. CUDA only was built.
Measured on 0.2.0-beta (engine 0.1.45.3, 2 x RTX 3080 20 GB, same binary with and without the flag, two lanes of about 256k tokens, appended turns):
- Slot move 0.36-0.5 s with the pool against 3.5-5.2 s copied; first token of an appended short question 1.1-1.2 s against 2.3-6.7 s; both lanes decoding an appended turn 59-62 against 25-38 tok/s.
- 3.2 GiB more idle
MemAvailable(14.5 against 11.4 GiB, 3 starts each), 3.06 GiB less pinned shared memory. - No short-context speed effect (differences within the round-to-round spread).
- Without the pool, the host memory guard stopped the engine twice in the 245k run (two of three no-pool starts), so the no-pool 256k timings are one full run and two partial ones. It never fired with the pool.
- With the pool a third lane (three conversations of about 104k) fits at about 13 GiB
MemAvailableidle.
Not measured: three lanes without the pool; pool-full paths; more than 3 lanes; HIP, SYCL, Metal, Windows; STRATA_SLOT_CKPT_MOVE=1 and STRATA_SLOT_COPY_DIRECT end to end; the slot-choice layer. One box only. The full list will be in the PR.
Overlap.
- [Engine] Preserve decoded batch-slot conversations before slot reuse #2526 (park an idle slot before reuse, a slot gives its checkpoints back with its conversation): the same idea and the same admission code as our checkpoint-chain commit. Ours exists only with the pool (the chain follows the chunks). I will rebase on whichever lands first.
- [Engine] Share identical KV prefix blocks across concurrent conversations #1962 (share identical KV prefix blocks): a different mechanism and complementary; the pool does not share prefixes.
- [Engine] restore a session file without holding its K/V in RAM #3251 (restore a session file without its K/V in RAM): needs the same chunk translation in
conversation_snapshot.cpp. - [Engine] Branching follow-ups can be admitted into the wrong batch slot and lose another conversation's cached prefix affects Strata v0.2.6 #2518 (turn-start rule in
pick_slot): our separate slot-choice change (pr/pick-slot) is not part of the pool PR; main's rule runs first there and ours is only a fallback. It would be its own PR, if you want it.
What I am asking
- Is an opt-in memory feature like this in scope? If not, it stays a branch.
- If yes, in what shape: the whole branch as one PR (pool core plus the checkpoint-chain commit, which only exists with the pool), or only the pool core and the chain commit later (for example after [Engine] Preserve decoded batch-slot conversations before slot reuse #2526)?
- Any evidence you want beyond the above.
I am reopening #1614 as a draft with the rebased branch now, so the code and the evidence are in one place and nothing competes with it. If you want a different shape, I will change the PR rather than open another.
Activity
noon-at-cgn commentedon Oct 25, 2026
#1614 is now ready for review, rebased on main
41cf05a0(0.2.5), with the checks this issue listed as missing:--kv-pool-tokens, greedy output at 1k, 32k and 128k tokens is byte-identical to main, the STATE_HASH fields are equal, and the expert-cache size is the same. This gate caught one default-path difference: the batch slots shared a RoPE table even without the pool, which changed the expert-cache size. That sharing now happens only with the pool.kv_pool_full, and hostMemAvailablenever below 17.2 GiB.The main point from the issue: at 2 x 256k the pool cut the first token of an appended question from 2.3-6.7 s to 1.1-1.2 s, and appended-turn decode rose from 25.5-37.7 to 59.1-61.5 tok/s. Without the pool, the host memory guard stopped the engine in 2 of 3 starts. Short-context speed is unchanged.
The PR body has the commands and the Passed / Failed / Not run lists. Your questions 1 and 2 above, whether the pool is in scope and in what shape, are still open. I will change the PR to whatever shape you prefer.
leungwoods commentedon Oct 29, 2026
Environment: Linux, dual Xeon (2 NUMA nodes), RTX 3090 Ti 24 GB, driver 595.91.07, engine 0.1.52 (release 0.2.7), Qwen3.8-Flash-Next IQ3_S (
--pack+--native),--max-context 131072 --kv int8 --kv-resident 32768,--spec 6 --mtp-max-t 4 --spec-min-p 0.7,--pcie-frac 0.20 --pool-workers 20 --expert-cache auto.What I measured (same 12 prompts, same seed, one fresh engine per arm, arms interleaved within each prompt):
parallelabsent (1 slot)"parallel": 2The two-slot engine log:
strata batch: 373 windows, avg 1.99 rows, 25.10 ms/window = run 19.81 (CUDA0 GPU-reach wait 10.60 + CPU experts 8.17) + commit 0.89 + emit 0.01 + adapt wait 4.39. Without--batch-mtpthe slots have no per-slot draft K/V (drafts accepted 0 of 0), so each slot decodes at about 31 tok/s.Question for this scope: #3351 describes one shared
--kv-pool-tokenspool for the K/V of all lanes. Would that design also return an idle slot's session VRAM (0.73 GiB per slot here) to the expert cache while the slot is not decoding? Today the N slot sessions are reserved at start and the expert cache stays smaller for the whole run, so a single user pays -25% decode with no aggregate gain on a card that is already saturated (the GPU-reach wait and CPU expert time fill each window). If that is out of scope for #3351, should "return an idle slot's VRAM to the expert cache" be its own issue?This is not a speed claim for the pool; it is about the resource reservation of
--batch Nwhen fewer than N requests are active.