SGLang 深度研究报告
一份面向工程实践的系统剖析,覆盖设计动机、前后端协同架构、RadixAttention、约束解码、零开销调度、投机解码、分布式部署与真实性能基准。
版本基线:SGLang v0.5.x(2026 年中),论文原作 NeurIPS 2024。
0. 执行摘要
SGLang 把"写 LLM 程序"和"跑 LLM 服务"这两件事重新合到了一起。它由两个被共同设计的层组成:一个嵌入在 Python 里的前端语言 (用来描述多轮对话、分支、约束、并行生成),以及一个后端运行时 SRT(SGLang Runtime) (用来把程序高效地调度到 GPU 上)。前后端之间的关键纽带是 RadixAttention------用一棵基数树(radix tree)把请求的 KV 缓存按前缀自动复用,而不是像多数引擎那样在请求结束后把缓存丢弃。
这套设计的实际收益很直接:对于"同一个系统提示词被几千次请求复用""一条 prompt 分叉出多个并行子任务""输出必须是合法 JSON"这类常见负载,SGLang 在论文里报告了最高 6.4× 的吞吐提升;在与 vLLM 的线上基准对比中,它的首 token 延迟(TTFT)和 token 间延迟(ITL)明显更低,代价是在长上下文、超高并发等特定场景下吞吐落后于 vLLM。前端语言是可选的------一个普通的 OpenAI 兼容请求会直接进入 SRT,完全不经过 DSL 层。
下面的内容按"为什么、是什么、怎么实现、跑得怎么样、什么时候该用"的顺序展开。
1. 问题:为什么单条请求的优化不够
今天的大模型应用很少是"一条 prompt 进、一段文本出"。典型的形态是:
- 多轮链式调用:先分类,再基于分类结果分叉出几个并行的子任务,最后合并(branch-solve-merge)。
- 固定前缀重复:同一份系统提示词、同一批 few-shot 样例,被成千上万个请求反复携带。
- 结构化输出:要求模型吐出可被程序直接解析的 JSON、正则约束串或特定 schema。
- 与外部交互:调用工具、检索、拼装上下文,再喂回模型。
这类负载有几个被传统推理引擎忽视的结构性机会:
- 前缀高度重叠。如果 1000 个请求都带着同一段 2000 token 的系统提示词,vLLM 这类引擎虽然用 PagedAttention 做了块级缓存,但前缀共享往往需要用户显式指定,或者只能按固定 16-token 的块对齐来命中。更麻烦的是,多数引擎在请求结束后直接丢弃 KV 缓存,下一次相同前缀又要从头算。
- 调用间的并行被浪费 。程序里
fork出的三个分支本可以并行生成,但用裸 API 拼出来的代码往往是串行的。 - 约束解码太慢 。现有系统用有限状态机(FSM)逐个 token 地屏蔽非法候选,即使后面一串字符是确定的(比如
{"summary": "这种常量序列),也得一个 token 一个 token 地过前向。
SGLang 的核心判断是:这些机会属于"程序结构",只有同时理解前端在做什么、后端怎么跑,才能系统性地利用它们。这就是前后端协同设计的由来。
2. 总体架构:前端语言 + 后端运行时
SGLang 不是"又一个推理引擎",也不是"又一个 prompt 编排框架",而是把两者用一套共享的优化目标绑在一起。
#mermaid-svg-LKaWOqJ8pg6HNL1n{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LKaWOqJ8pg6HNL1n .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LKaWOqJ8pg6HNL1n .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LKaWOqJ8pg6HNL1n .error-icon{fill:#552222;}#mermaid-svg-LKaWOqJ8pg6HNL1n .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LKaWOqJ8pg6HNL1n .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LKaWOqJ8pg6HNL1n .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LKaWOqJ8pg6HNL1n .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LKaWOqJ8pg6HNL1n .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LKaWOqJ8pg6HNL1n .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LKaWOqJ8pg6HNL1n .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LKaWOqJ8pg6HNL1n .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LKaWOqJ8pg6HNL1n .marker.cross{stroke:#333333;}#mermaid-svg-LKaWOqJ8pg6HNL1n svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LKaWOqJ8pg6HNL1n p{margin:0;}#mermaid-svg-LKaWOqJ8pg6HNL1n .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LKaWOqJ8pg6HNL1n .cluster-label text{fill:#333;}#mermaid-svg-LKaWOqJ8pg6HNL1n .cluster-label span{color:#333;}#mermaid-svg-LKaWOqJ8pg6HNL1n .cluster-label span p{background-color:transparent;}#mermaid-svg-LKaWOqJ8pg6HNL1n .label text,#mermaid-svg-LKaWOqJ8pg6HNL1n span{fill:#333;color:#333;}#mermaid-svg-LKaWOqJ8pg6HNL1n .node rect,#mermaid-svg-LKaWOqJ8pg6HNL1n .node circle,#mermaid-svg-LKaWOqJ8pg6HNL1n .node ellipse,#mermaid-svg-LKaWOqJ8pg6HNL1n .node polygon,#mermaid-svg-LKaWOqJ8pg6HNL1n .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LKaWOqJ8pg6HNL1n .rough-node .label text,#mermaid-svg-LKaWOqJ8pg6HNL1n .node .label text,#mermaid-svg-LKaWOqJ8pg6HNL1n .image-shape .label,#mermaid-svg-LKaWOqJ8pg6HNL1n .icon-shape .label{text-anchor:middle;}#mermaid-svg-LKaWOqJ8pg6HNL1n .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LKaWOqJ8pg6HNL1n .rough-node .label,#mermaid-svg-LKaWOqJ8pg6HNL1n .node .label,#mermaid-svg-LKaWOqJ8pg6HNL1n .image-shape .label,#mermaid-svg-LKaWOqJ8pg6HNL1n .icon-shape .label{text-align:center;}#mermaid-svg-LKaWOqJ8pg6HNL1n .node.clickable{cursor:pointer;}#mermaid-svg-LKaWOqJ8pg6HNL1n .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LKaWOqJ8pg6HNL1n .arrowheadPath{fill:#333333;}#mermaid-svg-LKaWOqJ8pg6HNL1n .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LKaWOqJ8pg6HNL1n .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LKaWOqJ8pg6HNL1n .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LKaWOqJ8pg6HNL1n .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LKaWOqJ8pg6HNL1n .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LKaWOqJ8pg6HNL1n .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LKaWOqJ8pg6HNL1n .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LKaWOqJ8pg6HNL1n .cluster text{fill:#333;}#mermaid-svg-LKaWOqJ8pg6HNL1n .cluster span{color:#333;}#mermaid-svg-LKaWOqJ8pg6HNL1n div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LKaWOqJ8pg6HNL1n .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LKaWOqJ8pg6HNL1n rect.text{fill:none;stroke-width:0;}#mermaid-svg-LKaWOqJ8pg6HNL1n .icon-shape,#mermaid-svg-LKaWOqJ8pg6HNL1n .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LKaWOqJ8pg6HNL1n .icon-shape p,#mermaid-svg-LKaWOqJ8pg6HNL1n .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LKaWOqJ8pg6HNL1n .icon-shape .label rect,#mermaid-svg-LKaWOqJ8pg6HNL1n .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LKaWOqJ8pg6HNL1n .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LKaWOqJ8pg6HNL1n .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LKaWOqJ8pg6HNL1n :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 后端:SGLang Runtime (SRT)
前端:SGLang 语言层
客户端 / 应用层
复用命中前缀的 KV 位置
SGLang DSL 程序
(@sgl.function)
OpenAI 兼容 API 请求
(/v1/chat/completions)
解释器 + 流式执行器
(StreamExecutor)
编译器 + 图执行器
(GraphExecutor)
Tokenizer Manager
分词 / 去分词
Scheduler
调度 / 连续批处理
RadixAttention Cache
前缀 KV 复用树
Model Runner
前向计算
Grammar Backend
约束解码 (xGrammar)
Sampling
采样
Workers
TP / PP / DP / EP
几个要点:
- 前端是可选的 。裸 API 请求直接进
TokenizerManager,绕过 DSL。SRT 是两条路径的共同归宿,也是真正做性能优化的地方。 - 前端给后端"递 hint" 。例如执行
fork时,前端会先把共享前缀发下去,确保它被插进基数树;随后才发各分支的剩余部分。这种"前端提示"让运行时的前缀匹配与调度更简单,是协同设计的具体体现。 - 运行时兼容现有优化。RadixAttention 与 PagedAttention、连续批处理(continuous batching)、张量并行等并不冲突,是叠加在它们之上的缓存管理层。
3. 前端:SGLang 编程语言
SGLang 的前端是一门嵌入在 Python 里的领域特定语言(DSL)。它的基本想法是用 += 往一个"提示词状态" s 上不断追加内容,用 gen / select 等原语触发模型生成,用 fork / join 做并行。
3.1 核心原语
| 原语 | 作用 |
|---|---|
s += "..." / extend |
往提示词状态追加字符串 |
gen(name, ...) |
调用模型生成,结果存到 s["name"];支持 regex、json_schema、choices、stop 等约束 |
select(name, choices) |
让模型从给定选项中挑选概率最高的一个 |
s.fork(n) / join |
把当前提示词状态复制成 n 份并行推进,再合并 |
image(...) / video(...) |
嵌入多模态输入 |
system / user / assistant |
角色包装,自动映射到 chat template |
3.2 一个完整例子:分支-求解-合并评测
论文里的 multi_dimensional_judge 是理解这套原语的最佳样本------它评测一篇图文相关的文章,从多个维度并行打分,再汇总成带等级的结果:
python
@sgl.function
def multi_dimensional_judge(s, path, essay):
s += system("Evaluate an essay about an image.")
s += user(image(path) + "Essay: " + essay)
s += assistant("Sure!")
# 先判断是否相关
s += user("Is the essay related to the image?")
s += assistant(select("related", choices=["yes", "no"]))
if s["related"] == "no":
return
# 从多个维度并行评测
forks = s.fork(len(dimensions))
for f, dim in zip(forks, dimensions):
f += user("Evaluate based on: " + dim)
f += assistant(gen("judgment", max_tokens=512))
# 合并各维度判断,生成摘要与等级
s += "Summarize the judgments and give a letter grade."
s += assistant(gen("output", regex=r'\{"grade": "[ABCD][+-]?"\}'))
return s["output"]
等价的裸 OpenAI 风格代码大约要多写 2.1 倍的行数------多出来的主要是手动字符串拼接、串行调用、以及对并行和约束的手工管理。更重要的是,SGLang 版本里 fork 的并行、共享前缀的 KV 复用 、以及 regex 约束的加速,都是运行时自动拿到的,不需要开发者操心。
3.3 执行模式:解释器 vs 编译器
- 解释器模式(默认) :把提示词状态当作一条异步流。每个
gen/select是非阻塞提交,Python 代码可以继续往下走,需要结果时才阻塞读取。每个提示词由一个后台线程里的流执行器管理,从而支持"程序内并行"。 - 编译器模式:把 DSL 程序追踪成一张数据流图,用图执行器跑,为后续编译优化(代码移动、算子选择、自动调优)留出空间。
fork / join 是 SGLang 并行能力的来源。以 §3.2 的多维评测为例,主线程构造完公共前缀后,fork(3) 把状态复制成三份各自并行生成,最后 join 合并------这张图对应那段程序的实际执行流:
#mermaid-svg-RAFpeMYhaF8f7SHw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RAFpeMYhaF8f7SHw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RAFpeMYhaF8f7SHw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RAFpeMYhaF8f7SHw .error-icon{fill:#552222;}#mermaid-svg-RAFpeMYhaF8f7SHw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RAFpeMYhaF8f7SHw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RAFpeMYhaF8f7SHw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RAFpeMYhaF8f7SHw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RAFpeMYhaF8f7SHw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RAFpeMYhaF8f7SHw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RAFpeMYhaF8f7SHw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RAFpeMYhaF8f7SHw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RAFpeMYhaF8f7SHw .marker.cross{stroke:#333333;}#mermaid-svg-RAFpeMYhaF8f7SHw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RAFpeMYhaF8f7SHw p{margin:0;}#mermaid-svg-RAFpeMYhaF8f7SHw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RAFpeMYhaF8f7SHw .cluster-label text{fill:#333;}#mermaid-svg-RAFpeMYhaF8f7SHw .cluster-label span{color:#333;}#mermaid-svg-RAFpeMYhaF8f7SHw .cluster-label span p{background-color:transparent;}#mermaid-svg-RAFpeMYhaF8f7SHw .label text,#mermaid-svg-RAFpeMYhaF8f7SHw span{fill:#333;color:#333;}#mermaid-svg-RAFpeMYhaF8f7SHw .node rect,#mermaid-svg-RAFpeMYhaF8f7SHw .node circle,#mermaid-svg-RAFpeMYhaF8f7SHw .node ellipse,#mermaid-svg-RAFpeMYhaF8f7SHw .node polygon,#mermaid-svg-RAFpeMYhaF8f7SHw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RAFpeMYhaF8f7SHw .rough-node .label text,#mermaid-svg-RAFpeMYhaF8f7SHw .node .label text,#mermaid-svg-RAFpeMYhaF8f7SHw .image-shape .label,#mermaid-svg-RAFpeMYhaF8f7SHw .icon-shape .label{text-anchor:middle;}#mermaid-svg-RAFpeMYhaF8f7SHw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RAFpeMYhaF8f7SHw .rough-node .label,#mermaid-svg-RAFpeMYhaF8f7SHw .node .label,#mermaid-svg-RAFpeMYhaF8f7SHw .image-shape .label,#mermaid-svg-RAFpeMYhaF8f7SHw .icon-shape .label{text-align:center;}#mermaid-svg-RAFpeMYhaF8f7SHw .node.clickable{cursor:pointer;}#mermaid-svg-RAFpeMYhaF8f7SHw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RAFpeMYhaF8f7SHw .arrowheadPath{fill:#333333;}#mermaid-svg-RAFpeMYhaF8f7SHw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RAFpeMYhaF8f7SHw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RAFpeMYhaF8f7SHw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RAFpeMYhaF8f7SHw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RAFpeMYhaF8f7SHw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RAFpeMYhaF8f7SHw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RAFpeMYhaF8f7SHw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RAFpeMYhaF8f7SHw .cluster text{fill:#333;}#mermaid-svg-RAFpeMYhaF8f7SHw .cluster span{color:#333;}#mermaid-svg-RAFpeMYhaF8f7SHw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RAFpeMYhaF8f7SHw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RAFpeMYhaF8f7SHw rect.text{fill:none;stroke-width:0;}#mermaid-svg-RAFpeMYhaF8f7SHw .icon-shape,#mermaid-svg-RAFpeMYhaF8f7SHw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RAFpeMYhaF8f7SHw .icon-shape p,#mermaid-svg-RAFpeMYhaF8f7SHw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RAFpeMYhaF8f7SHw .icon-shape .label rect,#mermaid-svg-RAFpeMYhaF8f7SHw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RAFpeMYhaF8f7SHw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RAFpeMYhaF8f7SHw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RAFpeMYhaF8f7SHw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
主线程:构造公共前缀
system + user + 判断是否相关
s'related' == 'no' ?
直接返回
s.fork(3) 复制状态成 3 份
分支1:维度A 并行 gen
分支2:维度B 并行 gen
分支3:维度C 并行 gen
join:合并三路判断
生成摘要 + 等级(regex 约束)
返回结构化结果
三条分支共享同一段前缀 KV,因此命中 RadixAttention 缓存后,三路并行生成只需为各自的"后缀 + 结果"付出计算------这正是前端与后端协同设计带来的复合收益。
3.4 它和 Guidance / LMQL / LangChain 的关系
SGLang 属于"低级"系统(直接操纵 prompt 和原语),和 LMQL、Guidance 同类;而 LangChain、DSPy 属于"高级"系统(提供预定义或自动生成的 prompt)。一个关键差异是:SGLang 自带协同设计的运行时,而 Guidance/LMQL 通常跑在别人的推理后端上。反过来,DSPy 这类高级系统可以把 SGLang 当作底层后端来编译到。
4. 后端运行时(SRT)的内部构成
SGLang Runtime 不是一个单体函数,而是一组通过明确接口协作的组件。理解它们的职责边界,是读懂后续优化(尤其是 RadixAttention 与零开销调度)的前提。
#mermaid-svg-OLDSMbD97Fb5XmNn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OLDSMbD97Fb5XmNn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OLDSMbD97Fb5XmNn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OLDSMbD97Fb5XmNn .error-icon{fill:#552222;}#mermaid-svg-OLDSMbD97Fb5XmNn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OLDSMbD97Fb5XmNn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OLDSMbD97Fb5XmNn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OLDSMbD97Fb5XmNn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OLDSMbD97Fb5XmNn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OLDSMbD97Fb5XmNn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OLDSMbD97Fb5XmNn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OLDSMbD97Fb5XmNn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OLDSMbD97Fb5XmNn .marker.cross{stroke:#333333;}#mermaid-svg-OLDSMbD97Fb5XmNn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OLDSMbD97Fb5XmNn p{margin:0;}#mermaid-svg-OLDSMbD97Fb5XmNn .entityBox{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-OLDSMbD97Fb5XmNn .relationshipLabelBox{fill:hsl(80, 100%, 96.2745098039%);opacity:0.7;background-color:hsl(80, 100%, 96.2745098039%);}#mermaid-svg-OLDSMbD97Fb5XmNn .relationshipLabelBox rect{opacity:0.5;}#mermaid-svg-OLDSMbD97Fb5XmNn .labelBkg{background-color:rgba(248.6666666666, 255, 235.9999999999, 0.5);}#mermaid-svg-OLDSMbD97Fb5XmNn .edgeLabel .label{fill:#9370DB;font-size:14px;}#mermaid-svg-OLDSMbD97Fb5XmNn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-OLDSMbD97Fb5XmNn .edge-pattern-dashed{stroke-dasharray:8,8;}#mermaid-svg-OLDSMbD97Fb5XmNn .node rect,#mermaid-svg-OLDSMbD97Fb5XmNn .node circle,#mermaid-svg-OLDSMbD97Fb5XmNn .node ellipse,#mermaid-svg-OLDSMbD97Fb5XmNn .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OLDSMbD97Fb5XmNn .relationshipLine{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-OLDSMbD97Fb5XmNn .marker{fill:none!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-OLDSMbD97Fb5XmNn .edgeLabel{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-OLDSMbD97Fb5XmNn .edgeLabel .label rect{fill:rgba(232,232,232, 0.8);}#mermaid-svg-OLDSMbD97Fb5XmNn .edgeLabel .label text{fill:#333;}#mermaid-svg-OLDSMbD97Fb5XmNn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 管理生命周期
委托分词
match_prefix / insert / evict
驱动前向批次
组织前缀树
约束解码
采样选 token
分布到多卡
RadixAttention 内核
提供 KV 槽位
持有 token ids
SCHEDULER
REQUEST
TOKENIZER_MANAGER
RADIX_CACHE
MODEL_RUNNER
KV_NODE
GRAMMAR_BACKEND
SAMPLING
WORKER
ATTENTION_BACKEND
各组件的分工:
- Tokenizer Manager:负责分词与去分词,是请求的入口。
- Scheduler:整个引擎的"交通指挥"。它持有等待队列,决定下一批跑哪些请求的哪些 token,并维护 RadixAttention 缓存。
- Radix Cache:一棵存在于 CPU 上的基数树,记录"哪些 token 前缀已经算过 KV、对应 GPU 上的哪些槽位"。维护开销极低。
- Model Runner :加载权重、组装
ForwardBatch、选择注意力后端、返回 logits 或采样结果。 - Grammar Backend :把
regex/json_schema约束编译成可执行的判定逻辑(底层用 xGrammar / llguidance)。 - Sampling:在 grammar 屏蔽非法 token 之后做最终采样。
- Workers:实际在 GPU 上算的进程,支持张量并行(TP)、流水线并行(PP)、数据并行(DP)、专家并行(EP)。
5. RadixAttention:把 KV 缓存当缓存来管
这是 SGLang 最值得花篇幅讲清楚的部分,也是它和 vLLM 的根本分野。
5.1 传统做法的浪费
自回归生成时,每一层注意力都要用到之前所有 token 的 Key/Value 张量,这些就是 KV 缓存。它的特点是:只取决于前缀 token。两个请求只要前缀相同,这段前缀的 KV 就可以复用。
问题在于,vLLM 的 PagedAttention 把 KV 切成固定 16-token 的块,用哈希表管理;而更早期的引擎在请求一结束就把 KV 丢弃。前者能复用块,但前缀共享要么要用户手动指定,要么只能按块对齐命中;后者则完全复用到不了跨请求。当负载里充斥着"几千个请求共享同一系统提示词"这类模式时,这种浪费被放大成数量级的冗余计算。
5.2 基数树:用前缀树管理 KV 映射
RadixAttention 用一棵基数树 (压缩前缀树,trie 的空间高效变体)来记录"token 序列 → KV 张量"的映射。基数树的边可以标注变长 token 序列,而不只是单个 token,这让共享前缀的存储非常紧凑。
#mermaid-svg-LSP3tC4DwoCJP0Ts{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LSP3tC4DwoCJP0Ts .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LSP3tC4DwoCJP0Ts .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LSP3tC4DwoCJP0Ts .error-icon{fill:#552222;}#mermaid-svg-LSP3tC4DwoCJP0Ts .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LSP3tC4DwoCJP0Ts .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LSP3tC4DwoCJP0Ts .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LSP3tC4DwoCJP0Ts .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LSP3tC4DwoCJP0Ts .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LSP3tC4DwoCJP0Ts .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LSP3tC4DwoCJP0Ts .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LSP3tC4DwoCJP0Ts .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LSP3tC4DwoCJP0Ts .marker.cross{stroke:#333333;}#mermaid-svg-LSP3tC4DwoCJP0Ts svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LSP3tC4DwoCJP0Ts p{margin:0;}#mermaid-svg-LSP3tC4DwoCJP0Ts .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LSP3tC4DwoCJP0Ts .cluster-label text{fill:#333;}#mermaid-svg-LSP3tC4DwoCJP0Ts .cluster-label span{color:#333;}#mermaid-svg-LSP3tC4DwoCJP0Ts .cluster-label span p{background-color:transparent;}#mermaid-svg-LSP3tC4DwoCJP0Ts .label text,#mermaid-svg-LSP3tC4DwoCJP0Ts span{fill:#333;color:#333;}#mermaid-svg-LSP3tC4DwoCJP0Ts .node rect,#mermaid-svg-LSP3tC4DwoCJP0Ts .node circle,#mermaid-svg-LSP3tC4DwoCJP0Ts .node ellipse,#mermaid-svg-LSP3tC4DwoCJP0Ts .node polygon,#mermaid-svg-LSP3tC4DwoCJP0Ts .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LSP3tC4DwoCJP0Ts .rough-node .label text,#mermaid-svg-LSP3tC4DwoCJP0Ts .node .label text,#mermaid-svg-LSP3tC4DwoCJP0Ts .image-shape .label,#mermaid-svg-LSP3tC4DwoCJP0Ts .icon-shape .label{text-anchor:middle;}#mermaid-svg-LSP3tC4DwoCJP0Ts .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LSP3tC4DwoCJP0Ts .rough-node .label,#mermaid-svg-LSP3tC4DwoCJP0Ts .node .label,#mermaid-svg-LSP3tC4DwoCJP0Ts .image-shape .label,#mermaid-svg-LSP3tC4DwoCJP0Ts .icon-shape .label{text-align:center;}#mermaid-svg-LSP3tC4DwoCJP0Ts .node.clickable{cursor:pointer;}#mermaid-svg-LSP3tC4DwoCJP0Ts .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LSP3tC4DwoCJP0Ts .arrowheadPath{fill:#333333;}#mermaid-svg-LSP3tC4DwoCJP0Ts .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LSP3tC4DwoCJP0Ts .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LSP3tC4DwoCJP0Ts .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LSP3tC4DwoCJP0Ts .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LSP3tC4DwoCJP0Ts .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LSP3tC4DwoCJP0Ts .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LSP3tC4DwoCJP0Ts .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LSP3tC4DwoCJP0Ts .cluster text{fill:#333;}#mermaid-svg-LSP3tC4DwoCJP0Ts .cluster span{color:#333;}#mermaid-svg-LSP3tC4DwoCJP0Ts div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LSP3tC4DwoCJP0Ts .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LSP3tC4DwoCJP0Ts rect.text{fill:none;stroke-width:0;}#mermaid-svg-LSP3tC4DwoCJP0Ts .icon-shape,#mermaid-svg-LSP3tC4DwoCJP0Ts .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LSP3tC4DwoCJP0Ts .icon-shape p,#mermaid-svg-LSP3tC4DwoCJP0Ts .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LSP3tC4DwoCJP0Ts .icon-shape .label rect,#mermaid-svg-LSP3tC4DwoCJP0Ts .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LSP3tC4DwoCJP0Ts .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LSP3tC4DwoCJP0Ts .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LSP3tC4DwoCJP0Ts :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} (root)
系统提示词 + few-shot 样例(共享前缀,已缓存)
分支A:用户问题 1
分支B:用户问题 2
分支C:fork 出来的并行副本
生成结果 A
生成结果 B
生成结果 C
一次新请求进来时,运行时做的事是:
match_prefix:从树根往下走,找到与当前 token 序列最长的已缓存前缀,返回对应的 KV 槽位索引。调度器只需对这个"未命中后缀"做 prefill,命中部分直接跳过。insert:前向算完后,把新算出的 token 路径写回树(可能触发节点分裂,以暴露精确的分界点)。lock/unlock:请求活跃时,它占用的前缀节点被加锁,禁止被驱逐;请求结束或释放分支后解锁。evict:当 GPU 显存紧张且树上出现可驱逐的叶子时,按策略回收。
一次请求经过缓存的闭环
把上面四个操作串成一条实际路径,更容易看清"自动复用"发生在哪一步:
#mermaid-svg-0ZsuRxEJmCp9qmto{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0ZsuRxEJmCp9qmto .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0ZsuRxEJmCp9qmto .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0ZsuRxEJmCp9qmto .error-icon{fill:#552222;}#mermaid-svg-0ZsuRxEJmCp9qmto .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0ZsuRxEJmCp9qmto .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0ZsuRxEJmCp9qmto .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0ZsuRxEJmCp9qmto .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0ZsuRxEJmCp9qmto .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0ZsuRxEJmCp9qmto .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0ZsuRxEJmCp9qmto .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0ZsuRxEJmCp9qmto .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0ZsuRxEJmCp9qmto .marker.cross{stroke:#333333;}#mermaid-svg-0ZsuRxEJmCp9qmto svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0ZsuRxEJmCp9qmto p{margin:0;}#mermaid-svg-0ZsuRxEJmCp9qmto .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0ZsuRxEJmCp9qmto .cluster-label text{fill:#333;}#mermaid-svg-0ZsuRxEJmCp9qmto .cluster-label span{color:#333;}#mermaid-svg-0ZsuRxEJmCp9qmto .cluster-label span p{background-color:transparent;}#mermaid-svg-0ZsuRxEJmCp9qmto .label text,#mermaid-svg-0ZsuRxEJmCp9qmto span{fill:#333;color:#333;}#mermaid-svg-0ZsuRxEJmCp9qmto .node rect,#mermaid-svg-0ZsuRxEJmCp9qmto .node circle,#mermaid-svg-0ZsuRxEJmCp9qmto .node ellipse,#mermaid-svg-0ZsuRxEJmCp9qmto .node polygon,#mermaid-svg-0ZsuRxEJmCp9qmto .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0ZsuRxEJmCp9qmto .rough-node .label text,#mermaid-svg-0ZsuRxEJmCp9qmto .node .label text,#mermaid-svg-0ZsuRxEJmCp9qmto .image-shape .label,#mermaid-svg-0ZsuRxEJmCp9qmto .icon-shape .label{text-anchor:middle;}#mermaid-svg-0ZsuRxEJmCp9qmto .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0ZsuRxEJmCp9qmto .rough-node .label,#mermaid-svg-0ZsuRxEJmCp9qmto .node .label,#mermaid-svg-0ZsuRxEJmCp9qmto .image-shape .label,#mermaid-svg-0ZsuRxEJmCp9qmto .icon-shape .label{text-align:center;}#mermaid-svg-0ZsuRxEJmCp9qmto .node.clickable{cursor:pointer;}#mermaid-svg-0ZsuRxEJmCp9qmto .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0ZsuRxEJmCp9qmto .arrowheadPath{fill:#333333;}#mermaid-svg-0ZsuRxEJmCp9qmto .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0ZsuRxEJmCp9qmto .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0ZsuRxEJmCp9qmto .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0ZsuRxEJmCp9qmto .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0ZsuRxEJmCp9qmto .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0ZsuRxEJmCp9qmto .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0ZsuRxEJmCp9qmto .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0ZsuRxEJmCp9qmto .cluster text{fill:#333;}#mermaid-svg-0ZsuRxEJmCp9qmto .cluster span{color:#333;}#mermaid-svg-0ZsuRxEJmCp9qmto div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0ZsuRxEJmCp9qmto .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0ZsuRxEJmCp9qmto rect.text{fill:none;stroke-width:0;}#mermaid-svg-0ZsuRxEJmCp9qmto .icon-shape,#mermaid-svg-0ZsuRxEJmCp9qmto .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0ZsuRxEJmCp9qmto .icon-shape p,#mermaid-svg-0ZsuRxEJmCp9qmto .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0ZsuRxEJmCp9qmto .icon-shape .label rect,#mermaid-svg-0ZsuRxEJmCp9qmto .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0ZsuRxEJmCp9qmto .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0ZsuRxEJmCp9qmto .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0ZsuRxEJmCp9qmto :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
是
否
新请求到达 Scheduler
Tokenizer: prompt → token ids
radix_cache.match_prefix(token_ids)
命中前缀长度 > 0 ?
跳过命中段 prefill
仅对后缀做 extend
全量 prefill
Model Runner 前向计算
还有 token 要生成 ?
单步 decode → logits → 采样
radix_cache.insert 更新前缀树
(必要时分裂节点)
请求结束
unlock 前缀节点
(lock_ref 归零)
显存紧张且
有可驱逐叶子 ?
按 LRU / LFU 驱逐叶子
释放 KV 槽位
完成
注意 match_prefix 这一步决定了省不省计算:命中越长,需要重新 prefill 的 token 越少;而 insert 在每步 decode 后把新路径写回树,使后续请求能继续复用。
5.3 内存与驱逐
GPU 显存很快会被 KV 占满,所以必须能驱逐。SGLang 的几个设计点很关键:
- 引用计数 + 锁 :每个节点维护一个
lock_ref,表示当前有多少正在跑的请求在用它。只有lock_ref == 0的节点才可被驱逐。这保证绝不会回收一个活请求还在用的前缀。 - LRU / LFU 策略:默认按最近最少使用驱逐叶子;支持按命中次数(LFU)等其它策略,适配不同负载。
- 和运行中请求共享显存池 :SGLang 不预分配一块固定大小的"缓存池",而是让缓存 token 和正在跑的请求共用同一块显存。当等待队列里请求足够多时,系统会清空缓存、让位给更大的批大小------缓存是"有空就留、不够就让"的。
- 层级回收:先驱逐叶子;叶子没了,它的公共祖先才会变成新的叶子,进而也可能被驱逐。这样最大化保留"被更多人共享"的深层前缀。
5.4 缓存感知调度(Cache-Aware Scheduling)
光有树还不够。连续批处理里,等待队列的处理顺序会显著影响命中率:如果调度器在几个互不相关的请求间频繁横跳,会造成缓存抖动(cache thrashing),命中率骤降。
SGLang 的调度算法按已匹配前缀长度排序,优先处理命中更长前缀的请求(longest-shared-prefix-first),而不是简单的先来先服务。直观地说,把共享同一前缀的请求挨着跑,能让这段前缀在显存里多活一会儿、被更多人复用。论文还给出了离线最优调度的理论证明。
5.5 它能自动处理的几种共享模式
论文里画了四种典型的多调用 KV 共享形态------同一前缀多输出、单前缀分叉、链式追加、以及不规则树状共享。在 SGLang 之前,没有哪个系统能自动处理全部四种;而 RadixAttention 在运行时靠一棵树就把它们都覆盖了。
一个常被误解的点:RadixAttention 不是一个新的注意力内核。它是一套"前缀缓存的数据结构 + 调度辅助",真正算注意力的是挂在它下面的 FlashAttention / TRT-LLM / FA3 等后端。
5.6 命中率的现实约束
前缀复用是精确匹配 :它比对的是完全相同的 token id 序列(外加一个 extra_key,用于区分 LoRA 适配器、cache salt、版本、检索上下文等命名空间)。这意味着:
- 稳定的系统提示词、few-shot 样例 → 命中率高。
- 在每个请求开头塞时间戳、随机 ID、易变内容 → 直接废掉整段命中。
- 语义相近 ≠ 可复用。相似意思不会生成相同的隐藏状态,所以语义相似度救不了 KV 复用------该用检索或应用层逻辑去处理。
- 跨副本不自动共享 。每副本各自有一棵树。负载均衡器若把相同 prompt 打到不同副本,各自命中率都可能偏低。这正是
sgl-router做缓存感知路由的原因(见第 10 节)。
6. 结构化输出:压缩有限状态机
约束解码解决的是"让模型输出严格符合某种格式"。SGLang 的 gen(regex=...) 和 gen(json_schema=...) 都走这条路。
现有系统的做法:把正则/语法编译成 FSM,解码时维护当前状态,查下一步允许的 token,把非法 token 的概率置零,一次只解码一个 token 。问题在于,很多情况下"下一步唯一合法的"是一整段确定字符串(比如 {"summary": " 这种常量),却仍要一个 token 一个 token 地过前向,纯属浪费。
SGLang 的改进是把 FSM 压缩 :把相邻"单值转移边"合并成一条边,于是当一段多 token 序列在压缩后的边上时,可以一次前向直接解码多个 token (jump-forward)。它通用、适用于所有正则,论文报告 JSON 解码最高加速约 3×。底层实现现在主要接 xGrammar / llguidance,并和采样路径深度集成,使约束判定在每步只增加有限的 CPU 开销。
下面这张图把"逐 token 解码"和"压缩后一次解出整段"放在同一序列上对比,能直观看到加速从哪来:
渲染错误: Mermaid 渲染失败: Parse error on line 2: ..."常量序列 {\\"summary\\": \\" 在 FSM 中的表示" -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'STR'
合并相邻的单值转移边后,这段没有任何分支的常量串不必再一个 token 一个 token 地过前向------一次就能解出,这正是约束解码在 SGLang 里明显更快的来源。
需要清醒的是:grammar 只能保证语法 合法,不能保证语义正确。一个合法 JSON 里字段值对不对,是应用层的事。
7. 零开销调度与重叠执行
调度器跑在 CPU 上,模型前向跑在 GPU 上。如果二者串行------CPU 先排好下一波、再交给 GPU、GPU 算完、CPU 再排下一波------GPU 总有一段时间在等 CPU。SGLang v0.4 引入的零开销批调度器 以及后续 v0.5 的 overlap 调度器(Spec V2 同款机制) ,核心目标就是消灭这段空隙:让 CPU 的后处理(停词检测、请求元数据更新)和下一批的 KV 分配,与 GPU 当前批次的计算重叠进行。
#mermaid-svg-LQTs3AT9w2pMViCE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LQTs3AT9w2pMViCE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LQTs3AT9w2pMViCE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LQTs3AT9w2pMViCE .error-icon{fill:#552222;}#mermaid-svg-LQTs3AT9w2pMViCE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LQTs3AT9w2pMViCE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LQTs3AT9w2pMViCE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LQTs3AT9w2pMViCE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LQTs3AT9w2pMViCE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LQTs3AT9w2pMViCE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LQTs3AT9w2pMViCE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LQTs3AT9w2pMViCE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LQTs3AT9w2pMViCE .marker.cross{stroke:#333333;}#mermaid-svg-LQTs3AT9w2pMViCE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LQTs3AT9w2pMViCE p{margin:0;}#mermaid-svg-LQTs3AT9w2pMViCE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LQTs3AT9w2pMViCE .cluster-label text{fill:#333;}#mermaid-svg-LQTs3AT9w2pMViCE .cluster-label span{color:#333;}#mermaid-svg-LQTs3AT9w2pMViCE .cluster-label span p{background-color:transparent;}#mermaid-svg-LQTs3AT9w2pMViCE .label text,#mermaid-svg-LQTs3AT9w2pMViCE span{fill:#333;color:#333;}#mermaid-svg-LQTs3AT9w2pMViCE .node rect,#mermaid-svg-LQTs3AT9w2pMViCE .node circle,#mermaid-svg-LQTs3AT9w2pMViCE .node ellipse,#mermaid-svg-LQTs3AT9w2pMViCE .node polygon,#mermaid-svg-LQTs3AT9w2pMViCE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LQTs3AT9w2pMViCE .rough-node .label text,#mermaid-svg-LQTs3AT9w2pMViCE .node .label text,#mermaid-svg-LQTs3AT9w2pMViCE .image-shape .label,#mermaid-svg-LQTs3AT9w2pMViCE .icon-shape .label{text-anchor:middle;}#mermaid-svg-LQTs3AT9w2pMViCE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LQTs3AT9w2pMViCE .rough-node .label,#mermaid-svg-LQTs3AT9w2pMViCE .node .label,#mermaid-svg-LQTs3AT9w2pMViCE .image-shape .label,#mermaid-svg-LQTs3AT9w2pMViCE .icon-shape .label{text-align:center;}#mermaid-svg-LQTs3AT9w2pMViCE .node.clickable{cursor:pointer;}#mermaid-svg-LQTs3AT9w2pMViCE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LQTs3AT9w2pMViCE .arrowheadPath{fill:#333333;}#mermaid-svg-LQTs3AT9w2pMViCE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LQTs3AT9w2pMViCE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LQTs3AT9w2pMViCE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LQTs3AT9w2pMViCE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LQTs3AT9w2pMViCE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LQTs3AT9w2pMViCE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LQTs3AT9w2pMViCE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LQTs3AT9w2pMViCE .cluster text{fill:#333;}#mermaid-svg-LQTs3AT9w2pMViCE .cluster span{color:#333;}#mermaid-svg-LQTs3AT9w2pMViCE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LQTs3AT9w2pMViCE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LQTs3AT9w2pMViCE rect.text{fill:none;stroke-width:0;}#mermaid-svg-LQTs3AT9w2pMViCE .icon-shape,#mermaid-svg-LQTs3AT9w2pMViCE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LQTs3AT9w2pMViCE .icon-shape p,#mermaid-svg-LQTs3AT9w2pMViCE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LQTs3AT9w2pMViCE .icon-shape .label rect,#mermaid-svg-LQTs3AT9w2pMViCE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LQTs3AT9w2pMViCE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LQTs3AT9w2pMViCE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LQTs3AT9w2pMViCE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 重叠执行:CPU 与 GPU 并行
GPU 计算 批次 N
CPU 后处理 批次 N-1
(停词 / 元数据)
CPU 预分配 KV
批次 N
实测上,这个调度器能把 GPU 利用率推到 95% 以上,而 CPU 的调度开销被压到几乎不可见。投机解码的 V2 引擎(见下节)正是建立在这套 overlap 机制之上------它把 draft、verify、extend 各阶段的 CUDA graph 之间的"胶水" host 工作也藏到了前向背后。
8. 请求的数据流
把一个请求从进来到出去的全过程串起来,能看清前面所有组件是怎么咬合的。
Grammar Backend Model Runner Radix Cache Scheduler Tokenizer Manager 客户端 Grammar Backend Model Runner Radix Cache Scheduler Tokenizer Manager 客户端 #mermaid-svg-jrbTSaFJqep0S2x4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jrbTSaFJqep0S2x4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jrbTSaFJqep0S2x4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jrbTSaFJqep0S2x4 .error-icon{fill:#552222;}#mermaid-svg-jrbTSaFJqep0S2x4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jrbTSaFJqep0S2x4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jrbTSaFJqep0S2x4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jrbTSaFJqep0S2x4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jrbTSaFJqep0S2x4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jrbTSaFJqep0S2x4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jrbTSaFJqep0S2x4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jrbTSaFJqep0S2x4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jrbTSaFJqep0S2x4 .marker.cross{stroke:#333333;}#mermaid-svg-jrbTSaFJqep0S2x4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jrbTSaFJqep0S2x4 p{margin:0;}#mermaid-svg-jrbTSaFJqep0S2x4 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jrbTSaFJqep0S2x4 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-jrbTSaFJqep0S2x4 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-jrbTSaFJqep0S2x4 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-jrbTSaFJqep0S2x4 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-jrbTSaFJqep0S2x4 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-jrbTSaFJqep0S2x4 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-jrbTSaFJqep0S2x4 .sequenceNumber{fill:white;}#mermaid-svg-jrbTSaFJqep0S2x4 #sequencenumber{fill:#333;}#mermaid-svg-jrbTSaFJqep0S2x4 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-jrbTSaFJqep0S2x4 .messageText{fill:#333;stroke:none;}#mermaid-svg-jrbTSaFJqep0S2x4 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jrbTSaFJqep0S2x4 .labelText,#mermaid-svg-jrbTSaFJqep0S2x4 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-jrbTSaFJqep0S2x4 .loopText,#mermaid-svg-jrbTSaFJqep0S2x4 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-jrbTSaFJqep0S2x4 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-jrbTSaFJqep0S2x4 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-jrbTSaFJqep0S2x4 .noteText,#mermaid-svg-jrbTSaFJqep0S2x4 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-jrbTSaFJqep0S2x4 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jrbTSaFJqep0S2x4 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jrbTSaFJqep0S2x4 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jrbTSaFJqep0S2x4 .actorPopupMenu{position:absolute;}#mermaid-svg-jrbTSaFJqep0S2x4 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-jrbTSaFJqep0S2x4 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jrbTSaFJqep0S2x4 .actor-man circle,#mermaid-svg-jrbTSaFJqep0S2x4 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-jrbTSaFJqep0S2x4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop每个 decode step 发送请求 (prompt + 采样/约束参数)分词 → token idsGenerateReqInputmatch_prefix(token_ids)命中前缀长度 + 对应 KV 槽位仅对"未命中后缀"做 prefill加载约束 (regex / json_schema)前向计算 → logits屏蔽非法 token(压缩 FSM)采样 → next tokeninsert / 更新前缀树节点流式返回 token请求结束 → 解锁前缀 / 可能 evict
两个工程细节值得点出:
- Prefill 与 Decode 形状不同。Prefill 一次吃很多 prompt token,偏计算密集;Decode 每步只吃一两个 token、反复读 KV,偏延迟与显存带宽敏感。SGLang 默认把 prefill 批次和 decode 批次分开(也可通过 mixed chunked prefill 混合),并支持 chunked prefill 把超长 prompt 切成块,避免一个长请求卡住整批。
- Radix 匹配是精确且按页对齐的。如果某个缓存策略要求对齐单位(page),那么一个不完整的末页不能当作完整前缀复用。这直接决定了"稳定的前缀放前面、易变内容放后面"这个工程经验的有效性。
9. 投机解码:用草稿换步数
自回归解码每生成一个 token 都要跑一遍完整前向,是延迟的主要来源。投机解码的思路是:用一个更便宜的"草稿模型/机制"先猜 K 个 token,再用目标模型一次性验证------猜得准就能一步前进多个 token。
SGLang 是目前开源引擎里投机解码支持最丰富的之一,v0.5 提供:
| 方法 | 草稿来源 | 何时用 |
|---|---|---|
| EAGLE-3 | EAGLE 草稿模型 | 速度与质量兼顾(推荐默认) |
| EAGLE-2 | EAGLE 草稿模型(树状) | 广泛兼容 |
| MTP | 模型自带的多 token 预测头 | 模型本身支持 MTP 时 |
| DFlash | 专用 DFlash 草稿模型 | 有匹配草稿 checkpoint 时(如 Qwen 3.5 397B-A17B) |
| DSpark | 半自回归块草稿 + 置信度调度 | 负载随时间变化、需动态验证长度时 |
| STANDALONE | 一个更小的草稿 LLM | 手头有小模型时 |
| NGRAM | 从历史 token 建的 n-gram 树 | 无额外模型、CUDA 环境 |
投机解码的核心是一个"先猜后验"的循环。下面这张图把一次 draft--verify 的过程画出来,能看清"接受"和"被拒"两条分支分别怎么走:
输出流 目标模型 (GPU) 草稿模型 / 机制 输出流 目标模型 (GPU) 草稿模型 / 机制 #mermaid-svg-iOKwRP4kZBsooPXe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iOKwRP4kZBsooPXe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iOKwRP4kZBsooPXe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iOKwRP4kZBsooPXe .error-icon{fill:#552222;}#mermaid-svg-iOKwRP4kZBsooPXe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iOKwRP4kZBsooPXe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iOKwRP4kZBsooPXe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iOKwRP4kZBsooPXe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iOKwRP4kZBsooPXe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iOKwRP4kZBsooPXe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iOKwRP4kZBsooPXe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iOKwRP4kZBsooPXe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iOKwRP4kZBsooPXe .marker.cross{stroke:#333333;}#mermaid-svg-iOKwRP4kZBsooPXe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iOKwRP4kZBsooPXe p{margin:0;}#mermaid-svg-iOKwRP4kZBsooPXe .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-iOKwRP4kZBsooPXe text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-iOKwRP4kZBsooPXe .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-iOKwRP4kZBsooPXe .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-iOKwRP4kZBsooPXe .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-iOKwRP4kZBsooPXe .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-iOKwRP4kZBsooPXe #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-iOKwRP4kZBsooPXe .sequenceNumber{fill:white;}#mermaid-svg-iOKwRP4kZBsooPXe #sequencenumber{fill:#333;}#mermaid-svg-iOKwRP4kZBsooPXe #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-iOKwRP4kZBsooPXe .messageText{fill:#333;stroke:none;}#mermaid-svg-iOKwRP4kZBsooPXe .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-iOKwRP4kZBsooPXe .labelText,#mermaid-svg-iOKwRP4kZBsooPXe .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-iOKwRP4kZBsooPXe .loopText,#mermaid-svg-iOKwRP4kZBsooPXe .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-iOKwRP4kZBsooPXe .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-iOKwRP4kZBsooPXe .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-iOKwRP4kZBsooPXe .noteText,#mermaid-svg-iOKwRP4kZBsooPXe .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-iOKwRP4kZBsooPXe .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-iOKwRP4kZBsooPXe .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-iOKwRP4kZBsooPXe .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-iOKwRP4kZBsooPXe .actorPopupMenu{position:absolute;}#mermaid-svg-iOKwRP4kZBsooPXe .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-iOKwRP4kZBsooPXe .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-iOKwRP4kZBsooPXe .actor-man circle,#mermaid-svg-iOKwRP4kZBsooPXe line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-iOKwRP4kZBsooPXe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 一步前进 K 个 token(加速生效) 后续草稿作废,下一轮重猜 alt全部接受第 j 个被拒 提交 K 个草稿 token(一次前向)并行验证 K 个 token提交 K 个 token提交前 j-1 个 token目标模型自采样第 j 个(修正)
两个近一年里的重要进展:
- DFlash(2026-06) :关键洞察是"目标模型对自己的上下文最懂"。它从目标模型抽取上下文 token 的隐藏表示,直接注入草稿模型的 KV 缓存 ,让草稿模型跳过从头建模上下文、只专注预测下一块 token。配合 SGLang 新默认的 Spec V2(overlap 调度) ,在 Qwen 3.5 397B-A17B、8×B200 上,HumanEval 数据集、并发 1 时达到 baseline 的 >4.3× 吞吐,且优于原生 MTP。
- DSpark(2026-07) :传统投机解码有个尴尬------批量 B、投机 K 个 token 时,目标模型每步要验证 B×K 个 token,负载一高反而亏。DSpark 用"半自回归块草稿 + 按草稿自身置信度决定每个请求的验证窗口长度"来破局:负载高、接受率低时自动缩短验证长度,省下的步数就是实打实的加速。它在 DeepSeek-V4-Pro(TP8、B300)上达到 383.7 tok/s(accept length ≈ 5),并完整落在 SGLang 的 overlap 调度里,几乎不需要特判。
需要提醒:投机解码不是免费午餐。接受长度是唯一的健康指标------草稿接受率低时,草稿计算反而变成纯开销。不同模型、不同负载、不同加速器下,应该实测后选方法,而不是无脑开。
10. 分布式与规模化部署
当单卡放不下或吞吐不够时,SGLang 提供多条正交的并行轴,各自解决不同的"装不下 / 太慢"问题:
- TP(张量并行):把单层权重切到多卡,适用单请求太大放不下。
- PP(流水线并行):按层切分,不同层在不同设备,适合超深模型。
- DP(数据并行):多份完整副本各自接不同请求,提高总吞吐。
- EP(专家并行):针对 MoE 模型,把专家分到不同设备;SGLang 在大规模 EP(如 GB200 NVL72 上的 DeepSeek)上做了大量优化,配合弹性 EP 可在运行时扩缩。
更有意思的是 Prefill-Decode 解耦(P/D disaggregation):把计算密集的 prefill 和延迟敏感的 decode 放到不同的 worker 池,之间传输 KV 状态。这能各自独立扩缩容,避免长 prompt 的 prefill 拖慢正在 decode 的请求。缓存与计算是解耦的两半,它们之间的数据搬运长这样:
#mermaid-svg-2iGyi5ki27PPNDu3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2iGyi5ki27PPNDu3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2iGyi5ki27PPNDu3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2iGyi5ki27PPNDu3 .error-icon{fill:#552222;}#mermaid-svg-2iGyi5ki27PPNDu3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2iGyi5ki27PPNDu3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2iGyi5ki27PPNDu3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2iGyi5ki27PPNDu3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2iGyi5ki27PPNDu3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2iGyi5ki27PPNDu3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2iGyi5ki27PPNDu3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2iGyi5ki27PPNDu3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2iGyi5ki27PPNDu3 .marker.cross{stroke:#333333;}#mermaid-svg-2iGyi5ki27PPNDu3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2iGyi5ki27PPNDu3 p{margin:0;}#mermaid-svg-2iGyi5ki27PPNDu3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2iGyi5ki27PPNDu3 .cluster-label text{fill:#333;}#mermaid-svg-2iGyi5ki27PPNDu3 .cluster-label span{color:#333;}#mermaid-svg-2iGyi5ki27PPNDu3 .cluster-label span p{background-color:transparent;}#mermaid-svg-2iGyi5ki27PPNDu3 .label text,#mermaid-svg-2iGyi5ki27PPNDu3 span{fill:#333;color:#333;}#mermaid-svg-2iGyi5ki27PPNDu3 .node rect,#mermaid-svg-2iGyi5ki27PPNDu3 .node circle,#mermaid-svg-2iGyi5ki27PPNDu3 .node ellipse,#mermaid-svg-2iGyi5ki27PPNDu3 .node polygon,#mermaid-svg-2iGyi5ki27PPNDu3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2iGyi5ki27PPNDu3 .rough-node .label text,#mermaid-svg-2iGyi5ki27PPNDu3 .node .label text,#mermaid-svg-2iGyi5ki27PPNDu3 .image-shape .label,#mermaid-svg-2iGyi5ki27PPNDu3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-2iGyi5ki27PPNDu3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2iGyi5ki27PPNDu3 .rough-node .label,#mermaid-svg-2iGyi5ki27PPNDu3 .node .label,#mermaid-svg-2iGyi5ki27PPNDu3 .image-shape .label,#mermaid-svg-2iGyi5ki27PPNDu3 .icon-shape .label{text-align:center;}#mermaid-svg-2iGyi5ki27PPNDu3 .node.clickable{cursor:pointer;}#mermaid-svg-2iGyi5ki27PPNDu3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2iGyi5ki27PPNDu3 .arrowheadPath{fill:#333333;}#mermaid-svg-2iGyi5ki27PPNDu3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2iGyi5ki27PPNDu3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2iGyi5ki27PPNDu3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2iGyi5ki27PPNDu3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2iGyi5ki27PPNDu3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2iGyi5ki27PPNDu3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2iGyi5ki27PPNDu3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2iGyi5ki27PPNDu3 .cluster text{fill:#333;}#mermaid-svg-2iGyi5ki27PPNDu3 .cluster span{color:#333;}#mermaid-svg-2iGyi5ki27PPNDu3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2iGyi5ki27PPNDu3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2iGyi5ki27PPNDu3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-2iGyi5ki27PPNDu3 .icon-shape,#mermaid-svg-2iGyi5ki27PPNDu3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2iGyi5ki27PPNDu3 .icon-shape p,#mermaid-svg-2iGyi5ki27PPNDu3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2iGyi5ki27PPNDu3 .icon-shape .label rect,#mermaid-svg-2iGyi5ki27PPNDu3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2iGyi5ki27PPNDu3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2iGyi5ki27PPNDu3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2iGyi5ki27PPNDu3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 请求
Prefill 池
(计算密集)
传输 KV 状态
(跨节点 / 显存)
Decode 池
(延迟敏感)
流式输出 token
多节点部署时,SGLang 用 Rust 写的 sgl-router 做负载均衡,它的特殊之处在于缓存感知路由:
渲染错误: Mermaid 渲染失败: Parse error on line 7: ...-> W1 Router -. .-> W2 Router -. ----------------------^ Expecting 'STR', 'MD_STR', 'UNICODE_TEXT', 'EDGE_TEXT', got 'LINK'
每个 worker 会把自己的缓存变化(插入/驱逐了哪些前缀)上报给 router,router 维护一棵近似 radix 树,在收到新请求时预测它打到哪个 worker 命中率最高,再结合各 worker 当前负载做加权选择。核心权衡是:缓存命中率 vs 负载均衡------把相同前缀尽量送到同一副本,命中率才高;但也不能把流量全压到一个副本上。这是分布式 SGLang 部署里最该调的参数之一。
11. 真实性能:和 vLLM 比到底怎么样
性能是 SGLang 最常被追问、也最容易说错的部分。先把结论摆清楚:没有"谁永远更快",取决于并发、上下文长度、模型和工作负载。下面是一组公开基准(硬件与版本不同,数字只反映趋势,不代表你的环境)。
11.1 SGLang 明显占优的场景
来自 SGLang v0.3 vs vLLM v0.6 的线上基准(ShareGPT 数据集,Llama 3.1 70B、4×H100):
| 指标 (RPS=4) | SGLang | vLLM | 说明 |
|---|---|---|---|
| Median TTFT | 53.94 ms | 179.15 ms | SGLang 快约 3.3× |
| Median ITL | 21.67 ms | 231.23 ms | SGLang 快约 10× |
结合 vLLM 开启 multi-step 的对照:SGLang 的中位 TTFT 约为 vLLM 的 1/3,中位 ITL 约为 1/10。在 GPT-OSS-120B(2×H100)上,并发 50 时 SGLang TTFT 约 3.08s 对 vLLM 7.55s;并发 10 时吞吐 988 vs 863 tok/s。单用户 / 低并发、且前缀可复用时,SGLang 的优势最明显------这正对应 RadixAttention 和高重叠负载的设计甜区。
11.2 vLLM 反超的场景
同一批基准里,当并发拉到很高、或上下文很长时,天平会转向 vLLM:
- 超高并发(c≥100):在 GPT-OSS-120B 上 vLLM 吞吐反超;在多数生产级并发(c=20--50)的对比里,vLLM 吞吐高出约 11--20%。
- 长上下文(8K 上下):多个独立基准显示 vLLM 在 8K 上下文上约有 2× 吞吐优势。原因之一是默认配置差异(chunked prefill 策略、prefill/decode 是否混合批处理等)。
- 生态成熟度:vLLM 历史更长、部署选项和周边工具更丰富,对新模型的"开箱即用"覆盖面也更广。
11.3 怎么读这些数字
把上面的矛盾放在一起看,规律其实很干净:
- SGLang 赢在:低/中并发、前缀高度重叠(RAG、agent、固定系统提示)、对 TTFT/ITL 稳定度敏感的场景。
- vLLM 赢在:超高并发、长上下文、追求极限峰值吞吐的场景。
- 二者都已非常成熟,都能跑满 GPU。选型应基于你真实的并发画像、上下文长度、模型家族来压测,而不是看某一张榜单。
12. 局限与适用边界
写一份"深度报告"而不谈边界,是不负责任的。SGLang 当前的主要注意点:
- 前缀命中是精确匹配。prompt 前端一丁点不稳定(时间戳、随机串)就会击穿缓存。这需要应用在 prompt 构造上有纪律。
- 副本间缓存不共享 。多副本部署必须靠
sgl-router的缓存感知路由来维持命中率,否则加副本反而会稀释命中。 - 投机解码依赖接受率。低接受率时草稿计算变纯开销;不同负载要实测选方法。
- 并行策略对多卡性能影响巨大 。有基准显示,SGLang 多卡下用数据并行(
--dp)反而比张量并行(--tp)差很多;切到--tp后一致性和吞吐都显著回升。这提醒我们:多卡 SGLang 的并行参数不是"设了就行",要测。 - 约束解码的 CPU 成本。grammar 准备和每步 mask 是 host 侧工作,极大量小请求时这部分会显现。
- 快速演进的兼容性矩阵。SGLang 的版本迭代很快(v0.5.x 几乎每月一个大版本,不断加入新模型、新注意力后端、新投机算法),某些组合(特定模型 × 特定加速器 × 特定后端)的兼容性需要查对应 release note。
什么时候该优先选 SGLang:做 agent、RAG、需要稳定低延迟的结构化输出、或者负载里有大量可复用前缀。什么时候 vLLM 可能更省心:长上下文、超高并发、追求生态成熟度和开箱即用的广度。
13. 演进轨迹与展望
从时间线看 SGLang 的发力方向,能预判它接下来要解决的问题:
- 2024 初:RadixAttention + 压缩 FSM + 前端语言,奠基。
- 2024 中---末 :v0.2--v0.3 模型覆盖扩展;v0.4 引入零开销批调度器、缓存感知负载均衡、更快结构化输出。
- 2025:大规模专家并行、GB200 机架级并行(PD 解耦、大规模 EP)、原生 TPU 后端(SGLang-Jax)、稀疏注意力(DeepSeek-V3.2 DSA)。
- 2026 上半年:投机解码 V2(overlap 调度)、DFlash、DSpark;UnifiedRadixTree 成为 SWA/Mamba/DSA 模型的默认缓存;扩展到 Kimi K3、Inkling、DeepSeek-V4 等超大 / 混合架构;并登陆 TPU(RadixArk + Google)。
一个清晰的主线是:SGLang 把"缓存复用"从"前缀块命中"一路推到了"整个请求树 + 跨副本路由 + 异构架构(线性注意力、MoE、稀疏)的统一缓存抽象",同时在调度侧把 CPU/GPU 的同步空隙压到极限。这两件事合起来,正是它能在"结构化、多调用、高重叠"的真实负载上持续拉开差距的根本原因。
附:术语速查
| 术语 | 含义 |
|---|---|
| SRT | SGLang Runtime,后端运行时,真正做性能优化的地方 |
| RadixAttention | 用基数树自动复用跨请求 KV 缓存前缀的机制(非注意力内核) |
| Radix Cache | SRT 里维护前缀树的组件,存于 CPU,记录 token→KV 槽位映射 |
| 压缩 FSM | 把约束解码的有限状态机压缩,支持一次前向解码多 token |
| Overlap Scheduler | 让 CPU 调度与 GPU 计算重叠、消除空隙的调度器(v0.4+) |
| Spec V2 | 投机解码的 V2 引擎,建立在 overlap 调度之上 |
| PD 解耦 | Prefill 与 Decode 分到不同 worker 池并传输 KV |
| sgl-router | Rust 写的缓存感知负载均衡器,用近似 radix 树做路由 |
| TP / PP / DP / EP | 张量 / 流水线 / 数据 / 专家 并行 |