对应 Maps to:Stanford CS349D AI Inference Infrastructure(Spring 2026),#6 Wed Apr 15 Disaggregation。必读:Y. Zhong, S. Liu, J. Chen, J. Hu, Y. Zhu, X. Liu, X. Jin, H. Zhang, DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving(OSDI 2024);NVIDIA 技术博客 Introducing NVIDIA Dynamo, A Low-Latency Distributed Inference Framework for Scaling Reasoning AI Models(2025 年 3 月)。选读:P. Patel 等, Splitwise: Efficient Generative LLM Inference Using Phase Splitting(ISCA 2024);A. Agrawal 等, Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve(OSDI 2024)。 依据说明:原课没有公开 slides。本讲依据 DistServe、Sarathi-Serve、Splitwise 三篇论文的全文和 Dynamo 博客的全文。为了让”分块 prefill”和”解耦”这两条路线的对比更清楚,本讲先讲选读的 Sarathi-Serve(§2),再讲必读的 DistServe(§3)。§5 的对比实验和 §7”之后的发展”是我补充的。 配套代码 Code:
code/lecture07/(标准库 + numpy;4 个脚本 + 1 个模块pd_engine.py:三种共置调度(prefill 优先 / 混合批 / 分块 prefill)与解耦部署的离散事件模拟器、KV 传输、goodput 的二分查找、DistServe 的排队公式;代码 03 约 1 分钟,代码 04 约 30 秒,其余几秒) 预计阅读时间:3–4 小时(含运行代码)
0. 本讲导航 · How to read this lecture
0.1 学习目标 Learning goals
学完这一讲,你应该能够:
- 解释共置(colocated)时 prefill 与 decode 的相互干扰:长 prefill 让同批的 decode 卡顿(generation stall),decode 占用的算力又推迟了新请求的 prefill;以及两个阶段被迫共用同一种资源配置和并行方式。
- 说清 Sarathi-Serve 的两个技术:分块 prefill(无损,KV 读取变多)与不停顿的批(每步一个 token 预算,decode 优先);解释它为什么利用的是 decode 批里”免费”的算力;知道 token 预算在 TTFT 与 TBT 之间的取舍。
- 说清 DistServe 的思路:把 prefill 和 decode 放到不同的 GPU 上,各自选择资源和并行方式,以每 GPU 的 goodput 为目标搜索放置方案;能用 M/D/1 公式解释 prefill 实例为什么在低到达率时偏好张量并行、高到达率时偏好流水线并行。
- 估算 KV 传输的大小和时间,解释为什么解耦需要高带宽互连、GQA 让解耦容易得多、逐层传输(Splitwise)能把传输藏起来。
- 解释 Splitwise 的异构部署(prefill 用新卡、decode 用旧卡或限功耗)为什么可能更省钱,以及它什么时候不划算。
- 判断”分块 prefill 还是解耦”:知道结论取决于 SLO 的松紧、负载的输入输出比例、显存容量和部署规模;知道 Dynamo 这类框架为什么需要一个动态的 Planner。
0.2 先修与本讲范围 Prerequisites & scope
| 默认你已经会 Assumed | 本讲只给直觉,后面再展开 Previewed only |
|---|---|
| 第 1 讲:prefill / decode、TTFT / TPOT、goodput、roofline | 排队论(M/D/1、Little 定律)的推导(前置讲 P4) |
| 第 2 讲:迭代级调度、混合批(Orca)、prefill 插入的方式 | 互连与集合通信(前置讲 P5、第 6 讲) |
| 第 3 讲:KV cache 的显存管理 | KV cache 的分层存储与跨机器复用(第 9 讲 Mooncake) |
| 第 6 讲:张量并行、流水线并行、α–β 通信模型 | KV 感知的请求路由(第 11 讲) |
0.3 本讲地图 Map
§1 问题:两个阶段的相互干扰、生成停顿、资源与并行方式的耦合;指标 ← 代码 02 演示 1–2
§2 Sarathi-Serve:分块 prefill + 不停顿的批 + token 预算 ← 代码 01、代码 02 演示 3
§3 DistServe:解耦、以 goodput 为目标的放置、排队分析、KV 传输 ← 代码 03 演示 1、代码 04 演示 1、3、4
§4 Splitwise:阶段特性、逐层 KV 传输、异构与限功耗的集群 ← 代码 04 演示 2、5
§5 共置、分块还是解耦?——在 SLO 下比 goodput ← 代码 03 演示 2–3
§6 NVIDIA Dynamo:解耦的生产框架;负载变化与 Planner ← 代码 04 演示 6
§7 之后的发展
§8–12 小结、常见误区、练习、延伸阅读、下一讲预告
0.4 记号 Notation
| 符号 | 含义 |
|---|---|
| TTFT | 首 token 延迟:prefill 阶段(含排队) |
| TPOT | 每个请求的平均 token 间隔:(完成时间 − 首 token 时间) / (输出长度 − 1)(DistServe 用它) |
| TBT / ITL | 相邻两个 token 的间隔;Sarathi-Serve 看它的 P99 |
| SLO 达成率 | 同时满足 TTFT 和 TPOT 两个 SLO 的请求比例 |
| goodput | DistServe:SLO 达成率 ≥ 90% 时能承受的最大到达率;“每 GPU 的 goodput” = 它 ÷ GPU 数 |
| 容量(capacity) | Sarathi-Serve:满足 P99 TBT 的 SLO、且中位调度延迟 ≤ 2 s 时的最大到达率 |
| token 预算 | 分块 prefill 每一步最多处理多少个 token(decode 每个算 1 个) |
| xPyD | 解耦部署:x 个 prefill 实例、y 个 decode 实例 |
| DistServe:让 GPU 饱和的 prefill 长度,prefill 批凑到它附近 | |
| 、 | DistServe 排队分析:单卡的 prefill 执行时间、张量并行的加速比() |
0.5 关于数字 · About the numbers
本讲的数字有三类:(1) tiny_llm 上的真实计算(代码 01 演示 1):分块 prefill 与一次 prefill 的结果核对、运算量与 KV 读写量的计数;(2) 纸上 GPU 上的离散事件模拟(代码 01 演示 2–3、代码 02–04):每一步的时间来自第 1 讲的 hw_model(张量并行时加上第 6 讲的 α–β 通信模型),KV 传输用 α–β 模型;它只代表数量级与趋势,没有运行时开销、内核细节,也比真实系统乐观(代码 03 演示 1:只做 prefill 时的容量约是 DistServe 实测的 2 倍);(3) 排队论公式与模拟(代码 04 演示 3)。论文里的数字写成”论文报告”。
1. 问题:两个阶段挤在一起 · The problem
1.1 两个截然不同的阶段
第 1 讲已经说过,一个请求有两个阶段:
- prefill:一次处理整段提示,产出第一个 token。提示有几百上千个 token,矩阵乘法的强度高,compute-bound;它决定 TTFT。
- decode:每步只为每个请求处理 1 个新 token。要读全部权重和 KV,算得很少,memory-bound;它决定 TPOT / TBT。批处理对 decode 收益巨大(分摊读权重),对 prefill 几乎没有(单个长提示就能用满算力)。
第 2 讲的连续批处理把两者放在同一组 GPU、甚至同一个批里。这样做提高了 GPU 利用率,却带来三个问题。
1.2 问题一:干扰
把一个 prefill 加进一批 decode,这一步要多久?代码 02 演示 1 用 hw_model 算了 Llama-3-8B 在 H100 上的情况(decode 的上下文 512):
| prefill 长度 | decode 批 | 纯 decode | decode + 1 个 prefill | decode 被拖慢 | prefill 被拖慢 |
|---|---|---|---|---|---|
| 128 | 32 | 6.90 ms | 6.91 ms | 1.00× | 1.13× |
| 1024 | 32 | 6.90 ms | 22.25 ms | 3.22× | 1.03× |
| 4096 | 32 | 6.90 ms | 90.15 ms | 13.06× | 1.01× |
| 4096 | 128 | 9.31 ms | 92.27 ms | 9.91× | 1.03× |
短 prefill 落在 decode 批”免费”的算力里,几乎不影响;1024 个 token 让这一步慢 2.6–3.5 倍,4096 个 token 慢 10–14 倍——同一步里所有 decode 请求的这个 token 都要等它。反方向的影响小一些:prefill 本身是 compute-bound 的,加进一批 decode 只多了读 KV 和少量计算(decode 批越大越明显)。DistServe 图 2 在 13B 模型上测到了同样的两个方向。
即使不把它们放进同一个批、而是分开调度,问题也还在:decode 要排队等正在跑的 prefill,prefill 要排队等 decode;优先哪一边,另一边的延迟就受损。
1.3 问题二:生成停顿
干扰最直观的后果是 generation stall(Sarathi-Serve 的说法):一批请求正在流畅地生成,突然来了一个长提示,所有人都卡住。代码 02 演示 2 模拟了这个场景——32 个请求正在生成,t = 0.5 s 来了一个 4096 个 token 的提示(只统计 0.5 s 之后的间隔):
| 调度方式 | 老请求 TBT 最大 | 老请求 TBT 中位数 | 新请求 TTFT |
|---|---|---|---|
| prefill 优先(vLLM) | 96.58 ms | 7.19 ms | 90.13 ms |
| 混合批(Orca) | 90.15 ms | 7.19 ms | 91.21 ms |
| 分块,预算 512 | 12.19 ms | 7.20 ms | 106.70 ms |
| 分块,预算 256 | 7.32 ms | 7.20 ms | 141.29 ms |
prefill 优先和混合批都在某一步里把整个 4096 个 token 的 prefill 做完,这一步要九十多毫秒,32 个请求全部卡住。混合批也没用:它把 decode 放进了同一步,可这一步本身就很长。Sarathi-Serve 图 1a 在 vLLM 上看到持续好几秒的停顿(Yi-34B、长提示的摘要负载)。注意 TBT 的中位数几乎不变:停顿只发生在少数几步上,平均值会把它掩盖。
1.4 问题三:资源与并行方式的耦合
第 6 讲的结论是:prefill 和 decode 喜欢的并行方式不同——prefill 受 TTFT 约束,愿意用更多的张量并行换延迟;decode 受限于带宽和显存容量,更在意批能做多大。Pope 等人在 PaLM 上也给 prefill 和 decode 选了不同的布局和批大小,甚至提出”batch 1 的 prefill 服务器喂给 batch 64 的 decode 服务器”。共置时,两个阶段只能用同一种资源配置和并行方式,只好按两者中更苛刻的那个去配置,常常要过度配置资源。
1.5 用什么衡量
两篇主要的论文用了不同的指标,这会影响结论:
- DistServe 用 TTFT 和每个请求的平均 TPOT,goodput 定义为”同时满足两个 SLO 的请求比例 ≥ 90%(或 99%)时的最大到达率”,并除以 GPU 数得到每 GPU 的 goodput——它直接对应每个请求的成本。
- Sarathi-Serve 用中位 TTFT 和 P99 TBT(每一个 token 间隔都算),容量定义为”满足 P99 TBT 的 SLO、并且负载可持续(中位调度延迟 ≤ 2 s)时的最大到达率”。
平均 TPOT 对偶尔的停顿不敏感(一个请求 200 个间隔里有一个 90 ms,平均值只涨 0.4 ms),P99 TBT 则很敏感。所以 Sarathi-Serve 更关心停顿,DistServe 更关心两个阶段的资源分配。(我们的模拟在 goodput 的二分查找里同时用了 DistServe 的 90% 达成率和 Sarathi-Serve 的可持续条件,原因见 §5.1。)
2. Sarathi-Serve:分块 prefill 与不停顿的批 · Sarathi-Serve
2.1 两类调度器各有缺陷
Sarathi-Serve 把已有的调度器分成两类(论文图 2):
- decode 优先(FasterTransformer 这类请求级批处理):一批请求一起 prefill,然后一起 decode 到最后一个结束才接新请求。TBT 很稳(新请求不会打断正在 decode 的请求),但吞吐差——请求陆续结束后批越来越小(第 2 讲)。
- prefill 优先(Orca、vLLM 的迭代级批处理):显存一有空就立刻准入新请求、先做它们的 prefill。后续 decode 的批更大,吞吐高,但会造成生成停顿,TBT 的尾部很差。vLLM 的批要么全是 prefill、要么全是 decode(论文 Algorithm 2);Orca 支持混合批,但混合批里的长 prefill 照样让这一步很长。
降低批大小可以减少停顿(Orca 论文的建议),却直接牺牲吞吐。Sarathi-Serve 的目标是同时拿到高吞吐和低 TBT。
2.2 关键观察:decode 批里有免费的算力
decode 批受限于读权重,算力大量闲置——这是第 1 讲以来反复出现的事实。往一个 decode 批里”顺带”放一些 prefill token,这一步几乎不变慢,直到越过屋脊点。代码 01 演示 2(Llama-3-8B,H100,32 个请求、上下文 1024):
| 顺带的 prefill token | 一步时间 | 比纯 decode 慢 | 瓶颈 |
|---|---|---|---|
| 0 | 7.70 ms | — | memory |
| 256 | 7.72 ms | +0% | memory |
| 512 | 11.64 ms | +51% | compute |
| 1024 | 22.26 ms | +189% | compute |
| 4096 | 90.16 ms | +1070% | compute |
放 256 个 token 时,每个顺带的 prefill token 只让这一步多 52 ns;单独 prefill 时每个 token 要 21 µs。batch 128 时 decode 要读的 KV 更多,“免费”的区间更宽(放 512 个只慢 10%)。Sarathi-Serve 图 5、图 6 在 LLaMA2-70B 上看到同样的曲线:线性层的时间在 128–512 个 token 的范围内几乎不变,越过一个临界点后才线性增长(论文说理论上 A100 约 200 个 token 就该 compute-bound,实际因为固定开销在 500–600 个左右)。
2.3 分块 prefill
问题是真实的提示动辄几千个 token(论文的两个数据集中位数分别是 1730 和 7059),整段放进 decode 批里就会造成停顿。**分块 prefill(chunked prefill)**把一个长提示切成若干块,分几次 forward 做完,每次只处理一块。它依据两点:中等长度的块已经足以让 GPU 的算力饱和;而真实的提示通常长得多,切开之后每块仍然足够大。
分块 prefill 是无损的:第 块的 query 要和前面所有块以及本块的 key 做注意力,因果掩码保证它看不到后面的块,数学上与一次做完完全相同。代码 01 演示 1 在 tiny_llm 上核对(192 个 token 的提示):
| 块大小 | 块数 | logits 最大差 | 之后 40 个 token | KV 读写 / 一次做完 |
|---|---|---|---|---|
| 192(不切) | 1 | 0 | 相同 | 1.0× |
| 64 | 3 | 相同 | 2.0× | |
| 16 | 12 | 相同 | 6.5× | |
| 1 | 192 | 相同 | 96.5× |
运算量几乎不变(多出来的一点恰好是每块最后一个位置的 lm_head——tiny_llm 每次 forward 至少算一个位置的 logits,真实系统中间块不算)。代价在 KV 的读取:第 块要把前面 块的 KV 都读一遍,切成 块共读约 块的量——这正是 DistServe 批评分块 prefill 时说的 次 KV 加载。Sarathi-Serve 的回应是:即使块很小,prefill 的注意力仍然是 compute-bound 的(每读一块 KV,块里所有 query 都用它),多读的 KV 大多被计算掩盖。
2.4 不停顿的批
有了分块 prefill,Sarathi-Serve 的调度器(论文 Algorithm 3)这样组每一个批:
- 根据 TBT 的 SLO 定一个 token 预算 (每步最多处理多少个 token);
- 先把所有正在 decode 的请求放进批(每个 1 个 token);
- 再放之前没做完的 prefill,每个最多用掉剩下的预算;
- 最后才准入新请求,同样按剩下的预算切块。
decode 永远排在 prefill 前面,prefill 只用”剩下的”预算,所以每一步的时间都有上界、decode 永远不会被停顿——这就是”不停顿”(stall-free)的含义。同时每一步都尽量填满预算,利用了 decode 批里的空闲算力,所以吞吐也高。论文图 7 用 A、B、C、D 四个请求的时间线对比了 vLLM、Orca、FasterTransformer 和 Sarathi-Serve 的调度。
pd_engine.run_colocated 实现了这三种共置调度(prefill 优先、混合批、分块)。自检验证了”预算无限大的分块 = 混合批”。
2.5 token 预算的取舍
预算是一个旋钮:
- 预算小:每步时间短,TBT 低;但 prefill 被切得更碎、每步分到的 token 更少,TTFT 变长;块太小还会让 GPU 用不满。
- 预算大:prefill 快,TTFT 低;但每步更长,TBT 高,接近混合批。
代码 02 演示 3 在稳定的负载下扫了预算(Llama-3-8B,H100,提示均值 755、输出均值 200,30 请求/秒,600 个请求):
| 调度方式 | TTFT P50 | TTFT P99 | TBT P99 | TBT 最大 | 全部完成用时 |
|---|---|---|---|---|---|
| prefill 优先(vLLM) | 26.69 ms | 161.07 ms | 129.94 ms | 366.83 ms | 29.43 s |
| 混合批(Orca) | 28.34 ms | 167.61 ms | 70.33 ms | 156.84 ms | 27.21 s |
| 分块,预算 128 | 5.96 s | 12.02 s | 7.80 ms | 7.91 ms | 38.23 s |
| 分块,预算 256 | 123.19 ms | 465.98 ms | 8.93 ms | 8.99 ms | 26.55 s |
| 分块,预算 512 | 33.67 ms | 177.54 ms | 11.91 ms | 13.18 ms | 26.59 s |
| 分块,预算 2048 | 28.12 ms | 150.80 ms | 43.60 ms | 49.46 ms | 27.18 s |
预算 512 时 TTFT 与 prefill 优先相近(P99 178 ms 对 161 ms),TBT 的 P99 却从 130 ms 降到 12 ms;同样的 600 个请求还更早做完(26.59 s 对 29.43 s)——prefill 塞进了 decode 步的空闲算力里,而 prefill 优先的每个纯 prefill 步里所有 decode 都暂停。这正是 Sarathi-Serve 说”不停顿的批既降延迟又提吞吐”的原因。
预算太小会出事:预算 128 时,同时在 decode 的请求就有上百个,每步的预算几乎被 decode 用光,prefill 挤不进来,队列越排越长,TTFT 的中位数涨到 6 秒。预算必须明显大于同时 decode 的请求数。
还有两个真实 GPU 上的因素纸上 GPU 没有建模。一是分块的开销:代码 01 演示 3 里,切成 512 个 token 一块只多 3%–4% 的时间、256 一块多 8%–9%(主要是每步的固定开销);Sarathi-Serve 在 Yi-34B 上测得块 512 时最多约 25%、块 2048 时几乎可以忽略。二是tile 量化:矩阵维度不是 tile 大小的整数倍时,有的线程块做了多余的计算——块大小 257 比 256 慢 32%。论文的做法是用 Vidur 模拟器一次性地为部署选预算:严格的 SLO 用 512,宽松的用 2048。
2.6 流水线并行的气泡
第 6 讲 §2.5 提到,流水线并行要靠多个微批填满各级,而 LLM 推理的微批里 prefill 和 decode 的比例各不相同,各级的时间不均衡,产生气泡。论文举例:Falcon-180B 上一个 4K 的 prompt 要约 1150 ms,batch 32 的纯 decode 约 200 ms,交错起来就有约 950 ms 的气泡。分块 prefill 让每个微批的计算量接近(都是”decode + 一块 prefill”,总 token 数接近预算),气泡少得多。这让跨节点的流水线并行变得可行(跨节点的张量并行太慢,第 6 讲代码 03 演示 4)。
2.7 评测与论文卡片
论文在 Mistral-7B(1 块 A100)、Yi-34B(2 块 A100,TP-2)、LLaMA2-70B(8 块 A40,TP4-PP2)、Falcon-180B(两台机器各 4 块 A100,TP4-PP2,100 Gbps 以太网)上,用 openchat_sharegpt4 和 arxiv_summarization 两个数据集评测(论文报告):
- 严格的 SLO(P99 TBT = 无干扰 decode 迭代时间的 5 倍)下,容量相对 vLLM:Mistral-7B 最多 2.6 倍、Yi-34B 最多 3.7 倍;相对 Orca 最多 4.0 倍。用流水线并行的大模型收益更大:LLaMA2-70B 相对 Orca 6.3 倍、相对 vLLM 4.3 倍;Falcon-180B 端到端最多 5.6 倍。
- 吞吐–延迟的取舍:vLLM 在 batch 上限 32、64、128 下的容量几乎一样——严格的 TBT 限制下,PagedAttention 带来的大批根本用不上,被 prefill 优先的调度卡住了。Sarathi-Serve 在严格 SLO(Mistral-7B,100 ms)下用预算 512 比 vLLM 高 3.5 倍;宽松 SLO(Yi-34B,1 s)下用预算 2048 高 1.65 倍。
- 流水线并行:Falcon-180B 上跨节点 TP-8 的 decode 延迟是”TP-4 + 跨节点 PP-2”的 2 倍以上;Sarathi-Serve 相对 vLLM 的混合并行配置,在严格和宽松的 SLO 下容量分别高 3.6 倍和 1.48 倍。
- 消融(Yi-34B,预算 1024,openchat_sharegpt4):只用混合批时中位 TTFT 0.53 s、P99 TBT 0.68 s;只用分块 prefill 时 1.04 s、0.17 s;两者结合 0.76 s、0.14 s。两个技术缺一不可:只分块不混合,TTFT 变长;只混合不分块,停顿还在。
| 项目 | 内容 |
|---|---|
| 论文 | Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve,Agrawal 等,OSDI 2024(Microsoft Research India、Georgia Tech) |
| 一句话 | 把长 prefill 切成小块,和所有 decode 拼进一个有 token 预算的批,decode 永不停顿,同时利用 decode 批的空闲算力 |
| 问题 | 迭代级调度要么优先 prefill(吞吐高、TBT 尾部差,有生成停顿),要么优先 decode(TBT 稳、吞吐差) |
| 现有方案的缺陷 | vLLM / Orca 的 prefill 优先造成持续数秒的停顿;减小批大小牺牲吞吐;流水线并行下微批不均衡产生气泡 |
| 关键洞察 | decode 批是 memory-bound 的,几百个顺带的 prefill token 几乎免费;中等长度的块已足以让 GPU 饱和 |
| 机制 | 分块 prefill;不停顿的批(decode → 未完成的 prefill → 新请求,受 token 预算约束);按 SLO 用模拟器选预算;均匀的微批减少流水线气泡 |
| 评测 | 容量相对 vLLM 最多 2.6×(Mistral-7B)、3.7×(Yi-34B)、5.6×(Falcon-180B,流水线并行)(论文报告) |
| 局限与假设 | 分块有开销(小块时 GPU 用不满、KV 重复读取);预算要按 SLO 和硬件调;prefill 仍和 decode 共享资源与并行方式,TTFT 比”独占 GPU 的 prefill”慢一些;论文没有和解耦方案定量比较 |
| 与本课的关系 | 今天的推理引擎(vLLM、SGLang 等)普遍默认开启分块 prefill;它是”共置”路线的代表,§5 与解耦对比;第 2 讲的迭代级调度在这里进化为带预算的调度 |
3. DistServe:把 prefill 和 decode 拆开 · DistServe
3.1 动机:两个阶段分开跑,每块 GPU 能干更多活
DistServe 的出发点是论文图 1:在一块 A100 上服务 13B 模型(输入 512、输出 64),共置的系统在 SLO 下只能承受约 1.6 req/s;如果一块 GPU 只做 prefill,能承受 5.6 req/s;只做 decode,10 req/s。那么 2 块做 prefill、1 块做 decode,整体就能承受 10 req/s,每块 GPU 3.3 req/s——是共置的 2.1 倍(论文报告)。差距来自两个阶段的相互干扰和资源耦合(§1)。
于是 DistServe 提出解耦(disaggregation):prefill 实例只做 prefill,算完把 KV cache 传给 decode 实例,decode 实例只做 decode。每类实例各自选择 GPU 数量和并行方式。这消除了干扰,代价是要在实例之间传 KV、而且 prefill 实例的显存不再用来存 KV(§5 会看到这个代价)。
代码 03 演示 1 在纸上 GPU 上复现了这个实验(OPT-13B 规模的多头注意力模型,A100,TTFT 的 SLO 固定为 0.25 s,扫 TPOT 的 SLO):
| TPOT SLO | prefill 优先 | 混合批 | 分块 512 | 只做 decode | 组合 | 组合 / 最好的共置 |
|---|---|---|---|---|---|---|
| 0.04 s | 5.81 | 7.46 | 8.36 | 35.05 | 8.33 | 1.00× |
| 0.03 s | 3.94 | 5.49 | 5.90 | 24.95 | 7.59 | 1.29× |
| 0.025 s | 2.99 | 3.69 | 3.81 | 17.05 | 6.66 | 1.75× |
| 0.02 s | 1.16 | 1.59 | 1.68 | 7.34 | 4.39 | 2.61× |
(只做 prefill 时 = 10.92 req/s;“组合”假设 GPU 可以按任意比例分给两个阶段,是解耦的理想上限。)
TPOT 的 SLO 是 0.04 s 时,按比例组合的解耦与最好的共置一样,没有收益:每个请求的 TPOT 是 63 个间隔的平均值,偶尔几步被 prefill 拖慢会被平均掉,干扰是负担得起的。SLO 越严格,共置掉得越快(8.36 → 1.68),解耦的优势从 1.00× 涨到 2.61×。我们的纸上 GPU 在绝对容量上比 DistServe 的实测乐观约 2 倍(只做 prefill 时 10.9 对 5.6 req/s),也没有运行时开销,所以同样的 SLO 在我们这里显得更宽松。结论取决于 SLO 落在步时间的什么位置:SLO 越接近一个 decode 步的时间,干扰越致命,解耦越划算。
3.2 prefill 实例:批多大、用什么并行
批处理。 prefill 是 compute-bound 的。论文图 3:13B 模型上一个 512 token 的序列就能让 A100 饱和;饱和之后再加请求,只会按比例延长整批的时间、推迟所有人。所以 DistServe 先离线测出让 GPU 饱和的长度 ,只在请求比 短时才把几个请求凑成一批(凑到总长接近 ),长请求单独做。prefill 实例的批通常很小。
并行方式:排队论的视角。 两块 GPU 做 prefill,用张量并行(算子内)还是流水线并行(算子间)?解耦之后,一个只做 prefill、请求长度相同、FCFS、不批处理的实例就像一个 M/D/1 队列(泊松到达、确定的服务时间、一个服务台;前置讲 P4)。设单卡执行时间为 、到达率为 ,平均 TTFT 为(论文公式 1)
第一项是执行时间,第二项是排队时间。2 路流水线:单个请求的延迟仍约为 ,但最慢的一级只要 ,处理能力翻倍(公式 2):
2 路张量并行:执行时间缩短到 (,因为有通信),处理能力只提高 倍(公式 3):
代码 04 演示 3 代入 = 0.1 s:
| 到达率 | 流水线 | 张量 =1.5 | 张量 =1.7 | 张量 =1.9 | 单卡 |
|---|---|---|---|---|---|
| 2 req/s | 102.78 ms | 71.79 ms | 62.75 ms | 55.73 ms | 112.50 ms |
| 8 req/s | 116.67 ms | 104.76 ms | 84.97 ms | 71.77 ms | 300.00 ms |
| 11 req/s | 130.56 ms | 158.33 ms | 112.75 ms | 88.82 ms | 不稳定 |
| 13 req/s | 146.43 ms | 283.33 ms | 154.41 ms | 109.65 ms | 不稳定 |
到达率低时排队可以忽略,张量并行缩短了执行时间,更好;到达率高时排队成为主角,流水线的处理能力更高,反超张量并行。交叉点取决于 : = 1.5 时在 8–11 req/s 之间, = 1.7 时在 11–13 之间, = 1.9 时到 13 req/s 张量并行仍然更好。用 Lindley 递推模拟 20 万个请求,结果与公式吻合( = 8 时流水线 116.62 ms 对公式 116.67 ms)。DistServe 据此说:TTFT 的 SLO 越严、到达率越低,越该用算子内并行; 越小(通信越贵),张量并行的优势区越窄。
3.3 decode 实例:批越大越好,受限于显存
decode 实例从 prefill 实例接收 KV 和第一个 token,之后一个一个生成。decode 严重受限于带宽,批处理是避免 GPU 闲置的关键。共置时增大 decode 批与满足延迟目标相冲突;解耦之后,可以让多个 prefill 实例喂一个 decode 实例,在专门的 GPU 上攒出大批,而不牺牲 TPOT。
decode 批的上限常常是显存容量(所有活跃请求的 KV 都要放下)。用模型并行把权重分到更多卡上、或者用 PagedAttention、GQA 节省 KV,可以让批继续增大。论文图 5:批很大时,张量并行降低延迟但收益递减,流水线并行几乎线性地提高吞吐;所以 TPOT 的 SLO 严格时需要张量并行,此外用流水线并行扩展吞吐。模型一块卡放得下时,复制实例也是好选择。
3.4 实际问题:长度不一、KV 传输
长度不一。 真实请求的提示长度各不相同,用流水线并行的 prefill 实例会因为各批时间不同而产生气泡(和 §2.6 是同一个问题)。DistServe 用”按 凑批”让各批的时间接近。
KV 传输。 一个请求的 KV 可能很大。代码 04 演示 1:
| 模型 | 提示 | KV 大小 | NVLink | InfiniBand | 以太网 25 Gb/s | prefill 时间 |
|---|---|---|---|---|---|---|
| OPT-66B 规模(MHA) | 512 | 1.21 GB | 5.04 ms | 30.20 ms | 483.20 ms | 86.41 ms(TP=4) |
| OPT-66B 规模 | 4096 | 9.66 GB | 40.27 ms | 241.60 ms | 3.87 s | 696.78 ms |
| Llama-3-8B(GQA) | 512 | 67.11 MB | 0.19 ms | 1.68 ms | 26.86 ms | 10.92 ms |
| Llama-3-70B(GQA) | 512 | 167.77 MB | 0.47 ms | 4.20 ms | 67.13 ms | 33.46 ms(TP=4) |
OPT-66B 一个 512 token 的请求有 1.21 GB(1.12 GiB,DistServe 说约 1.13 GB),每秒 10 个请求就需要约 97 Gb/s——跨节点 25 Gb/s 的以太网根本不够,单个请求要传近半秒。在 NVLink 上,传输时间只有 prefill 时间的零头。GQA 让 KV 小了一个数量级:DistServe 特意用多头注意力的 OPT 给传输”施加压力”,今天的 GQA / MLA 模型让解耦容易得多。
3.5 放置算法
给定模型、负载(输入 / 输出长度分布、到达率)、SLO 和集群,DistServe 要决定:prefill 和 decode 实例各用什么并行方式(张量并行度、流水线并行度)、各部署几个、放在哪些机器上。这叫一个放置(placement),目标是每 GPU 的 goodput 最大。
高节点亲和的集群(节点之间有高速的 InfiniBand,KV 传输不是问题)用 Algorithm 1:对 prefill 实例枚举所有可行的(节点内张量并行度, 流水线并行度),用模拟器 simu_prefill 二分查找每种配置能承受的最大到达率,取每 GPU goodput 最高的;对 decode 实例同样用 simu_decode;最后按目标流量复制:、。复杂度 ( 是每个实例的节点数上限, 是每节点的 GPU 数),论文最大的设置下不到 1.3 分钟。
模拟器是关键:SLO 达成率没有解析公式,在真实集群上逐个测又太慢。DistServe 从历史 trace 拟合输入 / 输出长度的分布、重新采样出负载,用一个 FLOPs 和显存访问的延迟模型(附录 A,系数用实测插值)估计每一步的时间,再离散事件地模拟——这和 FlexFlow(第 6 讲)“用模拟器代替实测”是同一个思路,它依赖的也是神经网络执行时间高度可预测这个事实。论文报告模拟器与真实系统的 SLO 达成率误差在 2% 以内。
低节点亲和的集群(节点之间带宽低,例如论文的测试集群跨节点只有 25 Gbps)用 Algorithm 2:KV 传输只发生在 prefill 和 decode 的对应层之间,所以把两个实例按流水线并行切成段,让同一段(同样的那几层)的 prefill 部分和 decode 部分放在同一台机器上,KV 只走节点内的 NVLink。每台机器内部枚举可行的并行配置,用模拟器选最好的,再复制。
代码 04 演示 4 做了 Algorithm 1 的简化版(OPT-66B 规模,A100,对话负载,SLO 2.5 s / 0.15 s;只枚举张量并行度):
| 阶段 / 配置 | 单实例最大到达率 | 每 GPU |
|---|---|---|
| prefill TP=2 / 4 / 8 | 3.43 / 7.71 / 15.10 | 1.715 / 1.927 / 1.887 |
| decode TP=2 / 4 / 8 | 0.25 / 10.23 / 86.14 | 0.126 / 2.559 / 10.767 |
| 共置 TP=8,分块 512 | 11.37 | 1.421 |
prefill 的每 GPU goodput 在 TP=4 时最高——DistServe 为 OPT-66B 对话负载选的也是 prefill TP=4。decode 完全不同:TP=2 时每块 A100 要放 66 GB 权重,只剩约 6 GB 给 KV,一个实例只放得下几个请求,批做不大,几乎没法用;TP=8 时 KV 的空间大得多,每 GPU 的 goodput 达到 10.8。decode 要更多 GPU 是为了显存容量,不是为了算力——Dynamo 博客也说 decode 用高张量并行”改善内存操作”。(DistServe 为 decode 选的是 TP=2 × PP=2,也是 4 块卡;我们没有建模流水线并行。)
按流量复制实例时,实例是整块分配的:
| 目标流量 | 解耦:GPU 数 | 共置:GPU 数 |
|---|---|---|
| 2 req/s | 1 × TP4 + 1 × TP8 = 12 | 1 × TP8 = 8 |
| 10 req/s | 2 × TP4 + 1 × TP8 = 16 | 1 × TP8 = 8 |
| 50 req/s | 7 × TP4 + 1 × TP8 = 36 | 5 × TP8 = 40 |
| 100 req/s | 13 × TP4 + 2 × TP8 = 68 | 9 × TP8 = 72 |
小流量时取整的浪费很明显,解耦反而多用 GPU;流量大时浪费被摊薄,解耦才开始省 GPU。解耦的收益在规模上才显现。DistServe 在讨论里也承认,只有一两块 GPU 的资源受限场景,设计空间太小,非解耦的架构可能更合适。
3.6 在线调度
DistServe 的运行时(论文 §4.3)很简单:所有请求到一个中心控制器,FCFS;分给队列最短的 prefill 实例,prefill 完再分给负载最轻的 decode 实例。几个针对真实负载的改进:
- 减少流水线气泡:prefill 批按 凑,decode 批以 为最大批大小,让各批的时间接近。
- 应对突发:突发的请求会让大量 KV 涌向 decode 实例、撑爆显存。DistServe 用拉取(pull)而不是推送:decode 实例有空间时才去 prefill 实例那里取 KV,prefill 实例的显存当缓冲队列用。两类实例各按自己的节奏运行,不需要复杂的协调。
pd_engine.run_disaggregated也是这样:decode 实例显存不够时,KV 在 prefill 那边等。 - 重新规划:负载模式变化时(平均输入 / 输出长度、到达率),重新运行放置算法;算法几秒到几分钟、重新加载权重几分钟,远快于小时级的负载变化。
- 没做的事:抢占(FCFS 会有护航效应,长请求挡住短请求)和容错(一个 decode 实例故障可能拖垮喂给它的多个 prefill 实例)留作未来工作。
3.7 评测与论文卡片
论文在 4 台机器 × 8 块 A100-80GB(节点内 NVLink,跨节点 25 Gbps)上评测 OPT-13B / 66B / 175B,三种应用(论文报告):
| 应用 | 模型 | TTFT / TPOT 的 SLO | 数据集(平均输入 / 输出) |
|---|---|---|---|
| 对话 | OPT-13B / 66B / 175B | 0.25 s / 0.1 s;2.5 s / 0.15 s;4.0 s / 0.2 s | ShareGPT(755 / 200) |
| 代码补全 | OPT-66B | 0.125 s / 0.2 s | HumanEval(171 / 98) |
| 摘要 | OPT-66B | 15 s / 0.15 s | LongBench(1738 / 91) |
- 90% 达成率下,相对 vLLM:对话 2.0–4.6 倍的请求率、1.8–3.2 倍更严的 SLO;代码补全 5.7 倍、1.4 倍;摘要 4.3 倍、12.6 倍。相对 DeepSpeed-MII(支持分块 prefill):对话 1.6–7.4 倍;代码补全 1.6 倍;摘要 1.8 倍、2.6 倍更严的 SLO。99% 达成率下相对 vLLM 3–8 倍。
- 选出的放置并不显然:OPT-175B 对话负载,prefill 实例 inter-op 3 × intra-op 3,decode 实例 inter-op 3 × intra-op 4(附录表 3:OPT-13B 是 prefill TP2、decode TP1;OPT-66B 是 prefill TP4、decode TP2 × PP2)。
- KV 传输:OPT-175B 上不到总延迟的 0.1%,95% 的请求传输时间不到 30 ms——低节点亲和的放置让传输只走 NVLink。
- 消融:给 vLLM 加上并行方式的搜索(vLLM++)没有任何提升,说明收益来自解耦本身。
| 项目 | 内容 |
|---|---|
| 论文 | DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving,Zhong、Liu、Chen 等,OSDI 2024(北京大学、UC San Diego 等) |
| 一句话 | 把 prefill 和 decode 放到不同的 GPU 上,用模拟器为每个阶段搜索资源和并行方式,最大化每 GPU 的 goodput |
| 问题 | 共置导致两个阶段相互干扰、资源和并行方式耦合,要满足 TTFT 和 TPOT 两个 SLO 就得过度配置 |
| 现有方案的缺陷 | 连续批处理有干扰;分块 prefill 只是缓解干扰,还带来额外开销和块大小的两难;优先级调度顾此失彼 |
| 关键洞察 | 两个阶段的计算特性、延迟目标和并行偏好不同;现代集群的带宽让 KV 传输可以忽略;可以把放置当作搜索问题,用模拟器求解 |
| 机制 | prefill / decode 实例;按 凑批;M/D/1 分析;高 / 低节点亲和的放置算法(模拟器 + 二分查找 + 复制);同段同机放置;拉取式 KV 传输;周期性重新规划 |
| 评测 | 相对 vLLM 最多 7.4 倍的请求率或 12.6 倍更严的 SLO;KV 传输 < 0.1% 的延迟(论文报告) |
| 局限与假设 | 需要足够的规模和带宽;prefill 实例的显存不放 KV;放置针对一种负载,负载变化要重新规划;没有抢占和容错;评测用多头注意力的 OPT 与 2048 的上下文上限 |
| 与本课的关系 | 解耦路线的代表;它的模拟器 + 搜索延续了 FlexFlow(第 6 讲);KV 在机器之间的流动引出第 9 讲(Mooncake 以 KV 为中心的解耦架构);Dynamo 把它做成了生产框架(§6) |
4. Splitwise:阶段拆分与异构硬件 · Splitwise
Splitwise(ISCA 2024,Microsoft)和 DistServe 几乎同时提出了”把两个阶段放到不同机器上”,但它的视角是集群设计:用什么硬件、配多少台、多大的功耗和成本。
4.1 用生产 trace 刻画两个阶段
论文用 Azure 的两个 LLM 推理服务(代码补全和对话)的生产 trace,在 DGX-A100 和 DGX-H100 上刻画了两个阶段(论文报告,几条主要的观察):
- 不同服务的长度分布差别很大:代码补全的提示中位数 1500 个 token、输出只有 13 个;对话的提示中位数 1020、输出 129(接近双峰)。
- 混合的连续批处理大部分时间只在处理很少的 token:对话负载 60%–70% 的时间里活跃 token 不超过 20 个。
- 大多数请求的端到端时间主要花在 token 阶段:BLOOM-176B 上,1500 个 token 的 prompt 阶段和只生成 6 个 token 的时间相当。
- prompt 阶段的批应该限制(超过 2048 个 token 后吞吐反而下降);token 阶段的批越大越好,直到显存耗尽。
- prompt 阶段对功耗上限很敏感,token 阶段几乎不敏感:功耗限到一半(700 W → 350 W),token 阶段的延迟几乎不变。
- token 阶段不需要最新 GPU 的算力:从 A100 换到 H100,TTFT 降到约 0.5 倍,TBT 只降到 0.7 倍;按成本和能耗算,A100 不比 H100 差。
4.2 设计
Splitwise 维护三个机器池:prompt 池、token 池和一个按需伸缩的混合池。集群级调度器用”最短队列优先”(JSQ,队列长度按待处理的 token 数算)为每个请求同时分配一台 prompt 机器和一台 token 机器;排队太长时从混合池借机器,混合池满了再把对方池的机器临时拉进来做混合批处理。机器级调度器:prompt 机器 FCFS,批的总 token 数限制在 2048;token 机器 FCFS、尽量把批做大,显存将满时开始排队。
KV 传输用 InfiniBand,并做了一个关键优化:逐层传输——prompt 机器每算完一层,就异步地把这一层的 KV 发出去,和后面层的计算重叠;小提示的 KV 小,用串行传输,大提示用逐层传输。代码 04 演示 2 估算了这个优化的效果(Llama-3-70B,TP=4,H100,经 InfiniBand):
| 提示长度 | prefill 时间 | 串行:额外等待 | 逐层:额外等待 |
|---|---|---|---|
| 512 | 33.46 ms | 4.20 ms(13%) | 52.49 µs(0.2%) |
| 8192 | 528.66 ms | 67.11 ms(13%) | 838.92 µs(0.2%) |
逐层传输时,prefill 结束时只剩最后一层(1/80)的 KV 没传完。论文实测:可见的传输开销小于 prompt 计算时间的 7%,逐层传输时不重叠的部分恒定在约 8 ms(A100)和 5 ms(H100);对第二个 token 的延迟只增加 16.5%(串行传输是 64%),对端到端延迟只增加 0.8%。
4.3 异构、限功耗的集群
Splitwise 设计了四种集群:Splitwise-AA(两类机器都是 DGX-A100)、Splitwise-HH(都是 DGX-H100)、Splitwise-HA(prompt 用 H100、token 用 A100)、Splitwise-HHcap(都是 H100,但 token 机器限功耗到 70%)。给定 trace、性能模型、SLO 和目标(吞吐、成本或功耗),用事件驱动的集群模拟器搜索两类机器的数量。例如代码补全负载在 70 RPS 的峰值下,成本最优的 Splitwise-HH 是 27 台 prompt 机器 + 3 台 token 机器;同样的功耗下,代码补全需要更多 prompt 机器(35P, 5T),对话需要更多 token 机器(25P, 15T)。
论文报告:相对现有设计,Splitwise 集群能以 20% 更低的成本获得 1.4 倍的吞吐,或者在同样的功耗和成本下获得 2.35 倍的吞吐;同功耗下,Splitwise-HHcap 能以低 25% 的功耗达到与基线相同的吞吐。
代码 04 演示 5 用纸上 GPU 估算了”decode 放在 A100 上”是否划算(Llama-3-70B,每实例 TP=4,2 个 H100 prefill 实例,对话负载,价格取 Splitwise 表 I 的 DGX 每小时价格 ÷ 8:H100 4.75 美元、A100 2.20 美元):
| TPOT SLO | 1 个 decode(H100) | 1 个 decode(A100) | 2 个 decode(A100) |
|---|---|---|---|
| 0.035 s | 37.3 req/s,2,357 /美元·时 | 37.3 req/s,2,871 /美元·时 | 37.3 req/s,2,416 /美元·时 |
| 0.03 s | 37.3,2,357 | 24.6,1,894 | 37.3,2,416 |
| 0.025 s | 37.3,2,357 | 3.7,287 | 7.9,509 |
TPOT 的 SLO 宽松时,一个 A100 decode 实例就够,每美元能服务的请求多约 22%;SLO 收紧到 0.03 s,一个 A100 跟不上(decode 慢了 1.64 倍,只能用小批),要两个 A100 实例才追平,优势几乎消失;到 0.025 s,A100 几乎无法满足 TPOT。异构部署是否划算,取决于 TPOT 的 SLO 离旧卡的 decode 速度有多近。
| 项目 | 内容 |
|---|---|
| 论文 | Splitwise: Efficient Generative LLM Inference Using Phase Splitting,Patel 等,ISCA 2024(University of Washington、Microsoft) |
| 一句话 | 把 prompt 阶段和 token 阶段放到不同的机器池上,并为每个阶段选最合适(最省钱、最省电)的硬件 |
| 问题 | 两个阶段的算力、显存、功耗特性截然不同,同一种昂贵的新 GPU 上混跑既浪费又不稳定 |
| 关键洞察 | token 阶段不需要最新 GPU 的算力、对功耗上限不敏感;KV 可以在机器之间快速传输 |
| 机制 | prompt / token / 混合三个池;JSQ 路由;逐层 KV 传输;AA / HH / HA / HHcap 集群设计 + 模拟器搜索机器数量 |
| 评测 | 同成本吞吐 1.4 倍(成本低 20%)或同功耗同成本吞吐 2.35 倍;KV 传输对端到端只增加 0.8%(论文报告) |
| 局限与假设 | 依赖高带宽的后端网络;集群级调度器可能成为瓶颈;故障时请求从头重来 |
| 与本课的关系 | 把解耦从”调度问题”扩展为”集群设计问题”(硬件、功耗、成本);逐层 KV 传输成为后续系统的标准做法 |
5. 共置、分块还是解耦?· Which one wins?
两篇论文各自展示了对”原来的系统”的大幅改进,但 Sarathi-Serve 把和解耦的定量比较留给了未来的工作,DistServe 对比的分块 prefill 实现是 DeepSpeed-MII。我们在同一个模拟器里把它们放在一起比较(代码 03)。
5.1 一个方法上的坑:有限的实验与宽松的 SLO
第一次跑摘要负载时,3P2D 的解耦配置得到了每 GPU 10.09 req/s 的 goodput,高得可疑。检查发现:在那个到达率下系统已经过载,队列越排越长,但实验只有 400 个请求,在 TTFT 超过 15 s(摘要的 TTFT SLO 很宽松)之前就全部做完了,90% 的请求”达标”。真实服务的请求源源不断,过载迟早会超标。于是我们在 goodput 的二分查找里加了 Sarathi-Serve 的可持续条件(中位 TTFT ≤ 2 s)。它只影响摘要负载(其他负载的 TTFT SLO 本来就远小于 2 s)。这是一个值得记住的教训:用有限长度的实验测”最大可承受负载”时,要检查系统是否真的稳定(第 2 讲的 steady_throughput 讨论过同类问题)。
5.2 三种应用
代码 03 演示 2 仿照 DistServe 的评测(OPT-13B 规模的多头注意力模型,A100,三种负载与 DistServe 表 1 的 SLO),比较每 GPU 的 goodput(req/s/GPU);解耦搜索了 x ≤ 3、y ≤ 2 的 xPyD 配置:
| 应用 | prefill 优先 | 混合批 | 分块 512 | 分块 2048 | 最好的解耦 | 解耦 / 最好的共置 |
|---|---|---|---|---|---|---|
| 对话(0.25 s / 0.1 s) | 3.63 | 3.66 | 3.27 | 3.81 | 4.07(3P2D) | 1.07× |
| 代码补全(0.125 s / 0.2 s) | 25.77 | 24.74 | 23.38 | 24.74 | 31.51(3P1D) | 1.22× |
| 摘要(15 s / 0.15 s) | 3.60 | 3.90 | 5.31 | 4.06 | 4.14(3P2D) | 0.78× |
- 对话和代码补全上,最好的解耦比最好的共置高 1.07×、1.22×(只和 prefill 优先比是 1.12×、1.22×)。方向与 DistServe 一致,幅度比论文报告的(相对 vLLM 2.0–4.6×、5.7×)小得多——我们的纸上 GPU 没有运行时开销、比真实系统乐观,而且共置一侧包含了分块 prefill。
- 比例比”解耦”本身更重要:代码补全(提示短、输出短、TTFT 严格)需要更多 prefill 实例,3P1D 最好;对话是 3P2D;比例选错(例如对话的 1P2D 只有 1.65)比任何共置调度都差。
- 摘要上解耦反而吃亏。OPT-13B 是多头注意力,每个 token 的 KV 有 800 KB,一块 A100 放完权重后只能放约 5.6 万个 token;摘要请求平均要预留约 1600 个 token,一个 decode 实例只装得下三十几个请求,decode 批做不大,需要很多 decode GPU;而 prefill GPU 的显存却闲着没放 KV。共置的分块 prefill 让每块 GPU 的显存都用来放 KV、空闲的算力都用来做 prefill。这正是 Sarathi-Serve 对解耦的批评之一。
5.3 SLO 的松紧
代码 03 演示 3 把对话应用的两个 SLO 同时乘以 :
| SLO | 最好的共置 | 最好的解耦 | 解耦 / 共置 | |
|---|---|---|---|---|
| 0.75 | 0.188 s / 0.075 s | 0.89(prefill 优先) | 2.92(3P2D) | 3.30× |
| 1.0 | 0.25 s / 0.1 s | 3.81(分块 2048) | 4.07(3P2D) | 1.07× |
| 1.5 | 0.375 s / 0.15 s | 4.70(分块 512) | 5.45(3P2D) | 1.16× |
| 2.0 | 0.5 s / 0.2 s | 4.82(分块 512) | 5.96(3P2D) | 1.24× |
SLO 收紧到 0.75 倍时,共置几乎撑不住,解耦高 3.3 倍:TTFT 严格到接近一个长提示的 prefill 时间时,共置 GPU 上的任何排队都会超标,而 3 个专门的 prefill 实例可以分担。SLO 是原值和放宽时,差距只有 1.07–1.24 倍,而且不单调。另一个有意思的现象是:最好的共置调度方式本身也随 SLO 变化——TTFT 很严时 prefill 优先最好,TPOT 是瓶颈时分块最好。
5.4 结论:看 SLO、负载和规模
把这些放在一起:
| 更适合解耦 | 更适合共置 + 分块 prefill |
|---|---|
| SLO 严格(尤其 TPOT 接近一个 decode 步的时间) | SLO 宽松、或吞吐优先的离线负载 |
| 规模大(取整的浪费被摊薄)、有高带宽互连 | 只有少数几块 GPU |
| KV 小(GQA / MLA),传输便宜 | KV 大、显存紧张(prefill 实例的显存不能浪费) |
| prefill 和 decode 想要不同的并行方式或硬件 | 负载的输入输出比例经常变化(共置不用选比例) |
两篇论文自己也承认对方的适用场景:DistServe 说吞吐优先或资源受限时分块 prefill 可能更合适;Sarathi-Serve 说解耦能以最高效率执行 prefill(TTFT 更好),但需要高带宽互连、prefill 实例的显存也没被利用。两条路线并不互斥:解耦部署的 decode 实例内部仍然用连续批处理,prefill 实例也可以分块;生产框架(§6)往往同时支持两者,按负载选择。
6. NVIDIA Dynamo:解耦的生产框架 · NVIDIA Dynamo
6.1 背景
Dynamo 是 NVIDIA 在 2025 年 3 月(GTC 2025)开源的推理服务框架,定位是 Triton Inference Server 在生成式 AI 时代的继任者,面向大规模、多节点的分布式部署。它不替代推理引擎,而是在 TensorRT-LLM、vLLM、SGLang、PyTorch 这些后端之上做编排。博客列出的关键能力:解耦的 prefill / decode、根据需求动态调度 GPU、LLM 感知的请求路由(避免重算 KV)、GPU 之间的异步高速数据传输、KV cache 在多级存储之间的卸载。
博客报告的两个结果(论文报告式的数字,来自博客):在 GB200 NVL72 上服务 DeepSeek-R1,用解耦把服务的请求数提高到最多 30 倍(TensorRT-LLM、FP4、输入 / 输出 32K / 8K;不解耦时 TEP16·PP4·DP4,解耦时 prefill 用 EP4·DP16、decode 用 EP64·DP3,标注为预测性能);在 Hopper 上服务 Llama 70B,吞吐提高一倍以上(vLLM、FP8、输入 / 输出 3K / 50;不解耦时 TP8·DP2,解耦时 prefill 用 TP2·DP4、decode 用 TP8)。注意第二个例子:prefill 用低张量并行(减少通信)、decode 用高张量并行(改善内存操作)——和我们在 §3.5 的放置实验里看到的”decode 要更多 GPU 是为了显存”一致。
6.2 四个组件
- Planner:解耦并不总是最好的。博客举的例子:长输入、短输出的摘要请求激增时,prefill GPU 成为瓶颈,decode GPU 却闲着;这时让 decode GPU 也做 prefill(回到共置)或者把它们转去做 prefill 更好。Planner 持续监控 GPU 的容量指标,结合 TTFT、ITL 的 SLO,决定新请求走解耦还是共置、要不要给某个阶段加 GPU——要考虑 KV 的传输时间、各 GPU 的排队时间、两种方式的预估处理时间。
- Smart Router:对请求做哈希,存进一棵基数树,跟踪整个集群里哪些 GPU 上已经有哪些 KV(第 3 讲 SGLang 的 RadixAttention 在集群层面的版本);新请求到来时计算它和各 GPU 上已有 KV 的重叠分数,结合负载分布路由,减少 KV 的重算。(第 11 讲的 Preble 研究的就是这个问题。)
- 分布式 KV Cache Manager:把较老或较少访问的 KV 块卸载到更便宜的存储(CPU 内存、本地 SSD、网络对象存储),需要时再取回;在 GPU、节点、集群三个层次上管理(第 9 讲的主题)。
- NIXL(NVIDIA Inference Transfer Library):一个面向推理的点对点通信库,用统一的异步 API 在 HBM、CPU 内存、SSD、网络存储之间搬数据,支持非阻塞、非连续的传输,底层自动选择 NVLink、InfiniBand、RoCE、以太网、GPUDirect Storage 等。它负责解耦部署里 prefill → decode 的 KV 传输。
6.3 为什么需要 Planner:负载在变
代码 04 演示 6 用 4 块 H100(Llama-3-8B,SLO 0.5 s / 0.02 s)比较了几种分配在三种负载下能承受的总流量(req/s):
| 负载 | 4 × 共置(分块 512) | 1P3D | 2P2D | 3P1D |
|---|---|---|---|---|
| 长输出(提示 300 / 输出 600) | 244.5 | 191.3 | 518.1 | 177.1 |
| 对话(755 / 200) | 188.3 | 64.1 | 141.8 | 232.1 |
| 长提示摘要(4000 / 50) | 30.0 | 8.0 | 22.4 | 37.0 |
输出长的负载最需要 decode 能力,2P2D 最好;对话和长提示的摘要更需要 prefill 能力,3P1D 最好。为一种负载调好的比例换到另一种负载上,可能只剩一半甚至更少的能力(长输出负载下 3P1D 只有 2P2D 的约三分之一)。共置的分块 prefill 从来不是最好,但也从来不是最差——它不需要选比例,负载变化时更”稳”。这就是 Dynamo 要有 Planner、DistServe 要周期性地重新规划、Splitwise 要有混合池的原因。
7. 之后的发展 · Since then
(这一节是我的补充,不是原课内容。)
- 分块 prefill 成为默认:主流推理引擎(如 vLLM 的 V1 引擎)把分块 prefill 作为默认的调度方式,token 预算成为常见的配置项。
- 解耦进入主流引擎和框架:vLLM、SGLang、TensorRT-LLM 都支持 prefill / decode 解耦;Dynamo 之外,还有 Mooncake(Kimi 的服务平台,以 KV cache 为中心的解耦架构,第 9 讲的必读论文)、llm-d 等。KV 传输库(NIXL、Mooncake Transfer Engine 等)成为独立的基础设施。
- 更细粒度的解耦:把注意力和 FFN(专家)也拆开放在不同的 GPU 上——MegaScale-Infer(第 8 讲)对 MoE 做”注意力–专家解耦”;还有工作把 decode 阶段的注意力(读 KV、memory-bound)卸载到便宜的设备上。
- 动态的资源分配:LoongServe(第 6 讲 §5.2)的弹性序列并行让同一组 GPU 随阶段改变并行度;还有工作让 GPU 在 prefill 和 decode 角色之间在线切换,或在同一块 GPU 内用 SM 划分的方式同时跑两个阶段,兼得共置的显存利用率和解耦的隔离性。
- 长上下文与推理模型:推理模型输出很长(几千到几万个 token),decode 占主导,解耦和”decode 实例的显存容量”变得更重要;超长输入则让 prefill 本身需要多卡并行(上下文并行)。
8. 小结 · Summary
- 共置时 prefill 和 decode 相互干扰:一个 4096 token 的 prefill 让同批 decode 慢 10–14 倍,造成生成停顿(代码 02);两个阶段还被迫共用资源和并行方式。TPOT 的平均值会掩盖停顿,P99 TBT 不会。
- Sarathi-Serve:decode 批里有免费的算力(顺带 256 个 prefill token 几乎不变慢,代码 01);分块 prefill 是无损的(代价是 KV 读取 ),不停顿的批先放 decode、再用剩余的 token 预算 做 prefill。预算 512 时 P99 TBT 从 130 ms 降到 12 ms、TTFT 相近、还更早做完;预算太小(小于同时 decode 的请求数)时 prefill 挤不进来(代码 02)。论文报告容量相对 vLLM 最多 2.6–5.6 倍。
- DistServe:把两个阶段放到不同的 GPU 上,用模拟器 + 二分查找为每个阶段选资源和并行方式,按流量复制,最大化每 GPU 的 goodput;prefill 实例的并行选择可以用 M/D/1 分析(低到达率偏好张量并行,高到达率偏好流水线,代码 04 演示 3);decode 实例要更多 GPU 常常是为了显存容量(代码 04 演示 4)。论文报告相对 vLLM 最多 7.4 倍的请求率。
- KV 传输:多头注意力的大模型一个请求就有 1 GB 以上,需要 NVLink 或高速 InfiniBand;GQA 小一个数量级;逐层传输(Splitwise)把额外等待从 prefill 时间的 13% 降到 0.2%(代码 04 演示 1–2)。
- Splitwise:token 阶段不需要新 GPU 的算力、对功耗上限不敏感,可以用更便宜或限功耗的硬件;我们的估算显示这是否划算取决于 TPOT 的 SLO(代码 04 演示 5)。
- 谁更好看条件:我们的模拟里,解耦相对最好的共置在 DistServe 的 SLO 下只高 1.07–1.22 倍,SLO 收紧时高到 3.3 倍,在 KV 大、显存紧的摘要负载上反而更差(0.78 倍);实例比例选错比不解耦还糟;小流量时整块分配让解耦多用 GPU(代码 03、04)。Dynamo 把解耦做成生产框架,并用 Planner 应对负载变化(不同负载的最优分配不同,代码 04 演示 6)。
9. 常见误区 · Common pitfalls
- “分块 prefill 会改变模型的输出。” 不会。因果掩码保证分块与一次做完数学上相同(代码 01 演示 1:生成的 token 完全相同)。它改变的只是 prefill 分几步做。
- “分块 prefill 没有代价。” 后面的块要重读前面块的 KV(),块太小时 GPU 用不满、还有 tile 量化;预算太小时 prefill 挤不进来,TTFT 爆炸(代码 02 演示 3)。
- “TPOT 达标就说明没有卡顿。” TPOT 是平均值;一次 90 ms 的停顿在 200 个间隔里只让平均值涨 0.4 ms。要看 P99 TBT 或最大 TBT(代码 02 演示 2)。
- “解耦总比共置好。” 取决于 SLO、负载、显存和规模。在我们的模拟里,SLO 宽松时收益很小,KV 大、显存紧时解耦反而更差,小流量时整块分配还会多用 GPU(§5、代码 04 演示 4)。
- “解耦之后,prefill 和 decode 各用一半 GPU 就行。” 比例必须按负载搜索:代码补全要 3P1D,长输出负载要 2P2D;比例选错比不解耦还糟(代码 03 演示 2、代码 04 演示 6)。
- “decode 实例算得少,用小的张量并行度就够了。” decode 常常受限于显存容量:OPT-66B 在 2 块 A100 上只剩几 GB 放 KV,批做不大;用更多 GPU 是为了显存(代码 04 演示 4)。
- “KV 传输一定是解耦的瓶颈。” 在 NVLink 或高速 InfiniBand 上、配合逐层传输,它通常可以忽略(DistServe 报告 < 0.1% 的延迟);但在普通以太网上、对多头注意力的大模型,它确实是瓶颈(代码 04 演示 1)。
- “旧 GPU 做 decode 一定更省钱。” 只在 TPOT 的 SLO 足够宽松时成立;SLO 一紧,旧卡要用更多实例甚至达不到(代码 04 演示 5)。
- “用几百个请求跑一下就能测出最大可承受的负载。” TTFT 的 SLO 宽松时,过载的系统可能在队列超标之前就跑完了;要加可持续条件或用足够长的实验(§5.1)。
10. 练习 · Exercises
练习 1、2、4 的参考答案里的数字是用
hw_model.py、pd_engine.py的函数实际算出来的。
练习 1(免费的算力)。Llama-3-8B、H100、32 个请求在 decode(上下文 1024)。用代码 01 演示 2 的数字:(a) 这一步最多能顺带多少个 prefill token 而不明显变慢?(b) 一个 4096 token 的提示,如果每步只顺带 256 个 token,要几步做完?decode 请求的 TBT 是多少?(c) 如果一步做完呢?
参考答案
(a) 256 个 token 时这一步几乎不变(7.70 → 7.72 ms),512 个时已经 compute-bound(11.64 ms,+51%);所以几百个 token 以内是”免费”的,拐点在 256 到 512 之间(约等于屋脊点对应的 token 数)。(b) 4096 / 256 = 16 步,每步约 7.72 ms,decode 的 TBT 几乎不受影响;这个提示的 prefill 用了约 16 × 7.7 ≈ 124 ms(比单独 prefill 的约 89 ms 慢,但没有拖慢任何人)。(c) 一步做完:这一步约 90 ms,32 个 decode 请求都要等,这就是生成停顿。
练习 2(分块的 KV 读取)。一个 个 token 的提示切成 个 token 一块(共 块)。(a) 第 块的注意力要读多少个 token 的 KV(含本块)?(b) 总共读多少?与一次做完相比是多少倍?(c) 用 tiny_llm 的数字核对:192 个 token、块大小 16 时,代码 01 演示 1 给出 6.5 倍。
参考答案
(a) 前面 块加本块: 个 token。(b) ;一次做完读 个,比值约 。(c) = 192、 = 16:,与代码 01 的 6.5× 一致(tiny_llm 的计数规则是”读已有的 + 写新的”,和这个公式相同)。
练习 3(预算的下限)。一个共置实例上同时有 300 个请求在 decode。token 预算设成 256 会怎样?设成 512 呢?预算至少要多大,才能保证每一步都能做一点 prefill?
参考答案(要点)
不停顿的批先放所有 decode,每个 1 个 token:300 个 decode 就占掉 300 个 token。预算 256 时 decode 已经超出预算(实现里 decode 照放,prefill 一个 token 也分不到),新请求永远进不来,TTFT 无限增长——代码 02 演示 3 里预算 128 就是这种情况。预算 512 时每步还剩 212 个 token 给 prefill。所以预算必须明显大于同时 decode 的请求数(还要留出足够的余量让 prefill 跟上到达率)。这也说明 token 预算和最大批大小要一起调。
练习 4(M/D/1)。prefill 的单卡执行时间 = 0.2 s,到达率 3 req/s。(a) 单卡的平均 TTFT 是多少?(b) 2 路流水线呢?(c) 2 路张量并行、 = 1.6 呢?(d) 到达率升到 6 req/s 时,三者各是多少?
参考答案
(a) : s。(b) s。(c) s——张量并行最好。(d) 单卡 ,不稳定(队列无限增长);流水线 s;张量并行 s——到达率高时流水线反超。可以用 avg_ttft_inter、avg_ttft_intra、md1_avg_ttft 核对。
练习 5(KV 传输的带宽需求)。Llama-3-70B(每 token KV 320 KB),平均提示 2000 个 token,每秒 20 个请求。(a) 每秒要传多少 KV?(b) 一张 400 Gb/s 的 InfiniBand 网卡(有效 40 GB/s)够吗?(c) 如果是多头注意力(KV 是 8 倍),呢?
参考答案
(a) 字节 ≈ 13.1 GB/s。(b) 40 GB/s 够用,占约三分之一;逐层传输还能把每个请求的可见延迟藏掉。(c) 8 倍是 105 GB/s,超过了一张网卡的能力,需要多张网卡或者让传输只走 NVLink(DistServe 的低节点亲和放置)。这说明 GQA 对解耦有多重要。
练习 6(取整的代价)。某负载下,一个 prefill 实例(4 块 GPU)能承受 7.7 req/s,一个 decode 实例(8 块 GPU)能承受 86 req/s,共置实例(8 块 GPU)能承受 11.4 req/s。分别求目标流量 5、30、200 req/s 时两种部署需要的 GPU 数。从什么流量开始解耦更省?
参考答案
| 流量 | 解耦 | 共置 |
|---|---|---|
| 5 | 1×4 + 1×8 = 12 | 1×8 = 8 |
| 30 | 4×4 + 1×8 = 24 | 3×8 = 24 |
| 200 | 26×4 + 3×8 = 128 | 18×8 = 144 |
流量 30 左右两者持平,之后解耦更省(200 时省 11%)。理想的每 GPU goodput 是 对 ,解耦高约 14%;但实例是整块分配的,小流量时取整的浪费抵消了它(代码 04 演示 4 的结构相同)。
练习 7(思考:TPOT 还是 P99 TBT)。一个对话产品的 SLO 写成”P90 TPOT ≤ 50 ms”,另一个写成”P99 TBT ≤ 100 ms”。哪一个对生成停顿更敏感?它们分别会把系统设计推向分块 prefill 还是解耦?
参考答案(要点)
P99 TBT 对停顿极其敏感:只要 1% 的 token 间隔超过 100 ms 就违约,一次长 prefill 造成的停顿就足以让很多请求超标;它会推动系统采用小 token 预算的分块 prefill(或解耦)。P90 TPOT 是每个请求的平均值,偶尔的停顿被平均掉,它对”持续的慢”更敏感——例如 decode 批太大、或者 decode 和 prefill 长期争抢算力;它更容易推动解耦(给 decode 专门的、批合适的 GPU)。两篇论文结论侧重点的不同,部分就来自这个指标的选择。
练习 8(思考:解耦的故障)。DistServe 说解耦带来了”故障传播”的风险。为什么共置的副本没有这个问题?你会怎样设计容错?
参考答案(要点)
共置时每个副本独立处理完整的请求,一个副本故障只影响它自己的请求,其他副本照常工作。解耦时一个 decode 实例可能接收多个 prefill 实例的请求,它故障时,这些 prefill 实例产出的 KV 无处可去,可能拖垮整个服务;prefill 实例故障也会让对应的请求重来。可能的设计:decode 实例故障时,把它的请求重新分给其他 decode 实例,KV 从 prefill 实例(如果还保留着)或检查点重传,否则重新 prefill;在 KV 存储层(第 9 讲 Mooncake 的分布式 KV 池)保留一份副本;健康检查 + 快速重新规划。Splitwise 提到可以把 prompt 阶段之后的 KV 检查点到内存数据库里,恢复时跳过 prefill。
11. 延伸阅读 · Further reading
本讲的必读材料
- Y. Zhong, S. Liu, J. Chen, J. Hu, Y. Zhu, X. Liu, X. Jin, H. Zhang. DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving. OSDI 2024。
- A. Elmeleegy, H. Kim, D. Zier, K. Kranen, N. Shah, R. Olson, O. Kahalon. Introducing NVIDIA Dynamo, A Low-Latency Distributed Inference Framework for Scaling Reasoning AI Models. NVIDIA Technical Blog,2025 年 3 月。
选读
- P. Patel, E. Choukse, C. Zhang, A. Shah, Í. Goiri, S. Maleki, R. Bianchini. Splitwise: Efficient Generative LLM Inference Using Phase Splitting. ISCA 2024。
- A. Agrawal, N. Kedia, A. Panwar, J. Mohan, N. Kwatra, B. S. Gulavani, A. Tumanov, R. Ramjee. Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve. OSDI 2024。
相关工作
- A. Agrawal et al. SARATHI: Efficient LLM Inference by Piggybacking Decodes with Chunked Prefills. 2023(分块 prefill 的前身)。
- C. Hu et al. Inference without Interference: Disaggregate LLM Inference for Mixed Downstream Workloads(TetriInfer). 2024。
- A. Agrawal et al. Vidur: A Large-Scale Simulation Framework for LLM Inference. MLSys 2024(Sarathi-Serve 用它选 token 预算)。
- R. Qin et al. Mooncake: A KVCache-centric Disaggregated Architecture for LLM Serving. 2024(第 9 讲的必读论文)。
背景
- 前置讲 P4(排队论与性能评估):M/D/1、到达过程、goodput 的测量方法。
- 前置讲 P5(分布式与通信基础):KV 传输用到的互连与带宽。
12. 下一讲预告 · Next lecture
到这里,我们讨论的都是稠密模型:每个 token 都要经过全部参数。可今天最强的开源模型(DeepSeek-V3、Qwen3、Llama 4 等)大多是混合专家模型(MoE):总参数几千亿,每个 token 只激活其中一小部分”专家”。这让推理的账本完全变了样:权重大得要很多卡才放得下,每个专家分到的批却很小、更加 memory-bound;token 要通过 all-to-all 发给负责它的专家(第 6 讲代码 01 演示 4 的那个集合通信);专家之间的负载还不均衡。第 8 讲:混合专家模型的推理读两篇论文:MegaScale-Infer(字节跳动)把注意力和专家解耦到不同的 GPU 上,用 ping-pong 流水线让两边都忙起来——这是本讲”解耦”思想在模型内部的延伸;Helix Parallelism(NVIDIA)在长上下文 decode 中让注意力按 KV 切分、FFN 用张量 / 专家并行,两部分用不同的并行方式。我们会模拟 top-k 门控的负载分布、专家并行的 all-to-all 与负载不均衡,并用第 4 讲的 ⊕ 合并验证按 KV 切分的注意力。