Skip to content

native cuda: TF_KV_SHARE=n keeps a paged family's prompts as its core pool's pages in one prompt_radix store (Kolibri-1 first) - #1755

Open
jschmied wants to merge 6 commits into
ashhart:mainfrom
jschmied:kv-share-radix
Open

jschmied wants to merge 6 commits into
ashhart:mainfrom
jschmied:kv-share-radix

Conversation

@jschmied

@jschmied jschmied commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Part of #1657: the native-CUDA adapter over prompt_radix, off by default, with Kolibri-1 as its first user. Stacked on #1659 (merged) and on #1620.

Stacking. The first four commits are #1620's, cherry-picked unchanged onto today's main. #1619's Gemma commits are not needed for them. GitHub shows this PR as conflicting because #1620 conflicts with #1653, which is now on main. Once #1620 is rebased, I'll restack this PR on it. The last two commits are this PR:

What it does

  • TF_KV_SHARE=n turns it on for a family on the core pool that declares share_pool and share_attach. That family gets one prompt_radix.Store over its pool (native/cuda_prompt_pages.zig).
  • The tree holds the prompt pages themselves. The family's snap_* decls supply the state past a page edge, and n caps those states.
  • The host makes no prompt_cache store, and the cache budget becomes pool pages.
  • The family calls core/kv_prompts.zig:
    • begin: the resume point, adopted pages and the planned grid's marks;
    • keep at each mark;
    • report.
  • A kept state's ring rows go in pool pages the store holds (Pool.hold). hold asks the store to reclaim first, as promise does.
  • Unset or 0: as before.

Gates (GB10, Kolibri-1)

gate result
tf-cuda-test kolibri-pages, final binary TF_KV_SHARE=4: PASS twice (and once more on the head before the rebase onto today's main), resume at 1024 plain and drafted, reply and logits same as linear. Unset: PASS
chat suite, 64 turns (content + reasoning) nocache / copy / share: 64/64 equal. Cached tokens: copy 230,719, share 218,112
long chat, 48 turns 48/48 equal. Cached tokens: copy 228,065, share 221,184
capacity, round-robin, 4 GiB, 72 turns 72/72 equal. Copy store: 24 states, 140,500 cached tokens. Share: 76 states, 159,744 cached tokens, failed 0
evictions, round-robin, 1 GiB 72/72 equal. Copy: 7 states, 3,479 cached tokens. Share: 40 states, 81,920 cached tokens, evicted 56, failed 0
v.sh (lean 0 new, zig build test, CI set, diff-check) green on the final head

Share misses are the first turns: the system prompt is shorter than one 512-token page.

Default path (TF_KV_SHARE unset, --prompt-cache-gib 0). Base (the stack without these two commits) vs this head, fresh server per arm, ABBA over 2 rounds (4 at 4 streams), aggregate tok/s, greedy token_sha equal in every cell:

streams delta of medians (chat/code, T 1.0/greedy)
1 +0.05 / +0.15 / +0.21 / −0.32 %
4 −0.14 / −0.00 / −0.05 / +0.03 % (4 rounds: rounds 1–2 had head −0.3..−0.9 %, rounds 3–4 +0.1..+0.4 %)
8 −0.08 / −0.01 / +0.17 / +0.05 %

The default path runs no new code in the step: the store is absent and the branches are on prompts == null.

Left. Gemma 4 wiring, after #1619.

…ry stream leasing; sliding layers keep their rings

Pages of 512 rows (tree attention's key groups). Rows reach the pages by tf_wrows_put, prompts read them by
new tf_window_prompt_h4_pg/_h8_pg twins, decode and drafted verify by tree attention's pages with a table in
the call's stream order. Kept states walk the slot's pages. TENSORFOLD_KOLIBRI_KV_PAGES=0 or kv_pages = false
keeps linear caches. tf-cuda-test kolibri-pages compares both.

(cherry picked from commit 0e5f0a8)
…_h8_pg) over shuffled pages with a spare-page control; their records measured

(cherry picked from commit c2971c3)
…ey (tree attention and head dim 512 _pg kernels), SASS of the others unchanged

(cherry picked from commit 58a8cfb)
… pool's pages, in one prompt_radix store

A family on the core KV pool that declares share_pool and share_attach gets one prompt_radix store over that
pool (native/cuda_prompt_pages.zig): the tree holds the prompt pages themselves, a page edge's state past them
comes from the family's snap_* decls, and n caps those states. The host makes no prompt_cache store then, and
the cache budget becomes pool pages (Budget.kept_ppm). core/kv_prompts.zig is what the family calls: begin
(resume point, adopted pages, marks), keep at a mark, report, and Blob, a state's copied rows in pages the store
holds. Pool.hold takes those pages, asking the store to reclaim first as promise does. Unset or 0: as before.
…state's rings in store-held pages

With the shared store a prompt pass begins at the store's resume point: the matched pages adopted into the
stream's lease, its sliding rings restored there from the kept state. Chunks cut at the store's marks (page
edges, 512 tokens); at each the store takes the pages it lacks, the stream's copies swapped for the tree's,
and the state's rings go into pool pages the store holds (snap_bytes counts those). Unset: as before.
tf-cuda-test kolibri-pages with TF_KV_SHARE=4 resumes a kept prompt plain and drafted, equal to linear.
@ashhart

ashhart commented Oct 31, 2026

Copy link
Copy Markdown
Owner

Heads-up @jschmied: #1756 is merged (6d3fa01), so main has the native CUDA TF_KV_SHARE adapter, and this PR's adapter commit is already there. What's left here is Kolibri's own wiring on top of #1620. When #1620 is ready we'll take them together.

@ashhart

ashhart commented Nov 1, 2026

Copy link
Copy Markdown
Owner

Thanks @jschmied. The share receipts are strong: 76 kept states against 24 at 4 GiB, and 81,920 cached tokens against 3,479 at 1 GiB, with every turn equal. Since #1756 put the adapter on main, this PR is now Kolibri's wiring on top of #1620. When you restack: 1. Drop the adapter commit. Main has it (40c03bf13). 2. #1620 turns Kolibri's pages on by default, so it needs its own sweep against main. When we parked it on 30 Oct, decode was 0.6-2.1% slower. If the once-a-tile page read fixes that, a decode sweep at 1 to 64 streams against main, level or better, settles it. The default-path table here is against #1620's stack, so it doesn't cover that. 3. With TF_KV_SHARE set, three of #1657's gates: concurrent equal to solo at 1, 2, 4 and 8; the lane pages at idle the same before and after shedding; and the cache bytes for each kept state before and after.

Then we'll run both on our GB10s and take them together.

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.

2 participants