Lecture 7 · Prefill 与 Decode:分块 prefill、解耦与 NVIDIA Dynamo · Disaggregation

AI Inference Infrastructure · Lecture 07。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

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

本篇目录

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

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

  1. 解释共置(colocated)时 prefill 与 decode 的相互干扰:长 prefill 让同批的 decode 卡顿(generation stall),decode 占用的算力又推迟了新请求的 prefill;以及两个阶段被迫共用同一种资源配置和并行方式。
  2. 说清 Sarathi-Serve 的两个技术:分块 prefill(无损,KV 读取变多)与不停顿的批(每步一个 token 预算,decode 优先);解释它为什么利用的是 decode 批里”免费”的算力;知道 token 预算在 TTFT 与 TBT 之间的取舍。
  3. 说清 DistServe 的思路:把 prefill 和 decode 放到不同的 GPU 上,各自选择资源和并行方式,以每 GPU 的 goodput 为目标搜索放置方案;能用 M/D/1 公式解释 prefill 实例为什么在低到达率时偏好张量并行、高到达率时偏好流水线并行。
  4. 估算 KV 传输的大小和时间,解释为什么解耦需要高带宽互连、GQA 让解耦容易得多、逐层传输(Splitwise)能把传输藏起来。
  5. 解释 Splitwise 的异构部署(prefill 用新卡、decode 用旧卡或限功耗)为什么可能更省钱,以及它什么时候不划算。
  6. 判断”分块 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 的请求比例
goodputDistServe:SLO 达成率 ≥ 90% 时能承受的最大到达率;“每 GPU 的 goodput” = 它 ÷ GPU 数
容量(capacity)Sarathi-Serve:满足 P99 TBT 的 SLO、且中位调度延迟 ≤ 2 s 时的最大到达率
token 预算 τ\tau分块 prefill 每一步最多处理多少个 token(decode 每个算 1 个)
xPyD解耦部署:x 个 prefill 实例、y 个 decode 实例
LmL_mDistServe:让 GPU 饱和的 prefill 长度,prefill 批凑到它附近
DD、KKDistServe 排队分析:单卡的 prefill 执行时间、张量并行的加速比(1<K<21 < K < 2)

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 批纯 decodedecode + 1 个 prefilldecode 被拖慢prefill 被拖慢
128326.90 ms6.91 ms1.00×1.13×
1024326.90 ms22.25 ms3.22×1.03×
4096326.90 ms90.15 ms13.06×1.01×
40961289.31 ms92.27 ms9.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 ms7.19 ms90.13 ms
混合批(Orca)90.15 ms7.19 ms91.21 ms
分块,预算 51212.19 ms7.20 ms106.70 ms
分块,预算 2567.32 ms7.20 ms141.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 慢瓶颈
07.70 ms—memory
2567.72 ms+0%memory
51211.64 ms+51%compute
102422.26 ms+189%compute
409690.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 是无损的:第 kk 块的 query 要和前面所有块以及本块的 key 做注意力,因果掩码保证它看不到后面的块,数学上与一次做完完全相同。代码 01 演示 1 在 tiny_llm 上核对(192 个 token 的提示):

块大小块数logits 最大差之后 40 个 tokenKV 读写 / 一次做完
192(不切)10相同1.0×
6433.9×10−73.9 \times 10^{-7}相同2.0×
16124.8×10−74.8 \times 10^{-7}相同6.5×
11921.0×10−61.0 \times 10^{-6}相同96.5×

运算量几乎不变(多出来的一点恰好是每块最后一个位置的 lm_head——tiny_llm 每次 forward 至少算一个位置的 logits,真实系统中间块不算)。代价在 KV 的读取:第 kk 块要把前面 k−1k-1 块的 KV 都读一遍,切成 NN 块共读约 N2/2N^2/2 块的量——这正是 DistServe 批评分块 prefill 时说的 O(N2)O(N^2) 次 KV 加载。Sarathi-Serve 的回应是:即使块很小,prefill 的注意力仍然是 compute-bound 的(每读一块 KV,块里所有 query 都用它),多读的 KV 大多被计算掩盖。

