Skip to content

[Engine] one shared KV pool for all lanes (--kv-pool-tokens): is it wanted, and in what shape? (scope agreement before I reopen #1614) #3351

Open

Description

@noon-at-cgn

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 MemAvailable idle.

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.

What I am asking

  1. Is an opt-in memory feature like this in scope? If not, it stays a branch.
  2. 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)?
  3. 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

  1. noon-at-cgn commented on Oct 25, 2026

    @noon-at-cgn
    ContributorAuthor

    #1614 is now ready for review, rebased on main 41cf05a0 (0.2.5), with the checks this issue listed as missing:

    • Off by default and identical to main: without --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.
    • HIP (ROCm 7.2.4, gfx1100 / gfx1151 / gfx1201) and SYCL (oneAPI 2026.1.1) build on the branch and on main. SYCL does not compile the pool.
    • Pool on at 2 x 256k: runs end to end on 0.2.5, with every needle answered, no kv_pool_full, and host MemAvailable never 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.

  2. leungwoods commented on Oct 29, 2026

    @leungwoods

    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):

    parallel absent (1 slot) "parallel": 2
    slot sessions — 2 x 0.73 GiB = 1.46 GiB of VRAM
    expert cache 7995 slots / 15.16 GiB 7222 slots / 13.69 GiB
    single request, decode 122.5 tok/s 91.4 tok/s (-25%)
    two concurrent, per slot — ~31 tok/s
    two concurrent, aggregate — ~33.5 tok/s (1.04x the single-request rate)

    The 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-mtp the 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-tokens pool 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 N when fewer than N requests are active.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions