Unified Radix Cache in SGLang: one tree for hybrid model prefix caching

Radix Cache is a major component in SGLang for cross-request cache reuse. Nowadays models apply hybrid attention mechanisms, but the traditional Radix Cache is only designed for full attention KV. Therefore we designed SGLang's Unified Radix Cache for hybrid attention mechanisms. Multi-turn hit rates stay near 98% with a Mooncake L3 tier. The session-aware configuration lowers SWE-bench TTFT by up to 16.6%. The Rust tree core lowers SWA TTFT by up to 42%.

Adapted from the LMSYS article on Unified Radix Cache.

Background

Prefix caching reuses KV when requests share a token prefix. Under full attention, a later request can reuse the cached prefix and compute only the new tokens.

Hybrid models combine three kinds of data with different reuse rules: FULL (full attention) needs KV along the matched prefix, SWA (sliding window attention) needs a continuous window immediately before the reuse boundary, and MAMBA needs a snapshot of the recurrent state, or checkpoint, at that boundary.

Reuse semantics diagramCandidate boundary t8 · SWA window W = 4 · gray cells are outside the required range
Matched token prefixt1t2t3t4t5t6t7t8t9t10t11t12
FULL · whole path
SWA · required window
MAMBA · exact checkpoint

DeepSeek-V4 pairs FULL with SWA, Kimi-K3 pairs FULL with MAMBA for its KDA recurrent state, and Inkling uses all three. SGLang handles these combinations on one shared tree, with each component defining its own reuse rule.

Class matrix vs components diagram
Before: one specialized cache class per combination × capability · 2 in total
FULLmatchinsertlockevict
FULL + SWAmatchinsertlockevict
After: one tree plus pluggable components · components ×2
UnifiedTreeCore: shared matching, split, insert, lock, evict mechanicsUnifiedRadixCache: pool orchestration
Components:FULLSWAsidecar ×N

Unified Radix Cache

Design and mechanism

Each radix node stores a token span and a slot for each component. FULL and SWA slots hold KV page indices; MAMBA holds a checkpoint at the prefix endpoint. SWA data outside the required window can stay cached or be evicted independently, leaving an empty slot called a tombstone.

Components handle their own splits, inserts, locks, and evictions. Reclaiming part of a node’s SWA data starts by splitting the node at the retention boundary. A MAMBA checkpoint stays at its original prefix endpoint; a request reusing it obtains a private copy through copy-on-write, then continues updating its state.

Unified radix tree replayWindow W = 4 · one token per cell · earlier CSFA retained after request 2
Request 1 · token streamABCSFA
rootreuse boundary: 0
t = 0/8

The safe reuse boundary

Matching walks the matched path, and each component checks its data at every candidate boundary. Each candidate is evaluated independently. Computation resumes from the deepest boundary accepted by all components.

Reuse-boundary voting diagramScenario: n3 has tombstoned SWA window slots; n4 has no MAMBA checkpoint
n1
n2
n3
n4
FULL
SWA
MAMBA
▲ Safe boundary▲ Safe boundary▲ Safe boundary▲ Safe boundary
Reused directlyRecomputed from here
t = 0/5

HiCache across memory tiers

HiCache extends Unified Radix Cache across GPU L1, host memory L2, and external storage L3. See HiCache in SGLang for its tiering and data transfers.

Components and sidecars

DeepSeek-V4’s FULL and SWA use separate page indices, mapped by the allocator. Auxiliary pools for compressed KV, indexer buffers, and compressor states act as sidecars: they share their component’s indices and transfers. Three follow FULL and two follow SWA.

Index reuse diagramThe blog's normalized six-page case · only the final two SWA pages retained here
FULL pages (component)
sidecars ×3 follow FULL
↓↓allocator translates
SWA window slots (component)
sidecars ×2 follow SWA
Page 4: FULL's F4 and its three sidecars share page number 4. The allocator translates F4 to SWA's S0, and SWA's two sidecars copy page number 0.

Multi-turn benchmark