2.4 不停顿的批

有了分块 prefill,Sarathi-Serve 的调度器(论文 Algorithm 3)这样组每一个批:

  1. 根据 TBT 的 SLO 定一个 token 预算 τ\tau(每步最多处理多少个 token);
  2. 先把所有正在 decode 的请求放进批(每个 1 个 token);
  3. 再放之前没做完的 prefill,每个最多用掉剩下的预算;
  4. 最后才准入新请求,同样按剩下的预算切块。

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 P50TTFT P99TBT P99TBT 最大全部完成用时
prefill 优先(vLLM)26.69 ms161.07 ms129.94 ms366.83 ms29.43 s
混合批(Orca)28.34 ms167.61 ms70.33 ms156.84 ms27.21 s
分块,预算 1285.96 s12.02 s7.80 ms7.91 ms38.23 s
分块,预算 256123.19 ms465.98 ms8.93 ms8.99 ms26.55 s
分块,预算 51233.67 ms177.54 ms11.91 ms13.18 ms26.59 s
分块,预算 204828.12 ms150.80 ms43.60 ms49.46 ms27.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 SLOprefill 优先混合批分块 512只做 decode rdr_d组合 1/(1/rp+1/rd)1/(1/r_p + 1/r_d)组合 / 最好的共置
0.04 s5.817.468.3635.058.331.00×
0.03 s3.945.495.9024.957.591.29×
0.025 s2.993.693.8117.056.661.75×
0.02 s1.161.591.687.344.392.61×

