对应 Maps to:Stanford CS349D AI Inference Infrastructure(Spring 2026),#8 Wed Apr 22 客座讲座 SGLang(Ying Sheng,SGLang 作者之一、RadixArk 创始人)。 依据说明:客座讲座没有公开的 slides 或录像,本讲是我依据公开材料重建的,不代表讲者的内容。依据:L. Zheng, L. Yin, Z. Xie 等, SGLang: Efficient Execution of Structured Language Model Programs(NeurIPS 2024;第 3 讲已经细读);LMSYS / SGLang 团队的技术博客:Fast and Expressive LLM Inference with RadixAttention and SGLang(2024-01-17)、Fast JSON Decoding for Local LLMs with Compressed Finite State Machine(2024-02-05)、Achieving Faster Open-Source Llama3 Serving with SGLang Runtime(v0.2,2024-07-25)、SGLang v0.3 Release(2024-09-04)、SGLang v0.4: Zero-Overhead Batch Scheduler, Cache-Aware Load Balancer, Faster Structured Outputs(2024-12-04)、Deploying DeepSeek with PD Disaggregation and Large-Scale Expert Parallelism on 96 H100 GPUs(2025-05-05)、Deploying DeepSeek on GB200 NVL72 with PD and Large Scale EP (Part I)(2025-06-16)。§6 的综合和 §7”之后的发展”是我补充的。讲义里的纸上数字全部来自配套代码的实际运行;博客里的数字写成”博客报告”。 配套代码 Code:
code/lecture16/(numpy;3 个脚本 + 1 个模块sgl_runtime.py:把第 3 讲的基数树、连续批处理、分块 prefill、缓存感知调度和本讲的重叠调度拼成一个 SGLang 式的迷你运行时,每个特性都能单独关掉;以及 DeepSeek 的 DP 注意力和两批重叠的代价模型。代码 02 约 30 秒,其余几秒以内) 预计阅读时间:2.5–3.5 小时(含运行代码)
0. 本讲导航 · How to read this lecture
0.1 学习目标 Learning goals
学完这一讲,你应该能够:
- 说出 SGLang 的整体架构(前端语言、运行时的分词 / 调度 / 模型执行 / 去分词)以及它从 2023 年底到 2025 年的几次关键演进。
- 解释推理引擎里的 CPU 开销从哪里来,“零开销”的重叠调度器怎样把它藏到 GPU 计算后面,以及它在什么情况下收益最大、有什么代价。
- 用一个拼起来的迷你运行时说明:前缀缓存、缓存感知调度、分块 prefill、重叠调度各在什么负载上值多少,以及它们之间的相互作用(例如”什么时候把前缀插进树”)。
- 解释为了服务 DeepSeek(MLA + MoE),SGLang 做的数据并行注意力、大规模专家并行、两批重叠、prefill / decode 解耦各自的道理和代价。
- 总结”研究原型变成生产系统”要补上的东西:性能工程、广度、可靠性、可观测性、诚实的基准测试。
0.2 先修与本讲范围 Prerequisites & scope
| 默认你已经会 Assumed | 本讲只给直觉,后面再展开 Previewed only |
|---|---|
| 第 2 讲:连续批处理、迭代级调度 | GPU 内核的实现细节(FlashInfer、DeepGEMM 的内部) |
| 第 3 讲:RadixAttention、SGLang 前端、压缩有限状态机 | SGLang 的源代码结构(只讲设计) |
| 第 7 讲:分块 prefill、prefill / decode 解耦 | 量化(FP8 只作为背景) |
| 第 8 讲:MoE、专家并行、MLA 的 KV | 多模态模型的服务 |
| 第 11 讲:缓存感知的路由 |
0.3 本讲地图 Map
§1 SGLang 是什么:来历、架构、演进
§2 零开销批调度器:把 CPU 藏起来 ← 代码 01
§3 把零件拼起来:一个 SGLang 式的迷你运行时 ← 代码 02
§4 其他工程:缓存感知路由、结构化输出、内核
§5 服务 DeepSeek:DP 注意力、大规模 EP、两批重叠、PD 解耦 ← 代码 03
§6 从研究原型到生产系统
§7 之后的发展
§8–12 小结、常见误区、练习、延伸阅读、下一讲预告
0.4 记号 Notation
| 符号 | 含义 |
|---|---|
| SRT | SGLang Runtime,SGLang 的后端运行时(相对于前端语言) |
| 重叠调度 overlap scheduling | CPU 在 GPU 计算第 i 批时准备第 i + 1 批(SGLang v0.4 的”零开销批调度器”) |
| LPM | longest prefix match,最长前缀优先的排队策略(第 3 讲) |
| DP 注意力 DP attention | 注意力部分用数据并行(每块 GPU 负责一部分请求),MoE 部分用张量 / 专家并行 |
| EP、EPLB | 专家并行;专家并行负载均衡器(DeepSeek 开源) |
| TBO | two-batch overlap,两批重叠:一个微批计算时另一个微批通信 |
| PD 解耦 | prefill 与 decode 放在不同的服务器上(第 7 讲) |
0.5 关于数字 · About the numbers
本讲有两类数字。博客报告的数字来自 SGLang 团队在真实 GPU 上的测量,写成”博客报告”。代码的数字来自纸上的模型:GPU 时间是第 1 讲的 roofline(Llama-3-8B / H100 等),CPU 开销是假设值(每轮 0.8 毫秒加每个请求几微秒),运行时用第 3 讲真实的基数树做前缀匹配。代码能说明各个特性的相对价值和机制,不能替代真实的基准测试。
1. SGLang 是什么:来历、架构、演进 · What SGLang is
1.1 来历
SGLang 出自 LMSYS(由 UC Berkeley 等学校的学生和教师发起的开放研究组织);论文的作者里还有本课的授课教师 Kozyrakis 和助教 Zhiqiang Xie。LMSYS 运营 Chatbot Arena,用 FastChat 服务数百万用户,“深知高效服务对 AI 产品和研究有多重要”(v0.2 博客)。2023 年 12 月的 SGLang 论文(第 3 讲)提出了三样东西:一个嵌在 Python 里的前端语言(gen、select、fork 等原语,描述多次调用的 LLM 程序)、RadixAttention(用基数树自动复用 KV cache)、压缩有限状态机(加速结构化输出)。
之后的两年,它从”一篇论文的配套代码”变成了一个被广泛部署的引擎。按博客的时间线:
| 时间 | 版本 / 博客 | 主要内容(博客报告) |
|---|---|---|
| 2024-01 | RadixAttention 与 SGLang | 自动 KV 复用 + 前端语言;智能体、推理、抽取、聊天、少样本等负载上比 Guidance、vLLM 最多高 5 倍吞吐(A10G) |
| 2024-02 | 压缩有限状态机 | JSON 解码的跳跃式解码:延迟最多降一半、吞吐最多 2.5 倍 |
| 2024-07 | v0.2 | 专注运行时:Llama-8B 到 405B,常常与 TensorRT-LLM 持平、比 vLLM 快最多 3.1 倍;核心调度器不到 4,000 行 Python |
| 2024-09 | v0.3 | DeepSeek MLA 的优化(权重吸收、分组 decode 内核、FP8):3–7 倍吞吐;torch.compile 小批延迟降 1.5 倍 |
| 2024-12 | v0.4 | 零开销批调度器(1.1 倍)、缓存感知负载均衡(最多 1.9 倍)、DeepSeek 的 DP 注意力(decode 最多 1.9 倍)、xgrammar 结构化输出(最多 10 倍) |
| 2025-05 | 大规模 EP + PD 解耦 | 96 块 H100 上复现 DeepSeek 的推理系统:每节点每秒 5.23 万输入 token、2.23 万输出 token |
| 2025-06 | GB200 NVL72 | decode 每块 GPU 每秒 7,583 token,是 H100 的 2.7 倍 |
1.2 架构
┌──────────────────────── SGLang Runtime(SRT)────────────────────────┐
HTTP / OpenAI │ 分词管理器 ──▶ 调度器 ─────────────────────▶ 模型执行(TP 工作进程) │
兼容的 API ──▶│ (tokenize) · 等待队列与排队策略(FCFS / LPM) · CUDA graph │
前端语言 ──▶ │ · 基数树(前缀缓存,LRU) · 注意力内核(FlashInfer│
│ · KV 显存池(分页) / Triton)、MoE 内核 │
│ · 组批:连续批处理 + 分块 prefill · 采样 │
│ ◀──── 输出 token ───┘ │
│ 去分词管理器 ◀─────────────────────────────────────────────── │
└──────────────────────────────────────────────────────────────────────────┘
几个设计选择值得注意:
- 调度器用 Python 写。v0.2 博客的说法是:SGLang 的批调度器完全用 Python 实现,规模化得很好,常常赶上甚至超过用 C++ 写的闭源实现;“这篇博客里的加速主要来自出色的系统工程”。Python 让研究者容易修改和扩展;热点路径(注意力、MoE、采样)则交给 CUDA / Triton 内核。
- 分词、调度、去分词在不同的进程里,让 Python 的全局解释器锁不成为瓶颈,也让 CPU 上的前后处理和调度并行。
- 站在别人的肩膀上:博客写明它借鉴了 LightLLM、vLLM、Guidance 的设计,用 FlashInfer 的注意力内核,借鉴 gpt-fast 集成 torch.compile。好的工程系统很少从零发明一切。
2. 零开销批调度器:把 CPU 藏起来 · The zero-overhead batch scheduler
2.1 CPU 开销从哪里来
推理引擎的每一轮迭代里,GPU 做 forward,CPU 要做:组批(决定这一轮哪些请求 decode、哪些 prefill 多少)、在基数树里匹配和插入前缀、分配和释放 KV 显存、准备内核的输入、采样后处理(检查停止条件)、去分词并把结果交给前端。v0.4 博客:“一个没优化的推理引擎可能有一半时间花在 CPU 开销上。”
朴素的引擎是串行的:CPU 组好第 i 批 → GPU 算 → CPU 处理结果、组第 i + 1 批 → GPU 算……GPU 在 CPU 工作时闲着。
2.2 重叠:提前一批
v0.4 的做法(思路来自 NanoFlow):让调度器提前一批工作。GPU 在算第 i 批时,CPU 已经在为第 i + 1 批准备所有元数据,GPU 一算完就能接着算下一批。难点是依赖:第 i + 1 批的输入就是第 i 批采样出来的 token,CPU 准备时还不知道。SGLang 的解决办法是创建”未来 token”(future tokens)占位,在 GPU 上解析,并仔细安排 CUDA 事件和同步。博客用 Nsight 验证:连续 5 个 decode 批之间 GPU 没有任何空闲(用 Triton 注意力后端时;FlashInfer 后端当时还有一点空隙)。
代码 01 用一个很简单的模型说明这件事(sgl_runtime.iteration_time):不重叠时一轮 = CPU + GPU;重叠时一轮 = max(CPU, GPU) + 一次同步。CPU 开销的各项是假设值(每轮 0.8 毫秒,加每个请求 6 微秒)。代码 01 演示 1(稳态 decode,上下文约 1,500):
| 模型(张量并行) | 批大小 | GPU / 步 | CPU / 步 | 不重叠时 GPU 闲 | 重叠加速 |
|---|---|---|---|---|---|
| Llama-3.2-3B(TP = 1) | 8 | 2.93 ms | 0.85 ms | 22% | 1.28× |
| Llama-3-8B(TP = 1) | 64 | 10.32 ms | 1.18 ms | 10% | 1.11× |
| Llama-3-70B(TP = 8) | 64 | 13.27 ms | 1.18 ms | 8% | 1.09× |
| Llama-3-8B(TP = 8) | 64 | 3.26 ms | 1.18 ms | 27% | 1.35× |
| Llama-3-8B(TP = 8) | 256 | 5.51 ms | 2.34 ms | 30% | 1.42× |
CPU 的工作量和模型大小无关,GPU 的时间却随模型、批大小、张量并行度变化。GPU 越快,CPU 越是瓶颈——这就是博客说的”在小模型和大张量并行时最明显”。
2.3 两个补充
重叠藏不住比 GPU 还慢的 CPU。 重叠以后一步 = max(CPU, GPU)。代码 01 演示 2 把每轮的固定开销从 0.2 毫秒调到 5 毫秒:不重叠时 CPU 的占比从 5% 升到 34%;重叠能把它们全部藏住(只要 CPU 仍比 GPU 快),5 毫秒时快 1.52 倍。所以”把 CPU 做快”和”把 CPU 藏起来”是互补的:SGLang 的调度器从一开始就以高效著称,v0.4 再加上重叠;后来的缓存感知路由用 Rust 写(“比 Python 实现快 2 倍”),也是同一个思路。
重叠有代价。 第 i 批会不会产生结束符,要 GPU 算完、采样以后才知道;而第 i + 1 批已经组好了。所以刚刚结束的请求会被放进下一批、白算一个位置。对平均输出几百 token 的对话可以忽略,对只输出 1 个 token 的负载(分类、打分,RL 里的奖励模型推理)就是 50% 的浪费(代码 01 演示 3)——这时可以关掉重叠(SGLang 的 --disable-overlap)。
在一个完整的负载上(代码 01 演示 4,少样本 + 前缀缓存,600 个请求同时到达),重叠把吞吐从 6,069 提高到 6,681 token/s(1.10 倍),GPU 从 91% 的时间在忙变成几乎一直在忙——与博客报告的”相对上一版约 1.1 倍”一致。CPU 固定开销 3 毫秒的”慢”引擎不重叠时只有 5,640,重叠以后同样是 6,681。
3. 把零件拼起来:一个 SGLang 式的迷你运行时 · Putting the pieces together
前面各讲分别讲了连续批处理(第 2 讲)、前缀缓存与缓存感知调度(第 3 讲)、分块 prefill(第 7 讲)。真实引擎的价值在于把它们放在一起、而且彼此不打架。sgl_runtime.Runtime 做了一个纸上的版本:
- 第 3 讲真实的基数树(按 token 匹配、LRU 淘汰叶子、引用计数);
- 连续批处理:每一轮所有已完成 prefill 的请求各 decode 一个 token;
- 分块 prefill:剩下的预算(每轮最多 2,048 个提示 token)给正在 prefill 的请求;
- 排队策略:先来先服务,或最长前缀优先(同一轮里如果一个请求还没缓存的前缀与这一轮已接纳的请求大段重合,就推迟到下一轮);
- 重叠调度(§2);GPU 时间来自第 11 讲的 StepModel(与第 1 讲的 roofline 逐项一致)。
3.1 少样本负载:每一项都不大,合起来 2.8 倍
代码 02 演示 1(8 个应用,各有 2,000 token 的系统提示,600 个请求同时到达;Llama-3-8B / H100):
| 配置 | 吞吐 token/s | 命中率 | TTFT | 相对 |
|---|---|---|---|---|
| 全部打开(SGLang 式) | 6,681 | 91% | 4.66 s | 1.00 |
| 关掉重叠调度 | 6,069 | 91% | 5.19 s | 0.91 |
| 关掉前缀缓存 | 3,297 | 0% | 14.34 s | 0.49 |
| 关掉分块 prefill | 6,192 | 93% | 5.15 s | 0.93 |
| 先来先服务(不按前缀排序) | 4,900 | 65% | 9.96 s | 0.73 |
| 全部关掉(朴素引擎) | 2,401 | 0% | 20.18 s | 0.36 |
前缀缓存是这个负载上最大的一项;按前缀排序第二——先来先服务时,热门应用的请求同时涌入,在第一个请求的 prefill 做完、把系统提示插进基数树之前,后面的请求都要自己再算一遍。
一个相互作用的例子。 我的第一版运行时只在请求结束时把序列插进基数树,结果同时在跑的请求之间完全无法共享前缀,命中率只有 65%,而且显存越多(同时接纳的请求越多)命中率反而越低。SGLang 在 prefill 完成时就插入(第 3 讲也是这样)。改了以后吞吐从约 4,900 提高到 6,681。“什么时候插入”这种细节,论文里一句话带过,在系统里却值 1.4 倍(代码 README 第 1 条)。
3.2 多轮对话与无共享负载
代码 02 演示 2(300 个会话、每个 6 轮,下一轮在上一轮结束后 5–20 秒到达,共享 500 token 的系统提示):前缀缓存的命中率 66%,关掉它 TTFT 变成 3 倍、端到端延迟从 3.1 秒变成 5.1 秒;命中率不更高,是因为 300 个会话的历史超过了 KV 容量,一部分在用户思考的间隙被 LRU 淘汰——这正是第 9 讲把 KV 缓存扩展到 CPU 内存和 SSD 的动机。这里按前缀排序几乎不起作用:同一时刻共享同一段历史的请求很少。
代码 02 演示 3(没有共享前缀):前缀缓存和排序都不起作用,也不碍事(基数树的维护开销被重叠调度藏住了);重叠调度(0.92)和分块 prefill(0.84)照样有用。所以通用引擎的默认配置应该是”全部打开”。
3.3 显存很紧时
代码 02 演示 4(40 个应用、8 万 token 的系统提示):显存充裕时最长前缀优先比先来先服务快约 15%;但 KV 只有 6 万 token 时,两者命中率相同(85%),最长前缀优先的吞吐反而低 6%——推迟请求让批变小,而缓存里的前缀很快又被挤掉。缓存感知调度不是越紧越有用,这和第 3 讲”中等缓存时收益最大”的观察是一致的。
4. 其他工程 · More engineering
缓存感知的负载均衡(v0.4;第 11 讲 §5 已详细讲过)。路由器维护一棵”近似基数树”,记录每个前缀大概在哪个副本上(按派发过的请求更新,不需要和副本同步);命中率够高就派给匹配最长的副本,否则派给负载最轻的。博客报告:在多组长前缀的负载上,吞吐从 82,665 提高到 158,596 token/s,命中率从 20% 提高到 75%;用 Rust 实现,作为独立的 sglang-router 包发布,支持多节点。博客也提到 Preble(第 11 讲)是基于 SGLang 的另一种设计。
结构化输出(第 3 讲 §7.3)。2024 年 2 月的博客介绍了跳跃式解码:把 JSON schema 转成正则表达式和有限状态机,压缩其中”只有一条路”的链,遇到确定的部分(例如 {"name": )就一次 prefill 完,而不是一个 token 一次 forward;RadixAttention 让实现变得简单——跳跃时结束当前请求、排入一个新请求,前面的 KV 自动复用。难点在分词边界:模型更喜欢把 " 和后面的 , 合成一个 token,按字符串拼接、整体重新分词能解决大部分问题,代价约 4%。v0.4 接入了 xgrammar 作为语法后端,博客报告比其他开源方案快最多 10 倍。
内核与编译。v0.3 为 DeepSeek 的 MLA 做了权重吸收(把解压矩阵吸收进 query 和输出投影,直接在 576 维的潜空间里做注意力;第 8 讲)、分组 decode 内核、FP8 批量矩阵乘法、FP8 KV cache,吞吐 3–7 倍;对线性层、归一化、激活函数用 torch.compile,配合 FlashInfer 的注意力和采样内核,批 1–32 时延迟最多降 1.5 倍。
5. 服务 DeepSeek:DP 注意力、大规模 EP、两批重叠、PD 解耦 · Serving DeepSeek
DeepSeek-V3 / R1 有 6,710 亿参数(MoE,256 个路由专家、每个 token 选 8 个),注意力是 MLA。SGLang 2025 年 5 月的博客描述了怎样在 12 个节点(96 块 H100)上复现 DeepSeek 官方的推理系统。
5.1 DP 注意力
MLA 把每个 token 的 KV 压成一个 512 维的潜向量加 64 维的位置部分(第 8 讲):DeepSeek-Coder-V2 每 token 每层 1,152 字节,60 层共约 69 KB,同样 60 层的 GQA(8 个 KV 头)要 246 KB。可它只有”一个头”——张量并行切不开:8 块 GPU 每块都要存全部请求的 KV,等于复制了 8 份。
v0.4 的 DP 注意力:注意力部分用数据并行,每块 GPU 只负责一部分请求,只存、只读它们的 KV;进入 MoE 之前把各 GPU 的隐藏状态 all-gather 起来,MoE 之后再分回去。每个 DP 工作进程可以独立处理不同类型的批(prefill、decode、空闲)。代码 03 演示 1(DeepSeek-Coder-V2,FP8,8 × H100):
| 上下文 | TP 注意力:批 / 吞吐 | DP 注意力:批 / 吞吐 | 加速 |
|---|---|---|---|
| 256 | 1,861 / 46,433 | 4,096 / 83,009 | 1.79× |
| 2,048 | 232 / 7,653 | 1,416 / 44,062 | 5.76× |
| 8,192 | 58 / 2,062 | 352 / 12,453 | 6.04× |
上下文很短时(接近博客的测试:输入 1 个 token、输出 512 个),加速 1.79 倍,与博客报告的”decode 最多 1.9 倍”相当;上下文越长,张量并行能放下的批越小,DP 的优势越大。即使批大小相同,DP 也更快:张量并行时每块 GPU 都要把全部 KV 读一遍(上下文 512、批 930 时每层注意力 212 µs,DP 只要 81 µs)。代价是每块 GPU 都存一份注意力权重,以及各 GPU 的批不均衡时要互相等。
大规模 EP 的博客把这个思路推广到整个模型:稠密 FFN 和 LM head 也用数据并行。理由之一是显存:用 DP 注意力时,纯张量并行的稠密 FFN 每块 GPU 的显存约为 (k 是 CUDA graph 复制带来的额外开销),在 DP = TP 时最优的 TP 是 ——prefill 时每块 GPU 的 token 很多,最优 TP 不超过 3;decode 时约为 6。理由之二是通信:纯张量并行每个 FFN 要两次 all-reduce,DP 只需在注意力之后一次 reduce-scatter、下一层之前一次 all-gather,通信减半。
5.2 大规模专家并行
MoE 的专家权重是显存的大头,专家并行(第 8 讲)把它们分到很多块 GPU 上:每块 GPU 只存几个专家,显存省下来放更大的批——博客报告 decode 的收益”主要来自 EP 带来的 4 倍批大小”。SGLang 集成了 DeepSeek 开源的三个组件:
- DeepEP(通信):普通模式适合 prefill(长输入、最大吞吐,但形状不固定、不能用 CUDA graph),低延迟模式适合 decode(支持 CUDA graph,但要预分配固定的显存)。两种模式不能在同一个通信组里同时使用——这是要做 PD 解耦的原因之一。
- DeepGEMM(专家的分组矩阵乘法):连续布局的内核配合 prefill,带掩码的固定形状内核配合 decode 和 CUDA graph。
- EPLB(专家负载均衡,第 8 讲):在 256 个专家之外加 32 个冗余位(共 288),把最热的专家复制几份、把中等热度的专家和冷门专家放在一起;288 还让并行度不必是 2 的幂(可以是 12、72)。博客的模拟显示:节点越多越不均衡,EPLB 显著改善;要它有效,统计分布必须贴近真实负载——更大的批、定期重新均衡(权重分三步加载:预读到内存、后台 DMA 到显存、设备内拷贝)。
5.3 两批重叠
跨节点的 all-to-all 通信很慢。两批重叠(TBO,DeepSeek 的设计)把一批切成两个微批:一个微批做注意力和专家计算时,另一个做 dispatch / combine 通信。SGLang 的实现用”操作 + 让出点”(yield point)的抽象:代码按”只处理一个微批”来写,在让出点暂停、让另一个微批前进,避免为每个微批复制一份逻辑。
TBO 不是总有用。博客报告:prefill 时它让吞吐高 27%–35%,还因为每个微批只有一半的 token,峰值显存减半,能放下两倍的 token(每块 GPU 1.6 万个);decode 时要看批大小——每块 GPU 32 个 token 时慢 27%,256 个时快 25.5%,阈值在 64 到 128 之间。代码 03 演示 2 用一个简单的模型解释原因:切成两半以后,每个内核的固定开销(启动、同步、用不满的小矩阵乘法)变成两份;token 少时省下的通信时间抵不过它。在我们(假设的)参数下阈值约 104 个 token,但它随固定开销在 88 到 208 之间移动——只说明机制,不是对博客数字的复现。GB200 NVL72 的 72 块 GPU 都在一个 NVLink 域里,通信快得多,博客干脆关掉了 TBO。
5.4 PD 解耦
统一的引擎同时处理 prefill 和 decode 有三个问题(博客):新来的 prefill 打断正在进行的 decode(第 7 讲);DP 注意力下一个工作进程在 prefill、另一个在 decode,decode 要等;DeepEP 的两种模式不能混用。SGLang 的 PD 解耦:prefill 服务器和 decode 服务器先握手;decode 服务器预先分配好 KV 空间,通知 prefill 服务器开始计算;算完的 KV 通过 RDMA 直接传过去(集成 Mooncake、NIXL 等传输库,第 9 讲),传输在后台线程里进行,不阻塞调度循环。
代码 03 演示 3 用博客的吞吐(每节点 prefill 5.23 万、decode 2.23 万 token/s)算 prefill 和 decode 节点的配比:
| 请求(输入 / 输出 / 缓存命中) | P : D 节点比 |
|---|---|
| 2,000 / 500 / 0% | 1.71 : 1 |
| 2,000 / 500 / 50% | 0.85 : 1 |
| 8,000 / 500 / 0% | 6.82 : 1 |
| 500 / 2,000 / 0% | 0.11 : 1 |
最优配比随输入 / 输出长度和前缀缓存命中率大幅变化——这也是第 9 讲 Mooncake 用一个全局 KV 缓存池去提高命中率的经济学原因。博客的”每百万输出 token 0.20 美元”折算下来约为每 GPU 小时 2 美元,而且只算了 decode 节点。
5.5 结果与局限
博客报告:prefill(4 个节点,EP32)每节点每秒 5.77 万 / 5.45 万 / 5.03 万 token(输入 1K / 2K / 4K),比 TP16 的基线快最多 3.3 倍,假设完全均衡时与 DeepSeek 官方的剖析数据相差 5.6%;decode(9 个节点,EP72,DeepSeek 一半的规模)每节点每秒 22,282 token(2K 输入),比 TP16 快 5.2 倍;模拟 MTP(接受率按 70% 保守估计、人为放慢注意力)时 4K 输入为 17,373,比官方剖析低 6.6%。局限也写得很清楚:这套配置追求吞吐,TTFT 在 2–5 秒、token 间延迟约 100 毫秒,不适合实时场景;EPLB 用的是与测试同分布的统计;MTP 与 DP 注意力还没完全集成。GB200 NVL72 上(2025 年 6 月),decode 每块 GPU 每秒 7,583 token,是 H100 的 2.7 倍(不同序列长度 2.5–3.4 倍),原因包括更高的带宽和算力、更大的显存(更大的批)、以及全 NVLink 的通信。
6. 从研究原型到生产系统 · From research prototype to production
把 SGLang 的这段历史放在一起看,研究原型变成生产系统,至少要补上这几样东西:
- 性能工程,而且是全栈的:调度器的 CPU 开销(§2)、内核(FlashInfer、MLA、DeepGEMM)、编译(CUDA graph、torch.compile)、通信(DeepEP、RDMA)。论文里的一个算法思想(RadixAttention)只是起点。
- “细节”往往决定成败:什么时候把前缀插进树(§3.1,值 1.4 倍)、结束要晚一轮才知道(§2.3)、分词边界(§4)、两批重叠的阈值(§5.3)。这些在论文里是一句话,在系统里是几倍的差别。
- 广度:新模型(DeepSeek、LLaVA、Gemma-2 的交错窗口注意力……)、新硬件(H100、GB200)、新特性(结构化输出、多模态、PD 解耦),每一项都要和已有的特性组合起来、互不打架。
- 可用性与可修改性:v0.2 博客把”易用、易改、开源”和性能并列为目标,用 Python 写调度器正是为了这个;OpenAI 兼容的 API 让用户零成本迁移。
- 诚实的基准测试:v0.2 博客发布第二天就更新说明:原来的合成数据生成管线有问题、主要生成了短输入,已经修正并加入更多长短输入的配置。基准测试的负载决定了结论(第 1 讲 §8、第 17 讲),公开、可复现、发现错误就更正,是一个系统社区能够信任的前提。
这也是本课程的一条主线:推理系统的进步,一半来自新想法,一半来自把想法做扎实。
7. 之后的发展 · Since then
- SGLang 的路由器后来演化成独立的模型网关项目(第 11 讲引用过它的缓存感知路由代码);它也被一些 RL 训练框架用作 rollout 引擎(第 15 讲,verl 的生成后端可以选 vLLM 或 SGLang)。
- 讲者 Ying Sheng 创办了 RadixArk(课程网站的介绍)。
- PD 解耦 + 大规模 EP 已成为部署大型 MoE 模型的常见做法,各个开源引擎都在做;GB200 一类的大 NVLink 域改变了”通信要不要重叠”的权衡。
- 结构化输出、推测解码、多模态、长上下文的 KV 管理仍在持续演进。
8. 小结 · Summary
- SGLang 从一篇提出 RadixAttention、前端语言和压缩有限状态机的论文,成长为被广泛使用的引擎;它的架构把分词、调度、模型执行、去分词分开,调度器用 Python 写,热点交给内核。
- 零开销批调度器:CPU 提前一批工作,用”未来 token”解决依赖,把 CPU 开销藏在 GPU 计算后面。GPU 越快(小模型、大张量并行),收益越大(代码 01:3B 小批 1.28 倍,8B × TP 8 1.32–1.42 倍;完整负载 1.10 倍,与博客一致);代价是每个结束的请求白算一步。
- 把零件拼起来:在少样本负载上,前缀缓存、按前缀排序、分块 prefill、重叠调度合起来是朴素引擎的 2.8 倍(代码 02);“prefill 完成就插入基数树”这样的细节值 1.4 倍;缓存感知调度在显存很紧时反而略差。
- 服务 DeepSeek:MLA 的 KV 张量并行切不开,DP 注意力能放 8 倍的批(代码 03:短上下文 1.79 倍,长上下文 6 倍);大规模 EP 省下专家权重的显存、放更大的批;两批重叠在 token 多时有用、少时有害;PD 解耦让 prefill 和 decode 各用最合适的通信模式和批,配比取决于输入 / 输出长度和缓存命中率。
- 研究原型到生产系统,要补上全栈的性能工程、细节、广度、可用性和诚实的基准测试。
9. 常见误区 · Common pitfalls
- “推理引擎的瓶颈全在 GPU 上。” CPU 的调度开销在小模型、大张量并行时可以占两三成(代码 01);没优化的引擎可能一半时间在 CPU 上(博客)。
- “重叠调度没有代价。” 结束要晚一轮才知道,每个结束的请求白算一步;输出极短的负载上不划算(代码 01 演示 3)。
- “重叠能藏住任何 CPU 开销。” 只能藏住不超过 GPU 时间的部分;CPU 比 GPU 慢时,要先把 CPU 做快。
- “前缀缓存只要实现了基数树就够了。” 何时插入、怎样排队、同一轮里重复的前缀怎么处理,决定了命中率(代码 02 演示 1:65% vs 91%)。
- “缓存越紧,缓存感知调度越有用。” 太紧时推迟请求的代价可能超过多出来的命中(代码 02 演示 4)。
- “张量并行总能把 KV 切开。” MLA 只有一份潜向量,张量并行只会把它复制 N 份;要用数据并行注意力(代码 03 演示 1)。
- “计算和通信重叠总是好的。” 切成微批会增加固定开销,批小时两批重叠反而更慢(代码 03 演示 2;博客:每块 GPU 32 个 token 时慢 27%)。
- “PD 解耦的 prefill / decode 节点按 1 : 1 配。” 配比随输入 / 输出长度和缓存命中率变化,可以从 0.1 : 1 到 7 : 1(代码 03 演示 3)。
10. 练习 · Exercises
练习 1、3、4、5 的参考答案里的数字来自
sgl_runtime.py的函数或代码的实际输出。
练习 1(重叠的收益)。一个引擎每轮 GPU 4 毫秒、CPU 1.5 毫秒。(a) 不重叠和重叠时每轮各多久?加速多少?(b) 如果 CPU 是 5 毫秒呢?重叠后 GPU 还有多少时间在闲?
参考答案
(a) 不重叠 5.5 毫秒,重叠 max(4, 1.5) ≈ 4 毫秒(加一次同步约 0.02 毫秒),加速约 1.37 倍。(b) 不重叠 9 毫秒,重叠 5 毫秒(由 CPU 决定),加速 1.8 倍,但 GPU 有 (5 − 4) / 5 = 20% 的时间在等 CPU。这时还要继续压 CPU 的开销(用编译型语言写热点、减少每轮创建的对象、用 CUDA graph 减少内核启动)。
练习 2(什么时候关掉重叠)。一个 RL 训练任务用 SGLang 跑奖励模型:每个请求输入 2,000 token、输出 1 个 token(一个分数)。重叠调度会白算多少?你会怎么配置?
参考答案
每个请求只 decode 1 个位置,重叠又白算 1 个,decode 的计算翻倍(代码 01 演示 3:50%)。不过这个负载的时间主要花在 2,000 token 的 prefill 上,白算的 decode 位置占总计算的比例很小(约 1 / 2,000);真正的问题是 decode 步数极少、每轮的 CPU 开销被 prefill 的长时间摊薄,重叠本身也没多少可藏的。所以关掉重叠(--disable-overlap)几乎没有损失,还省下了白算的那一步和相应的 KV。更一般地:先估算 CPU 开销占比和白算的比例,再决定。
练习 3(DP 注意力的批)。DeepSeek-Coder-V2(FP8 权重 236 GB,其中注意力权重约 9 GB;每 token KV 69,120 字节),8 块 H100,每块 GPU 可用 80 × 0.78 = 62.4 GB。上下文 4,096 时:(a) 张量并行注意力最多放多少个请求?(b) 数据并行注意力呢?
参考答案
(a) 每块 GPU 存 1/8 的权重 29.5 GB,剩 32.9 GB;每个请求的 KV 4,096 × 69,120 ≈ 283 MB,而且每块 GPU 都要存全部请求的 KV:32.9 GB / 283 MB ≈ 116 个请求。(b) 每块 GPU 存 1/8 的非注意力权重加一整份注意力权重 ≈ 37.3 GB,剩 25.1 GB,能放 88 个请求的 KV;8 块 GPU 合计 704 个。dp_attention_decode(DEEPSEEK_V2, H100_SXM, 8, 4096, "tp" / "dp") 给出 116 和 704,decode 吞吐约 3,933 vs 24,012 token/s。
练习 4(PD 配比)。用代码 03 演示 3 的吞吐(每节点 prefill 52,300、decode 22,300 token/s)。一个代码补全服务的请求是输入 6,000、输出 100 token,前缀缓存命中 80%(同一个文件反复补全)。prefill 和 decode 节点该怎么配?如果没有缓存呢?
参考答案
有缓存:prefill 6,000 × 0.2 / 52,300 ≈ 22.9 毫秒·节点,decode 100 / 22,300 ≈ 4.5 毫秒·节点,P : D ≈ 5.1 : 1。没有缓存:114.7 / 4.5 ≈ 25.6 : 1。缓存命中率让 prefill 节点的需求降到五分之一——这类负载里,KV 缓存池(第 9 讲)和缓存感知路由(第 11 讲)的价值直接体现在要买多少台 prefill 机器上。
练习 5(两批重叠的阈值)。在 tbo_layer_time 的默认参数下(计算:60 µs + 1.0 µs/token;通信:40 µs + 1.2 µs/token),不重叠的时间是 comp(n) + comm(n);重叠时近似为 comp(n/2) + comm(n/2) + max(comp(n/2), comm(n/2))。求 TBO 开始划算的 token 数。
参考答案
不重叠:100 + 2.2n µs。n < 200 时 comp(n/2) = 60 + 0.5n 大于 comm(n/2) = 40 + 0.6n,重叠 = (60 + 0.5n) + (40 + 0.6n) + (60 + 0.5n) = 160 + 1.6n。令两者相等:100 + 2.2n = 160 + 1.6n → n = 100(代码按 8 的步长搜索,得到 104)。计算的固定开销翻倍到 120 µs 时:不重叠 160 + 2.2n;n < 800 时 comp(n/2) = 120 + 0.5n 仍然更大,重叠 = (120 + 0.5n) + (40 + 0.6n) + (120 + 0.5n) = 280 + 1.6n;令两者相等得 n = 200(代码给出 208)。固定开销越大,越要更大的批才值得切分。
练习 6(思考:研究原型缺什么)。找一个你熟悉的研究原型(或者本课程的某个配套模块),列出它要变成生产系统还缺哪些东西。按”性能、正确性与可靠性、广度、可用性、可观测性”分类。
参考答案(要点)
以 sgl_runtime.Runtime 为例:性能——GPU 时间是纸上的、没有真实内核和 CUDA graph;正确性与可靠性——不抢占、显存不足时只会排队,没有超时、取消、错误恢复,没有分页带来的碎片管理;广度——只有一种模型、一种 GPU,没有多模态、结构化输出、推测解码、多副本路由;可用性——没有 API、没有配置文件;可观测性——只有几项汇总指标,没有每个请求的追踪、没有 GPU 利用率的剖析。生产系统还需要灰度发布、版本兼容和社区维护。
练习 7(思考:基准测试的诚实)。v0.2 博客发布第二天发现合成数据”主要生成了短输入”,于是更正。(a) 为什么输入长度会改变引擎之间的排名?(提示:博客说 SGLang 在短输入上更好、TensorRT-LLM 在长输入上更好)(b) 你会怎样设计一个不容易”选到对自己有利的负载”的基准测试?
参考答案(要点)
(a) 短输入时 prefill 很轻,吞吐取决于调度器的开销和 decode 的批大小(SGLang 的强项);长输入时 prefill 和注意力内核的效率更重要(TensorRT-LLM 的内核高度优化)。负载换了,瓶颈换了,排名就换了。(b) 事先公开负载的选择和生成方法;覆盖多种输入 / 输出长度分布(包括真实 trace,例如 ShareGPT);同时报告离线吞吐和在线延迟(不同请求率);所有引擎用推荐配置、并请各引擎的维护者确认;公开脚本让别人复现;发现问题就更正并注明——这些也是第 17 讲”怎样评测一个推理系统”的内容。
11. 延伸阅读 · Further reading
本讲依据的材料
- L. Zheng, L. Yin, Z. Xie, C. Sun, J. Huang, C. H. Yu, S. Cao, C. Kozyrakis, I. Stoica, J. E. Gonzalez, C. Barrett, Y. Sheng. SGLang: Efficient Execution of Structured Language Model Programs. NeurIPS 2024(arXiv:2312.07104)。
- LMSYS / SGLang Team 的博客(lmsys.org/blog):Fast and Expressive LLM Inference with RadixAttention and SGLang(2024-01-17);Fast JSON Decoding for Local LLMs with Compressed Finite State Machine(2024-02-05);Achieving Faster Open-Source Llama3 Serving with SGLang Runtime (vs. TensorRT-LLM, vLLM)(2024-07-25);SGLang v0.3 Release: 7x Faster DeepSeek MLA, 1.5x Faster torch.compile, Multi-Image/Video LLaVA-OneVision(2024-09-04);SGLang v0.4: Zero-Overhead Batch Scheduler, Cache-Aware Load Balancer, Faster Structured Outputs(2024-12-04);Deploying DeepSeek with PD Disaggregation and Large-Scale Expert Parallelism on 96 H100 GPUs(2025-05-05);Deploying DeepSeek on GB200 NVL72 with PD and Large Scale EP (Part I)(2025-06-16)。
相关工作
- K. Zhu et al. NanoFlow: Towards Optimal Large Language Model Serving Throughput. 2024(重叠调度的思路来源)。
- Y. Dong et al. XGrammar: Flexible and Efficient Structured Generation Engine for Large Language Models. 2024。
- DeepSeek-AI. DeepSeek-V3 Technical Report. 2024;DeepSeek 开源周发布的 DeepEP、DeepGEMM、EPLB。
- 第 3 讲:RadixAttention 与压缩有限状态机;第 7 讲:分块 prefill 与 PD 解耦;第 8 讲:MoE 与专家并行;第 9 讲:Mooncake;第 11 讲:缓存感知路由。
12. 下一讲预告 · Next lecture
第 17 讲:总结把整门课串起来:从第 1 讲的几个基本事实出发,把各讲的模块组合成一个完整的迷你推理引擎全景——哪些优化可以叠加、哪些互相冲突;讨论怎样评测一个推理系统(负载、SLO、基线、消融,以及本讲提到的基准测试的诚实);最后回顾课程的主线,并列出开放问题和项目选题建议。