Skip to content

[Engine] shared KV pool for all lanes (--kv-pool-tokens): 256k lane moves 10x faster, 3 GiB less pinned RAM - #1614

Draft
noon-at-cgn wants to merge 19 commits into
Niko1221:mainfrom
noon-at-cgn:pr/kv-pool-141
Draft

noon-at-cgn wants to merge 19 commits into
Niko1221:mainfrom
noon-at-cgn:pr/kv-pool-141

Conversation

@noon-at-cgn

@noon-at-cgn noon-at-cgn commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Branch pr/kv-pool-141, head 81a5c6ae (19 commits on upstream main e88ee39d = v0.2.0-beta-12, engine 0.1.45.3). Reopens this PR, rebased from v0.1.44.1.

Problem and result

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, which at 256k takes seconds.

--kv-pool-tokens N (opt-in) pins N cells once. Sessions hold only the 4,096-cell chunks their conversation has reached, and a move between lanes swaps chunk tables instead of copying.

Measured on 0.2.0-beta, 2 x RTX 3080 20 GB, two lanes of about 256k tokens, the same binary in every arm and only the flag differing (--kv-pool-tokens 524288 present or removed):

with the pool without the pool
Moving a ~256k conversation to or from a slot 0.36-0.5 s (moved) 3.5-5.2 s (copied)
First token of an appended short question (both lanes) 1.1-1.2 s 2.3-6.7 s
Both lanes decoding an appended turn, aggregate 59.1 and 61.5 tok/s 25.5 and 37.7 tok/s
Idle MemAvailable (mean of 3 starts each) 14.5 GiB 11.4 GiB
Pinned shared memory (5 comparable starts) 71.8 GiB 74.9 GiB
Host memory guard (stops the engine below 1.5 GiB host MemAvailable) never fired stopped the engine twice at 245k

At about 133k per lane the move took 0.1-0.25 s with the pool and 0.9-2.0 s without. With the pool a third lane (three conversations of about 104k tokens) also fits: 13.1-13.2 GiB MemAvailable idle, every needle answer correct.

Short-context speed is unchanged. One stream 84.5 tok/s with the pool against 81.9 without, two streams 93.7 against 89.8 (means of the medians of three starts each), prompt reads within 1-3%. The +3-4% is smaller than the round-to-round spread (58-103 tok/s), so I make no speed claim. This is a memory and long-context lane-handling feature, not a decode-speed one.

The no-pool 256k numbers are one full run and two partial ones, because the host guard stopped two of the three no-pool starts (see Validation, Failed). They were also taken while the host had 1.1-1.8 GiB free and no free swap, which may add to the copy times.

Related discussion

Approach

A session keeps a chunk table: a device copy for the kernels and a host copy for DMA and copies. Chunks the conversation has not reached point at one spare "trash" chunk. Without the flag the table is the identity layout and nothing changes. A lane reserves chunks as it writes. When the pool is full an idle slot's cached conversation gives way first, 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 across a layer split: each card pins its own layers' share and a conversation uses the same chunks on every card. The MTP drafter ring is not pooled.

