SGLang and RadixAttention for Prefix-Heavy Workloads
SGLang treats the KV cache as a reusable resource and stores it in a radix tree. vLLM schedules requests first-come, first-served (FCFS). SGLang's cache-aware scheduler prioritizes requests with longer shared prefixes, which works like a depth-first radix traversal and keeps hot branches resident in HBM. On Llama 3.1 8B with ShareGPT-like 1K prompts, SGLang hits ~16,200 tok/s to vLLM's ~12,500, a ~29% edge. On prefix-heavy RAG workloads the advantage reaches 6.4x. On voice-cloning-shaped workloads, cache hit rate cleared 86%. It is deployed on 400,000+ GPUs in 2026 across xAI, LinkedIn, Cursor, Oracle, GCP, Azure, and AWS. The gotcha: the 6.4x number evaporates when prefix ordering is inconsistent, and ordering is the lever you control.
Learning Objectives
- Diagram RadixAttention: how prefixes are stored in a radix tree and how KV blocks are shared across sequences rooted at the same branch.
- Explain cache-aware scheduling and why FCFS is wrong for prefix-heavy traffic.
- Compute expected speedup for a workload given prefix-cache hit rate and prompt length distribution.
- Name the prompt-ordering discipline that makes the 6.4x number real vs a lost upside.
The Problem
Classic serving treats each request's prompt as opaque. Say 5,000 RAG requests all start with the same 2,000-token system prompt and the same retrieval preamble. vLLM without prefix caching prefills that 2,000-token prefix 5,000 times, and the GPU repeats the same work on every request.
Prompts in agentic and RAG workloads tend to share long prefixes. System prompt, tool schemas, few-shot examples, retrieval headers, conversation history: all of it repeats across requests. If you stored the KV cache for that prefix once and reused it, you would skip the prefill.
RadixAttention does this. Tokens are indexed in a radix tree, and each node owns the KV blocks for the token sequence on its path from the root. A new request walks the tree, and any node whose tokens match reuses that node's KV blocks. Prefill cost scales with the new suffix instead of the full prompt.
The hard part is scheduling. Suppose two requests share a 2,000-token prefix and a third shares only 200 tokens of it. You want to serve the two long-prefix requests together so the long prefix stays in HBM. FCFS does the opposite: it serves whoever arrived first, and it can evict the hot branch before the next long-prefix request shows up.
The Concept
The radix tree as a KV index
A radix tree (compact trie) stores token sequences. Each node owns a token range and the KV blocks computed for that range. Children extend the sequence one or more tokens.
root
|- "You are a helpful assistant..." (2,000 tokens, 125 KV blocks)
|- "Context: <doc A>..." (500 tokens, 32 blocks)
|- "Question: Alice..." (80 tokens, 5 blocks)
|- "Question: Bob..." (95 tokens, 6 blocks)
|- "Context: <doc B>..." (520 tokens, 33 blocks)
A new request comes in with system prompt + "Context: " + "Question: Carol". The scheduler walks: system prefix matches (125 blocks reused), doc-A branch matches (32 blocks reused), then allocates fresh blocks only for "Question: Carol" (4 blocks). Prefill cost: 4 blocks of new tokens. Without the tree: 161 blocks. ~40x savings on prefill.
Cache-aware scheduling
Radix-tree reuse buys nothing if the cache churns. Two policies keep it from churning:
- Depth-first dispatch. When picking the next request from the queue, prefer requests rooted at the same branch as the current running set. This keeps the hot branch pinned.
- LRU at branch level, not block level. Evict whole branches (starting from shortest-used leaves) rather than individual blocks, so cache shape matches radix shape.
FCFS violates both. Picture a request sharing 2,000 tokens stuck behind one sharing 50. The 2,000-token branch can get evicted to admit the 50-token one.
Benchmark numbers worth remembering
- Llama 3.1 8B, H100, ShareGPT 1K prompts: SGLang ~16,200 tok/s vs vLLM ~12,500 (~29% edge).
- Prefix-heavy RAG (same system + same doc, varying question): up to 6.4x on SGLang.
- Voice cloning workloads: 86.4% prefix-cache hit rate.
- Production hit rates across SGLang customers: 50-99% depending on prompt discipline.
- Deployed on 400,000+ GPUs in 2026.
The ordering gotcha
The 6.4x number relies on consistent prompt-template ordering. If your client constructs prompts as [system, tools, context, history, question] in some requests and [system, context, tools, history, question] in others, the tree cannot find the shared prefix. A human sees one shared prefix. The radix tree sees two distinct sequences.
Your prompt template is a cache key, and the order is yours to set. Put everything immutable (system, tools, schemas) first, retrieval context next, and the user question last. Keep dynamic content out of the prefix.
One reported case: moving dynamic content out of the cacheable prefix took a deployment from 7% to 74% cache hit rate in a single change.
Where RadixAttention wins and loses
Wins:
- RAG (same retrieval preamble, varying question).
- Agents (same tool schemas, varying query).
- Chat with long system prompt.
- Voice / vision workloads with repeated preambles.
Loses (returns to vLLM-level throughput):
- Single-shot generation with unique prompts (code completion, open-ended chat without system prompt).
- Dynamic prompts where every request interleaves unique content into the prefix.
Why this is a scheduler problem, not just a kernel problem
You can implement KV reuse as a kernel trick. SGLang's insight is that reuse only pays when the scheduler keeps the hot branch resident. A naive "reuse if available" policy can churn the cache under mixed load. The radix-tree-indexed scheduler is what turns the kernel trick into the ~29% edge.
Interplay with vLLM
The two systems overlap more than they compete. In 2026 vLLM added prefix caching (--enable-prefix-caching) and a cache-aware router (vLLM Router in Rust). The gap narrowed but is still there: SGLang's whole stack is radix-first, while vLLM added it on. For workloads dominated by prefix reuse, SGLang remains the default. For general-purpose serving without strong prefix patterns, vLLM is equal or better.
Use It
code/main.py implements a toy radix-tree KV cache and a scheduler with two policies, FCFS and cache-aware. It runs the same RAG workload through both and prints prefix-cache hit rate, then runs a scrambled-ordering workload to show the hit rate collapse. It reports hit rate and saved tokens only, not throughput.
Ship It
This lesson produces outputs/skill-radix-scheduler-advisor.md. Given a workload description (prompt-template shape, retrieval pattern, number of concurrent tenants), it produces a prompt-ordering prescription and a go/no-go for SGLang adoption.
I'd run main.py and read its output skeptically. It simulates hit rate only, so it can't confirm the throughput, 6.4x or GPU-count figures above, and I'd treat those as reported numbers. On the RAG workload, FCFS and cache-aware both land at 69.1% hit rate. In this toy the scheduler makes no difference there, so the scheduling argument in the Concept section is something the code doesn't show. The scrambled workload is where it does move: FCFS drops to 8.4% and cache-aware recovers to 26.5%, still far below 69.1%.
The KEY FINDING text at the bottom of main.py agrees with those numbers: about 69% on RAG with either scheduler, and a collapse when the prefix is scrambled. Exercise 1 asks you to check that yourself.
My bet is that ordering matters more than scheduling, and the toy agrees with me. Shuffling [SYSTEM, TOOLS, DOC] costs far more hit rate than any scheduler wins back. Treat the toy's constants (a 160-block budget, 16-token blocks) as pedagogical, since they are sized so eviction bites. They show the shape of the effect, not what your deployment will see.
Exercises
- Run
code/main.py. Compare FCFS and cache-aware on the same workload, and check the KEY FINDING line against the hit rates it prints. The toy reports hit rate only, so where would a delta show up in a real server: prefill savings, decode savings, or queue delay? - Modify the workload so prompts randomly permute
[system, tools, context]. Re-run. What happens to hit rate? Why? - Compute the HBM cost of keeping a 2,000-token system prompt resident as one radix branch on Llama 3.1 8B. Compare to the cost of a 16-sequence batch without prefix reuse.
- Read the SGLang RadixAttention paper. Explain in three sentences why tree-shaped LRU eviction beats block-shaped LRU under prefix-heavy load.
- A customer reports only 8% cache hit rate. Name three likely causes and the diagnostic you would run for each.
Key Terms
| Term | What people say | What it actually means |
|---|---|---|
| RadixAttention | "the SGLang thing" | KV cache indexed as a radix tree so shared prefixes reuse blocks |
| Radix tree | "compact trie" | Tree where each node owns a token range and its KV blocks |
| Cache-aware scheduler | "hot-branch-first" | Scheduler that prefers requests sharing the resident branch |
| Prefix-cache hit rate | "how much of your prompt was free" | Fraction of prompt tokens served from reused KV blocks |
| FCFS | "first-come first-served" | Default scheduling that breaks prefix locality |
| Branch-level LRU | "evict the leaf" | Eviction policy matched to radix shape |
| Prompt template ordering | "the cache key" | The prompt's component order determines what the tree can share |
| System prompt pinning | "resident prefix" | Keep the immutable system portion pinned to avoid eviction thrash |
Further Reading
- SGLang GitHub: source and docs.
- SGLang documentation: RadixAttention and scheduling details.
- SGLang paper: Efficiently Programming Large Language Models (arXiv:2312.07104): the design reference.
- LMSYS blog: SGLang with RadixAttention: benchmark numbers and scheduler rationale.
- vLLM: Prefix Caching: vLLM's own radix-like implementation, for comparison.
Code
- main.py (python, 175 lines)