A co-designed frontend DSL + serving runtime for multi-call LLM programs. The runtime keeps finished KV caches in a radix tree with LRU eviction and schedules requests longest-shared-prefix-first (provably DFS-optimal), and compresses constrained-decoding FSMs so single-path token runs decode in one forward pass. Up to $6.4\times$ throughput vs vLLM/Guidance/LMQL.
Modern LLM usage is programmatic: agents, tree/skeleton-of-thought, few-shot, JSON extraction, RAG, and multi-turn chat all issue multiple dependent generation calls ("LM Programs"). Two problems follow. (1) Programming these is tedious — string manipulation, brittle output parsing, manual parallelism. (2) Executing them is inefficient: state-of-the-art engines (vLLM, TGI, TRT-LLM) run "without direct knowledge of the workload," so they recompute the KV cache for each request even when calls share large prefixes (system prompts, few-shot examples, forked branches, chat history), and they decode constrained output one token at a time even when the format admits only one next token for many steps.
Two coupled parts (Fig. 1):
gen, select, extend/+=) and parallelism primitives (fork, join), plus image/video. An interpreter runs each prompt as an async stream in a background thread (intra-program parallelism); a compiler can trace it to a dataflow graph.gen calls, saving repeated input-token billing).核心技术壁垒: treating the KV cache as a tree-structured LRU cache whose eviction interacts correctly with the running batch via per-node reference counters, and proving that a cheap greedy schedule (longest-shared-prefix-first) equals the offline-optimal DFS traversal (Theorem 3.1). The hard part is not the radix tree data structure — it is making eviction, continuous batching, and cache-aware scheduling coexist without cache thrashing while sharing one memory pool between cached and live tokens.
Up to $6.4\times$ throughput and $3.7\times$ latency reduction on Llama-7B; up to $6\times$ on multi-modal LLaVA; cache-aware scheduling reaches 96% of the theoretical-optimal hit rate on average (hit rate 50–99% across workloads); RadixAttention overhead is <0.3% even with zero reuse; compressed FSM gives $1.6\times$ on JSON decoding. Production: one month in Chatbot Arena gave 52.4% (LLaVA-Next-34B) / 74.1% (Vicuna-33B) cache hit and $1.7\times$ lower first-token latency.

Paper's Figure 1 (caption: "System architecture: An interpreter executes language primitives with optimized runtime."). This pins the system scope: it is a serving framework (not training), covering both prefill and decode. The frontend interpreter owns the program-level control flow and parallelism; the runtime (SRT) owns KV memory, scheduling, and kernel dispatch. The two halves can run independently but are co-designed — e.g. the frontend sends "prefix hints" for fork so the runtime inserts the shared prefix into the tree before the branches.