Multi-turn conversations keep extending the reusable prefix. A comparison of L1 only, L1+L2, and an additional 500 GiB Mooncake L3 tier shows DeepSeek-V4-Flash reaching 145.5K effective input tokens/s with L3, up from 9.4K with L1 only, with average TTFT below 9 seconds.

Multi-turn benchmark results diagramEffective input throughput = total full prompt length ÷ wall-clock time; cache-hit prefix tokens count
DeepSeek-V4-FlashFULL+SWA · 4×H200 TP4 · 48 clients · 60 rounds · 4,096 in + 16 out per turnEffective input throughput (tokens/s)
L19.4K
L1+L214.3K
L1+L2+L3145.5K
Cache hit rate by round
Hit rate = cached prefix tokens ÷ complete prompt tokens, summed per round. Curves use approximate samples read from the source figure.0%25%50%75%100%0102030405055Round (zero-based)L1+L2+L3L1+L2L1Round (zero-based) 0 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈0%Round (zero-based) 5 L1: ≈83% L1+L2: ≈83% L1+L2+L3: ≈83%Round (zero-based) 10 L1: ≈91% L1+L2: ≈91% L1+L2+L3: ≈91%Round (zero-based) 15 L1: ≈94% L1+L2: ≈94% L1+L2+L3: ≈94%Round (zero-based) 20 L1: ≈0% L1+L2: ≈95% L1+L2+L3: ≈95%Round (zero-based) 25 L1: ≈0% L1+L2: ≈96% L1+L2+L3: ≈96%Round (zero-based) 30 L1: ≈0% L1+L2: ≈97% L1+L2+L3: ≈97%Round (zero-based) 35 L1: ≈0% L1+L2: ≈97% L1+L2+L3: ≈97%Round (zero-based) 40 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈98%Round (zero-based) 45 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈98%Round (zero-based) 50 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈98%Round (zero-based) 55 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈98%
Final metrics with L3hit rate~98%avg TTFT< 9 s
Inkling-SmallFULL+SWA+MAMBA · 8×H200 TP8 · 64 clients · 30 rounds · 1,216 in + 64 out per turnEffective input throughput (tokens/s)
L115.5K
L1+L221.1K
L1+L2+L367.1K
Cache hit rate by round
Hit rate = cached prefix tokens ÷ complete prompt tokens, summed per round. Curves use approximate samples read from the source figure.0%25%50%75%100%051015202529Round (zero-based)L1+L2+L3L1+L2L1Round (zero-based) 0 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈0%Round (zero-based) 1 L1: ≈6% L1+L2: ≈51% L1+L2+L3: ≈51%Round (zero-based) 2 L1: ≈0% L1+L2: ≈68% L1+L2+L3: ≈68%Round (zero-based) 3 L1: ≈0% L1+L2: ≈76% L1+L2+L3: ≈76%Round (zero-based) 4 L1: ≈0% L1+L2: ≈81% L1+L2+L3: ≈81%Round (zero-based) 5 L1: ≈0% L1+L2: ≈84% L1+L2+L3: ≈84%Round (zero-based) 6 L1: ≈0% L1+L2: ≈86% L1+L2+L3: ≈86%Round (zero-based) 7 L1: ≈0% L1+L2: ≈88% L1+L2+L3: ≈88%Round (zero-based) 8 L1: ≈0% L1+L2: ≈89% L1+L2+L3: ≈89%Round (zero-based) 9 L1: ≈0% L1+L2: ≈90% L1+L2+L3: ≈90%Round (zero-based) 10 L1: ≈0% L1+L2: ≈91% L1+L2+L3: ≈91%Round (zero-based) 11 L1: ≈0% L1+L2: ≈92% L1+L2+L3: ≈92%Round (zero-based) 12 L1: ≈0% L1+L2: ≈93% L1+L2+L3: ≈93%Round (zero-based) 13 L1: ≈0% L1+L2: ≈93% L1+L2+L3: ≈93%Round (zero-based) 14 L1: ≈0% L1+L2: ≈94% L1+L2+L3: ≈94%Round (zero-based) 15 L1: ≈0% L1+L2: ≈94% L1+L2+L3: ≈94%Round (zero-based) 16 L1: ≈0% L1+L2: ≈94% L1+L2+L3: ≈94%Round (zero-based) 17 L1: ≈0% L1+L2: ≈95% L1+L2+L3: ≈95%Round (zero-based) 18 L1: ≈0% L1+L2: ≈62% L1+L2+L3: ≈95%Round (zero-based) 19 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈95%Round (zero-based) 20 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈95%Round (zero-based) 21 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈96%Round (zero-based) 22 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈96%Round (zero-based) 23 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈96%Round (zero-based) 24 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈96%Round (zero-based) 25 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈96%Round (zero-based) 26 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈96%Round (zero-based) 27 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈97%Round (zero-based) 28 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈97%Round (zero-based) 29 L1: ≈0% L1+L2: ≈0% L1+L2+L3: ≈97%
Final metrics with L3hit rate96.8%avg TTFT1.23 s

Session-aware eviction

Applications use a stable session_id to identify requests from the same conversation. SGLang tracks that session’s references to cached entries. Calling /close_session removes its references; shared prefixes keep those from other sessions.

FULL orders unlocked, eligible entries by reference presence, reference count, and then the base eviction policy, usually reclaiming unreferenced data first. Session retention applies to L1/L2; the storage backend manages L3 eviction.

Session-aware eviction diagramFULL entries · unlocked and eligible for eviction · AB is shared by A and B
Ordinary LRU
A1ABB1C1C2recently used
Session-aware eviction
A1A · activeABA + B · 2B1B · activeC1unreferencedC2unreferenced
A and B retain session references, AB is shared by both, and C1/C2 are unreferenced.
t = 0/5

SWE-bench results

On SWE-bench, enabling Unified Radix Cache together with session-aware eviction lowers TTFT by up to 11.0% on DeepSeek-V4-Pro and 16.6% on Qwen3.5-397B-A17B, compared with HiRadixCache and LRU.

SWE-bench session-aware results diagram
DeepSeek-V4-Pro · TP8TTFT reduction vs baseline
batch 128−11%
batch 256−2.9%
Hit-ratio changebatch 128 · device hits 42% → 51%
Qwen3.5-397B-A17B · TP8TTFT reduction vs baseline
batch 32−13.5%
batch 64−16.6%
Hit-ratio changebatch 32 · device hits 5% → 34%batch 64 · device+host hits 58% → 67%

Rust tree core

SGLang runs tree traversal, lock bookkeeping, and LRU updates in Rust to reduce their CPU overhead on the scheduler. Python handles physical KV allocation and cache orchestration, applying the operations Rust returns to the pools.

Rust and Python ownership diagramOwnership of tree structure and pool operations
Python owns
  • request-to-token mappings
  • physical KV allocation
  • pool ops and orchestration
match / insert / evict calls→←deferred actions
Rust tree core owns
  • radix topology
  • per-component lock accounting
  • intrusive LRU lists
  • eviction walks

The L1-only Rust prototype #29074 reduced TTFT by 38% for SWA, 10% for full attention, and 5% for hybrid SSM against the Python tree in a 200-turn conversation benchmark.

Rust prototype benchmark diagramPrototype #29074 · L1 only · 200 turns · 100 in + 100 out per turn · 6 trials · sequential runs on the same GPUs
SWA gpt-oss-20b · TP2
All 200 turns−38%
Turns 176–200−42%
Full attention Qwen3-32B · TP2
All 200 turns−10%
Last 25 turns−18%
Hybrid SSM Qwen3-Next-80B-A3B · TP4
All 200 turns−5%
Last 25 turns−7%

The subsequently merged Rust TreeCore #32710 supports FULL, SWA, and MAMBA combinations and HiCache. Session-aware caching continues to use the Python tree core.

Try on SGLang Open the SGLang repo and run this in a real inference engine

Further reading

This visualized tutorial was written by Yichi Zhang.