What the rebase onto 0.2.0-beta changed (18 commits, 7 conflicts, all resolved by keeping main's code and re-applying the pool on top):

  • Two K/V sites that did not exist at the old base were not going through the chunk table and are translated here: conversation_kv_copy (the direct device-to-device slot copy) and conversation_kv_restore_prefix (used by borrow). conversation_snapshot_test now runs save, restore, verify, source, the direct copy and the prefix restore through a shared pool. With the direct copy's chunk branch disabled the test fails, and with restore_prefix bypassing the chunk table it fails; both changes were reverted afterwards.
  • The pool's host allocation now goes through main's kv_host_alloc, so it gets the WSL2 fallback and the pin budget.
  • Main's new multi-step and window variants all pass &st.host, which carries the chunk fields; I read every host-addressing site (append kernels, resolve and stage copies, verify, mtp, prefill, state hash).
  • The checkpoint chain moves with its chunks, as its own commit (96549a3e). Without it a client that changes its last question finds no checkpoint below the change.
  • One guard in 9a5dcb4b: an active slot is never the source of a checkpoint move while the pool moves chunks. It matters only with the opt-in STRATA_SLOT_CKPT_MOVE=1 plus the pool. The benchmark binary was built from eeaf7b3c, before this commit, so the guard was not in it and not exercised.
  • The slot-choice change (pick_slot, 64e7f13) is split off to its own branch pr/pick-slot (4fcad010) because it changes every --batch setup, pool or not, and main now has its own rule for the same family (Concurrent requests break prefix cache #2518). It will be a separate PR. The two touch the same spot in serve/server.py, so whichever lands second needs a small rebase.
  • Docs: docs/BATCHING.md and docs/DETAILS.md (pool section, the 0.2.0-beta table, older figures labelled with their engine version). The "changed last question" figures (24.5 s / 13.3 s, 118,784 and 225,792 tokens reused) come from engine 0.1.44.1 with the pick_slot rule and are labelled as such.

Validation

Environment: codebox, 2 x RTX 3080 20 GB, CUDA 13.0, sm_86, gcc, Ninja. Benchmark: lm-server, 2 x RTX 3080 20 GB at 220 W, a 90 GiB container. All raw outputs are kept and can be attached.

Passed:

  • Builds at every commit that conflicted: ab83596b (full tree), then strata, kv_chunks_test, conversation_cache_test, conversation_snapshot_test, conversation_validation_test at fb493d3d, 24695028, 3ef8b4f2, 596077c0, bd4eb343, 96549a3e: ninja rc=0, 0 errors. Tip 9a5dcb4b full build rc=0, with warnings only in code this branch does not touch. Configure: cmake -DCMAKE_BUILD_TYPE=Release -DSTRATA_ENABLE_CUDA=ON -DSTRATA_NATIVE_EXPERTS=ON -DSTRATA_MMQ_KQUANTS=ON -DSTRATA_BUILD_TESTS=ON -DSTRATA_BUILD_CONVERSATION_TESTS=ON -DCMAKE_CUDA_ARCHITECTURES=86.
  • CPU tests (CUDA_VISIBLE_DEVICES=-1): kv_chunks_test; conversation_cache_test (4791 checks); conversation_split_failure_test; conversation_memory_test (23); conversation_file_test (201); conversation_validation_test (972, host only); conversation_transfer_test (1548, host only); session_save_reclaim_test (119); batch_groups_test; batch_rows_test; memory_guard_test (134); aux_cpus_test.
  • GPU tests, ctest -j1 over the named list at eeaf7b3c: 28 of 28 passed (qsa, qsa_slots, qsa_topk, rope, verify, spec_verify, verify_batch and batch_rows parity; kv_q8, kv_stream, kv_window, kv_hybrid, kv_q4 and kv_steps parity; conversation_snapshot_test with 5007 checks including the new pooled variant; the conversation validation, transfer, file, memory, split_failure and cache tests; session_save_reclaim; draft_kv_plan; batch_rows and batch_groups; memory_guard; aux_cpus; kv_chunks).
  • python -m unittest discover -s serve -t . on the tip: 897 tests, 14 skipped, passed. The serve/test_kv_pool.py and serve/test_slot_alloc.py modules (23 tests) also pass on their own.
  • Benchmark (binary strata-w14, sha256 41fd4f0300ebc5c93f6acf9e87d8b5523cb8b347f4c93fb59cc60ab0a5a1daf5, built from the code of eeaf7b3c; serve layer without the pick_slot rule; 8 engine starts, each from a clean restart; no kernel OOM kill; every needle check of a completed request correct):
    • Config: production json with STRATA_BATCH_MTP_SERIAL=1, parallel 2, --max-context 262144 --kv int8 --kv-resident 32768 --spec 4 --batch-mtp --conversation-cache-mib 4096 --conversation-cache-slots 3 --prefill auto:16384. Only the pool flag differs per arm. 0.2.0 defaults (STRATA_SM86_PERF, the Linux file tier, the cgroup RAM check that counts clean page cache, zero-copy page-cached experts) are on in all arms.
    • Memory: 3 starts with the pool, 3 without, idle MemAvailable 14.90 / 14.72 / 13.99 GiB against 10.93 / 11.66 / 11.54 GiB. Run minimum 10.4 GiB against 7.6 GiB (means). Start log: KV pool: 524288 cells shared by the sessions and the stages of the layer split, 6.24 GiB of pinned RAM.
    • Short context: ab.py solo 12, ab.py c2 10, read96.py after every start; numbers in Problem and result.
    • 256k, two lanes (lanes_n.py 2 245000): pool, 2 starts, all steps completed. Without the pool, start 1 completed, starts 2 and 3 are in Failed.
    • 133k, two lanes: pool 2 starts, no pool 1 start.
    • Three lanes with the pool (parallel 3, about 104k per lane, 2 starts): all answered, MemAvailable 8.3-8.6 GiB at the run minimum, slot moves 89-326 ms.

Failed:

  • Without the pool, the host memory guard (guard_host.sh, stops the engine when host MemAvailable falls below 1.5 GiB) stopped the engine twice during the 245k run: start A1nopool2 at 22:45:17 (host MemAvailable 1103 MiB, during the appended turn) and start A1nopool3 at 23:22:59 (1403 MiB, during the second short question). It was the guard, not the kernel OOM killer (oom_kill stayed 0). The 133k runs of those two starts have no data ("connection refused"). Never fired in a pool arm. This is a failure of the no-pool configuration on this box, recorded as a failure.
  • After the rebase serve.test_kv_pool test_a_prompt_longer_than_the_context_is_a_400_not_a_503 failed: main's fit_max_tokens default turned the 400 into an engine call. The test now sets the strict mode.
  • Once, conversation_snapshot_test failed under ctest while another process held about 19 GB on both GPUs; it passed (5007 checks) when run alone a minute later.

Not run:

  • HIP and SYCL builds (no ROCm or oneAPI on the machine). The pool's device code is plain integer arithmetic and I expect it to compile, but that is not a build. The SYCL tree carries its own migrated copies of the touched files and does not list the pool sources, so a SYCL build does not contain the pool.
  • Metal; Windows and the WSL2 cudaHostRegister path (the allocation goes through kv_host_alloc, not run).
  • Any run with STRATA_SLOT_CKPT_MOVE=1, and so the guard in 9a5dcb4b. STRATA_SLOT_COPY_DIRECT end to end (unit test only). borrow end to end (main restores it only without a layer split; not applicable to the 2-GPU config). Session-file SAVE/RESTORE with a pool end to end (unit test only).
  • No run with the pick_slot layer (pr/pick-slot). The benchmark turns only append to one lane's history, and I did not send a changed-last-question request. That the results do not depend on it is my inference.
  • Three lanes without the pool (the two-lane no-pool run was already at 7.4-7.9 GiB minimum MemAvailable, and about 3.1 GiB more per slot would have reached the 4 GiB abort line). Pool full paths (no kv_pool_full in the logs). 4, 6 or 8 lanes, a larger pool, a single 512K conversation.
  • The full ctest (150 tests) and the Python tests under tools/. Without the flag the default path is the identity layout and the parity tests pass, but I did not run a byte-for-byte output comparison against a build without this branch.
  • A pool-versus-no-pool comparison inside one process: the two arms of ab.py were identical configs and measure noise only.

Limits

  • The pool is memory and long-context lane handling, not decode speed. At short context nothing measurable changed.
  • The gain grows with the conversation: the move was about 8x faster at 133k and about 10x at 256k per lane (the no-pool side at 133k is one start). The memory saving at 2 lanes, 3.1 GiB, is real but modest on a 90 GiB box.
  • Measured on one box (2 x RTX 3080 20 GB, one model, one quantization). No-pool 256k timings are one full and two partial runs and may be inflated by the host being 1-2 GiB from the guard line.
  • N is the operator's choice. Too small a pool means refused requests or shortened answers.
  • Setup and launch estimate K/V RAM for one session and do not know --kv-pool-tokens; pass it by hand in args.
  • CUDA only was built. The MTP drafter ring is not pooled. STRATA_SLOT_CKPT_MOVE with the pool and the SYCL, HIP, Metal and Windows paths are unverified.

@noon-at-cgn

Copy link
Copy Markdown
Contributor Author

@xdbxdbx this is your #1011 rebased onto 0.1.41. GitHub closed #1011 automatically when main was force-pushed on 2026-10-06, and I could not find a refile, so I rebased it and added what I needed to run it on two cards: the pool across a layer split, K8V4 and session-file support, and one fix for a stale K/V reuse image. Your four commits keep your authorship and are first in the branch. If you would rather refile it yourself or change anything, tell me and I will adjust or hand it over.

@noon-at-cgn
noon-at-cgn marked this pull request as ready for review October 8, 2026 19:45
@noon-at-cgn noon-at-cgn changed the title One shared KV pool for all lanes (--kv-pool-tokens): two 256k-context lanes in one 512k-token pool on two 20 GB cards, KV memory follows the tokens in use One shared KV pool for all lanes (--kv-pool-tokens): two 256k lanes in one 512k pool, 3.1 GiB less pinned RAM at 2 lanes, no speed change Oct 9, 2026
noon-at-cgn added a commit to noon-at-cgn/Strata that referenced this pull request Oct 9, 2026
Niko1221#1637 and the four older ones), 0.1.41 batch-MTP, adaptive-tier and pool measurements
@noon-at-cgn noon-at-cgn changed the title One shared KV pool for all lanes (--kv-pool-tokens): two 256k lanes in one 512k pool, 3.1 GiB less pinned RAM at 2 lanes, no speed change kv: one shared KV pool for all lanes (--kv-pool-tokens), 3.1 GiB less pinned RAM at 2 lanes Oct 9, 2026
@noon-at-cgn

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (v0.1.44.1) and resolved the conflicts with the ported memory guard, batch MTP on a split and the adaptive tier. It is the same 14 commits in the same order; xdbxdbx's four keep their authorship. One extra change inside the existing reuse-image commit: main's new ConversationCache::clear() now drops the reuse identity as well, so a cleared cache cannot hand back stale K/V. I checked main's changes since the old base for new K/V host-addressing sites the pool does not translate and found none (the MTP drafters' rings stay outside the pool, as before). Built at the commits that had conflicts and ran the CPU, GPU parity and serve tests. I have not yet run the pool end to end on this version of main; I will add the result here.

@noon-at-cgn

Copy link
Copy Markdown
Contributor Author

Result of running the pool end to end on this branch, and a fix I added (three commits on top, head 6ea1d2a).

End to end (2 x RTX 3080 20 GB, layer split 23, parallel 2, --max-context 262144 --kv-pool-tokens 524288 --batch-mtp --adapt-async 1 --aux-cpus auto, 90 GiB container, upstream 296b59d + the 14 pool commits): the start log shows the 524,288-cell pool (6.24 GiB pinned), one batch group, two batch-MTP drafters and equal lend slots, with no WARNING or ERR over the run. Speed, one start, same A/A tools as before: solo decode 84.0 tok/s (n=24), two streams 91.3 tok/s (n=20), prompt reads at 25k/51k/104k 2,096 / 2,433 / 2,743 tok/s; the earlier build on 0.1.41 with the pool gave 81.7 and 89.2 tok/s over three starts, so this is equal within restart scatter. Two concurrent ~256k prompts with a needle each: both needles correct in both rounds, no KV pool full, container MemAvailable never below 11.2 GiB. Moving a 133k-token conversation takes 0.11 s (0.34-0.43 s at 256k) against about 1.8 s to give it back without the pool.

A problem I found and the fix. An appended turn (same history, the answer, a new message) reused the cache on both lanes, from 30k to 256k tokens. A prompt with the same prefix but an edited last message reused nothing with the pool on: admitting a conversation into a slot dropped main's periodic checkpoint chain and kept only the last-turn checkpoint, which lies after the edit. The fix has two parts, in separate commits:

  • fdbcf102 (engine): the chain moves with the K/V in both directions. 2 lanes x ~133k, edited last question: 0 / 0 reused before; 118,784 on both lanes after, first token after 24.5 s and 13.3 s instead of 142 s and 53 s. At 256k per lane both lanes reuse 225,792 only with --conversation-cache-mib of at least 4528 (4608 tested). At 4096 one lane reads all 256k again.
  • 64e7f138 (serve): pick_slot treats a prompt that shares at least 3/4 of a slot's held tokens (and at least 1024) as that conversation's next turn. Before, the first request of an edited round took the other lane's slot and destroyed its conversation. This commit also applies with the pool off, and the 3/4 threshold is my judgment; if you would rather have it separate or tuned, I can move it.
  • 6ea1d2a4 (docs): what is reused and what it costs.

Costs and limits: each cached slot keeps up to 6 checkpoints, about 0.7 GB of host RAM per slot. An edit that lies below the first periodic checkpoint reuses nothing (30k tokens: 0 on both lanes; 60k: 31,232). Not measured: speed with the fix against without it, three or more lanes, pool-off at 256k. The CPU, GPU parity and serve tests pass; the C++ change was tested on the engine only in the runs above.

@Niko1221

Copy link
Copy Markdown
Owner

A memory feature with no speed claim (the author measured no change); handed to the cache review.

ggeorgovassilis added a commit to ggeorgovassilis/Strata that referenced this pull request Oct 17, 2026
- fix two PRs wrongly listed as already in main (Niko1221#1548/Niko1221#2224, Niko1221#1614)
- correct Niko1221#1358's merge state (dirty, not cleanly mergeable)
- add an implementation plan with one run command per phase for
  reading prompt/generation tok/s
@Niko1221

Copy link
Copy Markdown
Owner

Thanks for the pull request.

We're resetting the tracker for 0.2.0-beta. There are more than 1,300 open issues and pull requests, too many for us to review properly, so we're closing everything that isn't being worked on right now. Closed here does not mean rejected.

To reopen it takes about two minutes:

  1. Rebase on main if it no longer applies, and re-run your checks on 0.2.0-beta.
  2. Start the title with its area: [Engine], [GUI], [TUI], [Benchmark], [Docs] or [Other].
  3. Fill in the pull request template: the problem and result, and the validation (what you ran, on which revision, what you saw).
  4. Comment /reopen. A check runs and either reopens the pull request or lists what is still missing.

The rules are in CONTRIBUTING.md.

@Niko1221 Niko1221 closed this Oct 19, 2026
xdbxdbx and others added 15 commits October 19, 2026 21:44
With KV streaming (--kv-resident) every session pins the K/V of the
whole context for each QSA layer, so N batch slots plus the main session
pin (N + 1) x the context even when the conversations together hold
little of it. --kv-pool-tokens N pins N cells per QSA layer once; a
session holds only the chunks (4096 cells) its conversation has reached.

- A per-session chunk table (device copy for the kernels, host copy for
  the DMA movers) maps a session's blocks into the pool; unreserved
  entries point at a trash chunk. Without a pool the layout is the
  identity, as before.
- Lanes grow as they write. When the pool is full an idle slot gives its
  K/V back (one holding nothing first, then the cached conversation used
  longest ago); then a new request is refused ("ERR KV pool full", which
  the server answers 503 with Retry-After); a decoding lane that cannot
  grow ends ("length", with "truncated": true in the response).
- After a slot takes a conversation over, the main session gives its
  copy back.
- The engine reports the pool on a POOL line; /metrics shows it as
  live.kv_pool.
- The batch slots borrow the main session's RoPE table (64 MiB each at
  262K).

Single GPU only: refused with a layer split.

Measured, IQ2_XS, RTX 4090 + 64 GB RAM, --resident-experts, 4 slots x
262144 cells, --kv-pool-tokens 524288: the pool pins 6.6 GB (5 x 3.3 GB
without it); 4 lanes of 512 tokens 83 t/s total; two lanes at 134K and
143K tokens deep at once; available RAM never below 15.1 GB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ported from upstream PR Niko1221#1011 by xdbxdbx (commit 66082ec), re-applied onto the current main line.
…ing pool chunks

Admission and a slot giving a conversation back exchange the two sessions'
chunk tables instead of copying the streamed K/V, so the pool never holds a
conversation twice (a 104K-token admission: 52 ms instead of ~1.1 s copied).
The prompt read that gives way to a waiting request (BYIELD) still copies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ported from upstream PR Niko1221#1011 by xdbxdbx (commit 28ed67b), re-applied onto the current main line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ported from upstream PR Niko1221#1011 by xdbxdbx (commit 3c3e736), re-applied onto the current main line.
…3 paths

- A drafter streamed whole (its window covers the context) pins its own host
  copy; without one the conversation cache refused every snapshot.
- A chunk-table upload completes before the next launch reads it (the kernels
  run on non-blocking streams).
- pool_reserve gives nothing back when all the idle slots together would not
  cover the request, and a prompt read that gave way (BYIELD) gives way last
  instead of never.
- The pool's refusal is a 503 with Retry-After on non-streamed /v1/responses
  too; in a stream it carries code kv_pool_full (Anthropic: overloaded_error).
- A solo request that ends with finish "pool" ends there; it is not resent to
  a batch slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ported from upstream PR Niko1221#1011 by xdbxdbx (commit 2040789), re-applied onto the current main line.
…k table; a session RESTORE reserves its chunks

Three host-addressing sites that did not exist (or did not stream) when Niko1221#1011 was written:
- kv_hybrid_k_half/kv_hybrid_v_half build a fresh KvHostPools from the arrays only, which dropped the chunk table:
  a pooled --kv k8v4 layer wrote its cells at their session block numbers, into other sessions' chunks.
- conversation_kv_source (the SAVE <path> session file) read the authoritative pools linearly; it walks the chunks now
  (each_piece, as conversation_kv_save/restore/verify do).
- RESTORE <path> restored the file's K/V into the main session before any chunk of it was reserved (everything past
  the reservation went to the trash chunk): it reserves the file's cells first and refuses, retryable, when the
  lanes hold the pool.
