SGLang Study · 六章学习指南
SGLang:同一场比赛的
另一种打法
SGLang 出自 LMSYS(Chatbot Arena 与 Vicuna 的团队),与 vLLM 在服务端吞吐上常年互有胜负,如今驱动着全球 40 万+ GPU 的生产推理(xAI、NVIDIA、各大云都在用)。它的招牌是三件事:把前缀缓存长成一棵基数树(RadixAttention)、让调度器看着缓存排队、用压缩 FSM 在结构化输出里跳步生成。本指南默认你已读过 vLLM 指南——重合的基础只简述并跨指南引用,火力集中在 SGLang 的独门设计上。
全书地图
六章按「上手 → 核心机制 → 独有特性 → 源码 → 规模化」推进。第 2 章的 RadixAttention 是全书重心——它与 vLLM 的哈希链是同一个问题的两种数据结构,对照着读收获最大。
Chapter 1 · 0x01 · Positioning
定位与上手:另一位吞吐之王
SGLang 与 vLLM 是同一场比赛的两位选手:都做服务端高吞吐推理、都用分页 KV 与 continuous batching、benchmark 上互有胜负。值得学的不是「谁更快」(答案按月轮换),而是它在三个地方下了不同的注——这三张牌决定了它的独特价值。
1.1出身与押注:LMSYS 的三张牌
SGLang 出自 LMSYS(UC Berkeley 系的开源组织,Chatbot Arena、Vicuna 的缔造者),论文 SGLang: Efficient Execution of Structured Language Model Programs(NeurIPS 2024)。名字里的 SG = Structured Generation:它最初的问题意识不是「怎么把单条请求服务得更快」,而是「LLM 程序(多轮、分叉、带约束的调用图)怎么高效执行」。从这个问题出发,长出了三张牌:
- RadixAttention(第 2 章):LLM 程序天然带着大量共享前缀(few-shot 模板、多轮历史、分叉探索),于是把 KV 缓存组织成一棵基数树,任意深度、token 级粒度地复用——并让调度器「看着树」排队。
- 压缩 FSM 与 jump-forward(第 3 章):结构化输出里大段字符是语法注定的,何必逐 token 生成?把正则编译成压缩状态机,确定的片段整段「跳」过去。
- 前端 DSL(第 4 章):提供
gen / select / fork原语,把多步生成写成程序交给运行时——运行时看得见整棵调用树,才能做前两张牌的全局优化。
十年功力的另一半在工程侧:v0.4 起的 overlap 调度器(CPU 调度与 GPU 执行完全重叠,官方称 zero-overhead)、对 FlashInfer kernel 库的深度绑定、2025 年起对 DeepSeek 类模型「DP attention + 大规模 EP」的率先落地——xAI 用它跑 Grok,官方数据是全球 40 万+ GPU 在生产上跑 SGLang。它已从「学术项目」长成与 vLLM 并立的工业级引擎。
深入:SGLang 与 vLLM 到底谁快?怎么读 benchmark
诚实的回答:按月轮换,按场景分胜负。两个项目互相学习(SGLang 吸收了分页思想与 chunked prefill;vLLM 的 V1 重构、zero-overhead 化明显对标 SGLang 的调度器),公开 benchmark 常各自选取有利负载。读第三方对比时盯三件事:① 负载形状(共享前缀多的负载天然利好 SGLang;无前缀纯吞吐两家接近);② 版本与参数是否对等(有没有各自调优);③ 指标口径(吞吐 vs P99 延迟 vs goodput)。更有价值的姿势是把两家当作同一设计空间的两组解——本指南每章末尾的「vs vLLM」小结就是这个视角。
1.2何时选 SGLang,何时不选
radix tree 对任意深度前缀复用 + cache-aware 调度,命中率显著高于块粒度方案。
DP attention + 大规模 EP 的公开最佳实践出自 SGLang 团队博客,社区跟进最快。
压缩 FSM + jump-forward 在 JSON 密集负载上既快又稳。
vLLM 的模型日 0 支持、硬件后端广度与周边生态(K8s 编排、文档深度)目前仍占优。
一句话判据:负载里共享前缀越多、调用结构越复杂,SGLang 的相对优势越大;通用单轮补全负载上两者难分伯仲,选团队更熟的那个。
1.3上手:launch_server 与两套 API
shell · 安装与启动(Linux + NVIDIA GPU)
uv pip install "sglang[all]"
# 启动 server(默认端口 30000)
python -m sglang.launch_server \
--model-path Qwen/Qwen2.5-7B-Instruct \
--port 30000
python · OpenAI 兼容接口——与 vLLM 客户端代码零差异
from openai import OpenAI
client = OpenAI(base_url="http://localhost:30000/v1", api_key="EMPTY")
resp = client.chat.completions.create(
model="Qwen/Qwen2.5-7B-Instruct",
messages=[{"role": "user", "content": "用一句话解释 RadixAttention"}],
)
print(resp.choices[0].message.content)
除 OpenAI 接口外,SGLang 还有一个原生 /generate 接口(接受 token 级参数与返回 meta 信息,如本请求命中缓存的 token 数 cached_tokens)——学习期建议用它观察缓存行为,生产上对接现有系统用 OpenAI 接口。启动日志同样值得逐行读:显存测量、KV 池容量(#token 数)、选中的 attention backend,与 vLLM 的启动账一一对应。
1.4关键参数与压测
| 参数 | 作用 | 与 vLLM 的对应 |
|---|---|---|
--mem-fraction-static | 权重 + KV 池占显存的比例,调小可留余量 | ≈ --gpu-memory-utilization |
--context-length | 单请求最大上下文 | ≈ --max-model-len |
--max-running-requests | 同时运行的请求上限 | ≈ --max-num-seqs |
--chunked-prefill-size | prefill 分块大小(-1 关闭) | ≈ --max-num-batched-tokens(3.3 节) |
--schedule-policy lpm | 调度策略:lpm(最长前缀匹配,默认)/ fcfs 等 | vLLM 没有的东西——cache-aware 调度(2.4 节) |
--schedule-conservativeness | 调度器对显存预估的激进程度,影响抢占频率 | 类似 vLLM 的预算保守度,SGLang 显式暴露 |
--disable-radix-cache | 关闭 radix 缓存(做对照实验用) | ≈ --no-enable-prefix-caching |
--tp / --dp / --ep-size | 张量 / 数据 / 专家并行 | 同名概念,见第 6 章 |
--enable-torch-compile | 开启 torch.compile(小模型 decode 提速明显) | vLLM V1 默认开;SGLang 按需开 |
--speculative-algorithm EAGLE3 | 投机解码(EAGLE 系) | ≈ --speculative-config |
shell · 压测:与 vLLM 指南 §2.5 相同的方法学
python -m sglang.bench_serving --backend sglang \
--num-prompts 200 --request-rate 8
# 多轮/共享前缀负载对比:--dataset-name generated-shared-prefix
动手实验建议(记进笔记):① 用共享前缀数据集分别在默认与 --disable-radix-cache 下压测,对比 TTFT 与 cached_tokens——这是第 2 章的实证前菜;② 对照 --schedule-policy fcfs 与默认 lpm,观察命中率差异(2.4 节解释机制)。压测指标体系(TTFT/ITL/吞吐/goodput)与 vLLM 指南 §2.5 完全通用,不再重复。
第 1 章 · 我的笔记
第 1 章 · 完成清单
Chapter 2 · 0x02 · RadixAttention
RadixAttention:把前缀缓存长成一棵树
本章内容
前缀复用的问题两家都要答:怎么快速知道「新请求的前缀有没有算过」,怎么安全共享,怎么在显存紧张时决定丢谁。vLLM 的答案是把结构压扁成哈希表(vLLM 指南 §3.6);SGLang 的答案是保留结构本身——一棵基数树(radix tree)。本章把这棵树拆到操作级。
2.1从链到树:为什么值得换数据结构
回顾哈希链的两个内建限制:① 块粒度——命中以 16 token 的整块为单位,前缀在块中间断开的部分浪费掉;② 只认线性前缀——缓存里存的是「链」,两条请求共享前 100 token、再各自分叉时,哈希表看得见共享段,但表达不了「分叉」这个结构,逐出时也只能按块 LRU、无法按「子树」推理。
而 SGLang 的目标负载(LLM 程序)恰恰长成树形:few-shot 模板下挂 N 个问题、多轮对话逐层加深、self-consistency 一次 fork 出 5 条采样、agent 在同一上下文上尝试多个工具分支。用树存树形的东西,三个好处立刻出现:
- token 级粒度:radix cache 的匹配可以精确到任意 token 位置(树的边在任何点都能分裂),不受块边界约束——前缀有多长就复用多长。
- 结构可见:调度器能问「这个请求与树的最长匹配是多深」,于是有了 cache-aware 调度(2.4);逐出能以叶子为单位、天然保护被多方共享的内部节点。
- 分叉零成本:fork 出的分支就是同一父节点下的兄弟叶子,共享段只存一份、连「写时复制」都常常不需要(父节点只读)。
代价也要先说清:树是全局可变结构——匹配、插入、逐出都在改它,实现比「无锁哈希表查一下」复杂;每请求入场要走一次树(哈希链是 O(块数) 次字典查询,树是 O(前缀长度) 的逐字符/逐段比较,工程上都足够快但常数不同)。这笔账 2.5 节算总。
2.2数据结构:节点、边与两张记账表
树只是索引,KV 本体在池里。SGLang 的显存记账用两张表:token_to_kv_pool 存所有 token 的 KV(大池子),req_to_token_pool 记每个请求的 token 序列在池中的位置。radix tree 的节点持有的是池中位置的区间引用——命中即把这些位置接进新请求的映射表,与 vLLM「页表指向物理块」神似,只是粒度到了 token 级(早期实现 page_size=1;后续为适配 paged kernel 也支持更大页)。
2.3四个操作:匹配、插入、分裂、逐出
整个 RadixAttention 就靠四个操作运转。先玩动画再读文字:
四个请求先后到达。观察:请求 2、3 沿树走最长前缀匹配(金色闪烁 = 命中,KV 直接复用);请求 4 与已有边只有一半相同,触发节点分裂;最后显存吃紧,引用归零且最久未用的叶子被 LRU 逐出。缩进树形 = 父子关系。
- 匹配(match_prefix):新请求的 token 序列从 root 逐边比较,走到第一个不匹配处为止。命中路径上所有节点
ref+1、刷新访问时间;命中部分的 KV 位置直接接入请求的映射表,prefill 从失配点开始——与 vLLM 的「从第一个未命中块算起」同理,但边界可以停在任意 token。 - 插入(insert):请求完成(或 prefill 完成)后,把它新算出的 token 段作为新边/新节点挂到失配点下。
- 分裂(split):失配发生在某条边的中间时(比如已有边"ABCD",新请求前缀是"ABX"),把边切成"AB"→"CD"两段,新分支"X"挂到"AB"下。这是基数树保持紧凑的关键操作,也是「token 级粒度」的来源。
- 逐出(evict):KV 池不够时,从
ref=0的叶子里挑最久未访问的释放(其 KV 位置归还池子);内部节点只要还有子孙或引用就绝不会被逐出——结构天然保护了「共享度高的前缀」。对比 vLLM 的空闲队列 LRU(块级、无结构信息),树版逐出的「保护共享」是免费得到的。
深入:ref 计数的生命周期与「正在跑的请求」如何锁住路径
一个请求从入场到离场对树的影响:匹配时命中路径整条 ref+1(锁住,防止跑到一半被逐出);运行期间它新生成的 token 暂时只在自己的 req_to_token_pool 行里;结束时把整段序列 insert 回树(供后人复用),然后路径 ref-1。于是任意时刻:ref>0 的节点 = 有活跃请求正倚着它;ref=0 的叶子 = 纯缓存,可逐出。想在源码里验证:python/sglang/srt/mem_cache/radix_cache.py 的 match_prefix / insert / evict / inc_lock_ref / dec_lock_ref,第 5 章给出阅读路线。另外注意逐出的单位是「节点持有的整段 token」——一次逐出可能释放几十个 token 的 KV,比逐块回收更整批。
2.4cache-aware 调度:让队列看见树
树带来的第二重红利:调度器排队时可以先问一句「谁与当前的树匹配最深?」。SGLang 的默认调度策略 lpm(longest prefix match)按匹配长度降序处理等待队列——让相同前缀的请求扎堆执行,前缀 KV 在树里还热着就被反复命中;如果按到达顺序(FCFS)交错执行不同前缀的请求,缓存可能在两组前缀间来回逐出、反复重算。
4 个请求交错到达:A1、B1 前缀不同,A2 与 A1 同前缀、B2 与 B1 同前缀。假设显存只装得下一种前缀的缓存。上半场按 FCFS 处理——缓存来回失效,命中 0;下半场按 LPM 重排(A 组连续、B 组连续)——后到的同伴各命中一次。
论文消融里 cache-aware 调度把命中率从「碰运气」拉到接近最优;生产上它还与 DP 路由组合——sglang-router 在多副本间做同样的事:按「哪个副本的树与这条请求匹配最深」分发(6.2 节)。公平性由老化机制兜底:等太久的请求优先级逐步提升,避免冷前缀请求被饿死。vs vLLM:vLLM 的调度按 FCFS/优先级,不感知缓存内容,跨副本靠外部网关做前缀亲和(vLLM 指南 §6.4)——SGLang 把这层智能内置进了引擎与官方 router。
2.5对照 vLLM:一张表看清全部取舍
| 维度 | SGLang · radix tree | vLLM · 哈希链 |
|---|---|---|
| 命中粒度 | 任意 token 位置(边可分裂) | 16-token 整块 |
| 查找方式 | 从 root 走树,O(前缀长) | 逐块哈希查表,O(块数) |
| 结构表达 | 树原生表达分叉/共享,可按子树推理 | 扁平表,无结构信息 |
| 逐出策略 | 叶子 LRU,内部共享节点天然受保护 | 空闲队列 LRU(块级) |
| 调度联动 | LPM cache-aware 排队 + cache-aware DP 路由 | 调度不感知缓存;亲和靠外部路由 |
| 实现复杂度 | 全局可变树,锁/引用管理更重 | 字典 + 双向链表,极简,「零开销」承诺 |
| 多租户隔离 | 树按命名空间/盐值分隔(思路同 cache_salt) | cache_salt 入哈希(§3.6.3) |
收束成一句话:树用实现复杂度换「更细的粒度 + 结构可见」,表用粒度换极简与零开销。负载前缀越重、结构越树形,树的那侧越值;负载扁平时两者差距缩小到噪声。这也是为什么两个项目谁都没「抄走」对方的方案——它们各自最优的负载假设不同。
第 2 章 · 我的笔记
第 2 章 · 完成清单
Chapter 3 · 0x03 · Scheduling & Execution
调度与执行:overlap、分块与 jump-forward
本章内容
这一章讲 SGLang 的执行侧三件套:把调度开销藏进 GPU 执行影子里的 overlap 调度器、与 vLLM 同源但参数不同的 batching 体系、以及结构化输出的招牌——压缩 FSM + jump-forward。
3.1overlap 调度器:把 CPU 藏进 GPU 的影子里
vLLM 指南 §3.4 深入块讲过:调度是 CPU 干的活,一步几百微秒,若与 GPU 串行就是步间空隙。SGLang 是把这件事做成默认且彻底的先行者(v0.4 的「zero-overhead batch scheduler」):调度线程常年领先 GPU 一个 batch——GPU 跑第 N 批时,CPU 已把第 N+1 批的组批、radix 匹配、元数据与张量准备全部做完,GPU 一停就能立刻吃下一批。
重叠不是免费的。CPU 提前组批意味着它看不到第 N 批的采样结果——完成判定、radix 树插入、约束解码的状态推进都要小心地异步化。这套「提前一步 + 异步回填」的实现细节在 scheduler.py 的 overlap 事件循环里(第 5 章)。vLLM V1 后来将同类机制做进引擎(async scheduling),两家在此殊途同归——工程史上很常见:好设计会被对手学走,先做的人定义了标准。
3.2batching 与分块:熟悉的配方,SGLang 的口味
continuous batching、chunked prefill、抢占恢复——机制原理与 vLLM 指南第 3 章一致,不再重讲。值得记的是 SGLang 的三处口味差异:
- 组批单位是 ScheduleBatch:prefill 批与 decode 批在概念上仍分开组(prefill 批可夹带 decode,受
--chunked-prefill-size控制),不像 vLLM V1 那样彻底抹平——但效果等价:ITL 平滑、算力带宽混吃。 - 入场即匹配:新请求进 waiting 队列时就做 radix 匹配(决定 LPM 排序),而不是调度时才查——匹配结果也是「这个请求还需要算多少 token」的依据,缓存与调度深度耦合是 SGLang 的一贯风格。
- 显存预估用 conservativeness 旋钮:radix 缓存的可逐出部分让「还能塞几个请求」变成弹性问题,
--schedule-conservativeness控制预估的激进程度——激进则并发高但抢占多,保守反之。压测时遇到吞吐上不去或KV cache pool is full告警,先动这个旋钮。
3.3jump-forward:结构化输出的跳步生成
vLLM 的结构化输出(§2.3.2)用 bitmask 禁止非法 token——每步仍要跑一次完整前向。SGLang 团队多问了一句:既然语法自动机知道「下一段只有唯一合法内容」(比如 JSON 里的 ", "age": ),为什么还要让模型一个个「猜」这些注定的 token?答案就是 jump-forward:把正则/JSON schema 编译成压缩 FSM(把线性无分支的状态链合并成一条边),解码时遇到「唯一后继」的边,直接把整段 token 追加进序列、跳过前向;只有真正需要模型决策的位置(值、数字、自由文本)才逐 token 生成。
目标输出 {"name": "小明", "age": 18}。灰色格子 = 语法注定的字符(FSM 一次性整段写入,零次前向);青色格子 = 模型真正决策的内容(逐 token 前向)。右侧计数器对比两种方式消耗的前向次数。
收益立竿见影:JSON 越「骨架重、血肉轻」,跳过的比例越高——模板化输出场景常见 1.5~3× 的生成提速,且格式合法性 100% 保证(写进去的就是语法要求的)。一个微妙处值得深究:
深入:跳步会不会改变分布?tokenizer 边界的坑
两个细节让 jump-forward 不是「白拿」:① 分布视角——被跳过的 token 本来就只有唯一合法选择(其他 token 概率会被 mask 成 0),所以跳步不改变输出分布,这点与投机解码的「无损」论证异曲同工;② tokenizer 边界——直接把字符串拼进 token 序列,可能产生与模型自然生成不同的 token 切分(比如 ": " 与后续字符合并成不同 token),SGLang 的实现要做 retokenization 对齐:跳步后把尾部若干字符回退重切,保证后续生成的 token 边界与模型训练分布一致,否则质量会悄悄劣化。这类「看似显然、细节致命」的工程点,正是读源码(constrained/ 目录)的乐趣所在。现代后端(xgrammar 等)已把压缩 FSM + jump-forward 抽象成通用库,vLLM 亦可受益——又一次「先行者定义标准」。
3.4执行层:FlashInfer、CUDA Graph 与 torch.compile
- FlashInfer 深度绑定:SGLang 与 FlashInfer(面向服务场景的 attention kernel 库,vLLM 指南 §4.3.2)同气连枝,默认后端即是它;分组 decode、投机验证等 kernel 常在 SGLang 先用起来。
sgl-kernel包则收纳自研 kernel(含对 Blackwell 的适配)。 - CUDA Graph:decode 批按尺寸档位预录整图重放,原理同 vLLM §4.4;
--disable-cuda-graph做对照。 - torch.compile 按需开:
--enable-torch-compile对中小模型 decode 提速可观;大模型编译时间与收益要权衡——与 vLLM「默认全开」的取向不同,SGLang 把选择权留给部署者。 - 投机解码:EAGLE-2/EAGLE-3 是主打(团队还开源了 SpecForge 训练草稿头),
--speculative-algorithm EAGLE3一键启用;原理与适用边界见 vLLM §4.1,结论完全通用。
第 3 章 · 我的笔记
第 3 章 · 完成清单
Chapter 4 · 0x04 · Frontend DSL
前端 DSL:把「程序」交给推理引擎
SGLang 的「SG」部分常被吞吐光环掩盖,但它是理解这个项目的钥匙:如果调用方能把多步生成表达成程序,运行时就能对整棵调用树做全局优化——第 2 章的树缓存与第 3 章的跳步解码,都是为这类程序准备的执行引擎。
4.1四个原语:gen、select、fork、join
python · 一个带分叉的评估程序
import sglang as sgl
@sgl.function
def essay_judge(s, essay):
s += sgl.system("你是一位严格的作文评委。")
s += sgl.user("请评估这篇作文:\n" + essay)
# fork:从同一上下文分出 3 个并行分支——共享的前缀 KV 只算一份
forks = s.fork(3)
dims = ["立意", "结构", "文采"]
for f, dim in zip(forks, dims):
f += sgl.user(f"只就「{dim}」打分并给一句理由。")
f += sgl.assistant(sgl.gen("score", max_tokens=64))
forks.join() # 等三个分支完成
# select:让模型在有限选项中选择——按各选项的 logprob 比较,不靠自由生成
s += sgl.user("综合以上,给出最终评级。")
s += sgl.assistant(sgl.select("grade", choices=["优", "良", "中", "差"]))
state = essay_judge.run(essay="……")
print(state["grade"], state["score"]) # 按名字取各 gen 的结果
# 批量执行:essay_judge.run_batch([{"essay": e} for e in essays])
gen(name, ...):在当前位置让模型生成,结果存进 state 的命名槽位;可带regex=/json_schema=约束(直通第 3 章的压缩 FSM)。select(name, choices):不是「生成再解析」,而是分别计算各候选的对数似然取最优——分类/评级任务既稳又省 token。fork(n)/join():从当前状态分出 n 条分支并行推进。分支共享的历史在 radix tree 里就是同一条父路径——共享 KV 零拷贝,这是 DSL 与 RadixAttention 的合谋。run_batch:同一程序对一批输入并行执行;配合 LPM 调度,同模板的请求天然扎堆命中缓存。
4.2程序即图:运行时看见结构后能做什么
把上面的程序画出来是一棵树:system+essay 是树干,三个评分分支是枝条,最终评级接在主干上。对比「用 OpenAI API 手写 4 次独立调用」——语义相同,但运行时看不见调用间的关系,四次请求各自 prefill 完整前缀。DSL 让结构显式化后,运行时至少做三件事:
- 前缀零重算:fork 的分支直接挂在父路径下(第 2 章),树干 KV 一份三用;实测 few-shot + 多分支负载可省 50%+ 的 prefill 计算。
- 分支并行组批:三个分支进同一个 continuous batch 并行 decode,而不是串行三轮。
- 约束下推:
select/regex在引擎内用 logprob 比较与 FSM 执行,避免「生成→解析→重试」的应用层循环。
深入:论文里的「LLM 程序」视角与解释器实现
SGLang 论文把这套 DSL 形式化为「结构化语言模型程序」:前端把 Python 函数解释成原语流(python/sglang/lang/ 的 interpreter/IR),后端 runtime 接收原语并调度执行。论文的三大优化——RadixAttention、压缩 FSM、API 投机执行(对闭源 API 后端预取多步生成)——分别对应本指南第 2、3 章与一个较少人知的第三招。前端与后端解耦:同一程序可跑在本地 SGLang runtime,也可切到 OpenAI API 后端(此时树缓存优化自然失效,只保留编程模型)。今天多数人把 SGLang 当纯 server 用(OpenAI 接口),DSL 更多活跃在评测框架与 agent 研究里——但理解它,才能理解这个引擎为什么长成这样。
4.3何时用 DSL,何时用 OpenAI API
| 场景 | 建议 | 原因 |
|---|---|---|
| 已有系统对接、单轮补全 | OpenAI API | 零改造成本;radix 缓存对重复前缀照样生效 |
| 评测流水线(同模板 × 千样本) | DSL + run_batch | 模板前缀全量复用 + LPM 扎堆,吞吐差距可观 |
| 多分支 agent / self-consistency | DSL + fork | 分支共享 KV 零拷贝,OpenAI API 表达不了 |
| 重 JSON 抽取 | 两者皆可 + 约束参数 | 关键是把 schema 交给引擎(压缩 FSM),入口形式次要 |
实践提示:即使全程只用 OpenAI 接口,把「稳定模板在前、变量在后」的 prompt 纪律执行好(与 vLLM §3.6 同一条纪律),radix 缓存的收益就能吃到大半——DSL 是锦上添花的表达力,不是收益的唯一入口。
第 4 章 · 我的笔记
第 4 章 · 完成清单
Chapter 5 · 0x05 · Source Dive
源码结构:四进程与一棵树的实现
读过 vLLM 源码的你会觉得 SGLang 格外亲切:同样的「CPU 重活与 GPU 忙循环隔离」,同样的自顶向下阅读法。差异集中在两处——调度进程按 TP rank 分布(而非单个 EngineCore),以及显存记账从「页表」换成了「树 + 两张池表」。
5.1骨架:四个进程与 ZMQ 管道
与 vLLM 两进程架构的映射:TokenizerManager ≈ API 进程的前半,Detokenizer ≈ 后半(SGLang 拆得更碎);Scheduler 进程 ≈ EngineCore——但注意它每个 TP rank 一份(调度逻辑在 rank 间同步执行),而 vLLM 是单 EngineCore 指挥多 Worker。管道同为 ZMQ。对照阅读:vLLM 指南 §5.2。
5.2主线:请求的一生(七站)
entrypoints/http_server.pyTokenizer 进程managers/tokenizer_manager.pyTokenizer 进程TokenizedGenerateReqInput,ZMQ 发往 Scheduler。managers/scheduler.py + radix_cache.match_prefixScheduler 进程ref+1 锁定、KV 位置接入 req_to_token_pool;剩余部分决定它的 prefill 工作量与 LPM 队列位置。managers/schedule_batch.py + schedule_policy.pyScheduler 进程ScheduleBatch(prefill 批 / decode 批),显存预估受 conservativeness 调节;不够则逐出 ref=0 叶子,再不够则抢占。overlap 模式下这一切发生在 GPU 跑上一批的同时。model_executor/model_runner.pyGPUscheduler.py: process_batch_resultScheduler 进程insert 回 radix tree(供后人复用)、路径 ref-1。managers/detokenizer_manager.pyDetokenizer 进程5.3精读锚点:一棵树与两张表
两处最值得逐行读的代码(对应 vLLM 的 Scheduler + KVCacheManager 双精读):
mem_cache/radix_cache.py(第 2 章的实现本体):TreeNode的字段(children 按首 token 索引、parent、key/value 区间、lock_ref、last_access_time);match_prefix的逐边比较与边中分裂(_split_node);evict用小根堆按访问时间取 ref=0 叶子。读完自测:手画「ABCD 在树中,插入 ABX」的分裂过程。managers/schedule_batch.py+schedule_policy.py:Req与ScheduleBatch的状态字段;LPM 排序的实现(policy 里按prefix_len降序);显存预估如何把「可逐出的缓存」算进可用额度——这是 conservativeness 旋钮的落点。- 两张池表(
mem_cache/memory_pool.py):req_to_token_pool(请求 → token 位置行)与token_to_kv_pool(token 位置 → 各层 KV)。对照 vLLM:block table + 物理块池的角色分别由谁扮演?(答:前者 ≈ 页表但按 token 展开;后者 ≈ 块池但按 token 编址。)
深入:overlap 事件循环怎么读
scheduler.py 的主循环有普通与 overlap 两条路径。overlap 版的关键是两个在途 batch 的接力:当前迭代里,CPU 先 process_batch_result(上一批)(异步拿到上一批的采样结果、回填/判完成),再 get_next_batch_to_run()(组下一批),然后把下一批发给 GPU——GPU 与 CPU 手里各有一批,互不等待。读的时候盯三个问题:① 上一批的结果什么时候真正「落账」?② 一个请求「完成」的判定为什么要延迟一拍?③ radix insert 发生在哪一拍?把这三个时刻标到 3.1 节的时间线图上,overlap 就真懂了。
5.4目录地图与阅读方法
| 路径(python/sglang/) | 内容 | 精读优先级 |
|---|---|---|
srt/managers/ | 四进程的三个:tokenizer/scheduler/detokenizer + 组批与策略 | ★★★ |
srt/mem_cache/ | radix_cache、两张池表、HiCache 分层 | ★★★ |
srt/model_executor/ | ForwardBatch 构建与模型执行 | ★★☆ |
srt/layers/ · srt/models/ | 并行层与模型定义(与 vLLM 同构,读法同 §5.7) | ★★☆ |
srt/constrained/ | 压缩 FSM 与 jump-forward 实现 | ★★☆ |
srt/disaggregation/ · srt/distributed/ | PD 分离与并行通信 | 第 6 章 |
lang/ | 前端 DSL 解释器与 IR | ★☆☆ |
sgl-router/(仓库根) | Rust 实现的 cache-aware DP 路由 | 选读 |
方法与 vLLM 完全同款:最小脚本 + 断点跟一条请求;test/srt/ 的单测当行为文档;PR/issue 里搜设计讨论。入门顺序建议:radix_cache.py(500 行内、独立性强)→ 生命周期主线 → overlap 循环。路径以 v0.5 为准,对不上用 git grep 类名。
第 5 章 · 我的笔记
第 5 章 · 完成清单
Chapter 6 · 0x06 · Scale & Ecosystem
大规模与生态:从单机到十万卡
本章内容
SGLang 在大规模侧的名片,是 2025 年那篇公开复刻 DeepSeek 推理系统的博客:96 张 H100 上把 DeepSeek-V3 跑出接近官方公布的吞吐成本。本章以这套部署为主线——它把前几章的所有机制串成了一个真实系统。并行策略的理论(TP/PP/EP/DP 通信账)见 vLLM 指南 §6.2,此处只讲 SGLang 的落地形态。
6.1复刻 DeepSeek:DP attention + 大规模 EP
DeepSeek 类模型(MLA + 数百专家的 MoE)的标准并行形态——attention 走 DP、专家走大规模 EP——SGLang 是最早完整落地并公开细节的开源引擎(官方博客)。三个关键件:
- DP attention:MLA 的 latent KV 没有 head 维可切(原理见 vLLM §6.2 深入块),每个 DP rank 独立服务不同请求、各持自己的 radix tree;
--enable-dp-attention开启。 - 大规模 EP:专家散布全集群,dispatch/combine 走 DeepEP 通信 kernel(prefill 用 normal 模式、decode 用低延迟模式);热专家靠 EPLB 复制再平衡——负载画像每隔一段时间重算专家放置。
- 两批重叠(two-batch overlap):EP 的 All-to-All 通信期间 GPU 会闲——把 batch 拆成两个 micro-batch 交错执行,一个在通信时另一个在计算,通信被藏进计算影子里。这是 overlap 思想(3.1)在通信维度的续集。
这套部署的成绩(每节点输出吞吐、每百万 token 成本)与 DeepSeek 官方披露同量级,把「论文里的推理系统」变成了人人可复刻的开源配置——也是 SGLang 在 MoE 时代声望的来源。学习姿势:不必真有 96 张卡,读懂博客里每个配置项对应本指南哪一章的机制,就是最好的综合复习。
6.2PD 分离与 cache-aware 路由
- PD 分离:动机与架构同 vLLM §6.3(prefill/decode 干扰、SLO 分治);SGLang 的 KV 传输后端接 Mooncake / NIXL,`srt/disaggregation/` 里是传输编排。DeepSeek 复刻部署即为 PD 分离形态(prefill 组与 decode 组独立扩缩)。
- sglang-router:Rust 实现的 DP 路由器,核心是把第 2 章的 cache-aware 思想抬到集群层——路由器为每个副本维护近似的前缀树视图,新请求发给「树上匹配最深」的副本(弱亲和时退化为负载均衡)。对比 vLLM 生态把前缀亲和交给外部网关(llm-d 等),SGLang 官方自带这一层。
6.3HiCache:把树伸到 CPU 与存储
GPU 显存里的 radix tree 逐出的 KV 就丢了;HiCache(hierarchical cache)把逐出改成降级:GPU → CPU 内存 → 本地/远端存储三层,树的节点标注「KV 此刻在哪层」,命中冷层时异步搬回 GPU。多轮对话隔了几分钟回来、长文档反复被引用这类「温前缀」场景收益显著。与 vLLM 生态的 LMCache(§6.3)定位相同——分层 KV 已是两家共识,差别只在与树/表结构的耦合方式。--enable-hierarchical-cache 开启。
6.4生态、对照总表与参与
| 维度 | SGLang | vLLM |
|---|---|---|
| 前缀缓存 | radix tree · token 级 · cache-aware 调度 | 哈希链 · 块级 · 零开销 |
| 调度 | overlap(先行者)· LPM | V1 统一 token 预算 · async 化(后至) |
| 结构化输出 | 压缩 FSM + jump-forward(先行者) | xgrammar bitmask |
| 投机解码 | EAGLE 系为主 + SpecForge 训练 | ngram/EAGLE/Medusa/MTP 全谱 |
| MoE 大规模 | DeepSeek 复刻的公开标杆 | EP/DP 支持齐全,节奏稍后 |
| DP 路由 | 官方 Rust router(cache-aware) | 交给生态(llm-d/production-stack) |
| 模型/硬件覆盖 · 文档生态 | 主流模型全,广度稍逊 | 最广(日 0 支持常态) |
| 编程接口 | OpenAI + 原生 /generate + DSL | OpenAI + LLM 类 |
参与社区:仓库 sgl-project/sglang,路线图以季度 issue 公示(搜 "Development Roadmap");good first issue 的入门路径与 vLLM §6.6 相同;LMSYS 博客是设计动机的第一手来源。代码库比 vLLM 小一圈、层次直白,很多人反馈「读 SGLang 比读 vLLM 快进入状态」——把它当第二个精读对象正合适。
第 6 章 · 我的笔记
第 6 章 · 完成清单
Appendix · 0x07 · References
资料清单
论文与博客
| 资料 | 主题 | 章节 | 优先级 |
|---|---|---|---|
| SGLang 论文(NeurIPS 2024) | RadixAttention + 压缩 FSM + DSL,全书总纲 | 2/3/4 | ★★★ |
| 大规模 EP 复刻 DeepSeek 博客 | 96 卡 DeepSeek-V3 部署全配置 | 6 | ★★★ |
| SGLang v0.4 博客 | zero-overhead 调度、cache-aware DP router | 3/6 | ★★☆ |
| EAGLE / EAGLE-3 | SGLang 主打的投机路线(详见 vLLM 指南 §4.1) | 3 | ★★☆ |
| Mooncake | PD 分离与 KV 传输(详见 vLLM 指南 §6.3) | 6 | ★☆☆ |
官方文档与代码
- docs.sglang.ai —— 总入口:Server Arguments、Attention Backend、Speculative Decoding、Router 各专页。★★★
- github.com/sgl-project/sglang —— 源码、季度路线图 issue、设计讨论。★★★
- lmsys.org/blog —— 每个大特性的动机与实测,SGLang 版的「必读博客集」。★★★
与 vLLM 指南共用的基础
推理物理直觉、GPU 体系结构、分页 KV、continuous batching、量化谱系、投机解码原理、并行通信账——全部在 vLLM 指南,本指南不重复收录。建议的阅读闭环:vLLM 指南全书 → 本指南 → 回到 vLLM 指南 §6.5 重读「RadixAttention vs 哈希链」深入块,此时你会读出完全不同的层次。
术语表
正文里带虚线下划线的词可悬停查看解释;此处为本指南新增术语的汇总(与 vLLM 指南共用的术语在其附录)。
保持更新的姿势
SGLang 与 vLLM 同处高速迭代期(2026-07 为 v0.5.x)。盯两处:LMSYS 博客的特性发布文与仓库的季度路线图 issue。核心机制(radix cache、overlap、EP 体系、router)已稳定,参数名以当版本 --help 为准。