Repository navigation
feat(kvcache): host-resident prefix tier for hybrid GDN models (snapshots + KV pages) - #525
alvarorsouza-arch wants to merge 3 commits into
Conversation
- mirror_store_quant (new Triton kernel, graph-safe UVA): the write-through mirror now copies e4m3 codes + fp32 scales read from the pool AFTER quantization (the pool is the source of truth). The bf16-era mirror corrupted fp8 pages silently on rehydrate. - mirror_scales host bank + fused-copy descriptor extended with the scale banks (per-bank row sizes). - set_select_width takes max_bs: the whole batch selection must fit the physical pool by construction (fixes assert-crash on multi-row decode with small pools). - measured: 299,648 logical tokens with kv-cache-dtype fp8 + kv-host-pages, needle at 253k tokens answered correctly (~1026 t/s), quality suite identical to baseline.
Prefix reuse on hybrid GDN models needs KV pages AND a GDN state snapshot alive at the match point; the small global snapshot pool gets drained by interleaved auxiliary calls (agent title generators) and every match truncates to zero. Measured on real WorkBuddy agent turns: TTFT 20-150s recompute per message. This adds a host-resident tier: evicted GDN snapshots and KV prefix pages move to pinned RAM instead of dying. Matches rehydrate from host in ~4s (24k-token prefix) instead of a full recompute. - HybridRadixCache hooks: on_evict_node / on_match_dead_snapshot; evicted leaves become tombstones instead of cascade-freeing KV - HostPrefixTier: pinned GDN snapshot bank + cold-prefix index with its own KV slot space (logical ids get reused by the scheduler, so content is copied D2H on eviction, never referenced by id) - Admission rehydration; with KVHostOffloader active the copy is host-to-host into its mirror (no bulk H2D; lazy fetch covers residency) - Eviction copies run in 128-page slices (staging the whole prefix OOMed exactly under memory pressure, which is when eviction runs) Measured: churn test cached-token 0 -> 23,936 (16.5s -> 3.8s); total pool flush rehydrates at 24,000 cached tokens in 4.4s; quality suite identical (39/40 QA, 30/30 cloze); decode steady 33-37 t/s. All off by default (FT_GDN_HOST_TIER / FT_PREFIX_HOST envs).
…ybrid GDN models (snapshots + KV pages)
…cales they cannot mirror Upstream FlashML-org#499/FlashML-org#525 mirror KV codes plus the fp8 pool's per-token _scale_buffer; the nvfp4 pool (FlashML-org#408) keeps per-block scales in _block_scale_buffer, which neither the offloader nor the prefix tier copies, so --kv-host-pages or FT_PREFIX_HOST would rehydrate codes without scales. Both constructors now raise a clear error naming the knob; the GDN-snapshot-only tier (FT_GDN_HOST_TIER) never reads the KV pool and stays usable. Assisted-by: Claude Fable 5.1
…ybrid GDN models (snapshots + KV pages)
…cales they cannot mirror Upstream FlashML-org#499/FlashML-org#525 mirror KV codes plus the fp8 pool's per-token _scale_buffer; the nvfp4 pool (FlashML-org#408) keeps per-block scales in _block_scale_buffer, which neither the offloader nor the prefix tier copies, so --kv-host-pages or FT_PREFIX_HOST would rehydrate codes without scales. Both constructors now raise a clear error naming the knob; the GDN-snapshot-only tier (FT_GDN_HOST_TIER) never reads the KV pool and stays usable. Assisted-by: Claude Fable 5.1 (cherry picked from commit 4bb35fc)
|
A measurement that bears on this tier: a client's compaction replays a long conversation up to the tail it keeps, then its instruction, and on a hybrid model that resumes only where a snapshot survives. Serving Qwen3.8-Flash-Next at a 1M window, a 760,534-token replay of a cached 985,577-token prompt prefilled everything again, 798.9 s, because no snapshot below its end existed: the final chunk's is the only one a prompt keeps. Moving evicted snapshots to host memory, as this PR does, keeps the ones that were made; the replay also needs the intermediate chunks' snapshots to be made at all. #569 describes a patch that does both, keeping the residency on the radix node rather than in a second index, and what it measured (12.3 s for the same replay). I'd be glad to fold it into this work instead if that suits you better. |
Note: stacked on #499 (KV host-offload). The GDN-snapshot tier works standalone; the KV-page tier composes with the offloader.
Problem
On hybrid GDN models (qwen4_exp), prefix reuse needs two things alive at once: the KV pages in the radix tree and a GDN state snapshot at that point. The snapshot pool is small and global LRU. Any auxiliary call (WorkBuddy's title generator is our real-world case) drains the main conversation's snapshots between turns, and the match truncates to zero. Measured before the patch: agent turns with 20 to 150 s TTFT. After: stable single-digit seconds.
What the patch does
HybridRadixCache:on_evict_node(fired before unlink/free) andon_match_dead_snapshot(fired during the match walk). With the tier on, an evicted leaf becomes a tombstone and its KV is no longer cascade-freed.HostPrefixTier(new,kvcache/host_prefix_tier.py): a pinned host bank of GDN snapshots plus an index of cold prefixes with its own KV slot space. Scheduler logical page ids get reused, so content is copied D2H on eviction and never referenced by logical id afterwards.Flags:
FT_GDN_HOST_TIER=1,FT_GDN_HOST_SLOTS=32,FT_PREFIX_HOST=1,FT_PREFIX_HOST_GB=16. All off by default.Measured numbers (RTX 4070 Ti SUPER 16 GB, 251 GB RAM, PCIe Gen4)
Churn test (24k-token conversation, 10 auxiliary calls in between, then back):
Total flush test (two ~130k-token prefills through a 200k pool; every page of the conversation leaves the GPU): the conversation comes back with cached-token 24,000 in 4.4 s.
Regressions: our quality suite (40 known-answer questions + 30 cloze) is identical to baseline (39/40 both). Decode steady at 33-37 t/s.
Known limitations
Files
kvcache/host_prefix_tier.py,tests/kvcache/test_host_prefix_tier.py,tests/kvcache/test_tree_hooks.py,tests/kvcache/test_tier3.pykvcache/hybrid_radix_cache.py,scheduler/cache.py,engine/config.py