configuration the pool is for here, so a lane (the main session or a batch slot) now spans the stages:

- KvChunkMap (kv_chunks.hpp/.cpp): the chunk bookkeeping on its own (free list, per-lane tables, reserve/shrink/swap),
  no CUDA, so it has a CPU test (tests/core/kv_chunks_test.cpp).
- KvPool: init takes every stage's reference session (each stage pins its own QSA layers' arrays, in its GPU's
  context); attach opens a lane with its stage-0 session, attach_stage adds the session of a later stage with a copy of
  the same table in that GPU's memory; detach closes a lane before its sessions go. Reserve/shrink/release/swap act on
  the lane, named by any of its sessions, and upload the table to every stage.
- generate.cpp: the pool is created once every stage's session exists; the slot sessions join their lane per stage;
  pool_move swaps the lane's tables once and copies/resets the indexer and residency of every stage; the slot copies
  skip the streamed layers of every stage (the pool carried them), not only CUDA0's.
…unk tables

- kv_pool_policy.hpp: kv_pool_eviction_plan (lowest rank first, the longest unused, the lower slot; nobody is asked
  when all that can give way together would not cover the request); pool_reserve in generate.cpp acts on its answer.
- KvHostPools::for_each_piece: the walk that cuts a byte range where a chunk ends, moved out of conversation_snapshot.cpp
  so the test can check every byte against the block mapping.
