对应 Maps to:Stanford CS349E / EE339 Efficient ML Inference at Scale(Autumn 2026),Lecture 5 · Tue Oct 6 · Disaggregated Serving(Zain Asgar)。阅读材料:Y. Zhong et al., DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving(OSDI 2024);Gimlet Labs 博客 Low-Latency Inference with Speculative Decoding on d-Matrix Corsair and GPU(2026-03)。原课作业 2(Disaggregated Inference,10/5–10/12)的主题落在本讲。 依据说明:课程网站只公开了课表和阅读材料,没有 slides。本讲以两篇阅读材料为主线,另外用到这些公开文献:Sarathi-Serve(Agrawal et al., OSDI 2024,分块 prefill)、Splitwise(Patel et al., ISCA 2024)、Leviathan et al. 2023(投机解码的期望收益公式)、MegaScale-Infer(2025,注意力 / FFN 解耦)。补充材料是我选的,不代表课堂上实际讲了什么。Zain Asgar 是 Gimlet Labs 的联合创始人,课程的算力由 Gimlet 提供。 配套代码 Code:
code/lecture05/(numpy;一个离散事件模拟器 + 5 个脚本,最长的约 40 秒) 预计阅读时间:3–4 小时(含运行代码)
0. 本讲导航 · How to read this lecture
0.1 学习目标 Learning goals
学完这一讲,你应该能够:
- 用 TTFT、TPOT、SLO 达成率和 per-GPU goodput 描述一个推理服务的好坏,并解释为什么 goodput(而不是吞吐)才是成本的正确度量。
- 解释 prefill 和 decode 放在一起(共置)时的两个问题:互相干扰,以及资源和并行方式被绑在一起;知道分块 prefill 怎样缓解干扰、代价是什么。
- 用 M/D/1 排队模型分析 prefill 实例:为什么速率接近处理能力时 TTFT 会爆炸;两块 GPU 做 prefill 时张量并行和流水线并行各在什么速率下更好。
- 复述 DistServe 的放置算法(枚举并行配置 + 模拟器 + 二分查找 + 按流量复制)以及低带宽集群的变体;会算 KV cache 的搬运量和需要的网络带宽。
- 说清楚解耦什么时候值得、什么时候不值得:本讲的模拟发现,在理想化的每步时间模型下,分块 prefill 的共置系统是很强的对手。
- 把解耦推广到异构硬件:投机解码的草稿模型放到 SRAM 芯片上为什么划算;注意力和 FFN 为什么也可以拆开放。
0.2 先修与本讲范围 Prerequisites & scope
| 默认你已经会 Assumed | 本讲会讲 Covered here | 后面再展开 Later |
|---|---|---|
| 第 1 讲 §4.6、§6:prefill 和 decode 的”性格”、近存与远存 | 服务指标、干扰、DistServe 的分析与放置算法 | 连续批处理的调度细节(第 6 讲) |
| 第 2 讲的 KV cache;第 4 讲的张量并行 / 流水线并行 | 排队论入门(M/D/1) | 投机解码的正确性(第 6 讲) |
| P5 的链路带宽和延迟 | KV 传输的代价;异构解耦 | KV cache 的分页、缓存与分层存储(第 7 讲) |
0.3 本讲地图 Map
§1 服务的指标:TTFT、TPOT、SLO、goodput
§2 共置的两个问题:干扰;资源与并行方式的耦合;分块 prefill ← 代码 01
§3 解耦:DistServe 的出发点;按比例配 GPU ← 代码 01、02
§4 排队论:prefill 实例是 M/D/1;张量并行 vs 流水线;利用率与尾延迟;decode 实例 ← 代码 03
§5 正面比较:共置、分块 prefill、解耦——谁赢、为什么 ← 代码 02
§6 放置算法:在集群上怎么摆 ← 代码 04
§7 KV cache 的搬运账 ← 代码 04
§8 异构解耦:SRAM 芯片上的草稿模型;注意力 / FFN 解耦 ← 代码 05
§9 解耦的代价,以及什么时候不该解耦
§10 现状(2025–2026)
§11–15 小结、常见误区、练习(含答案)、延伸阅读、下一讲预告
0.4 关于模拟器 · About the simulator
本讲的代码核心是 serving_sim.py:一个离散事件模拟器。请求按泊松过程到达,长度从对数正态分布里抽;三种实例(共置、只做 prefill、只做 decode)按迭代执行;每一步的时间来自第 1 讲的 roofline(FLOPs / 峰值、字节 / 带宽)乘以效率折扣(算力 60%、带宽 80%)再加每步 1 ms 的固定开销。这些折扣和开销是假设值。它的用途是比较方案、理解机制,不是预测真实系统的绝对数字——§5 会看到,结论对”每步要多久”这个模型相当敏感。goodput 是对请求速率二分查找得到的,受有限请求数影响有约 ±10% 的噪声。
1. 服务的指标 · Metrics for serving
1.1 用户看到的:TTFT 和 TPOT
一个请求的生命周期分两段(第 1 讲 §1.1):prefill 一次处理整段提示,产生第一个 token;decode 一个一个地产生后面的 token。用户感受到两个延迟:
- TTFT(time to first token):从请求到达到第一个 token 出来——包括排队和 prefill。
- TPOT(time per output token):之后每个 token 的平均间隔。总延迟 = TTFT + TPOT × (输出长度 − 1)。
不同应用的要求不同(DistServe 表 1):
| 应用 | 模型 | TTFT | TPOT |
|---|---|---|---|
| 聊天(ShareGPT) | OPT-13B / 66B / 175B | 0.25 / 2.5 / 4.0 s | 0.1 / 0.15 / 0.2 s |
| 代码补全(HumanEval) | OPT-66B | 0.125 s | 0.2 s |
| 摘要(LongBench) | OPT-66B | 15 s | 0.15 s |
聊天要快速开口(TTFT 紧),TPOT 只要快过人的阅读速度就行;代码补全要立刻出结果(TTFT 很紧);摘要的提示很长,可以多等一会儿开口,但生成要快(TPOT 紧)。
1.2 服务方关心的:SLO 达成率与 goodput
SLO(service-level objective)是对 TTFT 和 TPOT 的上限要求。SLO 达成率是同时满足两者的请求比例。DistServe 定义:
per-GPU goodput = 在 SLO 达成率不低于目标(例如 90%)的前提下,每块 GPU 每秒能服务的最大请求数。
为什么不用”吞吐”(每秒生成的 token 数)?因为吞吐可以靠把批开得很大、让每个用户等得很久来刷高。goodput 只数”按时完成”的请求,直接对应每个查询的成本:goodput 高一倍,同样的流量就只需要一半的 GPU。
2. 共置的两个问题 · Two problems with colocation
2.1 现有系统:把 prefill 和 decode 混在一起批处理
vLLM 等系统用连续批处理(第 6 讲):每一步迭代,把新请求的 prefill 和正在进行的请求的 decode 放在同一批里(或者轮流做)。同一组 GPU 同时干两种活——这叫共置(colocation)。它的好处是 GPU 不闲着、吞吐高;DistServe 指出它在有 SLO 的时候有两个问题。
2.2 问题一:干扰
代码 01 演示 1(Llama-3.1-8B、一块 H100):32 个正在 decode 的序列(每个上下文 700)里塞进一个长度为 的新提示,放在同一步里算:
| 提示长度 | 只有 decode | decode + prefill | decode 变慢 | 只有 prefill | prefill 变慢 |
|---|---|---|---|---|---|
| 128 | 7.70 ms | 7.70 ms | 1.0× | 6.61 ms | 1.17× |
| 512 | 7.70 ms | 14.90 ms | 1.9× | 14.07 ms | 1.06× |
| 1024 | 7.70 ms | 28.19 ms | 3.7× | 27.36 ms | 1.03× |
| 2048 | 7.70 ms | 55.48 ms | 7.2× | 54.66 ms | 1.02× |
decode 的一步本来只要 7.7 ms(读权重和 KV 为主,算力几乎闲着)。塞进一个 512 token 的提示,这一步要算的 token 从 32 个变成 544 个,变成 compute-bound,一步翻倍——这一步里所有 32 个用户的 token 都晚了。提示越长,拖得越厉害。(128 token 的短提示不添乱:它的计算正好藏在读权重的时间里。)
请求一多,干扰就表现在 P90 延迟上。代码 01 演示 2(提示 512、输出 64,和论文图 1 的设置一样):
| 速率(请求/秒) | 共置:P90 TTFT | 共置:P90 TPOT | 只做 prefill:P90 TTFT | 只做 decode:P90 TPOT |
|---|---|---|---|---|
| 5 | 20.5 ms | 7.6 ms | 14.1 ms | 6.8 ms |
| 30 | 37.5 ms | 14.9 ms | 33.7 ms | 7.1 ms |
| 50 | 64.3 ms | 36.1 ms | 59.0 ms | 7.4 ms |
| 60 | 92.8 ms | 100.5 ms | 87.6 ms | 7.6 ms |
共置时 TPOT 先坏:每秒到达的 prefill 越多,decode 被打断得越频繁。同一块 GPU 如果只做 decode,P90 TPOT 几乎不随速率变化。
2.3 问题二:资源和并行方式被绑在一起
prefill 和 decode 想要的东西不同(第 1 讲 §4.6):prefill 是 compute-bound,要在 TTFT 内算完长提示,适合更多的张量并行;decode 是 memory-bound,要大批量摊薄读权重,要大显存放 KV。共置时,两个阶段只能用同一组 GPU、同一种并行方式、同一个 GPU 数量比例。为了满足更严格的那个 SLO,往往只能整体多买 GPU(“过度配置”)。
2.4 一个缓解办法:分块 prefill
分块 prefill(chunked prefill,Sarathi-Serve 等):每一步 = 所有 decode + 一块不超过预算的 prefill。长提示被切成几块分几步做完,decode 不再被一整段 prefill 卡住。代码 01 演示 4(速率 40 请求/秒):
| 策略 | 每步 token 预算 | P90 TTFT | P90 TPOT |
|---|---|---|---|
| 不分块(prefill 优先) | – | 48.5 ms | 21.6 ms |
| 分块 | 256 | 58.8 ms | 7.5 ms |
| 分块 | 512 | 54.4 ms | 11.0 ms |
| 分块 | 2048 | 50.6 ms | 13.0 ms |
TPOT 明显变好,TTFT 大体上变差(一个提示要分几步做完,而且每一步都带着全部 decode)。DistServe 的评价是:“它用 TTFT 换 TPOT,并没有消除干扰。“不过分块还有一个好处:decode 的 token 搭了 prefill 的便车(同一次读权重),每一步更”满”——§5 会看到,这让它成为一个很强的对手。
3. 解耦:DistServe 的出发点 · Disaggregation
3.1 思路
把 prefill 和 decode 放到不同的 GPU 上:prefill 实例只做 prefill,算完把 KV cache 传给 decode 实例,decode 实例只做 decode。两个好处:
- 没有干扰:decode 实例上没有 prefill 打断,TPOT 稳定;prefill 实例上没有 decode 抢资源,TTFT 只取决于 prefill 自己的排队。
- 各自配置:每个阶段可以选自己的 GPU 数量、并行方式、批处理策略;两个阶段的 GPU 数量可以按负载配比。
代价是把 KV cache 从 prefill 实例搬到 decode 实例(§7)。
3.2 论文图 1 的那笔账
DistServe 的开篇例子(OPT-13B、一块 A100、提示 512 / 输出 64):共置时每块 GPU 的 goodput 约 1.6 请求/秒;同一块 GPU 只做 prefill 能扛 5.6,只做 decode 能扛 10。用 2 块 GPU 做 prefill、1 块做 decode,整体 10 请求/秒,每块 GPU 3.3——是共置的 2.1 倍。
代码 01 演示 3 在我们的设置下(Llama-3.1-8B、H100;SLO:TTFT ≤ 100 ms、TPOT ≤ 15 ms)算了同一笔账:
| 配置 | GPU 数 | 整体 goodput | 每块 GPU |
|---|---|---|---|
| 共置(prefill 优先) | 1 | 30.3 | 30.3 |
| 只做 prefill | 1 | 62.2 | – |
| 只做 decode | 1 | 327.2 | – |
| 5 块 prefill + 1 块 decode | 6 | 311.1 | 51.9 |
decode 的能力远远多余(输出只有 64 个 token,H100 的带宽又大),所以要配很多块 prefill GPU。按比例配之后每块 GPU 约 52 请求/秒,是共置的 1.7 倍。
3.3 不同负载需要不同的比例
代码 02 演示 1(三种合成负载,Llama-3.1-8B、一块 H100):
| 负载 | 设置 | 只做 prefill | 只做 decode | 每块 decode GPU 需要几块 prefill GPU |
|---|---|---|---|---|
| 聊天 | 提示约 700、输出约 250;TTFT ≤ 200 ms、TPOT ≤ 20 ms | 34.9 | 60.9 | 1.7 |
| 代码补全 | 提示约 300、输出约 100;TTFT ≤ 50 ms、TPOT ≤ 20 ms | 64.0 | 485.8 | 7.6 |
| 摘要 | 提示约 4000、输出约 200;TTFT ≤ 2 s、TPOT ≤ 15 ms | 8.3 | 11.3 | 1.4 |
共置系统里这个比例是写死的(每块 GPU 两种活都干);解耦以后可以按负载配。代价是比例只能取整数,而且负载一变,最优比例就变了。
4. 排队论:prefill 实例是一个 M/D/1 队列 · Queueing analysis
4.1 M/D/1
解耦以后,prefill 实例只做一件事。如果提示长度相同(服务时间固定为 ),一个提示就能吃满 GPU(所以不批处理、先到先服务),请求按泊松过程以速率 到达,它就是排队论里的 M/D/1 队列(M:泊松到达;D:服务时间固定;1:一个服务台)。平均 TTFT(论文公式 1):
第一项是执行时间,第二项是排队时间。 是利用率。代码 03 演示 1(Llama-3.1-8B、H100、提示 512, ms):
| 利用率 | 公式:排队 | 公式:平均 TTFT | 模拟:平均 TTFT |
|---|---|---|---|
| 0.2 | 1.76 ms | 15.82 ms | 15.90 ms |
| 0.5 | 7.03 ms | 21.10 ms | 21.19 ms |
| 0.8 | 28.13 ms | 42.20 ms | 42.00 ms |
| 0.9 | 63.30 ms | 77.37 ms | 65.14 ms |
| 0.95 | 133.63 ms | 147.70 ms | 100.86 ms |
时公式和模拟吻合;越接近满负荷,模拟越偏低——队列要越久才能”涨”到稳态,有限的模拟时间看不到最坏的排队。关键的形状是:排队时间按 增长。 时排队是 的一半, 时是 4.5 倍。速率逼近处理能力时,延迟不是线性变差,而是爆炸。TTFT 的 SLO 越紧,prefill 实例能跑到的利用率越低,需要的 GPU 越多。
4.2 两块 GPU:张量并行还是流水线
两块 GPU 做 prefill,有两种用法(论文 §3.1):
- 流水线并行(inter-op):每块 GPU 放一半的层。一个请求仍要 (依次走过两段),但每 就能进一个新的。平均 TTFT(论文公式 2):。
- 张量并行(intra-op):两块 GPU 一起算一个请求,快 倍(,因为要做 all-reduce、矩阵也变小了)。平均 TTFT(论文公式 3):。
代码 03 演示 2(Llama-3.1-70B,两块 H100,提示 512; ms,张量并行 64.6 ms,):
| 速率 | 张量并行:公式 / 模拟 | 流水线:公式 / 模拟 | 更好的 |
|---|---|---|---|
| 2 | 69.4 / 69.4 ms | 125.7 / 125.8 ms | 张量并行 |
| 8 | 99.0 / 99.5 ms | 150.3 / 150.6 ms | 张量并行 |
| 13 | 233.1 / 221.4 ms | 235.7 / 236.5 ms | 张量并行 |
| 14 | 368.0 / 307.9 ms | 294.5 / 279.0 ms | 流水线 |
低速率时排队几乎为零,张量并行把执行时间缩短到 ,更好;速率升高后排队主导,流水线的处理能力是 、张量并行只有 ,流水线更好。交叉点约 13 请求/秒,和 有关: 时约 7.7, 时约 13.4。TTFT 的 SLO 越紧,越偏向张量并行(只有它能缩短单个请求)。 取决于输入长度、模型结构和链路带宽——这就是 DistServe 不靠手算、而是用模拟器搜索并行方式的原因。
4.3 利用率、尾延迟与”扎堆”
SLO 通常按 P90 或 P99 定,而不是平均值。代码 03 演示 3( ms):
| 利用率 | 到达方式 | 平均 TTFT | P99 | P99 / |
|---|---|---|---|---|
| 0.9 | 等间隔 | 14.07 ms | 14.07 ms | 1.0 |
| 0.9 | 泊松 | 65.14 ms | 230.30 ms | 16.4 |
| 0.9 | 扎堆(间隔的变异系数 2) | 272.24 ms | 1.04 s | 74.1 |
请求等间隔地来,利用率 0.9 也不排队——排队完全是随机性造成的。泊松到达时 P99 已是 的 16 倍;真实流量常常更”扎堆”,同样利用率下尾延迟再翻好几倍。所以系统要留足余量,DistServe 用历史流量拟合到达分布来做模拟。突发流量还会让 decode 实例的 KV 一下子涌入,DistServe 让 decode 实例主动”拉”KV(pull),而不是 prefill 实例”推”(push),用 prefill 实例的显存做缓冲。
4.4 decode 实例:批处理与显存
decode 实例的分析(论文 §3.2)简单一些:一个 decode 任务严重受限于带宽,批越大越划算。共置时批很难开大(批越大,被 prefill 打断时受影响的用户越多);解耦后多个 prefill 实例可以喂一个 decode 实例,批自然就大了。批大小的上限是显存(要放所有序列的 KV),这时张量并行(更多显存、更大带宽)或流水线(每块卡存更少的层、更多 KV)都能帮忙:TPOT 很紧时用张量并行降低每步延迟,之后用流水线近似线性地提高吞吐。
5. 正面比较:共置、分块 prefill、解耦 · Head-to-head
DistServe 报告:比 vLLM(共置、prefill 优先)多服务 2.0–4.6 倍的请求(聊天)、5.7 倍(代码补全)、4.3 倍(摘要);比使用分块 prefill 的 DeepSpeed-MII 多 1.6–7.4 倍;能撑住比 vLLM 紧 1.4–12.6 倍的 SLO。
我们的模拟做了一次公平的比较:每种系统都用 4 块 H100、完整模拟(解耦含 KV 传输),代码 02 演示 2:
| 负载 | 共置:prefill 优先 | 共置:分块 | 解耦(最好的配比) | 解耦 / prefill 优先 | 解耦 / 分块 |
|---|---|---|---|---|---|
| 聊天 | 20.4 | 29.0(预算 512) | 23.6(2P2D) | 1.16× | 0.81× |
| 代码补全 | 54.6 | 54.0(预算 2048) | 76.0(3P1D) | 1.39× | 1.41× |
| 摘要 | 3.0 | 6.6(预算 256) | 4.4(2P2D) | 1.50× | 0.67× |
(单位:每块 GPU 每秒的请求数。)读这张表要很小心:
- 对”prefill 优先”的共置,解耦全胜(1.2–1.5 倍)。方向和论文一致,幅度小一些。
- 对分块 prefill,只有 TTFT 很紧的代码补全上解耦明显更好;聊天和摘要上分块 prefill 反而更好。原因有二:分块让 decode 搭上了 prefill 的便车(同一次读权重,这在我们的 roofline 模型里一点效率都不损失);而 4 块 GPU 的解耦只能在 1P3D、2P2D、3P1D 里选,总有一边的能力被浪费(§3.3 的理想比例不是整数)。
- 这和论文的结论(比使用分块 prefill 的 DeepSpeed-MII 好 1.6–7.4 倍)不完全一致。我们的每步时间模型是理想化的:混合批次一点效率都不损失、注意力内核对混合批次一样高效——真实系统里分块 prefill 没有这么便宜(论文说”分块 prefill 比完整 prefill 慢”)。结论高度依赖于”每步要多久”这个模型,这也是 DistServe 要先校准模拟器(和真实系统的误差 < 2%)再做搜索的原因。
另一个角度是 SLO 能收多紧(代码 02 演示 3:代码补全,每块 GPU 固定 43.7 请求/秒,把两个 SLO 同比例缩小):
| 系统 | SLO × 1.0 | × 0.8 | × 0.6 | × 0.5 |
|---|---|---|---|---|
| 共置:prefill 优先 | 96% | 89% | 29% | 2% |
| 共置:分块 | 95% | 89% | 78% | 62% |
| 解耦 3P1D | 99% | 97% | 92% | 30% |
能撑住 90% 达成率的最紧 SLO:解耦是 0.6 倍,两种共置在 0.8 倍时已经不到 90%——解耦能撑住约 1.7 倍紧的 SLO(论文在代码补全上报告比 vLLM 紧 1.4 倍)。但这个 3P1D 是按原 SLO 选的配比:收到 0.5 倍时它掉到 30%,反而不如分块 prefill。SLO 变了,配比要重新搜索。
这组结果和后来业界的经验相符:中小模型、负载平稳时,分块 prefill 的共置系统已经很好;解耦的价值在于 TTFT 很紧、两个阶段想用不同的并行方式或不同的硬件(§6、§8),或者规模大到可以细调配比的时候。
6. 放置算法:在集群上怎么摆 · Placement
6.1 算法 1:高带宽集群
如果机器之间有高速网络(InfiniBand),prefill 实例和 decode 实例可以放在任意两台机器上。DistServe 的算法 1 分两层:
- 各自优化:对 prefill 和 decode 分别枚举所有可行的(张量并行度,流水线段数)。对每种配置,用模拟器 + 二分查找求它的 goodput(prefill 只看 TTFT、decode 只看 TPOT),选每块 GPU 的 goodput 最高的那个。
- 按流量复制:prefill 实例数 ,decode 实例数 。
模拟器根据历史流量拟合的长度分布和到达过程生成请求,每步时间由 FLOPs 和访存量的模型给出(论文附录 A)。复杂度 (:每个实例最多跨几台机器;:每台机器几块 GPU),最大的设置也只要 1.3 分钟。
代码 04 演示 1(Llama-3.1-70B BF16、H100,聊天类负载,SLO:TTFT ≤ 1 s、TPOT ≤ 50 ms):
| 配置 | GPU 数 | KV 容量 | prefill goodput(每 GPU) | decode goodput(每 GPU) |
|---|---|---|---|---|
| TP2 × PP1 | 2 | 9 K token | 4.65(2.33) | KV 太少 |
| TP2 × PP2 | 4 | 448 K | 11.65(2.91) | 27.9(6.96) |
| TP4 × PP1 | 4 | 448 K | 11.23(2.81) | 40.0(10.00) |
| TP4 × PP2 | 8 | 1,327 K | 24.79(3.10) | 121.1(15.14) |
| TP8 × PP1 | 8 | 1,327 K | 20.52(2.57) | 109.2(13.65) |
两个阶段对配置的排序不一样:prefill 只要放得下权重就行,KV 很少的 TP2 也能做;decode 要 KV 容量和读权重的带宽,TP2 根本做不了。在这个模型里两者的第一名恰好都是 TP4×PP2(论文里 OPT-175B 的 prefill 选了 3×3、decode 选了 3×4,并不相同)。每块 GPU 的 decode 能力约是 prefill 的 5 倍。目标流量 100 请求/秒:5 个 prefill 实例 + 1 个 decode 实例,48 块 GPU。完整模拟(含 KV 传输)的 goodput 是 120.7 请求/秒,每 GPU 2.52;同样的 SLO 下,共置系统每 GPU 1.81(TP4,prefill 优先)到 2.18(TP4,分块)。(解耦部署一共 48 块 GPU,共置只模拟了 4–8 块的一个实例,多实例的排队合并效应对共置不利,比较不完全公平。)
6.2 算法 2:低带宽集群
如果机器之间只有普通网络,跨机器搬 KV 太慢(§7)。DistServe 的观察:KV 只在对应的层之间传——prefill 实例第 层的 KV 只给 decode 实例的第 层。于是把模型按层切成流水线段,把同一段的 prefill 片段和 decode 片段放在同一台机器上,KV 全部走机器内的 NVLink。算法 2 枚举流水线段数,在一台机器内枚举每段的 prefill / decode 配置,用模拟器选最好的。论文的测量:OPT-175B 用这种放置,KV 传输不到总延迟的 0.1%,95% 以上的请求传输延迟小于 30 ms。
6.3 运行时的调度
DistServe 的运行时很简单:先到先服务;新请求发给队列最短的 prefill 实例,prefill 完成后发给负载最轻的 decode 实例。两个改进:
- 减少流水线气泡:prompt 长短不一会让流水线各段的时间不均。prefill 实例按”凑够 个 token”组批( 是让 GPU 吃满的最短提示长度),短提示合批、长提示单独做。
- 对付突发:decode 实例主动拉取 KV(§4.3)。
- 定期重新规划:负载模式变了(比如提示变长),用新的历史数据重新跑放置算法。
7. KV cache 的搬运账 · The cost of moving KV
代码 04 演示 2:
复现论文 §3.3:OPT-66B(MHA)一个 512 token 的请求,KV = 1.21 GB(1.12 GiB,论文写作 1.13 GB);每秒 10 个请求要约 12 GB/s ≈ 97 Gb/s(论文:90 Gbps)。一张 400 Gb/s 的 InfiniBand 网卡就够;机器内 NVLink 更不在话下。
今天的模型小得多:GQA 让 Llama-3.1-70B 每 token 只有 320 KiB(OPT-66B 是 2.25 MiB),同样 512 个 token 只要 0.17 GB。第 2 讲的”为推理而改的注意力”在解耦里又省了一次。
一个请求的 KV(BF16)在不同链路上要搬多久(括号里是相对 prefill 时间的比例):
| 模型 | 提示 | KV 大小 | prefill | NVLink | InfiniBand(一张网卡) | 8 张网卡 | 100 Gb 以太网 |
|---|---|---|---|---|---|---|---|
| Llama-3.1-8B | 1000 | 131 MB | 26.7 ms | 0.29 ms(1%) | 2.63 ms(10%) | 0.33 ms(1%) | 10.50 ms(39%) |
| Llama-3.1-70B(TP8) | 1000 | 328 MB | 45.2 ms | 0.73 ms(2%) | 6.56 ms(14%) | 0.82 ms(2%) | 26.22 ms(58%) |
| Llama-3.1-70B(TP8) | 8000 | 2.62 GB | 339 ms | 5.83 ms(2%) | 52.43 ms(15%) | 6.56 ms(2%) | 209.73 ms(62%) |
KV 的大小和 prefill 的计算量都 提示长度,所以比例几乎不变:在 InfiniBand 上是 prefill 时间的 10%–15%,多张网卡一起用或者走 NVLink 时只有 1%–2%。100 Gb 以太网就紧张了:要 prefill 时间的 36%–62%,一张网卡每秒只能搬约 38 个 70B 请求(1000 token)的 KV——这时就需要 §6.2 的低带宽放置。Llama-3.1-70B、平均提示 1000 个 token、每秒 30 个请求,KV 搬运需要约 9.8 GB/s(79 Gb/s)。
【系统联系】KV 搬运还可以和计算重叠:prefill 一层算完就开始传这一层的 KV,不必等整个 prefill 结束(很多实现都这样做)。而 KV 一旦能在机器之间搬,就自然引出第 7 讲 Mooncake 的问题:KV 能不能缓存在一个分布式的存储池里,给后续请求复用?
8. 异构解耦 · Heterogeneous disaggregation
8.1 从”不同的 GPU”到”不同的芯片”
DistServe 把两个阶段分到不同的 GPU 上,但用的还是同一种 GPU。第 1 讲 §4.7、§6 已经看到:SRAM 芯片(Groq、d-Matrix Corsair、Cerebras)带宽高几十倍、容量小几十倍;prefill 吃算力、decode 吃带宽。自然的下一步:每个阶段用最合适的芯片。Gimlet Labs 的”异构推理云”就是这个思路:把一个工作负载切开,分别放到 GPU、SRAM 芯片、CPU 等硬件上。
阅读材料的例子是投机解码(speculative decoding,第 6 讲详讲):一个小的草稿模型先猜几个 token,大的目标模型一次验证它们。草稿阶段是严重 memory-bound 的(模型小、一次一个 token),正好适合 SRAM 芯片。
8.2 投机解码的期望收益
每轮草稿 个 token,每个独立地以概率 被接受;第一个被拒绝之后的全部作废。被接受的草稿数的期望是 (第 个被接受要求前 个全被接受);验证时目标模型还总会多给一个 token,所以每轮平均前进
(Leviathan et al. 2023)。代码 05 演示 1 用这个公式复现了 Gimlet 博客的接受率表(代码补全类负载,草稿模型针对任务微调过):
| 草稿长度 | 每个 token 的接受率 | 被接受的 token(博客) | 公式 | 模拟 |
|---|---|---|---|---|
| 5 | 92.6% | 4 | 3.99 | 4.00 |
| 10 | 93.4% | 7 | 7.00 | 7.00 |
| 20 | 94.8% | 12 | 11.97 | 11.89 |
| 40 | 96.2% | 20 | 19.94 | 19.87 |
草稿越长,被接受的越多,但效率越低: 时只接受一半。所以草稿的代价决定了能草稿多长。
8.3 草稿放在哪
代码 05 演示 2 用一个参数化的玩具模型(目标模型按 gpt-oss-120b 的公开结构近似:36 层、128 个专家选 4 个,MXFP4;草稿模型 16 亿参数、FP8;固定开销和网络延迟是假设值)比较草稿放在 GPU 上和放在 SRAM 芯片上(单个用户,):
- 目标模型普通 decode 一步 2.51 ms;草稿一个 token:GPU 上 0.90 ms,SRAM 芯片上 0.057 ms。
| 草稿放在 | 目标模型的验证 | 最优 | 相对普通 decode 的加速 |
|---|---|---|---|
| GPU | MoE(碰到的专家随 token 数增加) | 3 | 1.25× |
| GPU | 验证代价不变(稠密近似) | 5 | 1.81× |
| SRAM 芯片 | MoE | 8 | 2.17× |
| SRAM 芯片 | 验证代价不变 | 20 | 7.65× |
三点:
- 草稿模型虽小,在 GPU 上生成一个 token 也要约 0.9 ms——它和目标模型一样受限于读权重和固定开销。草稿 5 个 token 就将近普通 decode 两步。所以 GPU 上草稿不能长,加速有限。
- 放到 SRAM 芯片上(150 TB/s 的片上带宽),草稿一个 token 只要几十微秒:多猜几个几乎不花钱,最优草稿长度变长、加速变大。这正是博客的结论:“草稿越便宜,越可以激进地草稿;也可以在同样延迟下用更大、更准的草稿模型。”
- MoE 目标模型的陷阱:验证 个 token 时,每个 token 各选 4 个专家,一层要读的专家从 4 个涨到几十个(21 个 token 时每层约 62 个),验证代价是普通一步的约 5 倍(第 3 讲 §7.6 的”批被稀释”,反过来用)。所以 MoE 上的最优草稿长度短得多。批大的时候专家本来就都被碰到了,这个陷阱会变小。
博客报告(按每千瓦吞吐归一化、有批处理):GPU 上的投机解码比单纯的 prefill / decode 解耦快 2–5 倍;草稿换到 Corsair 上,同样能耗下交互速度再快 2–10 倍(例子:一个 25 秒的请求,投机解码先快 5 倍多,Corsair 再快 2.6 倍)。我们的玩具模型只看单个用户、不算能耗,数字不可比,方向一致。
8.4 另一种切法:注意力 / FFN 解耦
代码 05 演示 3(Llama-3.1-70B decode,上下文 4096):
| 批大小 | 注意力核心:强度 | 投影 + FFN:强度 |
|---|---|---|
| 16 | 8.0 | 16 |
| 256 | 8.0 | 256 |
| 1024 | 8.0 | 1024 |
注意力核心的强度固定在组大小(GQA 的 8),批再大也不变;投影和 FFN 的强度等于批大小。批大到一定程度,一个是纯粹的带宽 / 容量问题,一个是算力问题。于是有系统(MegaScale-Infer、StepFun 的 Step-3 等)把每一层的注意力和 FFN 拆到不同的机器上:注意力放在显存大、带宽高的机器上(存所有序列的 KV),FFN(尤其是 MoE 的专家)放在算力强的机器上,用很大的批喂饱。每一层都要把激活在两组机器之间来回传——用网络换硬件的匹配度。NVIDIA 的产品线里也能看到同样的思路:Rubin CPX 专做长上下文 prefill,Groq 3 LPX 专做低延迟 decode(第 1 讲 §8)。
9. 解耦的代价,以及什么时候不该解耦 · Costs and when not to disaggregate
解耦不是免费的:
- 搬数据:KV cache 要从 prefill 实例搬到 decode 实例(§7);异构时每轮草稿 / 验证都要跨设备,注意力 / FFN 解耦每层都要跨机器。网络带宽和延迟是新的瓶颈。
- 配比和取整:两类实例的数量只能取整数,负载变了比例就不对(§3.3、§5)。规模小时浪费明显。
- 协调和调度:要有一个中心调度器,处理排队、负载均衡、KV 的流控(突发时 decode 实例的显存会被 KV 挤满)。
- 故障域:一个请求经过两组机器,任何一组出问题都会影响它;两组的扩缩容也要协调。
- 显存的重复:prefill 实例和 decode 实例各存一份完整的权重(共置时一份权重同时服务两个阶段)。
所以在这些情况下,解耦可能不值得:模型小、一块 GPU 就放得下;负载低,GPU 本来就闲着;提示很短(干扰小,§2.2 的 128 token 情形);TTFT 不紧,分块 prefill 已经够好(§5)。在这些情况下值得:TTFT 和 TPOT 都很紧;提示很长、干扰严重;规模大到可以按负载细调配比;两个阶段想用不同的并行方式(例如 MoE 的 decode 要大规模专家并行);或者想用不同的硬件(§8)。
10. 现状(2025–2026)· State of the field
- 解耦成为大规模部署的常见形态:NVIDIA Dynamo、llm-d、SGLang、vLLM 都支持 prefill / decode 解耦,配合 KV 传输库(如 NIXL)在 NVLink、InfiniBand、RDMA 之间搬 KV。DeepSeek 公开的部署方案把 prefill 和 decode 分开,并在 decode 上做大规模专家并行。
- 以 KV cache 为中心:Mooncake(Kimi)把 KV 放进一个分布式存储池,prefill 和 decode 都围着它转,还能复用前缀(第 7 讲)。
- 分块 prefill 仍是强基线:vLLM、SGLang 默认开启分块 prefill;很多中小规模的部署不解耦。
- 异构硬件进入推理系统:SRAM 芯片(Groq、Cerebras、d-Matrix)用于低延迟 decode 或草稿;NVIDIA 推出专门的 prefill 芯片(Rubin CPX);Gimlet 等异构推理云把不同阶段映射到不同厂商的硬件上。
- 注意力 / FFN 解耦用于大规模 MoE 推理(MegaScale-Infer、Step-3)。
11. 小结 · Summary
- goodput 而不是吞吐:用户感受 TTFT 和 TPOT,服务方要在 SLO 达成率 ≥ 90% 的前提下最大化每块 GPU 的请求数——它直接对应每个查询的成本。
- 共置的两个问题:prefill 打断 decode(干扰),以及两个阶段被迫共用资源和并行方式(耦合)。分块 prefill 用 TTFT 换 TPOT,缓解干扰。
- 解耦:prefill 和 decode 放到不同的 GPU 上,消除干扰、各自配置、按负载配比;代价是搬 KV。DistServe 用模拟器 + 二分查找搜索每个阶段的并行配置,再按流量复制;低带宽集群时把同一段的 prefill 和 decode 放在同一台机器上。
- 排队论:prefill 实例是 M/D/1,排队时间按 增长;低速率时张量并行好(缩短单个请求),高速率时流水线好(处理能力更高);尾延迟和到达的扎堆程度决定了要留多少余量。
- 诚实的比较:在理想化的 roofline 模型里,解耦对”prefill 优先”的共置全胜,但对分块 prefill 只在 TTFT 很紧时明显更好;结论高度依赖于每步时间的模型。
- 异构解耦:草稿模型放到 SRAM 芯片上,草稿几乎不花钱,可以草稿得更长;MoE 的验证会碰到更多专家;注意力和 FFN 的强度差一个数量级,也可以拆开放。
12. 常见误区 · Common pitfalls
- “吞吐高就是服务好。” 吞吐可以靠让用户等更久来刷高;要看在 SLO 下的 goodput(§1.2)。
- “解耦总是比共置好。” 对分块 prefill 的共置系统,解耦只在某些负载和 SLO 下更好;小规模时配比取整的浪费不小(§5、§9)。
- “解耦的代价主要是搬 KV。” 在有 InfiniBand / NVLink 的集群上 KV 搬运只占 prefill 时间的百分之几;更难的是配比、调度、突发和故障(§7、§9)。
- “利用率越高越省钱。” 利用率逼近 1 时排队时间爆炸,SLO 达成率会崩;要留余量(§4.1、§4.3)。
- “两块 GPU 做 prefill,张量并行总比流水线好(单个请求更快)。” 高速率时流水线的处理能力更高,平均 TTFT 反而更低(§4.2)。
- “分块 prefill 消除了干扰。” 它把干扰摊薄了,代价是 TTFT(§2.4)。
- “投机解码的草稿越长越好。” 被接受的比例随长度下降;草稿越贵,最优长度越短(§8.2、§8.3)。
- “MoE 的验证和普通一步一样便宜。” 验证多个 token 会碰到更多专家,读更多权重(§8.3)。
- “SLO 定好以后,最优配置就定了。” 负载分布、SLO 一变,最优的配比和并行方式就变;需要定期重新搜索(§5、§6.3)。
- “模拟器的数字就是真实系统的数字。” 模拟器的结论取决于每步时间的模型;DistServe 先校准到误差 < 2% 才用它做决策(§0.4、§5)。
13. 练习 · Exercises
带 💻 的题可以用本讲的模块验证。参考答案在每题后面。
练习 1(M/D/1)。一个 prefill 实例的服务时间 ms,请求以 16 个/秒到达。利用率是多少?平均 TTFT 是多少?
参考答案
。平均 TTFT 150 ms:排队是执行时间的两倍。
练习 2(张量并行 vs 流水线)。 ms,两块 GPU,张量并行的加速 。速率 8 和 14 请求/秒时,用公式 2、3 比较两种方式的平均 TTFT。
参考答案
:张量并行 93.75 ms,流水线 116.7 ms,张量并行更好。:张量并行 281 ms,流水线 158 ms,流水线更好。张量并行的处理能力只有 请求/秒,14 已经很接近了。
练习 3(KV 带宽)💻。Llama-3.1-8B(每 token 128 KiB,BF16),平均提示 2000 个 token,每秒 20 个请求。一个请求的 KV 多大?用一张 InfiniBand 网卡(50 GB/s、5 µs)搬要多久?整体需要多少带宽?
参考答案
MB; 5.25 ms;整体 GB/s ≈ 42 Gb/s。
练习 4(配比)。一块 GPU 只做 prefill 能扛 6 请求/秒,只做 decode 能扛 20 请求/秒(都满足各自的 SLO)。目标流量 60 请求/秒,需要几块 prefill GPU、几块 decode GPU?每块 GPU 的 goodput 是多少?如果共置时每块 GPU 只有 3 请求/秒,解耦好多少?
参考答案
prefill 块,decode 块,共 13 块,每块 4.6 请求/秒,是共置的约 1.54 倍。(如果目标流量是 50,就要 9 + 3 = 12 块,每块只有 4.2——取整的浪费。)
练习 5(投机解码)。、:被接受的草稿数、每轮前进的 token 数各是多少?如果草稿一个 token 花目标模型一步的 10%、验证花一步,加速是多少?
参考答案
被接受 2.36;每轮前进 3.36。一轮的时间 步,加速 2.4 倍。
练习 6(分块 prefill)。为什么分块 prefill 能降低 P90 TPOT,却会提高 TTFT?如果把每步的 token 预算设成 64(比 decode 的批还小),会发生什么?
参考答案
decode 不再被一整段 prefill 卡住,每一步只多做一小块,所以 TPOT 稳定;但一个提示要分成好几步做完,每一步还要带着所有 decode,所以 TTFT 变长。预算小于 decode 的批时,这一步除了 decode 已经塞不下任何 prefill token——新请求永远开始不了(饥饿),实现上要保证每步至少有一点 prefill 的份额。
练习 7(为什么 prefill 不批处理)。DistServe 说 prefill 实例”一般只用很小的批”,为什么?什么时候值得把几个提示合成一批?
参考答案
一个几百 token 的提示就能让 GPU compute-bound(第 1 讲 §4.6);这之后再合批不会提高效率,只会让批里每个请求都等更久(执行时间按 token 数线性增长)。只有提示短于 (让 GPU 吃满的最短长度)时,才值得把几个合成一批,凑够 个 token(§6.3)。
练习 8(MoE 的验证)。128 个专家、每个 token 选 4 个,验证 11 个 token 时,一层平均碰到几个不同的专家?是普通一步的几倍?
参考答案
37.7 个,约是 4 个的 9.4 倍(专家部分的字节)。加上非专家部分和固定开销,整步的时间增加得少一些,但验证明显比普通一步贵。
练习 9(注意力 / FFN)。Llama-3.1-70B BF16 在 H100(屋脊点 295)上 decode,批大小多大时投影和 FFN 变成 compute-bound?注意力核心呢?
参考答案
投影和 FFN 的强度 ≈ 批大小,所以 (代码 05 演示 3: 时计算 144 ms > 读权重 41 ms)。注意力核心的强度固定在组大小 8,任何批大小都是 memory-bound。这就是把两者拆到不同硬件上的理由。
练习 10(思考题)。你在部署一个 8B 模型,一台 8 卡 H100 机器,流量平稳,聊天类负载,SLO 宽松。你会做 prefill / decode 解耦吗?如果换成一个 671B 的 MoE 模型、几百块 GPU、代码补全类负载呢?
参考答案
8B、单机、SLO 宽松:大概率不解耦——每块 GPU 一个共置实例 + 分块 prefill,简单、没有 KV 搬运和配比的问题,§5 的模拟里分块 prefill 在聊天负载上甚至更好。671B MoE、几百块 GPU、TTFT 很紧:值得解耦——prefill 和 decode 想要的并行方式不同(decode 要大规模专家并行、大批量),规模大到配比取整的浪费很小,TTFT 紧正是解耦的优势区间;还要配合 KV 传输和调度系统(这正是 DeepSeek、Mooncake 等的做法)。
14. 延伸阅读 · 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(arXiv:2401.09670)。
- Gimlet Labs, Low-Latency Inference with Speculative Decoding on d-Matrix Corsair and GPU, 2026-03. https://gimletlabs.ai/blog
本讲补充材料
- A. Agrawal et al., Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve, OSDI 2024。—— 分块 prefill。
- P. Patel et al., Splitwise: Efficient Generative LLM Inference Using Phase Splitting, ISCA 2024。—— 同期的 prefill / decode 解耦,也讨论了用不同代的 GPU 做两个阶段。
- Y. Leviathan, M. Kalman, Y. Matias, Fast Inference from Transformers via Speculative Decoding, ICML 2023。—— 投机解码的期望收益公式(第 6 讲的阅读材料)。
- R. Zhu et al., MegaScale-Infer: Serving Mixture-of-Experts at Scale with Disaggregated Expert Parallelism, 2025。—— 注意力 / FFN 解耦。
- Gimlet Labs, The emerging role of SRAM-centric chips in AI inference, 2026。
本仓库的相关讲义
- CS349D 第 7 讲:分块 prefill、解耦与 NVIDIA Dynamo(系统角度)。
- CS349D 前置讲 P4:排队论与性能评估。
- 前置讲 P5 §7.5:解耦式推理的 KV 搬运。
15. 下一讲预告 · Next lecture
第 6 讲(Zain Asgar)讲优化自回归 decode,阅读材料是 Orca(迭代级调度)和 Leviathan et al. 的投机解码。本讲的模拟器里已经用了”连续批处理”(每一步迭代都可以加入新请求、移走完成的请求)——第 6 讲会解释它为什么比”按请求批处理”好那么多,以及 Orca 的选择性批处理怎样处理长短不一的序列。投机解码这一讲只算了”能快多少”;第 6 讲会证明它为什么一点都不改变输出的分布(拒绝采样),并推导最优的草稿长度。