Paper's Figure 2 (caption: "The implementation of a multi-dimensional essay judge in SGLang utilizes the branch-solve-merge prompting technique. Primitives provided by SGLang are shown in red."). This is the concrete "LM Program" the runtime optimizes: fork creates three parallel branches sharing a common prefix (a RadixAttention reuse opportunity), and the final JSON regex argument triggers compressed-FSM decoding. The equivalent OpenAI-API program is $2.1\times$ longer.
The request lifecycle and the scheduler/memory-manager separation demanded by a serving framework:
The scheduler is FCFS replaced by longest-shared-prefix-first (a greedy priority order). The memory manager is a radix tree whose allocation unit is one token per page (non-contiguous paged layout); cached tokens and running requests share one pool, so a large waiting batch can evict all cached tokens to grow batch size. Distribution: tensor parallelism needs no extra sync (each GPU shards its own KV, tree ops are identical); data parallelism uses a router meta-tree (§A.4).
The load-bearing formal result is Theorem 3.1: longest-shared-prefix-first scheduling achieves the optimal cache hit rate, and equals a DFS traversal of the batch's radix tree, given cache size $\geq$ max request length.
Notation table
| Symbol | Meaning | ||
|---|---|---|---|
| $R$ | the set of requests in a batch | ||
| $T$ | radix tree built from $R$ | ||
| $e$ | an edge of $T$ (a shared token substring) | ||
| $ | e | $ | size of the KV cache associated with edge $e$ |
| $C$ | total KV-cache computation complexity for $R$ | ||
| $r$ | an individual request $r \in R$ |
Cache hit rate (the scheduler's objective):
$$\text{cache hit rate} = \frac{\text{number of cached prompt tokens}}{\text{number of prompt tokens}}$$
Physical meaning: the fraction of prompt-token KV computations skipped. Higher hit rate → less prefill compute + less memory → larger batch → higher throughput and lower first-token latency.
Lower bound on compute — every distinct edge must be computed at least once (shared prefixes counted once, not per request):
$$C \geq \sum_{e \in \text{edges}(T)} |e|$$
Equality under DFS — a DFS with a big-enough cache computes each edge exactly once, hitting the bound:
$$C = \sum_{e \in \text{edges}(T)} |e|$$
The batch-level hit rate equals $1 - C / (\sum_{r\in R}\text{prefill tokens})$, so minimizing $C$ maximizes hit rate — hence DFS is optimal.
6 minimum checks
sum over edges (not requests): each unique prefix edge's KV is a shared physical quantity computed once; summing over requests would double-count shared prefixes. The sum-over-edges is exactly the irreducible work.Footnote caveat (honest): actual computation differs from the proof because the unpredictable number of output tokens can force KV recomputation, and greedy scheduling can cause starvation (left unsolved). This is a formal offline-optimality proof plus an online approximation argument — genuine, not merely empirical.
Scheduling & resource management (the framework-specific asks):

Paper's Figure 3 (caption abridged: "Examples of RadixAttention operations with an LRU eviction policy … green for newly added, blue for cached-accessed, red for evicted."). This is the mechanism's core: node splitting (step 4) lets two chat sessions share a system prompt; leaf eviction (steps 5, 8, 9) reclaims memory in LRU order; step 7 shows few-shot examples shared across a batch. It demonstrates the four sharing patterns a table-based cache cannot express.

Paper's Figure 5. The load-bearing throughput result: up to $6.4\times$ over baselines across MMLU, HellaSwag, ReAct, ToT/SoT, JSON, multi-turn chat, and RAG. Note the regime where it barely wins — long-output multi-turn chat, where decoding dominates and prefixes barely overlap.

Paper's Figure 6. Up to $3.7\times$ latency reduction; RadixAttention cuts first-token latency by skipping prefill of cached prefixes — the effect is largest for short-output, high-sharing workloads.

Paper's Figure 8. (a,b) confirm the causal chain: higher hit rate → larger batch → higher throughput + lower latency. (c) shows each component is load-bearing — removing tree structure, cache-aware scheduling (→FCFS/random), frontend parallelism, or the fork hint each degrades performance, evidencing the frontend↔runtime co-design.
Workload characterization — where it wins / loses:
| Workload regime | SGLang | Baseline (vLLM/Guidance/LMQL) | Why |
|---|---|---|---|
| few-shot / shared-prefix, high concurrency | up to $6.4\times$, hit rate 50–99% | recompute per request | RadixAttention reuses prefix KV, larger batch |
| JSON / constrained decode | $1.6\times$ | 1 token / forward pass | compressed FSM decodes multi-token runs at once |
| multi-turn chat, short output | strong speedup | — | prefix (history) reuse dominates cost |
| multi-turn chat, long output | "almost no speedup" | — | decode-bound, little cross-session sharing |
| zero-reuse (ShareGPT) | <0.3% overhead | baseline | tree ops are linear + tiny, safe to leave on |
Metric definitions (honest reading): throughput = programs/second at max batch; latency = average of single unbatched programs. Baselines: vLLM v0.2.5 (an earlier version — RadixAttention was later partially upstreamed to vLLM), Guidance v0.1.8 (llama.cpp, no batching/parallelism), LMQL v0.7.3 (HF Transformers, slow token-level). Guidance/LMQL are excluded from several benchmarks for lacking batching / TP — a fairness caveat: those baselines are weak on the harder workloads.

Paper's Table 2. 0.18→1.15 image/s and 0.02→0.10 frame/s (≈$6\times$), driven by hashing input images as radix keys so identical images reuse image-token KV.
| # | Step | Support |
|---|---|---|
| 1 | LM Programs make multiple dependent calls that share prefixes and need constrained output. | §1 taxonomy; Fig. 2 branch-solve-merge example |
| 2 | Existing engines recompute shared-prefix KV and decode constrained output token-by-token. | §1, §3 (workload-agnostic engines) |
| 3 | Retaining KV in a radix tree with LRU eviction enables automatic multi-pattern prefix reuse. | §3 RadixAttention; Fig. 3 nine-step trace |
| 4 | Scheduling requests longest-shared-prefix-first maximizes the hit rate and equals offline-optimal DFS. | Theorem 3.1; proof §A.3 |
| 5 | Compressing singular-transition FSM edges lets constant token runs decode in one forward pass. | §4; Fig. 4(b,d) |
| 6 | Together these yield up to $6.4\times$ throughput / $3.7\times$ latency, at <0.3% overhead, 96% of optimal hit rate. | Figs. 5,6,8; §6.3 |
The runtime radix cache is public in the SGLang repo (evolved past the paper but the paper's primitives are directly visible):
match_prefix in sglang/python/sglang/srt/mem_cache/radix_cache.py:355.insert at sglang/python/sglang/srt/mem_cache/radix_cache.py:415.evict at sglang/python/sglang/srt/mem_cache/radix_cache.py:563, which skips any node with lock_ref > 0 (see the eviction guard at radix_cache.py:789).inc_lock_ref / dec_lock_ref at radix_cache.py:592 and radix_cache.py:607; the running batch increments on schedule and decrements on finish. This is exactly Alg. 1's increase_ref_counter / decrease_ref_counter.核心技术壁垒 (dedicated note): the replication difficulty is not the radix tree — it is the invariant that cached tokens and live tokens share one memory pool while eviction must never touch a node any running request depends on. Implemented as lock_ref on every node: eviction (radix_cache.py:789) treats lock_ref > 0 as pinned, and node-splitting propagates lock_ref to the new child (radix_cache.py:681) so a split prefix stays pinned. Get this wrong and you either corrupt in-flight attention or deadlock the pool.
关键实现细节 (easy-to-miss tricks):
fork: the interpreter sends the shared prefix first as a hint so the runtime inserts it into the tree before the branches arrive — without it, concurrent branches race and the shared prefix may be computed multiple times (ablation "No Frontend Hint" degrades performance).{"summary": " must tokenize as the model expects); skipping this silently corrupts the KV/token stream.