- tests/core/kv_chunks_test.cpp (target kv_chunks_test, no CUDA): KvChunkMap rules and a random walk of
  reserve/shrink/swap that checks every chunk is free or held exactly once, the host mapping, the eviction plan.
…s borrow RoPE only from a stage that has QSA layers

--pipeline-windows launches windows ahead of each other and never reserved chunks per window like the serial loop
does (everything past the prompt's reservation would have gone to the trash chunk): it reserves max-context up front
and decodes serially when the pool cannot back it (it runs without --batch, so no slot has to give way).
…ation it describes

With the shared KV pool, an admission move clears main's live conversation but left the reuse image retained at a
cache restore.  limit_reuse() kept it for the next request that resumed from another slot, and the next park reused
its bytes for cells of a different conversation (silent wrong K/V in a parked entry).  The reuse image now carries the
token ids and image keys of its conversation; take_reuse() hands it back only for the prefix both conversations
share, and the paths that move a conversation out of main drop it.  Test: conversation_cache_test.
… (RAM, speed, restarts; what was not measured)
With --kv-pool-tokens an admission moved the K/V to the slot but left it only the deepest checkpoint (the turn
checkpoint) and emptied the main session's chain; a give-back to the solo path dropped even that one. A later turn
that changed the last question (or re-rendered the reply) has no checkpoint below the change except the periodic ones,
so both lanes of two long conversations read everything again. The chain now travels with the chunks both ways, and
an uncached slot drops its chain.

Measured on 2 lanes x 133k with a changed last question: both lanes reuse 118,784 tokens (time to first token 24.5 and
13.3 s) where the unfixed binary reused 0 (52 and 142 s).
The pool section said a changed last question resumes from a checkpoint but not where that stops. Measured with two
lanes: an edit below the first periodic checkpoint reuses nothing (30k: 0, 60k: 31,232); 133k per lane works with the
default 4096 MiB conversation cache, 256k per lane needs at least 4528 MiB (4608 tested); each cached slot now keeps up
to 6 checkpoints, about 0.7 GB of host RAM per slot.
…the direct copy and the prefix restore through a shared pool's chunk tables

A second streamed state (its chunks interleaved with the first's) shares the host arrays; every K/V format the streamed
test covered runs once more with the host copy in chunks, and checks that a neighbour's writes stay out of a state's chunks.
…e while the pool moves chunks

STRATA_SLOT_CKPT_MOVE (opt-in) lets a request mount a checkpoint from a slot that is still decoding. With --kv-pool-tokens the
slot's chunk table would move to the main session and be released under the slot's feet, so the pool keeps the idle-slot rule.
…s at 104k; what was not run)

Same binary with and without the flag on 2 x RTX 3080: a ~256k conversation
moves in 0.36-0.5 s instead of 3.5-5.2 s copied, 3.2 GiB more idle MemAvailable,
no short-context speed effect. Older 0.1.41 / 0.1.44.1 figures are labelled as
older. States that the binary was built from eeaf7b3 and that the serve layer
had no pick_slot rule.
@noon-at-cgn noon-at-cgn changed the title kv: one shared KV pool for all lanes (--kv-pool-tokens), 3.1 GiB less pinned RAM at 2 lanes [Engine] shared KV pool for all lanes (--kv-pool-tokens): 256k lane moves 10x faster, 3 GiB less pinned RAM Oct 20, 2026
@noon-at-cgn

Copy link
Copy Markdown
Contributor Author

/reopen

@github-actions

Copy link
Copy Markdown

The check passed, but GitHub would not reopen it (Validation Failed: ). If the branch was deleted, restore it or open a new pull request and link this one.

@noon-at-cgn

Copy link
Copy Markdown
Contributor Author

/reopen

@github-actions github-actions Bot reopened this Oct 20, 2026
@github-actions

Copy link
Copy Markdown

Reopened, thank you. A maintainer will pick it up from here. If it conflicts with main, rebase it first; that speeds up review.

@noon-at-cgn
noon-at-cgn marked this pull request as draft October 20, 2026 04:30
bsorensen110 added a commit to bsorensen110/Strata that referenced this pull request Oct 22, 2026
…PoolFull 503) — integ batch 19 [23 hunks via claude-strata; integ's take_reuse_for/retained_n flow supersedes PR reuse site; copy_to_slot 4-arg; NUMA binding ported into qsa_host_alloc; compile-verify pending]
bsorensen110 added a commit to bsorensen110/Strata that referenced this pull request Oct 22, 2026
- conversation_state.cpp: drop the integ-era checkpoint_validate copy and the duplicated
  snapshot_sources wrapper left side-by-side by the Niko1221#3341/Niko1221#3541 merges; restore the
  checkpoint_payload_sizes helper (Metal-aware) that the dedup slice clipped
- conversation_cache.hpp: re-apply Niko1221#3615's min-tokens parser + READ gate, clobbered by a
  later header-side merge
- generate.cpp: single parked Match (the Niko1221#3584 best_with_partial + Niko1221#3615 read gate wins;
  slot_source=-1 moves after it), carry_chain declared for Niko1221#1614's chain-carry in the
  integ move_cks block, one splice-aware read_from, 'moved' hoisted to the trace's scope
- CMakeLists: the WIN32 ws2_32 link for stage_link.cpp sat inside the HIP-only guard

Verified: cmake --build build-cu130 clean, strata.exe links
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants