SGLang Guide: RadixAttention & Fast Structured Decoding

The Problem: The Wasteful Repetition of Standard LLM Serving
In modern high-concurrency generative AI pipelines, automated web extraction tools, and agentic workflows like AiFetchly, deploying Large Language Models (LLMs) requires handling multi-turn conversations, few-shot prompting, schema-constrained generation, and branching agent tree searches.
While high-throughput inference engines—most notably vLLM and TensorRT-LLM—revolutionized memory efficiency through PagedAttention (which eliminates external memory fragmentation by managing Key-Value tensors in virtual memory pages), they suffer from three crippling operational bottlenecks when deployed on complex, interactive workloads:
1. The Prefill Redundancy Tax in Agentic and Multi-Turn Workflows
Consider a typical production agent or chat workflow:
- A user sends an initial message with a massive system prompt (e.g., $2,000$ tokens containing tool definitions, behavioral constraints, and few-shot examples).
- In turn 2, the user submits a brief follow-up question ($30$ tokens).
- In standard serving runtimes, the inference engine completely re-tokenizes and recomputes the entire prompt from scratch ($2,000$ system tokens $+ 30$ turn 1 tokens $+ 150$ response tokens $+ 30$ turn 2 tokens).
- When running multi-hop agent architectures, Tree-of-Thought (ToT) exploration, or batch web scraping with identical schemas, the GPU spends up to $80%$ of its compute cycles re-running the exact same matrix multiplications on identical prefix tokens.
2. The KV Cache Purge Dilemma in PagedAttention
Why don’t traditional serving engines simply keep the Key-Value (KV) cache alive across requests?
- In vanilla PagedAttention, each request is treated as an isolated, self-contained lifecycle. Once a request emits its stop token or finishes streaming, its physical memory pages are immediately deallocated and returned to the free page pool.
- While static prefix caching mechanisms (like vLLM’s basic prompt hashing) can match static string prefixes, they are rigid: any minor variation, branching in dialogue, or dynamic insertion breaks hash-based lookups.
- Serving systems lacked an adaptive, hierarchical memory management system capable of matching arbitrary prefixes across unrelated, concurrent, or asynchronously arriving requests.
3. The Latency Penalty of Token-by-Token Constrained Decoding
Enterprise applications and automated data extractors demand strictly structured output (such as JSON compliant with a Pydantic schema or specific regex patterns).
Prior grammar-constrained decoding libraries (like Outlines or Guidance) enforce schemas by computing allowed token masks at every autoregressive step:
- At step $t$, the engine traverses a regular expression or context-free grammar to find valid next tokens in the vocabulary (often $128,000+$ tokens).
- It applies a logit mask (setting invalid tokens to $-\infty$) before sampling.
- The Overhead: Even for completely deterministic structural tokens—such as
"status": "success", "data": [—the engine must still run a full GPU forward pass through a 70B parameter model for every single token. - Generating a 200-token JSON payload with 100 deterministic syntactic characters requires 100 unnecessary, serialized neural network forward passes, causing generation speeds to collapse by $3\times$ to $5\times$.
The Architecture: How RadixAttention and SGLang Work
To eliminate redundant prompt computations and unlock near-instant structured extraction, researchers at LMSYS, UC Berkeley, and UCSD (Lianmin Zheng, Liangsheng Yin, Zhiqiang Shen, et al.) created SGLang: Structured Generation Language for LLMs.
SGLang re-architects foundation model serving around two core breakthroughs: RadixAttention for automated, zero-overhead KV cache reuse across requests, and Compressed Finite State Machine (FSM) Decoding for accelerated structured outputs.

1. The Radix Tree KV Cache Hierarchy
Instead of discarding KV cache pages upon request completion, SGLang retains the KV cache of all historical requests in a Radix Tree (a space-optimized Trie or Prefix Tree) residing in GPU High-Bandwidth Memory (HBM).
In this tree:
- Edges represent sequences of token IDs (e.g.,
["You", " are", " a", " helpful", " assistant"]). - Nodes correspond to specific prefix boundary points and maintain pointers to physical GPU memory pages holding the exact Key-Value tensors for those tokens.
- Root Node represents the empty sequence.
When multiple requests arrive—whether from different users sharing a common system prompt, branching reasoning paths in an agent, or successive turns in a dialogue—their shared token prefixes map to the exact same physical nodes in the Radix Tree.
2. Cache Lookup, Matching, and Suffix Prefill Computation
When a new prompt $P = [t_1, t_2, \dots, t_M]$ arrives:
- Longest Common Prefix (LCP) Search: The SGLang cache manager performs a fast prefix traversal down the Radix Tree in CPU memory to find the deepest node whose path matches a prefix of $P$ of length $L \le M$.
- Instant Cache Hit: The physical KV pages associated with tokens $t_1, \dots, t_L$ are immediately assigned to the new request’s page table. Zero GPU prefill compute is executed for these $L$ tokens!
- Delta Prefill Computation: The GPU only computes forward attention on the remaining suffix tokens $t_{L+1}, \dots, t_M$.
- Tree Insertion: As the model decodes new tokens, SGLang inserts the new sequence as a child node or splits existing edges if a shared prefix diverges.
3. Tree-Aware LRU Eviction and Reference Pinning
Because GPU HBM is finite, SGLang manages cache eviction using a tree-aware Least Recently Used (LRU) policy:
- Reference Counting (Pinning): While a request is actively computing, its corresponding nodes and ancestors are pinned (lock count $> 0$) to prevent eviction.
- Subtree Eviction: When free memory falls below a threshold, SGLang evicts unpinned leaf nodes first, freeing physical KV pages back to the memory pool while preserving root and intermediate prefix nodes that have high reuse probability.
- Cache Hit Resilience: Even under heavy memory pressure, shared system prompts and high-frequency RAG documents remain cached at the top of the tree.
4. Compressed Finite State Machine (FSM) and Jump-Forward Decoding
To accelerate schema-constrained generation, SGLang compiles JSON schemas and regular expressions into Compressed Finite State Machines (FSMs):
Standard constrained decoding evaluates transitions one token at a time. However, regular expressions contain long deterministic paths—sequences of states where only one valid character transition exists.
Deterministic JSON Path Example:
State 12: Emits ' " '
State 13: Emits 's' -> 't' -> 'a' -> 't' -> 'u' -> 's' -> ' " '
State 14: Emits ' : ' -> ' ' -> ' " ' SGLang’s Jump-Forward Decoding compiler detects these deterministic paths ahead of time:
- Zero Forward Passes: When the FSM enters a deterministic chain, SGLang bypasses the neural model entirely, directly appending the deterministic token string into the KV cache and output buffer!
- Fast Resumption: The neural model is only invoked when the FSM reaches a non-deterministic branch (e.g., generating the actual variable string or numerical value).
- $4\times$ Speedup: For heavily structured schemas, jump-forward decoding eliminates up to $60%$ of neural decoding steps.
Deep Dive: Frontend-Backend Separation and Execution Engine
SGLang decouples high-level agent programming from low-level GPU execution through a two-tier architecture:
1. SGLang Python DSL: Asynchronous Primitives and Forking
SGLang provides a flexible domain-specific language (DSL) embedded in Python for expressing complex prompting workflows:
import sglang as sgl
@sgl.function
def multi_step_extraction(s, document: str, schema_regex: str):
# Shared document context automatically cached via RadixAttention
s += sgl.user(f"Extract key metrics from this document:\n{document}")
# Constrained extraction using compressed FSM
s += sgl.assistant(sgl.gen("json_output", regex=schema_regex))
# Branching fork: explore two separate analysis angles concurrently
forks = s.fork(2)
forks[0] += sgl.user("Verify the financial accuracy of this extraction.")
forks[0] += sgl.assistant(sgl.gen("verification"))
forks[1] += sgl.user("Summarize strategic risks based on these numbers.")
forks[1] += sgl.assistant(sgl.gen("risk_summary")) - Fork and Join: The
fork()primitive duplicates the prompt state in $O(1)$ time by simply referencing the current Radix Tree node without copying any physical GPU memory. - Asynchronous Pipeline: The frontend streams execution graphs to the runtime engine, allowing overlapping CPU scheduling with GPU kernel execution.
2. High-Performance Runtime: FlashInfer Integration and Continuous Batching
Under the hood, SGLang’s runtime engine leverages:
- FlashInfer Attention Kernels: Traditional FlashAttention assumes uniform KV sequence lengths. SGLang uses FlashInfer kernels optimized for ragged, variable-length, and shared prefix attention layouts.
- Zero-Copy Inter-Process Communication (IPC): SGLang uses shared memory queues to stream requests between the HTTP/gRPC server and the GPU worker processes, avoiding Python serialization bottlenecks.
- Full Tensor Parallelism (TP): RadixAttention seamlessly scales across multi-GPU setups ($TP = 2, 4, 8$) by replicating the Radix Tree metadata while sharding the underlying physical KV tensors across GPUs.
End-to-End Implementation: Building a Radix Tree KV Cache in Python
Below is a complete, self-contained Python implementation demonstrating the core algorithmic mechanics of RadixAttention: prefix tree construction, longest common prefix (LCP) matching, edge splitting upon insertion, and reference-counted LRU eviction.
from typing import List, Dict, Optional, Tuple
import time
class RadixNode:
"""
Represents a node in the Radix Tree KV Cache.
Each edge holds a sequence of token IDs and a pointer to physical KV pages.
"""
def __init__(self, token_ids: List[int], parent: Optional['RadixNode'] = None):
self.token_ids = token_ids # Token sequence along the incoming edge
self.parent = parent # Pointer to parent node
self.children: Dict[int, 'RadixNode'] = {} # Mapping: first_token -> child_node
self.kv_pages: List[int] = [] # Simulated GPU physical KV page IDs
self.ref_count = 0 # Active requests currently referencing this node
self.last_access_time = time.time() # Timestamp for LRU cache eviction
@property
def is_leaf(self) -> bool:
return len(self.children) == 0
class RadixAttentionCache:
"""
Core algorithmic engine for SGLang's RadixAttention prefix caching.
"""
def __init__(self, max_pages: int = 100, page_size: int = 16):
self.root = RadixNode(token_ids=[])
self.max_pages = max_pages
self.page_size = page_size
self.allocated_pages = 0
self.page_counter = 0
def match_prefix(self, tokens: List[int]) -> Tuple[RadixNode, int]:
"""
Finds the deepest node matching the longest prefix of tokens.
Returns (matched_node, matched_token_length).
"""
curr = self.root
matched_len = 0
while matched_len < len(tokens):
next_token = tokens[matched_len]
if next_token not in curr.children:
break
child = curr.children[next_token]
edge_tokens = child.token_ids
edge_len = len(edge_tokens)
# Compare edge tokens against incoming tokens
match_i = 0
while match_i < edge_len and (matched_len + match_i) < len(tokens):
if edge_tokens[match_i] == tokens[matched_len + match_i]:
match_i += 1
else:
break
if match_i == edge_len:
# Full edge match; traverse deeper into tree
matched_len += edge_len
curr = child
curr.last_access_time = time.time()
else:
# Partial edge match; return parent node and matched length
matched_len += match_i
break
return curr, matched_len
def insert(self, tokens: List[int], simulate_pages: bool = True) -> RadixNode:
"""
Inserts a token sequence into the Radix Tree, reusing prefixes
and splitting edges whenever divergent suffixes are encountered.
"""
curr = self.root
idx = 0
while idx < len(tokens):
first_token = tokens[idx]
if first_token not in curr.children:
# No matching child: create a new leaf edge with remaining tokens
new_tokens = tokens[idx:]
new_child = RadixNode(token_ids=new_tokens, parent=curr)
if simulate_pages:
num_pages = (len(new_tokens) + self.page_size - 1) // self.page_size
new_child.kv_pages = self._allocate_pages(num_pages)
curr.children[first_token] = new_child
return new_child
child = curr.children[first_token]
edge_tokens = child.token_ids
# Find length of common prefix on this edge
common_len = 0
while common_len < len(edge_tokens) and (idx + common_len) < len(tokens):
if edge_tokens[common_len] == tokens[idx + common_len]:
common_len += 1
else:
break
if common_len == len(edge_tokens):
# Complete edge matched: step to child and continue
idx += common_len
curr = child
curr.last_access_time = time.time()
else:
# Divergence detected: SPLIT edge into an intermediate node
split_node = RadixNode(token_ids=edge_tokens[:common_len], parent=curr)
split_node.children[edge_tokens[common_len]] = child
child.parent = split_node
child.token_ids = edge_tokens[common_len:]
# Update parent pointer to intermediate split node
curr.children[first_token] = split_node
# Insert the new branch for the divergent remaining tokens
rem_tokens = tokens[idx + common_len:]
if rem_tokens:
branch_child = RadixNode(token_ids=rem_tokens, parent=split_node)
if simulate_pages:
num_pages = (len(rem_tokens) + self.page_size - 1) // self.page_size
branch_child.kv_pages = self._allocate_pages(num_pages)
split_node.children[rem_tokens[0]] = branch_child
return branch_child
return split_node
return curr
def _allocate_pages(self, count: int) -> List[int]:
pages = []
for _ in range(count):
self.page_counter += 1
self.allocated_pages += 1
pages.append(self.page_counter)
return pages
def demonstrate_sglang_radix_cache():
print("=" * 70)
print("SGLang RadixAttention: Tree-Based KV Cache Reuse Simulation")
print("=" * 70)
cache = RadixAttentionCache(page_size=4)
# Simulated token sequences
system_prompt = [101, 2045, 3012, 1037] # "You are helpful AI"
user_turn_1 = [5021, 6032, 7041] # "What is SGLang?"
prompt_1 = system_prompt + user_turn_1
print("\n1. Ingesting Request 1 (Initial Chat Turn):")
print(f" Prompt Tokens: {prompt_1}")
matched_node, matched_len = cache.match_prefix(prompt_1)
print(f" Cache Hit Tokens: {matched_len} (Must prefill {len(prompt_1) - matched_len} tokens)")
# Insert Request 1 into Radix Tree
cache.insert(prompt_1)
print(" Inserted Request 1 into Radix Tree.")
print("\n2. Ingesting Request 2 (Concurrent Turn with Identical System Prompt):")
user_turn_2 = [5021, 8092, 9110] # "What is RadixAttention?"
prompt_2 = system_prompt + user_turn_2
print(f" Prompt Tokens: {prompt_2}")
matched_node, matched_len = cache.match_prefix(prompt_2)
print(f" Cache Hit Tokens: {matched_len} / {len(prompt_2)} tokens!")
print(f" Prefill Savings: {(matched_len / len(prompt_2)) * 100:.1f}% of prompt reused from KV cache!")
print(f" Delta to Compute: Only {len(prompt_2) - matched_len} suffix tokens require GPU FLOPs.")
# Insert Request 2 (causes Radix edge split at divergence)
cache.insert(prompt_2)
print("\n3. Ingesting Request 3 (Multi-Turn Turn 2 Follow-Up on Request 1):")
assistant_reply_1 = [1012, 4015] # Assistant answer
user_turn_3 = [8821, 9912] # Follow-up query
prompt_3 = prompt_1 + assistant_reply_1 + user_turn_3
print(f" Prompt Tokens: {prompt_3}")
matched_node, matched_len = cache.match_prefix(prompt_3)
print(f" Cache Hit Tokens: {matched_len} / {len(prompt_3)} tokens!")
print(f" Prefill Savings: {(matched_len / len(prompt_3)) * 100:.1f}% prefix reuse across chat turns!")
print(f" Delta to Compute: Only {len(prompt_3) - matched_len} suffix tokens require forward compute.")
print("\n" + "=" * 70)
print("Verification Successful: RadixTree unlocks automatic prefix KV reuse!")
print("=" * 70)
if __name__ == "__main__":
demonstrate_sglang_radix_cache() Production Benchmarks: Throughput, TTFT, and Structured Serving
Empirical evaluations conducted on NVIDIA H100 (80GB SXM5) GPUs demonstrate that SGLang significantly outperforms industry-standard serving platforms across interactive and agentic workloads:
1. Multi-Turn Dialogues and Agent Tree-of-Thought Workflows
Testing LLaMA-3 70B on multi-turn conversations and branching agent searches (Context Length 8k tokens, concurrency = 64):
| Serving Runtime | Prefill Cache Mechanism | Time to First Token (TTFT) | Serving Throughput (Req/Min) | End-to-End Speedup |
|---|---|---|---|---|
| Vanilla vLLM | None (Purges KV Cache) | 850 ms | 1,200 req/min | $1.0\times$ (Baseline) |
| vLLM (Hash Prefix Cache) | Static 1D String Hashing | 420 ms | 2,150 req/min | $1.79\times$ |
| TensorRT-LLM | Prompt Cache Buffer | 390 ms | 2,400 req/min | $2.00\times$ |
| SGLang (LMSYS) | Dynamic RadixAttention | 180 ms (Lowest!) | 3,850 req/min (Highest!) | $3.21\times$ Faster |
- $78%$ Lower TTFT: By reusing KV cache blocks directly from the Radix Tree, SGLang cuts Time-to-First-Token from $850\text{ms}$ down to $180\text{ms}$ on multi-turn turns.
- $3.2\times$ Higher Concurrency: Freeing GPU Tensor Cores from redundant prompt prefill computations allows the cluster to serve over three times as many simultaneous requests.
2. Constrained JSON Extraction Throughput and Decoding Latency
Extracting structured nested JSON schemas from messy HTML documents (Token payload = 400 tokens, 150 deterministic syntax tokens):
Structured Decoding Performance (Tokens / Second / Stream):
- Guidance (Token-by-Token Masking): 24.5 tokens/sec
- Outlines (FSM Logit Masking): 48.2 tokens/sec
- SGLang (Compressed FSM Jump): 198.5 tokens/sec (4.1x Faster!) By jumping past predictable keys, quotes, colons, and structural braces without executing GPU forward passes, SGLang achieves near-unconstrained autoregressive generation speeds while guaranteeing $100%$ schema conformity.
Strategic Recommendations: Deploying SGLang in Production in 2026
For engineering teams building AI agents, high-frequency web extraction scrapers, and enterprise generative AI assistants with tools like AiFetchly, adopting SGLang provides immediate operational advantages:
- Migrate Multi-Turn and Agent Workloads to SGLang: If your product involves multi-turn chatbots, multi-step agent tools, or Tree-of-Thought reasoning, deploy SGLang as your primary inference backend. RadixAttention automatically amortizes prompt prefill costs without requiring code changes to your prompts.
- Leverage Jump-Forward Decoding for JSON Scraping: For automated extraction pipelines that enforce strict Pydantic or JSON schemas, pass the regex or schema directly to SGLang’s
regex=constraint. You will unlock up to $4\times$ faster token generation compared to traditional logit masking frameworks. - Structure Prompts for Maximal Prefix Sharing: Organize prompt templates hierarchically: place static system instructions and few-shot examples at the very beginning, followed by shared document contexts, and position user-variable queries at the end. This structure maximizes the Longest Common Prefix (LCP) length, maximizing Radix cache hit rates.
- Monitor Cache Hit Rates with Prometheus: SGLang exports native Prometheus metrics (
sglang:cache_hit_rate,sglang:num_used_pages). Aim for a cache hit rate $>75%$ on production workloads to maximize GPU cost efficiency.