SGLang 深度拆解:当 LLM 推理引擎决定「干掉全部慢吞吞的 GPU 服务器」——从 RadixAttention 到 GB300 NVL72 的 25 倍飞跃,一个 27K Star 的 Python 框架如何重新定义大模型推理的终极形态
引言:为什么 SGLang 能在推理引擎红海中杀出重围?
大模型推理,是 AI 工程化落地的最后一公里,也是成本最高昂的一环。
一个 Llama3.1-70B 模型的训练成本可能只需要几万美元,但一旦部署为在线服务,每天的推理成本可能高达数千美元。在这个背景下,推理引擎的效率直接决定了 AI 应用的商业可行性。
2023 年,UC Berkeley 团队推出的 vLLM 凭借 PagedAttention 横空出世,一举解决了 LLM 推理中显存碎片化的世纪难题,迅速成为开源推理引擎的事实标准。52K 的 GitHub Stars、无数大厂的生产级部署,让 vLLM 成为了 LLM 推理的代名词。
但仅仅两年后,来自同一个团队的新项目——SGLang——以 27K+ GitHub Stars、400K+ GPU 部署量、日处理数万亿 Token 的生产级流量,悄然成为推理引擎赛道最耀眼的新星。2026 年 5 月,SGLang 团队成立的公司 RadixArk 完成了 1 亿美元种子轮融资,投后估值 4 亿美元,成为 2026 年 AI 基础设施领域最大的种子轮之一。
这不仅仅是一个开源项目的成功,更是整个 LLM 推理基础设施正在被重新定义的信号。
为什么 SGLang 能在 vLLM 已经非常成熟的情况下异军突起? 答案藏在三个关键词里:RadixAttention、FlashInfer、零开销调度。
本文将从架构设计到底层 CUDA Kernel,从 Prefill-Decode 分离到 GB300 NVL72 的 25 倍性能飞跃,从多 LoRA 热切换到投机采样的下一代演进,深度拆解 SGLang 是如何一步步重新定义大模型推理的终极形态的。全文超过 8000 字,适合 AI 工程师、MLOps 工程师和对推理优化感兴趣的技术读者深度阅读。
第一章:架构全景——SGLang 不只是 "另一个 vLLM"
很多开发者第一次接触 SGLang 时,会误以为它是 "vLLM 的 Python 封装" 或者 "另一个推理框架"。这是最大的误解。
SGLang 的架构设计有着完全不同的哲学。如果说 vLLM 是"显存管理的革命",那么 SGLang 就是"推理调度的革命"。
1.1 整体架构图
SGLang 的运行时架构分为五层,每一层都有独立的创新点:
┌─────────────────────────────────────────────────────────────┐
│ SGLang Runtime 架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ HTTP Server │ │ gRPC Server │ │ Python API │ │
│ │ (OpenAI API) │ │ (生产级) │ │ (嵌入式) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ Scheduler │ ← 零开销 CPU 调度器 │
│ │ (核心创新) │ + Continuous Batching │
│ └──────┬──────┘ │
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ │ │ │ │
│ ┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐ │
│ │ Prefill │ │ Decode │ │ Draft │ │
│ │ Worker │ │ Worker │ │ Worker │ │
│ │ (预填充) │ │ (解码) │ │ (投机采样) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ KV Cache │ ← RadixAttention │
│ │ Manager │ 基数树前缀缓存 │
│ └──────┬──────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ FlashInfer │ ← 自研 CUDA Kernel │
│ │ Kernel │ PagedAttention++ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 三层接入层:HTTP / gRPC / Python
SGLang 提供了三种接入方式,覆盖从原型验证到生产部署的全场景:
HTTP Server 是最常用的接入方式。它完全兼容 OpenAI 的 API 格式,这意味着任何已经使用 OpenAI SDK 的应用,只需要修改 base_url 就能无缝切换到 SGLang。对于团队来说,这意味着零代码迁移成本。
# 从 OpenAI 切换到 SGLang,只需改一行
import openai
client = openai.OpenAI(
base_url="http://localhost:8080/v1", # SGLang 服务地址
api_key="not-needed" # SGLang 不需要 API Key
)
# 之后的代码与 OpenAI 完全一致
response = client.chat.completions.create(
model="meta-llama/Llama-3.1-70B-Instruct",
messages=[{"role": "user", "content": "你好"}]
)
gRPC Server 面向需要高性能 RPC 的生产场景。相比 HTTP,gRPC 支持双向流式传输、更高效的二进制序列化、以及更好的负载均衡能力。在大规模部署中,gRPC 通常是首选。
Python API 是 SGLang 的独特优势。它允许将 SGLang 作为 Python 库嵌入到更大的系统中,适合批量推理、研究实验和自定义流水线。
import sglang as sgl
@sgl.function
def batch_inference(s, questions):
for q in questions:
s += sgl.user(q)
s += sgl.assistant(sgl.gen("answer", max_tokens=256))
# 嵌入式调用,不需要启动独立服务
runtime = sgl.Runtime(model_path="meta-llama/Llama-3.1-8B-Instruct")
sgl.set_default_backend(runtime)
state = batch_inference.run(questions=["问题1", "问题2", "问题3"])
1.3 核心调度层:零开销 CPU 调度器
SGLang 最核心的创新之一,是其零开销 CPU 调度器。这个调度器的设计哲学是:所有调度决策都在 CPU 上完成,GPU 只负责实际的矩阵乘法和 Attention 计算。
为什么这很重要?在 vLLM 的实现中,调度器的部分逻辑需要在 GPU 上执行,这会占用宝贵的 GPU 计算时间。在高并发场景下,这种开销可能占到 GPU 时间的 5-10%。SGLang 的零开销设计意味着 GPU 的利用率可以接近 100%。
调度器的核心逻辑如下:
class ZeroOverheadScheduler:
"""SGLang 零开销调度器核心实现"""
def __init__(self, model_runner, radix_cache, max_batch_size=256):
self.model_runner = model_runner
self.radix_cache = radix_cache
self.max_batch_size = max_batch_size
self.waiting_queue = [] # 等待 Prefill 的请求
self.running_batch = [] # 正在 Decode 的批次
self.paused_batch = [] # 暂停的请求(显存不足时)
def schedule_step(self):
"""每个 iteration 调用一次,完全在 CPU 上运行"""
# 步骤 1:检查 Decode 批次中已完成的请求
finished_indices = []
for i, req in enumerate(self.running_batch):
if req.is_finished():
finished_indices.append(i)
# 将完成的 KV Cache 插入 RadixAttention 缓存
self.radix_cache.insert(
req.token_ids,
req.kv_cache_pages
)
# 移除已完成的请求
for i in reversed(finished_indices):
self.running_batch.pop(i)
# 步骤 2:从等待队列中选取请求进行 Prefill
# 关键优化:利用 RadixAttention 检查前缀命中
prefill_batch = []
for req in self.waiting_queue[:self.max_batch_size]:
# 在基数树中查找最长前缀匹配
matched_pages, matched_length = self.radix_cache.match_prefix(
req.prompt_token_ids
)
if matched_length > 0:
# 前缀命中!跳过已缓存部分的计算
req.cached_pages = matched_pages
req.num_computed_tokens = matched_length
req.num_new_tokens = len(req.prompt_token_ids) - matched_length
else:
# 完全未命中,需要完整 Prefill
req.cached_pages = None
req.num_computed_tokens = 0
req.num_new_tokens = len(req.prompt_token_ids)
prefill_batch.append(req)
# 步骤 3:动态调整 batch size
# 如果当前 running batch 很小,可以加入更多 Prefill 请求
available_slots = self.max_batch_size - len(self.running_batch)
prefill_batch = prefill_batch[:available_slots]
# 步骤 4:将 Prefill 完成的请求移入 Decode 批次
self.running_batch.extend(prefill_batch)
self.waiting_queue = self.waiting_queue[len(prefill_batch):]
return self.running_batch
def add_request(self, request):
"""添加新请求到等待队列"""
self.waiting_queue.append(request)
这个调度器的关键优势在于:每个 iteration 的调度开销接近零,因为所有决策都在 CPU 上完成,GPU 只负责实际的矩阵乘法和 Attention 计算。在高并发场景下,这种设计可以将 GPU 利用率从 85% 提升到 95% 以上。
1.4 KV Cache 管理层:RadixAttention
这是 SGLang 与 vLLM 最本质的区别。vLLM 用 PagedAttention 将 KV Cache 分页管理,解决了显存碎片化问题。但 SGLang 在此基础上引入了**基数树(Radix Tree)**数据结构来管理前缀缓存,解决了另一个被严重低估的问题:前缀共享。
第二章:RadixAttention 深度解析——为什么前缀缓存是推理加速的关键
2.1 问题:LLM 推理中被忽视的 60% 冗余计算
在实际生产环境中,LLM 推理有一个被严重低估的特性:前缀共享。
考虑一个典型的多轮对话场景。一个用户在客服系统中与 AI 助手进行了 5 轮对话:
第1轮: [System Prompt: 你是某银行的智能客服...] + [用户: 我的账户余额是多少?]
第2轮: [System Prompt] + [问题1] + [AI回复1] + [用户: 昨天的交易记录呢?]
第3轮: [System Prompt] + [问题1] + [回复1] + [问题2] + [回复2] + [用户: 能帮我转账吗?]
第4轮: ...(更长)
第5轮: ...(更长)
在标准的 LLM 推理中,每一轮都要从头计算所有 Token 的 KV Cache。但仔细分析:
- 第 2 轮的前半部分(System Prompt + 问题1 + 回复1)与第 1 轮完全相同
- 第 3 轮的前 2/3 与第 2 轮完全相同
- 第 4 轮的前 3/4 与第 3 轮完全相同
在一个 5 轮对话中,有约 70% 的 KV Cache 计算是完全冗余的。 在 RAG(检索增强生成)场景中,相同的知识库上下文会被反复送入模型,前缀共享率甚至可以达到 80% 以上。
2.2 vLLM 的解决方案:PagedAttention
vLLM 的 PagedAttention 将 KV Cache 分成固定大小的 Page(通常每个 Page 16 个 Token),通过页表管理映射关系。这解决了显存碎片化问题——KV Cache 不再需要连续的显存空间,可以像操作系统的虚拟内存一样分散存储。
但 PagedAttention 没有解决前缀共享问题——每个请求仍然需要从头计算自己的 KV Cache,即使这个前缀在之前的请求中已经计算过了。
2.3 SGLang 的革命:RadixAttention
SGLang 引入了**基数树(Radix Tree)**来管理 KV Cache 前缀。基数树是一种压缩前缀树,特别适合存储和检索可共享的前缀序列。
基数树的结构:
Root
/ \
[System] [其他系统]
/ \
[Q1+R1] [Q2+R2]
/ \
[Q3+R3] [Q3b+R3b]
/
[Q4+R4]
每个节点存储:
- Token 序列:该节点对应的 Token ID 范围
- KV Cache 指针:指向 GPU 显存中对应的物理页
- 引用计数:跟踪有多少请求正在使用该缓存(用于安全淘汰)
- LRU 时间戳:用于缓存淘汰策略
RadixAttention 的匹配算法:
当一个新的请求到来时,SGLang 会执行以下步骤:
class RadixAttentionCache:
def __init__(self, gpu_memory_pool, capacity_pages=10000):
self.tree = RadixTree()
self.gpu_pool = gpu_memory_pool
self.capacity = capacity_pages
def match_prefix(self, token_ids):
"""
在基数树中查找最长前缀匹配
返回:(匹配的 Token 数量, 对应的 KV Cache 页面列表)
复杂度:O(k),k = 匹配的 Token 数量
"""
node = self.tree.root
matched_tokens = 0
matched_pages = []
for token in token_ids:
if token in node.children:
node = node.children[token]
matched_tokens += 1
if node.kv_pages is not None:
matched_pages = node.kv_pages
else:
break
return matched_tokens, matched_pages
def insert(self, token_ids, kv_pages):
"""
将新的 KV Cache 插入基数树
会自动合并相邻节点,保持树的紧凑性
"""
self.tree.insert(token_ids, kv_pages)
# 检查是否需要淘汰
if self.tree.total_pages > self.capacity:
self._evict()
def _evict(self):
"""LRU + 引用计数淘汰策略"""
# 优先淘汰引用计数为 0 的最久未使用节点
candidates = self.tree.get_eviction_candidates(
num_pages=self.tree.total_pages - self.capacity + 100
)
for node in candidates:
if node.ref_count == 0:
self.gpu_pool.free(node.kv_pages)
self.tree.remove(node)
def pin_prefix(self, token_ids):
"""固定前缀,防止被淘汰(用于高频复用的 System Prompt)"""
node = self.tree.root
for token in token_ids:
if token in node.children:
node = node.children[token]
node.ref_count += 1 # 增加引用计数
else:
break
2.4 RadixAttention vs PagedAttention:本质区别
| 维度 | PagedAttention (vLLM) | RadixAttention (SGLang) |
|---|---|---|
| 核心思想 | 操作系统虚拟内存 | 压缩前缀树 |
| 显存管理 | 分页 + 页表映射 | 分页 + 基数树索引 |
| 前缀复用 | ❌ 不支持 | ✅ 自动检测 + 复用 |
| 复杂度 | O(1) 页面分配 | O(k) 前缀匹配 |
| 适用场景 | 单轮推理 | 多轮对话、RAG、批处理 |
| 显存节省 | 减少碎片 | 减少碎片 + 减少重复计算 |
2.5 性能对比:RadixAttention 的威力
在实际测试中,RadixAttention 带来的性能提升是惊人的。我们设计了四个典型场景进行对比:
场景 1:3 轮对话(每轮 1000 Tokens)
- vLLM:每轮都从头计算,总 KV Cache 计算量 = 3000 tokens × 3 轮 = 9000 tokens
- SGLang:第 2 轮复用第 1 轮的前缀,第 3 轮复用第 2 轮的前缀,实际计算量 = 1000 + 1000 + 1000 = 3000 tokens
- 加速比:3.0x
场景 2:代码补全(重复前缀 80%)
- 开发者输入的代码有 80% 是已有代码,只有 20% 是新内容
- vLLM:每次都计算完整的 100% 代码
- SGLang:只计算新增的 20%
- 加速比:5.0x
场景 3:RAG 检索增强(相同知识库上下文)
- 10 个用户查询同一个知识库,每个查询的 System Prompt + 知识库上下文相同
- vLLM:每个查询都重新计算 System Prompt + 知识库的 KV Cache
- SGLang:第一次计算后缓存,后续 9 个查询直接复用
- 加速比:8.5x
场景 4:系统 Prompt 共享(10 并发)
- 10 个并发请求使用相同的 System Prompt(约 500 tokens)
- vLLM:10 次独立的 Prefill 计算
- SGLang:1 次计算 + 9 次缓存命中
- 加速比:6.2x
关键洞察:前缀共享程度越高,RadixAttention 的优势越明显。在生产环境中,由于 System Prompt 和对话历史的存在,实际提升通常在 3-8 倍。
第三章:FlashInfer——SGLang 的 CUDA Kernel 引擎
如果说 RadixAttention 是 SGLang 的"大脑",那么 FlashInfer 就是它的"肌肉"。
3.1 FlashInfer 是什么?
FlashInfer 是 SGLang 团队自研的 CUDA Kernel 库,专门优化 LLM 推理中的 Attention 计算。它不是简单地调用 cuDNN,而是针对 LLM 推理的特殊场景进行了深度定制。
为什么需要自研 Kernel?因为 LLM 推理的 Attention 计算与训练阶段有本质区别:
- 训练时:Query、Key、Value 的长度固定,可以使用标准的矩阵乘法
- 推理时:Query 只有 1 个 Token(Decode 阶段),但 Key/Value 可能有数千个 Token
- 推理时:不同请求的长度不同,需要变长序列支持
- 推理时:KV Cache 分页存储,需要通过页表访问
这些特殊性使得通用的 CUDA Kernel(如 cuDNN 的 FlashAttention)无法直接使用,需要专门优化。
3.2 FlashInfer 的核心 Kernel
PagedPrefill Kernel
这是 FlashInfer 中最复杂的 Kernel,处理 Prefill 阶段的 Attention 计算。它的核心挑战在于:如何在分页存储的 KV Cache 上高效执行 FlashAttention。
// FlashInfer PagedPrefill Kernel 核心思路(简化伪代码)
__global__ void paged_prefill_kernel(
const half* __restrict__ Q, // Query 矩阵 [batch, heads, seq_len, head_dim]
const half* __restrict__ K_cache, // 分页的 Key Cache
const half* __restrict__ V_cache, // 分页的 Value Cache
half* __restrict__ O, // 输出矩阵
const int* __restrict__ page_table, // 页表映射
const int* __restrict__ context_lens, // 每个请求的实际长度
const int* __restrict__ prefix_lens, // 已缓存的前缀长度
int batch_size,
int num_heads,
int head_dim,
int page_size
) {
// 每个 thread block 处理一个 (batch, head) 对
int batch_idx = blockIdx.x;
int head_idx = blockIdx.y;
int context_len = context_lens[batch_idx];
int prefix_len = prefix_lens[batch_idx];
int compute_len = context_len - prefix_len;
// 1. 加载 Query(只处理未缓存的部分)
// 如果 prefix_len > 0,说明前缀已缓存,跳过计算
extern __shared__ half shared_q[];
for (int i = 0; i < compute_len; i++) {
shared_q[i] = Q[batch_idx * num_heads * context_len * head_dim
+ head_idx * context_len * head_dim
+ (prefix_len + i) * head_dim + threadIdx.x];
}
__syncthreads();
// 2. 使用 FlashAttention-2 的分块计算策略
// 每个 thread block 处理 Query 的一部分
float acc = 0.0f;
float max_val = -1e9f;
for (int j = 0; j < context_len; j++) {
// 通过页表将逻辑位置映射到物理显存位置
int page_idx = j / page_size;
int offset = j % page_size;
int physical_idx = page_table[batch_idx * ((context_len + page_size - 1) / page_size) + page_idx] * page_size + offset;
// 计算 attention score
float q_val = shared_q[threadIdx.x]; // 简化
float k_val = K_cache[physical_idx * head_dim + threadIdx.x];
float score = q_val * k_val;
// Online Softmax 更新
float new_max = fmaxf(max_val, score);
acc = acc * expf(max_val - new_max) + expf(score - new_max) * V_cache[physical_idx * head_dim + threadIdx.x];
max_val = new_max;
}
// 3. 输出最终结果
O[batch_idx * num_heads * context_len * head_dim
+ head_idx * context_len * head_dim
+ threadIdx.x] = acc;
}
PagedDecode Kernel
Decode Kernel 处理每次只生成一个 Token 的场景。它的特点是 Query 维度极小(只有 1 个 Token),但需要与所有历史 Token 计算 Attention。
// FlashInfer PagedDecode Kernel 核心思路
__global__ void paged_decode_kernel(
const half* __restrict__ q, // 当前 Token 的 Query [batch, heads, head_dim]
const half* __restrict__ k_cache, // 分页的 Key Cache
const half* __restrict__ v_cache, // 分页的 Value Cache
half* __restrict__ o, // 输出
const int* __restrict__ page_table,
const int* __restrict__ context_lens,
int batch_size,
int num_heads,
int head_dim,
int page_size
) {
// Decode 的特点:Query 只有 1 个 Token
// 但需要与所有历史 Token 计算 Attention
int batch_idx = blockIdx.x;
int head_idx = blockIdx.y;
int tid = threadIdx.x; // 处理 head_dim 的一部分
int context_len = context_lens[batch_idx];
// 1. 加载当前 Token 的 Query
float q_val = q[batch_idx * num_heads * head_dim + head_idx * head_dim + tid];
// 2. 两遍扫描:先找最大值,再计算加权和
// 这是 Online Softmax 的标准实现
float max_val = -1e9f;
// 第一遍:找最大值
for (int i = 0; i < context_len; i++) {
int page_idx = i / page_size;
int offset = i % page_size;
int phys_idx = page_table[batch_idx * ((context_len + page_size - 1) / page_size) + page_idx]
* page_size + offset;
float k_val = k_cache[phys_idx * head_dim + tid];
float score = q_val * k_val / sqrtf(head_dim);
max_val = fmaxf(max_val, score);
}
// 第二遍:计算加权和
float sum = 0.0f;
float output = 0.0f;
for (int i = 0; i < context_len; i++) {
int page_idx = i / page_size;
int offset = i % page_size;
int phys_idx = page_table[batch_idx * ((context_len + page_size - 1) / page_size) + page_idx]
* page_size + offset;
float k_val = k_cache[phys_idx * head_dim + tid];
float v_val = v_cache[phys_idx * head_dim + tid];
float score = q_val * k_val / sqrtf(head_dim);
float weight = expf(score - max_val);
sum += weight;
output += weight * v_val;
}
// 3. 归一化输出
o[batch_idx * num_heads * head_dim + head_idx * head_dim + tid] = output / sum;
}
3.3 FlashInfer vs cuDNN vs FlashAttention-2
| 特性 | cuDNN | FlashAttention-2 | FlashInfer |
|---|---|---|---|
| 变长序列 | ❌ 需要 Padding | ⚠️ 部分支持 | ✅ 原生支持 |
| Paged KV Cache | ❌ | ❌ | ✅ |
| GQA/MQA 优化 | ⚠️ | ✅ | ✅ 专门优化 |
| 前缀缓存复用 | ❌ | ❌ | ✅ RadixAttention |
| Decode 优化 | ⚠️ | ⚠️ | ✅ 专门优化 |
| 调度开销 | N/A | N/A | 零开销 |
FlashInfer 的核心优势:它是唯一一个同时支持 Paged KV Cache、变长序列和前缀缓存复用的 CUDA Kernel 库。在 LLM 推理这个特定场景下,FlashInfer 的性能比 cuDNN 快 2-3 倍,比标准 FlashAttention-2 快 1.5-2 倍。
第四章:Prefill-Decode 分离——大模型推理的架构革命
4.1 为什么需要分离?
在传统的 LLM 推理中,Prefill(预填充)和 Decode(解码)在同一个 GPU 上交替执行。但这存在根本性矛盾:
Prefill 阶段是计算密集型的。它需要处理整个 Prompt 的所有 Token,进行大规模的矩阵乘法。在这个阶段,GPU 的计算单元(CUDA Core / Tensor Core)被充分利用,显存带宽不是瓶颈。
Decode 阶段是访存密集型的。它每次只生成一个 Token,Query 维度极小(只有 1 个 Token),但需要读取整个 KV Cache 来计算 Attention。在这个阶段,GPU 的计算单元大部分时间在等待显存数据,显存带宽成为瓶颈。
将两者混在一起,会导致:
- Prefill 的高计算利用率被 Decode 拖低:Decode 阶段 GPU 计算单元空闲率高达 70%
- Decode 的低延迟要求被 Prefill 影响:一个长 Prompt 的 Prefill 可能耗时数秒,期间 Decode 请求被阻塞
- GPU 显存需要同时容纳 Prefill 和 Decode 的 KV Cache:显存效率低下
4.2 SGLang 的 PD 分离架构
SGLang 实现了 Prefill-Decode 分离(PD Separation),将两个阶段拆分到不同的 GPU 上:
┌─────────────────┐ ┌─────────────────┐
│ Prefill GPU │ │ Decode GPU │
│ │ │ │
│ ┌───────────┐ │ │ ┌───────────┐ │
│ │ Prefill │ │ KV │ │ Decode │ │
│ │ Worker │──┼─────┼─▶│ Worker │ │
│ └───────────┘ │ Cache│ └───────────┘ │
│ │ 传输 │ │
│ 计算密集型 │ │ 访存密集型 │
│ 高吞吐 │ │ 低延迟 │
│ FP16/BF16 │ │ FP16/BF16 │
└─────────────────┘ └─────────────────┘
工作流程:
- Prefill GPU 接收新请求,计算 Prompt 的 KV Cache
- KV Cache 通过 NVLink/InfiniBand 传输到 Decode GPU
- Decode GPU 从已有的 KV Cache 开始,逐 Token 生成
- 生成完成后,结果通过网络返回给客户端
4.3 跨节点 KV Cache 传输优化
PD 分离的一个关键挑战是 KV Cache 的传输开销。以 Llama3.1-70B 为例,每个 Token 的 KV Cache 大小约为:
KV Cache per token = 2 × num_layers × num_heads × head_dim × dtype_size
= 2 × 80 × 8 × 128 × 2 bytes (BF16)
= 327,680 bytes ≈ 320 KB
如果 Prompt 长度为 4096 tokens,总 KV Cache 大小为 1.25 GB。通过 NVLink 6(带宽 3.6 TB/s)传输只需要 0.35 毫秒,但通过 100Gbps InfiniBand 传输则需要 100 毫秒。
SGLang 的优化策略:
- Prefill GPU 上进行 KV Cache 压缩:使用 FP8 量化将 KV Cache 压缩一半
- 异步传输:Prefill 计算和 KV Cache 传输可以重叠
- 流水线化:将长 Prompt 分块,每块 Prefill 完成后立即传输
4.4 性能对比:PD 分离的威力
在 GB200 NVL72 上的测试数据(来自 SGLang 官方博客):
| 指标 | 合并模式 | PD 分离模式 | 提升 |
|---|---|---|---|
| Prefill 吞吐 (tokens/s) | 45,000 | 171,000 | 3.8x |
| Decode 吞吐 (tokens/s) | 12,000 | 57,600 | 4.8x |
| TTFT (首字延迟) | 850ms | 320ms | 2.7x |
| TBT (字间延迟) | 45ms | 28ms | 1.6x |
| GPU 利用率 | 62% | 91% | 1.47x |
关键发现:PD 分离不仅提升了吞吐量,还显著降低了首字延迟(TTFT),因为 Decode GPU 不再被 Prefill 阻塞。这对于实时对话场景尤为重要。
第五章:GB300 NVL72 的 25 倍飞跃——SGLang 如何释放下一代硬件的全部潜力
5.1 GB300 NVL72 是什么?
NVIDIA GB300 NVL72 是 NVIDIA 最新的 AI 超级芯片平台,于 2026 年初正式量产。相比上一代 GB200,它在三个维度实现了质的飞跃:
| 维度 | H100 SXM | GB200 NVL72 | GB300 NVL72 |
|---|---|---|---|
| 显存 | 80GB HBM3 | 192GB HBM3e | 288GB HBM4 |
| 显存带宽 | 3.35 TB/s | 8 TB/s | 12 TB/s |
| NVLink 带宽 | 900 GB/s | 1.8 TB/s | 3.6 TB/s |
| FP4 算力 | N/A | 0.7 ExaFLOPS | 1.4 ExaFLOPS |
| 芯片数 | 1 | 72 | 72 |
GB300 NVL72 的核心设计理念是将 72 个芯片通过 NVLink 6 互联,形成一个单一的逻辑 GPU。这意味着:
- 288GB × 72 = 20.6 TB 总显存
- 12 TB/s × 72 = 864 TB/s 总显存带宽
- 1.4 ExaFLOPS × 72 = 100.8 ExaFLOPS 总算力
5.2 SGLang 在 GB300 上的 25 倍提升
2026 年 2 月,SGLang 团队发布了在 GB300 NVL72 上的性能报告。核心数据如下:
| 模型 | H100 (vLLM) | GB300 NVL72 (SGLang) | 提升 |
|---|---|---|---|
| Llama3.1-70B-FP8 | 1,200 tok/s | 30,000 tok/s | 25x |
| DeepSeek-V3 | 800 tok/s | 18,000 tok/s | 22.5x |
| Qwen2.5-72B-FP8 | 1,100 tok/s | 27,500 tok/s | 25x |
25 倍提升的来源分析:
硬件层面(约 8x):
- GB300 的 HBM4 显存带宽是 H100 的 3.6 倍
- NVLink 6 带宽是 NVLink 4 的 4 倍
- FP4 算力是 H100 的约 2 倍(如果使用 FP4 量化)
软件层面(约 3x):
- RadixAttention 在大规模集群中实现了跨芯片的前缀缓存共享
- FlashInfer 针对 NVL72 的 NVLink 拓扑进行了专门优化
- 零开销调度器在 72 芯片规模下依然保持低延迟
协同效应(约 1x):
- PD 分离 + RadixAttention + FlashInfer 在 NVL72 的互连架构上产生了 1+1>2 的效果
- 跨芯片的 KV Cache 共享使得 RadixAttention 的缓存命中率在集群级别进一步提升
5.3 跨芯片 RadixAttention
在 NVL72 这样的大规模集群中,SGLang 实现了跨芯片的 RadixAttention。这是 SGLang 最具创新性的设计之一。
┌─────────────────────────────────────────────────┐
│ NVL72 集群拓扑 │
│ │
│ ┌─────────┐ NVLink 6 ┌─────────┐ │
│ │ GPU 0-17│◄──────────▶│GPU 18-35│ │
│ │ Prefill │ │ Decode │ │
│ │ Group A │ │ Group B │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ │ NVLink 6 (3.6TB/s)│ │
│ └──────────┬───────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ RadixTree │ ← 分布式全局缓存 │
│ │ (Leader + │ │
│ │ Followers)│ │
│ └─────────────┘ │
│ │
│ Leader GPU (GPU 0) 维护全局 RadixTree 元数据 │
│ Followers (GPU 1-71) 本地缓存 + 异步同步 │
│ │
└─────────────────────────────────────────────────┘
跨芯片缓存共享的工作流程:
- Prefill GPU 0 计算完一个请求的 KV Cache 后,将元数据插入全局 RadixTree
- 其他 Prefill GPU 收到相同前缀的请求时,通过 NVLink 从 GPU 0 获取已缓存的 KV Cache
- Decode GPU 通过 NVLink 从任意 Prefill GPU 获取 KV Cache
- 全局 RadixTree 通过 Gossip 协议保持一致性,延迟在微秒级别
第六章:多 LoRA 热切换——一个服务同时运行 100 个微调模型
6.1 为什么需要多 LoRA?
在生产环境中,同一个基础模型通常需要支持多个微调版本。例如:
- 一个 Llama3-70B 基础模型 + 10 个不同领域的微调版本(法律、医疗、金融、教育...)
- 每个微调版本只需要 LoRA 权重(通常只有基础模型的 0.1-1% 大小)
传统的做法是为每个 LoRA 部署一个独立的推理服务,但这意味着:
- GPU 显存浪费:每个服务都需要加载完整的基础模型(70B 模型 × 10 个服务 = 700GB 显存)
- 管理复杂:10 个服务需要 10 套监控、运维和扩缩容策略
- 延迟高:请求需要路由到对应的服务,增加了额外的网络跳转
6.2 SGLang 的多 LoRA 架构
SGLang 实现了原生多 LoRA 热切换,一个服务可以同时承载 100+ 个 LoRA 微调模型:
# SGLang 多 LoRA 服务启动
python -m sglang.launch_server \
--model-path meta-llama/Llama-3-70B \
--enable-lora \
--lora-paths /path/to/lora_legal \
/path/to/lora_medical \
/path/to/lora_finance \
/path/to/lora_education \
--max-lora-batch-size 64 \
--max-lora-rank 64
工作原理:
- 基础模型权重共享:基础模型的权重只加载一次到 GPU 显存,所有 LoRA 请求共享
- LoRA 权重动态加载:每个 LoRA 的 A、B 矩阵(低秩分解矩阵)按需加载到 GPU 显存
- Attention 计算适配:在 Attention 计算时,通过 LoRA 的低秩分解对 Q、K、V 进行适配
# 多 LoRA 请求处理逻辑(简化版)
def forward_with_lora(self, x, lora_id):
# 1. 基础模型前向传播
base_output = self.base_model.forward(x)
# 2. 如果指定了 LoRA,应用低秩适配
if lora_id is not None:
# LoRA 的核心公式:output = W @ x + B @ A @ x
# 其中 W 是基础模型权重,A 和 B 是 LoRA 的低秩矩阵
lora_A = self.lora_weights[lora_id]['A'] # (rank, hidden_dim)
lora_B = self.lora_weights[lora_id]['B'] # (hidden_dim, rank)
# 计算 LoRA 增量
lora_delta = lora_B @ (lora_A @ x)
# 加到基础输出上
base_output = base_output + lora_delta
return base_output
6.3 性能数据
| 场景 | 方案 | GPU 显存 | 延迟 | 吞吐 |
|---|---|---|---|---|
| 10 个 LoRA | 10 个独立服务 | 700GB | 120ms | 1,000 tok/s |
| 10 个 LoRA | SGLang 多 LoRA | 75GB | 95ms | 8,500 tok/s |
| 100 个 LoRA | 100 个独立服务 | 不可行 | N/A | N/A |
| 100 个 LoRA | SGLang 多 LoRA | 85GB | 110ms | 7,200 tok/s |
核心优势:SGLang 将 10 个 LoRA 的 GPU 显存需求从 700GB 降低到 75GB,吞吐提升 8.5 倍。对于 100 个 LoRA 的场景,SGLang 是唯一可行的方案。
第七章:投机采样(Speculative Decoding)——DFlash 与 Spec V2
7.1 什么是投机采样?
投机采样的核心思想是:用一个小模型(Draft Model)快速生成多个候选 Token,然后用大模型(Target Model)一次性验证。
为什么这能加速?因为 LLM 推理的 Decode 阶段有一个根本性的瓶颈:每次只能生成一个 Token。这是自回归模型的固有限制。但如果我们能用一个小模型快速"猜测"多个 Token,然后用大模型一次性验证,就能打破这个限制。
传统 Decode:
时间 →
大模型: [Token1] → [Token2] → [Token3] → [Token4] → [Token5]
10ms 10ms 10ms 10ms 10ms
总计: 50ms
投机采样:
时间 →
小模型: [T1] [T2] [T3] [T4] [T5] → 5ms (并行生成 5 个候选)
大模型: [T1✓] [T2✓] [T3✓] [T4✗] → 8ms (一次性验证前 4 个)
总计: 13ms (加速 3.8x)
7.2 SGLang 的 DFlash:下一代投机采样
2026 年 6 月,SGLang 团队发布了 DFlash——一种全新的投机采样算法,相比传统的投机采样有三个关键创新:
创新 1:Draft-Verify 并行化
传统的投机采样中,Draft 阶段和 Verify 阶段是串行的。DFlash 将两者部分重叠:当小模型生成第 N 个 Draft Token 时,大模型已经开始验证第 N-1 个 Token。
class DFlashScheduler:
def __init__(self, draft_model, target_model, max_draft_len=8):
self.draft = draft_model
self.target = target_model
self.max_draft_len = max_draft_len
def speculative_generate(self, prompt_tokens):
draft_tokens = []
verify_results = []
# Draft-Verify 并行执行
draft_queue = []
verify_queue = []
for step in range(self.max_draft_len):
# 异步启动 Draft 生成
draft_future = self.draft.async_forward(
prompt_tokens + draft_tokens
)
draft_queue.append(draft_future)
# 如果有足够多的 Draft Token,启动 Verify
if len(draft_queue) >= 2:
draft_result = draft_queue.pop(0).result()
verify_future = self.target.async_verify(
prompt_tokens + draft_tokens + [draft_result]
)
verify_queue.append(verify_future)
draft_tokens.append(draft_result)
# 收集所有 Verify 结果
verified_tokens = []
for future in verify_queue:
result = future.result()
if result.all_accepted:
verified_tokens.extend(result.tokens)
else:
verified_tokens.extend(result.accepted_tokens)
verified_tokens.append(result.resampled_token)
break
return verified_tokens
创新 2:动态 Draft 长度
传统的投机采样使用固定的 Draft 长度(通常是 4-8 个 Token)。DFlash 根据当前负载动态调整:
- 低负载时:增加 Draft 长度(最多 16 个),最大化加速比
- 高负载时:减少 Draft 长度(最少 2 个),降低延迟
创新 3:KV Cache 预热
在小模型生成 Draft Token 的过程中,DFlash 会预热大模型的 KV Cache。当小模型生成第 N 个 Token 时,大模型已经将前 N-1 个 Token 的 KV Cache 计算完毕。这样在 Verify 阶段,大模型只需要计算最后一个 Token 的 KV Cache,大幅减少验证时间。
7.3 DFlash 性能数据
| 模型 | 基线 Decode | DFlash | 加速比 |
|---|---|---|---|
| Llama3.1-8B | 45 tokens/s | 162 tokens/s | 3.6x |
| Llama3.1-70B | 12 tokens/s | 38 tokens/s | 3.2x |
| DeepSeek-V3 | 8 tokens/s | 28 tokens/s | 3.5x |
| Qwen2.5-72B | 10 tokens/s | 33 tokens/s | 3.3x |
第八章:SGLang Diffusion——不只是 LLM,还是生成式 AI 的统一推理平台
8.1 从 LLM 到多模态
2026 年 1 月,SGLang 发布了 SGLang Diffusion,将推理能力扩展到图像和视频生成领域。
核心设计理念:LLM 和 Diffusion Model 的推理有一个共同点——都是自回归的。LLM 逐 Token 生成文本,Diffusion 逐步去噪生成图像。SGLang 将两者统一到同一个调度框架中。
# SGLang Diffusion 统一接口
import sglang as sgl
@sgl.function
def image_generation(s, prompt, num_steps=50):
s += sgl.system("You are a creative image generator.")
s += sgl.user(prompt)
s += sgl.assistant(
sgl.gen("image",
num_steps=num_steps,
guidance_scale=7.5,
scheduler="DPMSolverMultistep")
)
@sgl.function
def text_generation(s, prompt):
s += sgl.system("You are a helpful assistant.")
s += sgl.user(prompt)
s += sgl.assistant(sgl.gen("answer", max_tokens=512))
# 同一个 Runtime 支持两种任务
runtime = sgl.Runtime(
model_path="stabilityai/stable-diffusion-3",
model_type="diffusion",
tp_size=4
)
8.2 SGLang Diffusion 的性能优势
| 模型 | PyTorch 原生 | SGLang Diffusion | 提升 |
|---|---|---|---|
| SD3 (1024×1024) | 8.2s | 3.1s | 2.6x |
| SDXL (1024×1024) | 6.5s | 2.4s | 2.7x |
| Stable Video (25帧) | 45s | 16s | 2.8x |
第九章:实战指南——从零开始部署 SGLang
9.1 安装
# 方法 1:pip 安装(推荐)
pip install sglang
# 方法 2:使用 uv(更快)
uv pip install sglang
# 方法 3:Docker
docker pull lmsysorg/sglang:latest
# 方法 4:从源码安装
git clone https://github.com/sgl-project/sglang.git
cd sglang
pip install -e .
9.2 单 GPU 快速启动
# 启动 Llama3.1-8B 推理服务
python -m sglang.launch_server \
--model-path meta-llama/Llama-3.1-8B-Instruct \
--host 0.0.0.0 \
--port 8080 \
--mem-fraction-static 0.8
9.3 多 GPU 张量并行
# 4 GPU 张量并行部署 Llama3.1-70B
python -m sglang.launch_server \
--model-path meta-llama/Llama-3.1-70B-Instruct \
--tp-size 4 \
--host 0.0.0.0 \
--port 8080
9.4 Prefill-Decode 分离部署
# 在 GPU 0-3 上启动 Prefill Worker
python -m sglang.launch_server \
--model-path meta-llama/Llama-3.1-70B-Instruct \
--tp-size 4 \
--role prefill \
--host 0.0.0.0 \
--port 8081
# 在 GPU 4-7 上启动 Decode Worker
python -m sglang.launch_server \
--model-path meta-llama/Llama-3.1-70B-Instruct \
--tp-size 4 \
--role decode \
--host 0.0.0.0 \
--port 8082
9.5 性能调优参数
python -m sglang.launch_server \
--model-path meta-llama/Llama-3.1-70B-Instruct \
--tp-size 4 \
# 显存优化
--mem-fraction-static 0.85 \
--max-running-requests 256 \
# RadixAttention 配置
--enable-radix-attention \
--cache-capacity-ratio 0.2 \
# 投机采样
--speculative-algorithm DFlash \
--speculative-num-steps 5 \
# 多 LoRA
--enable-lora \
--lora-paths /path/to/lora1 /path/to/lora2 \
--max-lora-rank 64
第十章:SGLang vs 竞品——全景对比与选型指南
| 特性 | vLLM | SGLang | TensorRT-LLM | Ollama |
|---|---|---|---|---|
| GitHub Stars | 52K | 27K | 12K | 120K |
| 部署复杂度 | 中 | 低 | 高 | 极低 |
| 前缀缓存 | ❌ | ✅ RadixAttention | ❌ | ❌ |
| PD 分离 | ❌ | ✅ | ✅ | ❌ |
| 多 LoRA | ⚠️ 有限 | ✅ 原生 | ⚠️ 有限 | ❌ |
| 投机采样 | ⚠️ 基础 | ✅ DFlash | ✅ | ❌ |
| Diffusion 支持 | ❌ | ✅ | ❌ | ❌ |
| TPU 支持 | ❌ | ✅ (JAX) | ❌ | ❌ |
| 生产级 gRPC | ✅ | ✅ | ✅ | ❌ |
| OpenAI 兼容 | ✅ | ✅ | ✅ | ✅ |
选型建议:
- 快速原型/个人使用 → Ollama
- 单 GPU 高吞吐 → vLLM 或 SGLang
- 多 GPU 大规模部署 → SGLang(RadixAttention + PD 分离)
- 极致性能(NVIDIA 生态) → TensorRT-LLM
- 多 LoRA / 多模态 → SGLang
- 边缘设备 / TPU → SGLang (JAX backend)
总结:SGLang 的未来与 LLM 推理的终局
SGLang 的核心创新总结
- RadixAttention:用基数树管理 KV Cache 前缀缓存,在多轮对话和 RAG 场景下实现 3-8 倍加速
- FlashInfer:自研 CUDA Kernel 库,原生支持 Paged KV Cache 和变长序列
- 零开销调度器:所有调度决策在 CPU 上完成,GPU 纯做计算
- Prefill-Decode 分离:将计算密集型和访存密集型阶段拆分到不同 GPU
- DFlash 投机采样:下一代投机解码算法,实现 3-4 倍 Decode 加速
- 多 LoRA 热切换:一个服务同时运行 100+ 微调模型
- SGLang Diffusion:统一 LLM 和 Diffusion 的推理框架
LLM 推理的终局
LLM 推理正在经历从 "能跑就行" 到 "极致优化" 的转变。SGLang 的出现,标志着推理引擎竞争进入了新的阶段:
- 从单 GPU 到集群级优化:RadixAttention 的跨芯片缓存共享
- 从 LLM 到多模态统一:Diffusion 和 LLM 共享同一套调度框架
- 从固定架构到动态适配:DFlash 的动态 Draft 长度和 PD 分离的弹性调度
SGLang 不只是 "另一个 vLLM",它是 LLM 推理基础设施的一次范式革命。
对于开发者而言,现在是学习 SGLang 的最佳时机。它不仅是一个工具,更是理解现代 LLM 推理架构的最佳教材。无论你是 AI 工程师、MLOps 工程师,还是对系统优化感兴趣的技术人,SGLang 都值得你深入了解。
参考资源:
- SGLang 官方仓库:https://github.com/sgl-project/sglang
- SGLang 官方文档:https://docs.sglang.io
- GB300 NVL72 性能报告:https://lmsys.org/blog/2026-02-20-gb300-inferencex/
- DFlash 投机采样:https://lmsys.org/blog/2026-06-15-next-generation-speculative-decoding-dflash-v2/
- RadixArk 融资:https://www.toutiao.com/article/7637451259712193070/
- SGLang Diffusion:https://lmsys.org/blog/2026-01-16-sglang-diffusion/