(只做 prefill 时 rpr_p = 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 饱和的长度 LmL_m,只在请求比 LmL_m 短时才把几个请求凑成一批(凑到总长接近 LmL_m),长请求单独做。prefill 实例的批通常很小。

并行方式:排队论的视角。 两块 GPU 做 prefill,用张量并行(算子内)还是流水线并行(算子间)?解耦之后,一个只做 prefill、请求长度相同、FCFS、不批处理的实例就像一个 M/D/1 队列(泊松到达、确定的服务时间、一个服务台;前置讲 P4)。设单卡执行时间为 DD、到达率为 RR,平均 TTFT 为(论文公式 1)

TTFT‾=D+RD22(1−RD),\overline{TTFT} = D + \frac{R D^2}{2(1 - R D)},

第一项是执行时间,第二项是排队时间。2 路流水线:单个请求的延迟仍约为 DD,但最慢的一级只要 D/2D/2,处理能力翻倍(公式 2):

TTFT‾inter=D+RD24(2−RD).\overline{TTFT}_{inter} = D + \frac{R D^2}{4(2 - R D)}.

2 路张量并行:执行时间缩短到 D/KD/K(1<K<21 < K < 2,因为有通信),处理能力只提高 KK 倍(公式 3):

TTFT‾intra=DK+RD22K(K−RD).\overline{TTFT}_{intra} = \frac{D}{K} + \frac{R D^2}{2K(K - R D)}.

代码 04 演示 3 代入 DD = 0.1 s:

到达率流水线张量 KK=1.5张量 KK=1.7张量 KK=1.9单卡
2 req/s102.78 ms71.79 ms62.75 ms55.73 ms112.50 ms
8 req/s116.67 ms104.76 ms84.97 ms71.77 ms300.00 ms
11 req/s130.56 ms158.33 ms112.75 ms88.82 ms不稳定
13 req/s146.43 ms283.33 ms154.41 ms109.65 ms不稳定

到达率低时排队可以忽略,张量并行缩短了执行时间,更好;到达率高时排队成为主角,流水线的处理能力更高,反超张量并行。交叉点取决于 KK:KK = 1.5 时在 8–11 req/s 之间,KK = 1.7 时在 11–13 之间,KK = 1.9 时到 13 req/s 张量并行仍然更好。用 Lindley 递推模拟 20 万个请求,结果与公式吻合(RR = 8 时流水线 116.62 ms 对公式 116.67 ms)。DistServe 据此说:TTFT 的 SLO 越严、到达率越低,越该用算子内并行;KK 越小(通信越贵),张量并行的优势区越窄。

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 用”按 LmL_m 凑批”让各批的时间接近。

KV 传输。 一个请求的 KV 可能很大。代码 04 演示 1:

模型提示KV 大小NVLinkInfiniBand以太网 25 Gb/sprefill 时间
OPT-66B 规模(MHA)5121.21 GB5.04 ms30.20 ms483.20 ms86.41 ms(TP=4)
OPT-66B 规模40969.66 GB40.27 ms241.60 ms3.87 s696.78 ms
Llama-3-8B(GQA)51267.11 MB0.19 ms1.68 ms26.86 ms10.92 ms
Llama-3-70B(GQA)512167.77 MB0.47 ms4.20 ms67.13 ms33.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;最后按目标流量复制:n=⌈R/prefill 的 goodput⌉n = \lceil R / \text{prefill 的 goodput} \rceil、m=⌈R/decode 的 goodput⌉m = \lceil R / \text{decode 的 goodput} \rceil。复杂度 O(NM2)O(NM^2)(NN 是每个实例的节点数上限,MM 是每节点的 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 / 83.43 / 7.71 / 15.101.715 / 1.927 / 1.887
decode TP=2 / 4 / 80.25 / 10.23 / 86.140.126 / 2.559 / 10.767
共置 TP=8,分块 51211.371.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/s1 × TP4 + 1 × TP8 = 121 × TP8 = 8
10 req/s2 × TP4 + 1 × TP8 = 161 × TP8 = 8
50 req/s7 × TP4 + 1 × TP8 = 365 × TP8 = 40
100 req/s13 × TP4 + 2 × TP8 = 689 × TP8 = 72

小流量时取整的浪费很明显,解耦反而多用 GPU;流量大时浪费被摊薄,解耦才开始省 GPU。解耦的收益在规模上才显现。DistServe 在讨论里也承认,只有一两块 GPU 的资源受限场景,设计空间太小,非解耦的架构可能更合适。

3.6 在线调度

DistServe 的运行时(论文 §4.3)很简单:所有请求到一个中心控制器,FCFS;分给队列最短的 prefill 实例,prefill 完再分给负载最轻的 decode 实例。几个针对真实负载的改进:

  • 减少流水线气泡:prefill 批按 LmL_m 凑,decode 批以 LmL_m 为最大批大小,让各批的时间接近。
  • 应对突发:突发的请求会让大量 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 / 175B0.25 s / 0.1 s;2.5 s / 0.15 s;4.0 s / 0.2 sShareGPT(755 / 200)
代码补全OPT-66B0.125 s / 0.2 sHumanEval(171 / 98)
摘要OPT-66B15 s / 0.15 sLongBench(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 实例;按 LmL_m 凑批;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 时间串行:额外等待逐层:额外等待
51233.46 ms4.20 ms(13%)52.49 µs(0.2%)
8192528.66 ms67.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 SLO1 个 decode(H100)1 个 decode(A100)2 个 decode(A100)
0.035 s37.3 req/s,2,357 /美元·时37.3 req/s,2,871 /美元·时37.3 req/s,2,416 /美元·时
0.03 s37.3,2,35724.6,1,89437.3,2,416
0.025 s37.3,2,3573.7,2877.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.633.663.273.814.07(3P2D)1.07×
代码补全(0.125 s / 0.2 s)25.7724.7423.3824.7431.51(3P1D)1.22×
摘要(15 s / 0.15 s)3.603.905.314.064.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 同时乘以 ss:

ssSLO最好的共置最好的解耦解耦 / 共置
0.750.188 s / 0.075 s0.89(prefill 优先)2.92(3P2D)3.30×
1.00.25 s / 0.1 s3.81(分块 2048)4.07(3P2D)1.07×
1.50.375 s / 0.15 s4.70(分块 512)5.45(3P2D)1.16×
2.00.5 s / 0.2 s4.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)1P3D2P2D3P1D
长输出(提示 300 / 输出 600)244.5191.3518.1177.1
对话(755 / 200)188.364.1141.8232.1
长提示摘要(4000 / 50)30.08.022.437.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

  1. 共置时 prefill 和 decode 相互干扰:一个 4096 token 的 prefill 让同批 decode 慢 10–14 倍,造成生成停顿(代码 02);两个阶段还被迫共用资源和并行方式。TPOT 的平均值会掩盖停顿,P99 TBT 不会。
  2. Sarathi-Serve:decode 批里有免费的算力(顺带 256 个 prefill token 几乎不变慢,代码 01);分块 prefill 是无损的(代价是 KV 读取 O(N2)O(N^2)),不停顿的批先放 decode、再用剩余的 token 预算 做 prefill。预算 512 时 P99 TBT 从 130 ms 降到 12 ms、TTFT 相近、还更早做完;预算太小(小于同时 decode 的请求数)时 prefill 挤不进来(代码 02)。论文报告容量相对 vLLM 最多 2.6–5.6 倍。
  3. DistServe:把两个阶段放到不同的 GPU 上,用模拟器 + 二分查找为每个阶段选资源和并行方式,按流量复制,最大化每 GPU 的 goodput;prefill 实例的并行选择可以用 M/D/1 分析(低到达率偏好张量并行,高到达率偏好流水线,代码 04 演示 3);decode 实例要更多 GPU 常常是为了显存容量(代码 04 演示 4)。论文报告相对 vLLM 最多 7.4 倍的请求率。
  4. KV 传输:多头注意力的大模型一个请求就有 1 GB 以上,需要 NVLink 或高速 InfiniBand;GQA 小一个数量级;逐层传输(Splitwise)把额外等待从 prefill 时间的 13% 降到 0.2%(代码 04 演示 1–2)。
  5. Splitwise:token 阶段不需要新 GPU 的算力、对功耗上限不敏感,可以用更便宜或限功耗的硬件;我们的估算显示这是否划算取决于 TPOT 的 SLO(代码 04 演示 5)。
  6. 谁更好看条件:我们的模拟里,解耦相对最好的共置在 DistServe 的 SLO 下只高 1.07–1.22 倍,SLO 收紧时高到 3.3 倍,在 KV 大、显存紧的摘要负载上反而更差(0.78 倍);实例比例选错比不解耦还糟;小流量时整块分配让解耦多用 GPU(代码 03、04)。Dynamo 把解耦做成生产框架,并用 Planner 应对负载变化(不同负载的最优分配不同,代码 04 演示 6)。

9. 常见误区 · Common pitfalls

  1. “分块 prefill 会改变模型的输出。” 不会。因果掩码保证分块与一次做完数学上相同(代码 01 演示 1:生成的 token 完全相同)。它改变的只是 prefill 分几步做。
  2. “分块 prefill 没有代价。” 后面的块要重读前面块的 KV(O(N2)O(N^2)),块太小时 GPU 用不满、还有 tile 量化;预算太小时 prefill 挤不进来,TTFT 爆炸(代码 02 演示 3)。
  3. “TPOT 达标就说明没有卡顿。” TPOT 是平均值;一次 90 ms 的停顿在 200 个间隔里只让平均值涨 0.4 ms。要看 P99 TBT 或最大 TBT(代码 02 演示 2)。
  4. “解耦总比共置好。” 取决于 SLO、负载、显存和规模。在我们的模拟里,SLO 宽松时收益很小,KV 大、显存紧时解耦反而更差,小流量时整块分配还会多用 GPU(§5、代码 04 演示 4)。
  5. “解耦之后,prefill 和 decode 各用一半 GPU 就行。” 比例必须按负载搜索:代码补全要 3P1D,长输出负载要 2P2D;比例选错比不解耦还糟(代码 03 演示 2、代码 04 演示 6)。
  6. “decode 实例算得少,用小的张量并行度就够了。” decode 常常受限于显存容量:OPT-66B 在 2 块 A100 上只剩几 GB 放 KV,批做不大;用更多 GPU 是为了显存(代码 04 演示 4)。
  7. “KV 传输一定是解耦的瓶颈。” 在 NVLink 或高速 InfiniBand 上、配合逐层传输,它通常可以忽略(DistServe 报告 < 0.1% 的延迟);但在普通以太网上、对多头注意力的大模型,它确实是瓶颈(代码 04 演示 1)。
  8. “旧 GPU 做 decode 一定更省钱。” 只在 TPOT 的 SLO 足够宽松时成立;SLO 一紧,旧卡要用更多实例甚至达不到(代码 04 演示 5)。
  9. “用几百个请求跑一下就能测出最大可承受的负载。” 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 读取)。一个 NN 个 token 的提示切成 cc 个 token 一块(共 N/cN/c 块)。(a) 第 kk 块的注意力要读多少个 token 的 KV(含本块)?(b) 总共读多少?与一次做完相比是多少倍?(c) 用 tiny_llm 的数字核对:192 个 token、块大小 16 时,代码 01 演示 1 给出 6.5 倍。

参考答案

(a) 前面 k−1k-1 块加本块:k⋅ck \cdot c 个 token。(b) ∑k=1N/ckc=c⋅(N/c)(N/c+1)2≈N22c\sum_{k=1}^{N/c} k c = c \cdot \frac{(N/c)(N/c + 1)}{2} \approx \frac{N^2}{2c};一次做完读 NN 个,比值约 N2c+12\frac{N}{2c} + \frac12。(c) NN = 192、cc = 16:192/32+0.5=6.5192/32 + 0.5 = 6.5,与代码 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 的单卡执行时间 DD = 0.2 s,到达率 3 req/s。(a) 单卡的平均 TTFT 是多少?(b) 2 路流水线呢?(c) 2 路张量并行、KK = 1.6 呢?(d) 到达率升到 6 req/s 时,三者各是多少?

参考答案

(a) ρ=0.6\rho = 0.6:0.2+3×0.04/(2×0.4)=0.2+0.15=0.350.2 + 3 \times 0.04 / (2 \times 0.4) = 0.2 + 0.15 = 0.35 s。(b) 0.2+3×0.04/(4×1.4)=0.2+0.0214=0.2210.2 + 3 \times 0.04 / (4 \times 1.4) = 0.2 + 0.0214 = 0.221 s。(c) 0.2/1.6+3×0.04/(2×1.6×1.0)=0.125+0.0375=0.1630.2/1.6 + 3 \times 0.04 / (2 \times 1.6 \times 1.0) = 0.125 + 0.0375 = 0.163 s——张量并行最好。(d) 单卡 ρ=1.2\rho = 1.2,不稳定(队列无限增长);流水线 0.2+6×0.04/(4×0.8)=0.2750.2 + 6 \times 0.04 / (4 \times 0.8) = 0.275 s;张量并行 0.125+6×0.04/(2×1.6×0.4)=0.125+0.1875=0.31250.125 + 6 \times 0.04 / (2 \times 1.6 \times 0.4) = 0.125 + 0.1875 = 0.3125 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) 20×2000×327,68020 \times 2000 \times 327{,}680 字节 ≈ 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 数。从什么流量开始解耦更省?

参考答案
流量解耦共置
51×4 + 1×8 = 121×8 = 8
304×4 + 1×8 = 243×8 = 24
20026×4 + 3×8 = 12818×8 = 144

流量 30 左右两者持平,之后解耦更省(200 时省 11%)。理想的每 GPU goodput 是 1/(4/7.7+8/86)≈1.631/(4/7.7 + 8/86) \approx 1.63 对 11.4/8=1.4311.4/8 = 1.43,解耦高约 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 切分的注意力。

← 返回课程目录