Lecture 5 · 解耦式推理:prefill / decode 分离与异构硬件 · Disaggregated Serving

Efficient ML Inference at Scale · Lecture 05。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

个人整理的学习笔记,非官方讲义;课程来源与整理说明见课程介绍。配套示例未逐一运行验证。

本篇目录

对应 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

学完这一讲,你应该能够:

  1. 用 TTFT、TPOT、SLO 达成率和 per-GPU goodput 描述一个推理服务的好坏,并解释为什么 goodput(而不是吞吐)才是成本的正确度量。
  2. 解释 prefill 和 decode 放在一起(共置)时的两个问题:互相干扰,以及资源和并行方式被绑在一起;知道分块 prefill 怎样缓解干扰、代价是什么。
  3. 用 M/D/1 排队模型分析 prefill 实例:为什么速率接近处理能力时 TTFT 会爆炸;两块 GPU 做 prefill 时张量并行和流水线并行各在什么速率下更好。
  4. 复述 DistServe 的放置算法(枚举并行配置 + 模拟器 + 二分查找 + 按流量复制)以及低带宽集群的变体;会算 KV cache 的搬运量和需要的网络带宽。
  5. 说清楚解耦什么时候值得、什么时候不值得:本讲的模拟发现,在理想化的每步时间模型下,分块 prefill 的共置系统是很强的对手。
  6. 把解耦推广到异构硬件:投机解码的草稿模型放到 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):

应用模型TTFTTPOT
聊天(ShareGPT)OPT-13B / 66B / 175B0.25 / 2.5 / 4.0 s0.1 / 0.15 / 0.2 s
代码补全(HumanEval)OPT-66B0.125 s0.2 s
摘要(LongBench)OPT-66B15 s0.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

代码:01_interference.py

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)里塞进一个长度为 LL 的新提示,放在同一步里算:

提示长度 LL只有 decodedecode + prefilldecode 变慢只有 prefillprefill 变慢
1287.70 ms7.70 ms1.0×6.61 ms1.17×
5127.70 ms14.90 ms1.9×14.07 ms1.06×
10247.70 ms28.19 ms3.7×27.36 ms1.03×
20487.70 ms55.48 ms7.2×54.66 ms1.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
520.5 ms7.6 ms14.1 ms6.8 ms
3037.5 ms14.9 ms33.7 ms7.1 ms
5064.3 ms36.1 ms59.0 ms7.4 ms
6092.8 ms100.5 ms87.6 ms7.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 TTFTP90 TPOT
不分块(prefill 优先)–48.5 ms21.6 ms
分块25658.8 ms7.5 ms
分块51254.4 ms11.0 ms
分块204850.6 ms13.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。两个好处:

  1. 没有干扰:decode 实例上没有 prefill 打断,TPOT 稳定;prefill 实例上没有 decode 抢资源,TTFT 只取决于 prefill 自己的排队。
  2. 各自配置:每个阶段可以选自己的 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 优先)130.330.3
只做 prefill162.2–
只做 decode1327.2–
5 块 prefill + 1 块 decode6311.151.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 ms34.960.91.7
代码补全提示约 300、输出约 100;TTFT ≤ 50 ms、TPOT ≤ 20 ms64.0485.87.6
摘要提示约 4000、输出约 200;TTFT ≤ 2 s、TPOT ≤ 15 ms8.311.31.4

共置系统里这个比例是写死的(每块 GPU 两种活都干);解耦以后可以按负载配。代价是比例只能取整数,而且负载一变,最优比例就变了。


4. 排队论:prefill 实例是一个 M/D/1 队列 · Queueing analysis

代码:03_queueing_prefill.py

4.1 M/D/1

解耦以后,prefill 实例只做一件事。如果提示长度相同(服务时间固定为 DD),一个提示就能吃满 GPU(所以不批处理、先到先服务),请求按泊松过程以速率 RR 到达,它就是排队论里的 M/D/1 队列(M:泊松到达;D:服务时间固定;1:一个服务台)。平均 TTFT(论文公式 1):

Avg TTFT=D+RD22(1−RD).\text{Avg TTFT} = D + \frac{R D^2}{2(1 - RD)} .

第一项是执行时间,第二项是排队时间。ρ=RD\rho = RD 是利用率。代码 03 演示 1(Llama-3.1-8B、H100、提示 512,D=14.07D = 14.07 ms):

利用率 ρ\rho公式:排队公式:平均 TTFT模拟:平均 TTFT
0.21.76 ms15.82 ms15.90 ms
0.57.03 ms21.10 ms21.19 ms
0.828.13 ms42.20 ms42.00 ms
0.963.30 ms77.37 ms65.14 ms
0.95133.63 ms147.70 ms100.86 ms

ρ≤0.8\rho \le 0.8 时公式和模拟吻合;越接近满负荷,模拟越偏低——队列要越久才能”涨”到稳态,有限的模拟时间看不到最坏的排队。关键的形状是:排队时间按 1/(1−ρ)1/(1-\rho) 增长。ρ=0.5\rho = 0.5 时排队是 DD 的一半,ρ=0.9\rho = 0.9 时是 4.5 倍。速率逼近处理能力时,延迟不是线性变差,而是爆炸。TTFT 的 SLO 越紧,prefill 实例能跑到的利用率越低,需要的 GPU 越多。

4.2 两块 GPU:张量并行还是流水线

两块 GPU 做 prefill,有两种用法(论文 §3.1):

  • 流水线并行(inter-op):每块 GPU 放一半的层。一个请求仍要 DD(依次走过两段),但每 D/2D/2 就能进一个新的。平均 TTFT(论文公式 2):D+RD24(2−RD)D + \frac{RD^2}{4(2 - RD)}。
  • 张量并行(intra-op):两块 GPU 一起算一个请求,快 KK 倍(1<K<21 < K < 2,因为要做 all-reduce、矩阵也变小了)。平均 TTFT(论文公式 3):DK+RD22K(K−RD)\frac{D}{K} + \frac{RD^2}{2K(K - RD)}。

代码 03 演示 2(Llama-3.1-70B,两块 H100,提示 512;D=121.5D = 121.5 ms,张量并行 64.6 ms,K=1.88K = 1.88):

速率 RR张量并行:公式 / 模拟流水线:公式 / 模拟更好的
269.4 / 69.4 ms125.7 / 125.8 ms张量并行
899.0 / 99.5 ms150.3 / 150.6 ms张量并行
13233.1 / 221.4 ms235.7 / 236.5 ms张量并行
14368.0 / 307.9 ms294.5 / 279.0 ms流水线

低速率时排队几乎为零,张量并行把执行时间缩短到 D/KD/K,更好;速率升高后排队主导,流水线的处理能力是 2/D2/D、张量并行只有 K/DK/D,流水线更好。交叉点约 13 请求/秒,和 KK 有关:K=1.5K = 1.5 时约 7.7,K=1.9K = 1.9 时约 13.4。TTFT 的 SLO 越紧,越偏向张量并行(只有它能缩短单个请求)。KK 取决于输入长度、模型结构和链路带宽——这就是 DistServe 不靠手算、而是用模拟器搜索并行方式的原因。

4.3 利用率、尾延迟与”扎堆”

SLO 通常按 P90 或 P99 定,而不是平均值。代码 03 演示 3(D=14.07D = 14.07 ms):

利用率到达方式平均 TTFTP99P99 / DD
0.9等间隔14.07 ms14.07 ms1.0
0.9泊松65.14 ms230.30 ms16.4
0.9扎堆(间隔的变异系数 2)272.24 ms1.04 s74.1

请求等间隔地来,利用率 0.9 也不排队——排队完全是随机性造成的。泊松到达时 P99 已是 DD 的 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

代码:02_goodput_workloads.py

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.429.0(预算 512)23.6(2P2D)1.16×0.81×
代码补全54.654.0(预算 2048)76.0(3P1D)1.39×1.41×
摘要3.06.6(预算 256)4.4(2P2D)1.50×0.67×

(单位:每块 GPU 每秒的请求数。)读这张表要很小心:

  1. 对”prefill 优先”的共置,解耦全胜(1.2–1.5 倍)。方向和论文一致,幅度小一些。
  2. 对分块 prefill,只有 TTFT 很紧的代码补全上解耦明显更好;聊天和摘要上分块 prefill 反而更好。原因有二:分块让 decode 搭上了 prefill 的便车(同一次读权重,这在我们的 roofline 模型里一点效率都不损失);而 4 块 GPU 的解耦只能在 1P3D、2P2D、3P1D 里选,总有一边的能力被浪费(§3.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%
解耦 3P1D99%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

代码:04_placement_and_kv.py

6.1 算法 1:高带宽集群

如果机器之间有高速网络(InfiniBand),prefill 实例和 decode 实例可以放在任意两台机器上。DistServe 的算法 1 分两层:

  1. 各自优化:对 prefill 和 decode 分别枚举所有可行的(张量并行度,流水线段数)。对每种配置,用模拟器 + 二分查找求它的 goodput(prefill 只看 TTFT、decode 只看 TPOT),选每块 GPU 的 goodput 最高的那个。
  2. 按流量复制:prefill 实例数 =⌈R/prefill goodput⌉= \lceil R / \text{prefill goodput} \rceil,decode 实例数 =⌈R/decode goodput⌉= \lceil R / \text{decode goodput} \rceil。

模拟器根据历史流量拟合的长度分布和到达过程生成请求,每步时间由 FLOPs 和访存量的模型给出(论文附录 A)。复杂度 O(NM2)O(NM^2)(NN:每个实例最多跨几台机器;MM:每台机器几块 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 × PP129 K token4.65(2.33)KV 太少
TP2 × PP24448 K11.65(2.91)27.9(6.96)
TP4 × PP14448 K11.23(2.81)40.0(10.00)
TP4 × PP281,327 K24.79(3.10)121.1(15.14)
TP8 × PP181,327 K20.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 实例第 ii 层的 KV 只给 decode 实例的第 ii 层。于是把模型按层切成流水线段,把同一段的 prefill 片段和 decode 片段放在同一台机器上,KV 全部走机器内的 NVLink。算法 2 枚举流水线段数,在一台机器内枚举每段的 prefill / decode 配置,用模拟器选最好的。论文的测量:OPT-175B 用这种放置,KV 传输不到总延迟的 0.1%,95% 以上的请求传输延迟小于 30 ms。

6.3 运行时的调度

DistServe 的运行时很简单:先到先服务;新请求发给队列最短的 prefill 实例,prefill 完成后发给负载最轻的 decode 实例。两个改进:

  • 减少流水线气泡:prompt 长短不一会让流水线各段的时间不均。prefill 实例按”凑够 LmL_m 个 token”组批(LmL_m 是让 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 大小prefillNVLinkInfiniBand(一张网卡)8 张网卡100 Gb 以太网
Llama-3.1-8B1000131 MB26.7 ms0.29 ms(1%)2.63 ms(10%)0.33 ms(1%)10.50 ms(39%)
Llama-3.1-70B(TP8)1000328 MB45.2 ms0.73 ms(2%)6.56 ms(14%)0.82 ms(2%)26.22 ms(58%)
Llama-3.1-70B(TP8)80002.62 GB339 ms5.83 ms(2%)52.43 ms(15%)6.56 ms(2%)209.73 ms(62%)

KV 的大小和 prefill 的计算量都 ∝\propto 提示长度,所以比例几乎不变:在 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

代码:05_heterogeneous_spec_decode.py

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 投机解码的期望收益

每轮草稿 γ\gamma 个 token,每个独立地以概率 α\alpha 被接受;第一个被拒绝之后的全部作废。被接受的草稿数的期望是 ∑i=1γαi\sum_{i=1}^{\gamma} \alpha^i(第 ii 个被接受要求前 ii 个全被接受);验证时目标模型还总会多给一个 token,所以每轮平均前进

1−αγ+11−α 个 token\frac{1 - \alpha^{\gamma + 1}}{1 - \alpha} \text{ 个 token}

(Leviathan et al. 2023)。代码 05 演示 1 用这个公式复现了 Gimlet 博客的接受率表(代码补全类负载,草稿模型针对任务微调过):

草稿长度 γ\gamma每个 token 的接受率 α\alpha被接受的 token(博客)公式 ∑αi\sum \alpha^i模拟
592.6%43.994.00
1093.4%77.007.00
2094.8%1211.9711.89
4096.2%2019.9419.87

草稿越长,被接受的越多,但效率越低:γ=40\gamma = 40 时只接受一半。所以草稿的代价决定了能草稿多长。

8.3 草稿放在哪

代码 05 演示 2 用一个参数化的玩具模型(目标模型按 gpt-oss-120b 的公开结构近似:36 层、128 个专家选 4 个,MXFP4;草稿模型 16 亿参数、FP8;固定开销和网络延迟是假设值)比较草稿放在 GPU 上和放在 SRAM 芯片上(单个用户,α=0.93\alpha = 0.93):

  • 目标模型普通 decode 一步 2.51 ms;草稿一个 token:GPU 上 0.90 ms,SRAM 芯片上 0.057 ms。
草稿放在目标模型的验证最优 γ\gamma相对普通 decode 的加速
GPUMoE(碰到的专家随 token 数增加)31.25×
GPU验证代价不变(稠密近似)51.81×
SRAM 芯片MoE82.17×
SRAM 芯片验证代价不变207.65×

三点:

  1. 草稿模型虽小,在 GPU 上生成一个 token 也要约 0.9 ms——它和目标模型一样受限于读权重和固定开销。草稿 5 个 token 就将近普通 decode 两步。所以 GPU 上草稿不能长,加速有限。
  2. 放到 SRAM 芯片上(150 TB/s 的片上带宽),草稿一个 token 只要几十微秒:多猜几个几乎不花钱,最优草稿长度变长、加速变大。这正是博客的结论:“草稿越便宜,越可以激进地草稿;也可以在同样延迟下用更大、更准的草稿模型。”
  3. MoE 目标模型的陷阱:验证 γ+1\gamma + 1 个 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):

批大小 BB注意力核心:强度投影 + FFN:强度
168.016
2568.0256
10248.01024

注意力核心的强度固定在组大小(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

  1. goodput 而不是吞吐:用户感受 TTFT 和 TPOT,服务方要在 SLO 达成率 ≥ 90% 的前提下最大化每块 GPU 的请求数——它直接对应每个查询的成本。
  2. 共置的两个问题:prefill 打断 decode(干扰),以及两个阶段被迫共用资源和并行方式(耦合)。分块 prefill 用 TTFT 换 TPOT,缓解干扰。
  3. 解耦:prefill 和 decode 放到不同的 GPU 上,消除干扰、各自配置、按负载配比;代价是搬 KV。DistServe 用模拟器 + 二分查找搜索每个阶段的并行配置,再按流量复制;低带宽集群时把同一段的 prefill 和 decode 放在同一台机器上。
  4. 排队论:prefill 实例是 M/D/1,排队时间按 1/(1−ρ)1/(1-\rho) 增长;低速率时张量并行好(缩短单个请求),高速率时流水线好(处理能力更高);尾延迟和到达的扎堆程度决定了要留多少余量。
  5. 诚实的比较:在理想化的 roofline 模型里,解耦对”prefill 优先”的共置全胜,但对分块 prefill 只在 TTFT 很紧时明显更好;结论高度依赖于每步时间的模型。
  6. 异构解耦:草稿模型放到 SRAM 芯片上,草稿几乎不花钱,可以草稿得更长;MoE 的验证会碰到更多专家;注意力和 FFN 的强度差一个数量级,也可以拆开放。

12. 常见误区 · Common pitfalls

  1. “吞吐高就是服务好。” 吞吐可以靠让用户等更久来刷高;要看在 SLO 下的 goodput(§1.2)。
  2. “解耦总是比共置好。” 对分块 prefill 的共置系统,解耦只在某些负载和 SLO 下更好;小规模时配比取整的浪费不小(§5、§9)。
  3. “解耦的代价主要是搬 KV。” 在有 InfiniBand / NVLink 的集群上 KV 搬运只占 prefill 时间的百分之几;更难的是配比、调度、突发和故障(§7、§9)。
  4. “利用率越高越省钱。” 利用率逼近 1 时排队时间爆炸,SLO 达成率会崩;要留余量(§4.1、§4.3)。
  5. “两块 GPU 做 prefill,张量并行总比流水线好(单个请求更快)。” 高速率时流水线的处理能力更高,平均 TTFT 反而更低(§4.2)。
  6. “分块 prefill 消除了干扰。” 它把干扰摊薄了,代价是 TTFT(§2.4)。
  7. “投机解码的草稿越长越好。” 被接受的比例随长度下降;草稿越贵,最优长度越短(§8.2、§8.3)。
  8. “MoE 的验证和普通一步一样便宜。” 验证多个 token 会碰到更多专家,读更多权重(§8.3)。
  9. “SLO 定好以后,最优配置就定了。” 负载分布、SLO 一变,最优的配比和并行方式就变;需要定期重新搜索(§5、§6.3)。
  10. “模拟器的数字就是真实系统的数字。” 模拟器的结论取决于每步时间的模型;DistServe 先校准到误差 < 2% 才用它做决策(§0.4、§5)。

13. 练习 · Exercises

带 💻 的题可以用本讲的模块验证。参考答案在每题后面。

练习 1(M/D/1)。一个 prefill 实例的服务时间 D=50D = 50 ms,请求以 16 个/秒到达。利用率是多少?平均 TTFT 是多少?

参考答案

ρ=16×0.05=0.8\rho = 16 \times 0.05 = 0.8。平均 TTFT =0.05+16×0.0522(1−0.8)=0.05+0.1== 0.05 + \frac{16 \times 0.05^2}{2(1 - 0.8)} = 0.05 + 0.1 = 150 ms:排队是执行时间的两倍。

练习 2(张量并行 vs 流水线)。D=100D = 100 ms,两块 GPU,张量并行的加速 K=1.6K = 1.6。速率 8 和 14 请求/秒时,用公式 2、3 比较两种方式的平均 TTFT。

参考答案

R=8R = 8:张量并行 62.5+31.25=62.5 + 31.25 = 93.75 ms,流水线 100+16.7=100 + 16.7 = 116.7 ms,张量并行更好。R=14R = 14:张量并行 62.5+218.75=62.5 + 218.75 = 281 ms,流水线 100+58.3=100 + 58.3 = 158 ms,流水线更好。张量并行的处理能力只有 K/D=16K/D = 16 请求/秒,14 已经很接近了。

练习 3(KV 带宽)💻。Llama-3.1-8B(每 token 128 KiB,BF16),平均提示 2000 个 token,每秒 20 个请求。一个请求的 KV 多大?用一张 InfiniBand 网卡(50 GB/s、5 µs)搬要多久?整体需要多少带宽?

参考答案

2000×131,072=2622000 \times 131{,}072 = 262 MB;5 μs+262 MB/50 GB/s≈5\ \mu s + 262\text{ MB} / 50\text{ GB/s} \approx 5.25 ms;整体 20×262 MB=5.2420 \times 262\text{ MB} = 5.24 GB/s ≈ 42 Gb/s。

练习 4(配比)。一块 GPU 只做 prefill 能扛 6 请求/秒,只做 decode 能扛 20 请求/秒(都满足各自的 SLO)。目标流量 60 请求/秒,需要几块 prefill GPU、几块 decode GPU?每块 GPU 的 goodput 是多少?如果共置时每块 GPU 只有 3 请求/秒,解耦好多少?

参考答案

prefill ⌈60/6⌉=10\lceil 60/6 \rceil = 10 块,decode ⌈60/20⌉=3\lceil 60/20 \rceil = 3 块,共 13 块,每块 4.6 请求/秒,是共置的约 1.54 倍。(如果目标流量是 50,就要 9 + 3 = 12 块,每块只有 4.2——取整的浪费。)

练习 5(投机解码)。α=0.8\alpha = 0.8、γ=4\gamma = 4:被接受的草稿数、每轮前进的 token 数各是多少?如果草稿一个 token 花目标模型一步的 10%、验证花一步,加速是多少?

参考答案

被接受 0.8+0.64+0.512+0.4096=0.8 + 0.64 + 0.512 + 0.4096 = 2.36;每轮前进 3.36。一轮的时间 4×0.1+1=1.44 \times 0.1 + 1 = 1.4 步,加速 3.36/1.4≈3.36 / 1.4 \approx 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 数线性增长)。只有提示短于 LmL_m(让 GPU 吃满的最短长度)时,才值得把几个合成一批,凑够 LmL_m 个 token(§6.3)。

练习 8(MoE 的验证)。128 个专家、每个 token 选 4 个,验证 11 个 token 时,一层平均碰到几个不同的专家?是普通一步的几倍?

参考答案

128×(1−(124/128)11)≈128 \times (1 - (124/128)^{11}) \approx 37.7 个,约是 4 个的 9.4 倍(专家部分的字节)。加上非专家部分和固定开销,整步的时间增加得少一些,但验证明显比普通一步贵。

练习 9(注意力 / FFN)。Llama-3.1-70B BF16 在 H100(屋脊点 295)上 decode,批大小多大时投影和 FFN 变成 compute-bound?注意力核心呢?

参考答案

投影和 FFN 的强度 ≈ 批大小,所以 B≳295B \gtrsim 295(代码 05 演示 3:B=1024B = 1024 时计算 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。

本仓库的相关讲义


15. 下一讲预告 · Next lecture

第 6 讲(Zain Asgar)讲优化自回归 decode,阅读材料是 Orca(迭代级调度)和 Leviathan et al. 的投机解码。本讲的模拟器里已经用了”连续批处理”(每一步迭代都可以加入新请求、移走完成的请求)——第 6 讲会解释它为什么比”按请求批处理”好那么多,以及 Orca 的选择性批处理怎样处理长短不一的序列。投机解码这一讲只算了”能快多少”;第 6 讲会证明它为什么一点都不改变输出的分布(拒绝采样),并推导最优的草稿长度。

→ 第 6 讲:优化自回归 decode

← 返回课程目录