对应 Maps to:CMU 11-868 / 11-968 Large Language Model Systems(Spring 2026,Lei Li),Lecture 26 · Mon 4/20 · Serving with Disaggregated Prefill-Decoding。主线 slides:Vikram Sharma Mailthody(NVIDIA Research): Inference at Scale: Opportunities and Challenges(llmsys-26-dynamo-vikram_mailthody.pdf,46 页);补充 slides:课表末尾 Hao Zhang(UCSD): Disaggregating Prefill and Decode for Goodput-optimized LLM Serving(llmsys-29-disaggregating_prefill_decode_hao_zhang.pdf,68 页)。 阅读材料:Zhong et al. (2024) DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving(OSDI);参考:Patel et al. (2024) Splitwise(ISCA)、Agrawal et al. (2024) Sarathi-Serve(OSDI);NVIDIA Dynamo 文档。 依据说明:两份 slides 分工明确。Hao Zhang 的 slides 讲 DistServe:服务 vs 推理、连续批处理的逐步演示、应用的 SLO(TTFT / TPOT)、吞吐 ≠ goodput、预填充计算受限 / 解码访存受限、同置造成的干扰与过度配置、并行策略的耦合、分离后 2P1D 达到 2 倍 goodput、两个核心问题(放置、通信)、高 / 低带宽集群的放置算法、KV 传输的代价、实测 2.0–4.48 倍、“连续批处理与分离并不矛盾”、分离的发展史、基于分离的系统(LMCache、DeepSeek-V3、Dynamo)。Vikram 的 slides 讲大规模推理:AI 工厂(xAI 的 10 万卡集群)、算力与电力趋势、推理与训练的不同、推理的工作流程与 KV 缓存、SGLang 的框架、SLO 与 goodput、帕累托曲线、规模化推理的挑战、Dynamo 的模块(分离部署、KV 感知路由、规划器、AIConfigurator、分层的 KV 内存管理、NIXL、Grove、容错)与开放问题。本讲义的”纸面 GPU”时间模型、同置 / 分块 / 分离三种部署的离散事件模拟器、goodput 的二分查找、放置算法的实现、在城市 LLaMA 上真的交接 KV、KV 路由 / 规划器 / 请求迁移的模拟,都是本讲义自己设计的,不代表课堂上讲了什么;工作负载的长度分布只取了 DistServe 图 7 的均值,形状是假设的。 配套代码 Code:
code/lecture26/(5 个脚本 +disagg.py;03 号复用第 22 讲的前向与第 19 讲的模型;都在 30 秒以内) 预计阅读时间:3 小时(含运行代码)
0. 本讲导航 · How to read this lecture
0.1 学习目标 Learning goals
学完这一讲,你应该能够:
- 用算术强度解释为什么预填充是计算受限的、解码是访存受限的;算出两个阶段在一张 A100 上的大致时间。
- 定义 TTFT、TPOT、SLO 达成率与 goodput,说明为什么”吞吐高”不等于”goodput 高”。
- 说出同置部署的两个问题:预填充与解码的干扰、两个阶段资源与并行策略的耦合;以及分块预填充为什么只能缓解、不能消除干扰。
- 解释分离部署怎样工作(要交接什么状态)、怎样选 xPyD 的比例和每个阶段的并行策略,以及 KV 传输什么时候便宜、什么时候昂贵。
- 复述 DistServe 的两个放置算法(高带宽 / 低带宽集群)和它用的 M/D/1 排队分析。
- 说出 NVIDIA Dynamo 的主要部件(分离部署、KV 感知路由、规划器、配置搜索、分层 KV 内存、传输库、容错)各解决什么问题。
0.2 先修与本讲范围 Prerequisites & scope
| 默认你已经会 Assumed | 本讲只给直觉,后面再展开 Previewed only |
|---|---|
| 连续批处理、RadixAttention、缓存感知的路由(第 22 讲) | KV 缓存的压缩、分层存储与非前缀复用(第 27 讲) |
| PagedAttention、抢占与重算(第 24 讲) | Mooncake 以 KV 为中心的架构(附加讲 A2) |
| 张量并行与流水线并行(第 16 讲);屋顶线模型(第 4 讲) | Kubernetes 上的调度细节(Grove) |
| 排队论的基本概念(到达率、利用率、排队时间;前置讲 P4) | 推测解码、扩散式推理在服务中的位置 |
0.3 本讲地图 Map
§1 推理的规模:AI 工厂;推理与训练有什么不同 (slides)
§2 两个阶段、两种时延:预填充 vs 解码;TTFT、TPOT、goodput ← 代码 01
§3 同置的问题:干扰、生成卡顿、分块预填充、过度配置与耦合 ← 代码 02
§4 分离部署:交接 KV;xPyD;三种应用上的 goodput ← 代码 03
§5 放置:M/D/1 与并行策略;KV 传输;DistServe 的两个放置算法 ← 代码 04
§6 NVIDIA Dynamo:路由、规划器、配置搜索、分层内存、传输、容错 ← 代码 05
§7 分离的历史与基于分离的系统;开放问题
§8 小结 §9 常见误区 §10 练习 §11 延伸阅读 §12 下一讲预告
0.4 本讲的实验设置
没有 GPU 也能研究”在 GPU 集群上怎样部署”——DistServe 本身就是这么做的:它用一个模拟器估计每种放置的 SLO 达成率(论文报告模拟器与真实系统的达成率相差 < 2%),在模拟器里搜索放置。本讲义照着这个思路写了 disagg.py:
- 时间模型:一次迭代的时间 = max(计算时间, 访存时间) ÷ 张量并行的加速比 + 固定开销。计算 = 2 × 参数量 × 新 token 数 + 注意力;访存 = 读一遍权重 + 读历史 KV + 写新 KV。规格用 A100 / H100 的公开标称值;“矩阵乘效率随 token 数上升”、“张量并行 2 路的加速比约 1.74”、“每次迭代 1 ms 固定开销”是假设(DistServe 附录 A 用实测拟合这些常数)。
- 模拟器:同置(vLLM 式预填充优先、Sarathi 式分块预填充)、分离(n 个预填充实例 + k 个解码实例,各自的 tp / pp,中间按链路带宽传 KV)。每个请求记下第一个和最后一个 token 的时间,算 TTFT 与 TPOT。
- 工作负载:泊松到达;三个应用的长度均值取自 DistServe 图 7(ShareGPT 输入 755 / 输出 200、HumanEval 171 / 98、LongBench 1738 / 91),分布形状是假设的;SLO 取自 DistServe 表 1。
- goodput:二分查找”SLO 达成率 ≥ 90% 的最大到达率”,除以 GPU 数。
所以本讲义里的绝对数字(多少请求 / 秒)只说明趋势;每个脚本的观察里都会并列论文的实测数字。03 号脚本的第 1 部分是例外:它在第 19 讲的城市 LLaMA 上真的把 KV 从一个”实例”交给另一个。
1. 推理的规模 · Inference at scale
1.1 AI 工厂
Vikram 的 slides 从一个数字开始:xAI 的 Colossus 集群从空厂房到产出 token 用了不到 122 天,10 万张 GPU(正在扩到 20 万),功率超过 150 MW;4 个数据大厅、每个 2.5 万张卡,液冷机柜里每柜 64 张 GPU。用于训练 LLM 的算力每年增长约 4.2 倍(Epoch AI),而电力会成为 2030 年以后扩展训练的首要限制——这也是 GB200 NVL72 这类高密度机柜追求能效的原因。
这些”AI 工厂”产出的是 token。训练是一次性的大作业,推理则是每天都在发生、随用户规模增长的成本。slides 的一句话概括了本讲的立场:好模型 + 好系统 = 成功。
1.2 推理与训练有什么不同
slides 把两者对照着列出来:
| 训练 | 推理 | |
|---|---|---|
| 作业形态 | 一个大作业,有中心协调者 | 大量小作业,需要快速扩缩容 |
| 关心的事 | 并行策略、容错、强 / 弱扩展、拓扑感知、数据预处理、优化器 | 飞行中批处理(in-flight batching)、KV 缓存优化、量化、并行策略、用户自定义的调度 |
| 阶段 | 前向 + 反向,形状固定 | 预填充(P)+ 解码(D),两者性格迥异 |
推理的一个典型请求(slides 第 15–16 页的 CUDA 编程助手例子):系统提示(“你是 CUDA 专家……”)+ 用户提示 → 输出。系统提示在很多用户之间共享;输入可以长到 32K token,而且趋势是越来越长(写进更多”提示规则”能提高回答质量)。处理输入提示占了总时间的相当大一部分,甚至是主要部分。
计算上(slides 第 17–21 页,引自 PMPP 第 5 版):每层有 5 个矩阵乘;预填充时它们是矩阵 × 矩阵(GEMM);解码时每一步只给输入矩阵多加一行,借助 KV 缓存只算这一行——GEMM 退化成矩阵 × 向量(GEMV),从计算受限变成访存受限,同时 KV 缓存需要高效管理(PagedAttention、RadixAttention)。slides 用 SGLang 展示了一个推理框架的组成:API 服务器、分词器、调度器、去分词器、模型、带基数树的 KV 内存管理器——以及”用 Kubernetes 简单地多复制几份”这种最朴素的扩展方式。本讲就是要回答:只是复制,够吗?
2. 两个阶段、两种时延 · Prefill vs decode, TTFT vs TPOT
2.1 直觉:一个是”读整本书”,一个是”一个字一个字地写”
预填充一次处理整个提示:几百上千个 token 同时穿过模型,每个权重被复用几百上千次——这是 GPU 最擅长的大矩阵乘。解码每一步每个请求只处理 1 个 token:为了这 1 个 token,要把几十 GB 的权重和这个请求的全部 KV 从显存里读一遍——GPU 的算力几乎全闲着,时间都花在搬数据上。
01 号脚本用时间模型把这个直觉变成数字(OPT-13B,一张 A100):
| 预填充(1 个请求,输入 L) | L = 16 | 128 | 512 | 2048 |
|---|---|---|---|---|
| 时间 ms | 16.9 | 27.7 | 74.0 | 264.5 |
| 吞吐 token/s | 944 | 4,617 | 6,922 | 7,744 |
| 算术强度 FLOPs/字节 | 16 | 128 | 508 | 1986 |
A100 的屋脊点是 312 TFLOPS / 2.039 TB/s ≈ 153 FLOPs/字节(DistServe 附录 A 写的是 156)。预填充的算术强度随 L 线性增长,L ≈ 150 以后就越过屋脊点、变成计算受限;吞吐在 L ≥ 1024 时达到饱和值的 90%。DistServe 把这个”一个请求就能把 GPU 算满”的长度叫 L_m(论文里 13B 模型在 A100 上约 512):超过它,再往批里加请求只会让每个请求都等更久。所以预填充实例的批很小,优化目标是 TTFT。
| 解码(上下文 512,批大小 B) | B = 1 | 16 | 128 | 256 |
|---|---|---|---|---|
| 一步的时间 ms | 17.2 | 21.1 | 49.9 | 82.9 |
| 吞吐 token/s | 58 | 760 | 2,564 | 3,088 |
| 算术强度 | 1.0 | 12.9 | 42.4 | 50.6 |
B = 1 时 17 ms 几乎全花在读 26 GB 权重上,吞吐只有 58 token/s(2048-token 预填充的 0.8%)。批越大越划算:B = 128 时吞吐是 B = 1 的 44 倍,每步时间只多 2.9 倍。但批不能无限大——批里所有请求的 KV 每步都要读一遍,算术强度最多只到 50 左右;而且一张卡放下权重后只剩约 59,000 个 token 的 KV(OPT-13B 每个 token 800 KiB),上下文 512 时最多约 115 个请求。解码必须攒大批,批的上限是显存。
为什么这一节重要? 两个阶段”想要”的东西相反:预填充要小批、快速完成;解码要大批、显存越多越好。下面所有的设计都从这个矛盾出发。
2.2 两种时延与 SLO
一个请求的时间线:到达 → 排队 → 预填充(产生第一个 token)→ 若干个解码步(每步一个 token)→ 结束。于是有两个时延指标:
- TTFT(time to first token,首 token 时延):从到达到第一个 token。由预填充(和它前面的排队)决定。
- TPOT(time per output token,每个输出 token 的时延;也叫 TBT / ITL,token 间时延):除第一个 token 外,平均每个 token 的时间 = (最后一个 token 的时间 − 第一个 token 的时间) / (输出长度 − 1)。由解码决定。
- 端到端时延 = TTFT + TPOT × (输出长度 − 1)。01 号脚本:一个输入 755、输出 200 的聊天请求在空闲 GPU 上 ≈ 0.104 + 0.0307 × 199 ≈ 6.2 秒。
不同应用对两者的要求不同(DistServe 表 1;Hao Zhang slides 第 30 页):
| 应用 | 模型 | TTFT SLO | TPOT SLO | 为什么 |
|---|---|---|---|---|
| 聊天 | OPT-13B / 66B / 175B | 0.25 / 2.5 / 4.0 秒 | 0.1 / 0.15 / 0.2 秒 | 首 token 要快;TPOT 只要快过人的阅读速度(每分钟约 250 个词) |
| 代码补全 | OPT-66B | 0.125 秒 | 0.2 秒 | 实时的编程助手,两个都严 |
| 摘要 | OPT-66B | 15 秒 | 0.15 秒 | 能等首 token,但要求生成快 |
slides 的说法:聊天的 TPOT 按人的阅读速度定(P99 250 ms);给程序读的数据生成要严得多(P99 35 ms)。
2.3 吞吐 ≠ goodput
吞吐(每秒完成多少请求)是”每个请求花多少钱”的代理指标(slides 引用黄仁勋 GTC’25 的说法)。但用户在乎的是满足时延要求的请求。goodput = 每秒完成的、满足 SLO 的请求数。DistServe 的精确定义:SLO 达成率 ≥ 90% 的前提下系统能承受的最大到达率;除以 GPU 数就是 per-GPU goodput,它直接对应”每个合格请求的成本”。
01 号脚本第 4 部分(OPT-13B 聊天,一张 A100,vLLM 式同置):
| 到达率 请求/秒 | 0.5 | 1 | 2 | 4 | 6 | 8 |
|---|---|---|---|---|---|---|
| 吞吐 请求/秒 | 0.56 | 1.09 | 2.10 | 3.33 | 3.83 | 4.11 |
| SLO 达成率 | 92% | 90% | 88% | 71% | 5% | 1% |
| P90 TTFT 秒 | 0.22 | 0.25 | 0.26 | 0.46 | 37.7 | 81.0 |
到达率越高,批越大,吞吐一路上升;可满足 SLO 的比例在到达率 4 以后断崖式下跌——队列越排越长,TTFT 爆掉。slides 的例子:一个”高吞吐”系统吞吐 10 rps,goodput 只有 3 rps。以吞吐为目标优化的系统,可能 goodput 很差。
3. 同置的问题 · What goes wrong with colocation
3.1 连续批处理回顾
Hao Zhang 的 slides 先用十几页逐步演示了连续批处理(ORCA,第 22 讲):静态批处理要等批里所有请求都结束,提前结束的请求空占位置、新请求进不来;连续批处理每一步都让结束的请求退出、新请求加入。它把吞吐提高了约 10 倍(相对静态批处理),已经是所有推理引擎的标配。
问题在于:新请求”加入”意味着它的预填充和其他请求的解码被放进同一次迭代(或者轮流占用同一块 GPU)。这就是同置(colocation)。
3.2 干扰:一次迭代被更慢的那部分拖住
02 号脚本第 1 部分复现了 DistServe 图 2 的形状(OPT-13B,一张 A100):
| 输入长度 | 解码批 | 只有解码 ms | + 1 个预填充 ms | 解码变慢 | 预填充变慢 |
|---|---|---|---|---|---|
| 128 | 32 | 19.0 | 31.6 | 1.66× | 1.14× |
| 128 | 128 | 27.8 | 43.0 | 1.55× | 1.55× |
| 1024 | 32 | 33.4 | 140.4 | 4.20× | 1.03× |
| 1024 | 128 | 82.8 | 152.2 | 1.84× | 1.12× |
一个 1024-token 的预填充混进 32 人的解码批,这一步从 33 ms 变成 140 ms——批里每个请求的这个 token 都晚到了 4 倍多。反过来,批越大、上下文越长,预填充也被拖慢。一次迭代的时间由更慢的那部分决定,两种负载谁都没法单独优化。
“那就别放在一个迭代里,轮流跑?“——vLLM 当时的默认调度就是预填充优先:有新请求就先跑一个只含预填充的迭代。结果是正在解码的请求整体停下来等。DistServe §2.3 指出:分开调度也解决不了,解码要排在预填充后面、预填充也要排在解码后面;无论优先哪个,另一个的时延都会受损。
3.3 生成卡顿
用户的直观感受是什么?02 号脚本第 3 部分记录了每个输出 token 的时间(OPT-13B 聊天,到达率 1.5):
| 部署 | TBT P50 ms | TBT P99 ms | 最长 ms | 超过 P50 三倍的比例 |
|---|---|---|---|---|
| 同置:预填充优先(vLLM) | 20.7 | 146.1 | 447 | 2.8% |
| 同置:分块预填充,块 512(Sarathi) | 20.5 | 75.1 | 77 | 4.2% |
| 分离:2P1D | 20.0 | 23.4 | 47 | 0.0% |
预填充优先时,输出偶尔会卡住近半秒(一次塞了几千个 token 的预填充迭代)。Sarathi-Serve 管这叫 generation stall(生成卡顿)。
3.4 分块预填充:缓解,而非消除
Sarathi-Serve(Agrawal et al., OSDI’24)和 DeepSpeed-MII 的办法:把长提示切成块,每次迭代给一个固定的 token 预算,先放所有正在解码的请求,剩下的预算切一块提示来预填充(“捎带”,piggyback)。解码从此不会停(stall-free),上表的 P99 从 146 ms 降到 75 ms。Sarathi-Serve 报告在 Mistral-7B 上比 vLLM 多服务 2.6 倍、Falcon-180B 上 5.6 倍。
但 DistServe 指出它有两个本质的代价(02 号脚本第 4 部分全都看得到):
- TTFT 变差:块小了,一个提示要好几次迭代才做完;块大了,捎带的空间就少了,又回到卡顿。没有一个块大小能两全。
- 重复读 KV:第 k 块要把前 k − 1 块的 KV 再读一遍,总共 O(N²) 块。2048-token 的提示切成 256 的块,要重复读 7,168 个 token 的 KV(提示长度的 3.5 倍)。
| 工作负载 | 调度 | P90 TTFT 秒 | P90 TPOT ms | goodput 请求/秒 |
|---|---|---|---|---|
| 聊天(OPT-13B) | 不分块 | 0.25 | 25 | 1.60 |
| 分块 512 / 2048 | 0.30 / 0.25 | 24 / 24 | 0.26 / 1.23 | |
| 摘要(OPT-66B,tp 4) | 不分块 | 0.91 | 111 | 1.16 |
| 分块 256 / 512 / 2048 | 1.50 / 1.08 / 0.89 | 75 / 86 / 98 | 1.79 / 2.10 / 1.29 |
聊天的 TTFT 只有 0.25 秒,分块的额外开销直接让长提示超标——分块反而更差;摘要的 TTFT 宽松、TPOT 严,分块正好对症,goodput 提高 1.8 倍。这与 DistServe 的实测吻合:DeepSpeed-MII 在 13B 聊天上不如 vLLM,在长输入的摘要上比 vLLM 好。分块只是在 TTFT 与 TPOT 之间换来换去,干扰本身还在。
3.5 过度配置与耦合
干扰的直接后果是过度配置(Hao Zhang slides 第 39 页的 x1 → x4):为了同时满足两个 SLO,只好多买 GPU、让每张卡都轻载。
第二个问题更隐蔽:并行策略的耦合(slides 第 40 页)。预填充的 TTFT 紧,喜欢张量并行(缩短单个请求的执行时间);解码的 TPOT 松一些但要大批,喜欢流水线并行或数据并行(扩大批和吞吐)。同置时两个阶段只能共用一种切法,只能迁就更严格的那个。
02 号脚本第 2 部分复现了 DistServe 图 1 的论证(OPT-13B,输入 512、输出 64,一张 A100):同置时 goodput 4.74 请求 / 秒;同一张卡只做预填充能撑 10.89,只做解码能撑 30.85。两个阶段单独都比合在一起强得多。
4. 分离部署 · Disaggregating prefill and decode
4.1 想法
既然两个阶段合在一起互相拖累,就把它们放到不同的 GPU 上:预填充实例只做预填充,算完把 KV 缓存交给解码实例;解码实例只做解码(连续批处理照旧)。这立刻带来两个好处(Hao Zhang slides 第 44 页):
- 干扰消失:解码实例上没有预填充(上表 2P1D 的 P99 TBT 只有 23 ms)。
- 问题一分为二:预填充实例只管 TTFT,解码实例只管 TPOT;每个阶段各选最合适的并行策略和资源量。
“实例”(instance)指持有一份完整模型权重的一组 GPU。分离后两种实例各有一份权重;因为解码的 GPU 利用率低,通常让多个预填充实例对应一个解码实例,把解码的批攒大。
代价也有两个:KV 缓存的传输;以及”每张 GPU 的 goodput”变成一个牵涉工作负载、SLO、并行策略、资源配比、网络带宽的优化问题(slides 第 48 页)。§5 回答它们。
4.2 要交接的到底是什么?
03 号脚本第 1 部分在第 19 讲的城市 LLaMA 上真的做了一遍:预填充”实例”算出 KV 和第一个 token,用 torch.save 序列化成字节(模拟网络传输),解码”实例”(另一份权重)反序列化后接着生成。
| 请求 | 提示长度 | KV 字节(公式) | 传输字节 FP32 | 传输字节 FP16 | 贪心:FP32 / FP16 交接 | 采样:带 RNG 状态 |
|---|---|---|---|---|---|---|
| 0 | 33 | 67,584 | 69,225 | 35,433 | 相同 / 相同 | 相同 |
| 2 | 39 | 79,872 | 81,513 | 41,577 | 相同 / 相同 | 相同 |
交接之后的输出与”一个实例从头做到尾”逐 token 相同:KV 缓存(加上第一个 token)就是预填充留给解码的全部状态;采样时还要带上这个请求的随机数生成器状态。用 FP16 传,字节数减半,输出也没变——KV 可以有损地传输和存储,这是第 27 讲的出发点。
真实系统里 KV 不经过序列化:DistServe 用 NCCL 跨节点传、用异步 cudaMemcpy 在节点内传,不阻塞 GPU 计算;而且是解码实例来”拉”(pull)——预填充实例算完就把 KV 留在自己的显存里当缓冲区,解码实例有空位时再取。这样突发流量不会把解码实例的显存冲爆,两种实例各按自己的节奏工作。
4.3 2P1D:每张 GPU 两倍的 goodput
DistServe 图 1 的论证(Hao Zhang slides 第 45–47 页):既然一张卡只做预填充能撑 5.6 rps、只做解码能撑 10 rps,那么 2 张卡做预填充 + 1 张卡做解码就能撑约 10 rps,每张 GPU 3.3 rps——是同置(1.6 rps)的 2.1 倍。最简单的分离就能让每张 GPU 的 goodput 翻倍。
02 号脚本的纸面模型:同置 4.74,2P1D 实际模拟 24.21 请求 / 秒、每张 GPU 8.07,是同置的 1.7 倍(2P1D 比 2 × 单卡预填充还高一点:两个预填充实例共用一个队列、谁空谁接,排队更短)。
比例怎么选?03 号脚本第 2 部分固定 6 张卡、每个实例 1 张卡:
| 比例 | 1P5D | 2P4D | 3P3D | 4P2D | 5P1D |
|---|---|---|---|---|---|
| 输入 512 / 输出 64 | 10.89 | 24.21 | 38.56 | 51.15 | 30.84 |
| 聊天 | 1.88 | 8.54 | 17.40 | 15.50 | 7.38 |
最优比例取决于两个阶段的负担和各自的 SLO——没有通用的比例。比例选错,多出来的 GPU 就闲着;流量的组成变了,最优比例也跟着变(§6.3 的规划器)。
4.4 三种应用上的结果
03 号脚本第 3 部分在三种应用上比较三种部署(每张 GPU 的 goodput,请求 / 秒;“分离”一列是按算法 1 选配置、整体模拟 n 个预填充实例 + k 个解码实例、两个 SLO 同时达标 ≥ 90% 的结果):
| 应用 / 模型 | 同置(vLLM) | 分块(最好的块) | 分离 | 分离的放置 | vs 同置 |
|---|---|---|---|---|---|
| 聊天 / OPT-13B | 1.60 | 1.23 | 4.13 | 2×P(tp2, pp4) + 3×D(tp2, pp2) | 2.6× |
| 聊天 / OPT-66B | 0.68 | 0.84 | 0.95 | 1×P(tp1, pp8) + 1×D(tp4, pp2) | 1.4× |
| 代码补全 / OPT-66B | 0.59 | 0.65 | 1.84 | 4×P(tp8, pp1) + 1×D(tp4, pp2) | 3.1× |
| 摘要 / OPT-66B | 0.29 | 0.53 | 0.69 | 2×P(tp1, pp8) + 1×D(tp4, pp2) | 2.4× |
DistServe 的实测(4 个节点 × 8 张 A100):比 vLLM 高 2.0–4.6 倍(聊天)、5.7 倍(代码补全)、4.3 倍(摘要),比 DeepSpeed-MII 高 1.6–7.4 倍;Hao Zhang 的 slides 给的汇总是 2.0–4.48 倍。纸面模型的倍数小一些,但三点一致:分离最好;两个阶段选了完全不同的并行策略(代码补全的 TTFT 只有 0.125 秒,预填充选了张量并行 8 路;摘要的 TTFT 宽松,预填充选了流水线 8 段);分块在长输入上帮助最大、在 TTFT 很紧时帮倒忙。
DistServe 的另一种比法是 SLO scale:固定流量,看系统能承受多严格的 SLO。03 号脚本第 4 部分:每张 GPU 1 请求 / 秒时,同置只能承受表 1 的 SLO(0.99 倍),分离能把两个 SLO 同时收紧到 0.59 倍(论文:比 vLLM 严 1.8–3.2 倍,摘要上 12.6 倍)。省 GPU 和给用户更快的响应,是同一枚硬币的两面。
4.5 来回折腾?
Hao Zhang slides 第 57 页提了一个好问题:连续批处理把预填充和解码合到一起,分离又把它们拆开——是不是在来回折腾?不是。连续批处理的目标是 GPU 利用率,也就是吞吐;分离的目标是满足 SLO 的吞吐,也就是 goodput。而且连续批处理的两条关键洞见在分离后仍然成立:注意力与 MLP 分开批处理(选择性批处理);结束的请求尽快退出、新请求尽快加入(解码实例内部仍是连续批处理)。
DistServe 自己也说了分离不适合的场景(§7):只追求吞吐的离线任务(没有时延要求,分块预填充能把每个迭代都填到计算饱和);资源很少(一两张卡,没有拆分的空间)。
5. 放置:并行策略与 KV 传输 · Placement
5.1 预填充实例是一个 M/D/1 队列
分离后,预填充实例的行为非常规整:输入长度相同时,每个请求的执行时间是确定的 D;一个请求就能把 GPU 算满,所以一个一个按到达顺序做。泊松到达 + 确定服务时间 + 一个服务台 = M/D/1 队列,平均 TTFT 有闭式解(DistServe 式 (1)):
第一项是执行时间,第二项是排队时间(R 是到达率,要求 RD < 1)。2 张卡时有两种用法:
- 流水线并行 2 段(inter-op):单个请求的时间不变(≈ D),但每段只占 D/2,“服务台”每 D/2 就能接下一个:Avg TTFT = D + R D² / (4(2 − R D))(式 (2))。
- 张量并行 2 路(intra-op):单个请求变快到 D/K(K < 2 是通信损耗后的加速比),但仍是一个服务台:Avg TTFT = D/K + R D² / (2K(K − R D))(式 (3))。
04 号脚本第 1 部分(OPT-66B,2 张 A100,输入都是 512):流水线 D = 371 ms,张量 215 ms(K = 1.73)。
| 到达率 | 0.5 | 1 | 2 | 3 | 4 | 4.5 | 5 |
|---|---|---|---|---|---|---|---|
| 流水线:模拟 / 式 (2) | 0.381 / 0.381 | 0.391 / 0.392 | 0.423 / 0.426 | 0.475 / 0.488 | 0.593 / 0.638 | 0.758 / 0.841 | 1.30 / 1.57 |
| 张量:模拟 / 式 (3) | 0.228 / 0.227 | 0.243 / 0.244 | 0.289 / 0.295 | 0.388 / 0.408 | 0.749 / 0.864 | 1.96 / 3.21 | 27.4 / ∞ |
低到达率时排队很少,执行快的张量并行赢;到达率约 4 以后,排队主导,容量更大的流水线并行赢(张量并行最多撑 1/0.215 = 4.66 请求 / 秒,流水线 2/0.371 = 5.39)——这正是 DistServe 图 4a。结论:预填充的并行策略取决于到达率和 TTFT 的 SLO(SLO 越紧越偏向张量并行;K 越小,张量并行越不划算),需要搜索而不是一条规则。
解码实例的分析类似(DistServe §3.2、图 5):张量并行降低每一步的时间,但收益递减;流水线并行几乎线性地提高吞吐。TPOT 紧时必须用张量并行,此外用流水线并行扩吞吐。模型放得进一张卡时,复制(数据并行)也是一个选项:N 个副本把 R 换成 R/N。
5.2 KV 传输贵不贵?
04 号脚本第 2 部分:
| 模型 | 每个 token 的 KV | 512-token 请求 | 10 请求/秒 要的带宽 |
|---|---|---|---|
| OPT-13B(多头注意力) | 0.78 MiB | 0.39 GiB | 34 Gb/s |
| OPT-66B | 2.25 MiB | 1.12 GiB | 97 Gb/s |
| OPT-175B | 4.50 MiB | 2.25 GiB | 193 Gb/s |
| LLaMA-3.1-70B(GQA,8 组 KV) | 0.31 MiB | 0.16 GiB | 13 Gb/s |
DistServe §3.3 的例子:OPT-66B 一个 512-token 请求的 KV 约 1.13 GiB,10 请求 / 秒就要约 90 Gb/s。OPT 系列是多头注意力,KV 特别大——DistServe 特意选它”给传输施加足够的压力”;GQA / MQA 的现代模型压力小一个数量级。
传一个 OPT-66B 的 512-token 请求:
| 链路 | 单方向带宽 | 时间 | 相当于几个解码步(tp 4、批 32,约 43 ms) |
|---|---|---|---|
| NVLink(A100 节点内) | 300 GB/s | 4 ms | 0.09 |
| InfiniBand 800 Gb/s | 100 GB/s | 12 ms | 0.28 |
| PCIe 4.0 x16 | 32 GB/s | 38 ms | 0.87 |
| 以太网 25 Gb/s | 3.1 GB/s | 387 ms | 8.9 |
在节点内的 NVLink 或现代的 InfiniBand 上,传输比一个解码步还短,可以忽略(slides 第 55 页:即使 175B 模型走 PCIe,传输时间也小于一个解码步)。但如果只有 25 Gb/s 的跨节点网络,一条链路每秒只能传一两个请求的 KV。另外,长上下文时比例反而变好:KV 随长度线性增长,预填充的计算随长度超线性增长(DistServe §7)。
5.3 算法 1:高带宽集群
带宽足够时,两个阶段互不牵制,可以分开优化再复制(DistServe 算法 1):
对每个 intra-op(张量并行度)∈ {1, …, M},每个 inter-op(流水线段数):
如果模型切开后放得下:
用模拟器 + 二分查找求"只看 TTFT 的 goodput",更新每张 GPU 最好的预填充配置
用模拟器 + 二分查找求"只看 TPOT 的 goodput",更新每张 GPU 最好的解码配置
n = ⌈R / 预填充配置的 goodput⌉,m = ⌈R / 解码配置的 goodput⌉
复杂度 O(N·M²)(N 是一个实例最多跨几个节点,M 是每个节点的卡数)。模拟器要知道工作负载:DistServe 从历史请求里拟合长度分布、重新采样,用分析式的时延模型(附录 A:预填充 T = C₁(4th² + 2thm) + C₂·3ht₂/b + C₃,解码 T = C₄(4h² + 2hm) + C₅·3ht,常数靠测量拟合)计时。
04 号脚本第 3 部分(聊天,OPT-66B):
| 预填充配置 | 每张 GPU | 解码配置 | 每张 GPU |
|---|---|---|---|
| tp 1, pp 8 | 1.99 | tp 4, pp 2 | 1.92 |
| tp 1, pp 4 | 1.84 | tp 8, pp 1 | 1.78 |
| tp 2, pp 4 | 1.75 | tp 2, pp 4 | 1.64 |
| … | … | ||
| tp 8, pp 1 | 0.93 | tp 1, pp 2 | 0.17 |
两个阶段的最优配置截然不同:聊天的 TTFT(2.5 秒)宽松,预填充偏爱流水线并行(吞吐高);解码用张量并行压低每一步、用流水线扩大批。最好的解码配置是最差的 11.6 倍——选错配置的代价很大。按 R = 10 复制(各 1 个实例、16 张卡),整体模拟的达成率 100%:两个阶段的 SLO 各自满足,拼起来不会互相破坏。
5.4 算法 2:低带宽集群
DistServe 的测试床跨节点只有 25 Gb/s。04 号脚本第 4 部分:同样的放置,KV 从 800 Gb/s 换成 25 Gb/s,每张 GPU 的 goodput 从 0.952 掉到 0.105——每个聊天请求平均约 1.69 GB 的 KV,一条 25 Gb/s 的链路每秒只能传约 1.8 个,传输排成长队。
算法 2 的关键观察:KV 只在”对应的层”之间传。把预填充和解码实例都按流水线切成段,同一段的预填充和解码放在同一个节点里,KV 就只走节点内的 NVLink:
对每个 inter-op ∈ {1, …, N}(每对实例跨几个节点):
枚举节点内的配置 P_p(预填充段的张量并行)、P_d(解码段的张量并行),要求 P_p + P_d ≤ M
整体模拟这一对实例的 goodput,按每张 GPU 取最好
按流量复制
代价是搜索空间变小。纸面模型里它找到”每对跨 4 个节点、每个节点 P tp 2 + D tp 2”,每张 GPU 0.820(高带宽时算法 1 是 0.952;DistServe 的消融实验也是 DistServe-High > DistServe-Low)。
对 OPT-175B(350 GB),一个节点(640 GB)连一对实例都放不下,只能切段。04 号脚本第 5 部分的纸面模型选了”跨 4 个节点、每个节点 P tp 4 + D tp 4”;DistServe 在真实测试床上找到的是预填充 inter-op 3、intra-op 3,解码 inter-op 3、intra-op 4(每个节点 3 + 4 = 7 张卡)——tp = 3 这种非 2 的幂的配置人很难想到,搜索能找到。实测 OPT-175B 的 KV 传输只占总时延的 0.1% 以下,95% 的请求传输时间 < 30 ms。
5.5 运行时的调度
放置定下来之后,DistServe 的在线调度很简单(§4.3):请求先交给队列最短的预填充实例,FCFS;预填充批的总长凑到 L_m 左右(短提示拼批,长提示单独跑),减少流水线气泡;然后交给负载最轻的解码实例。KV 用”拉”的方式传。流量模式变了(平均长度、到达率),就重新运行放置算法(几秒到几分钟)并重新加载权重(几分钟)——比真实流量的小时级变化快得多。
DistServe 没做的两件事——抢占(FCFS 的”护航效应”:长请求挡住短请求)和容错(一个解码实例挂了会连累所有向它供货的预填充实例)——正是 Dynamo 这类生产系统要补上的。
6. NVIDIA Dynamo · Distributed inference at scale
6.1 规模化推理的挑战
Vikram 的 slides 列出了在规模上做推理要面对的问题:
- 为了达到 SLA,要按请求调整并行策略;输入 / 输出长度(ISL / OSL)各不相同,有多轮对话、有智能体发起的调用;
- 异构硬件(不同代、不同平台);多个模型同时服务;容错;
- 优化目标是 AI 工厂的总拥有成本(TCO):goodput、功耗、KV 缓存命中率……
2025 年的部署规模:输入 100 万 token 以上(RAG、视频理解、代码),输出越来越长(推理模型、智能体工作流),模型 1000 亿到 1 万亿参数。吞吐与时延之间是一条帕累托曲线(slides 引用 SemiAnalysis 的 InferenceMAX 基准):后训练(RL 的生成)要高吞吐,自动补全要低时延,同一个系统要能在曲线上移动。
Dynamo 是 NVIDIA 的开源分布式推理框架(GTC 2025 发布),定位是”推理引擎之上”的一层:支持 vLLM、SGLang、TensorRT-LLM 作为后端;模块化、各部件独立扩展。下面逐个看它的部件。
6.2 分离部署与 KV 感知的路由
分离部署是 Dynamo 的一个特性:预填充和解码各自扩展、各自选并行策略(slides 第 34–36 页,图取自 DistServe 与 Splitwise)。
KV 感知的路由器:多个工作节点各有自己的前缀缓存(第 22 讲的 RadixAttention),请求该发给谁?发给缓存命中最多的节点,预填充最省;但热门的前缀会让少数节点过载。Dynamo 的路由器对每个节点算一个代价——“还要算多少块”乘以权重,加上”节点当前的负载(活跃的块数)“——选代价最小的。
05 号脚本第 1 部分(8 个节点 × 一张 H100,LLaMA-3-8B;600 个多轮会话、6 个应用各有 2,048 token 的系统提示;每秒 120 个请求):
| 路由策略 | 命中率 | 平均 TTFT | P90 TTFT | 最忙节点 / 平均 |
|---|---|---|---|---|
| 轮询 | 56% | 192 ms | 458 ms | 1.04 |
| 最空闲 | 56% | 92 ms | 175 ms | 1.00 |
| KV 路由,权重 1(= 预计 TTFT 最短) | 87% | 24 ms | 39 ms | 1.68 |
| 只看命中(权重 1000) | 87% | 66 ms | 165 ms | 2.59 |
不看缓存的路由把同一个会话的各轮打散到不同节点,每个节点都要装下所有会话——装不下,基本只命中共享的系统提示。KV 路由让会话”粘”在一个节点上,8 个节点的缓存合起来装下整个工作集。但只看命中会让热门会话挤在少数节点上。权重就是在命中与均衡之间取舍。slides 引用的 Baseten 案例(Qwen3-Coder 480B):KV 感知路由让 TTFT 快 2 倍、TPOT 快 1.5 倍、吞吐高 1.6 倍。
6.3 规划器与配置搜索
规划器(Planner):LLM 的流量变化很快,规划器按 SLA(TTFT、TPOT)实时增减预填充 / 解码节点。它由三部分组成:离线剖析(每种配置在 SLA 下能撑多少)、负载预测(ARIMA、Prophet 等)、插值器(把预测的负载换算成节点数)。slides 引用阿里云的数据:TTFT 降低 80%,同时少用 5% 的 GPU。
05 号脚本第 2 部分用一个流体模型演示(LLaMA-3-8B,6 小时的流量,有周期起伏、一次 2 倍的突发、一段”输入变长一倍”的时期;新节点要 3 分钟才能用上):
| 做法 | GPU·时 | 超出能力的请求 | 预填充节点 | 解码节点 |
|---|---|---|---|---|
| 按峰值静态配置 | 120 | 0.00% | 13 | 7 |
| 按平均静态配置 | 48 | 18.60% | 5 | 3 |
| 规划器:最近 5 分钟的平均 | 52 | 1.73% | 2–14 | 1–7 |
| 规划器:趋势外推 | 51 | 3.32% | 1–14 | 1–7 |
规划器只用了峰值配置 43% 的 GPU·时;违反主要来自突发开始后的 20 多分钟(1.51%)——预测跟不上、新节点还在加载。趋势外推反而更差:它把最近一分钟的变化放大好几倍往前推,在拐点上过冲和欠配——预测器本身也要调。输入变长的那一小时,预填充 / 解码节点的比例从 1.98 升到 3.45:两个阶段独立伸缩,这是分离部署的又一个好处。
AIConfigurator:离线的配置搜索,与 DistServe 的放置算法同一思路——给定模型、GPU 数、硬件、输入 / 输出长度和 TTFT / TPOT 要求(slides 的例子:Qwen3-32B、32 张 H200、ISL 4000、OSL 500、TTFT 300 ms、TPOT 10 ms),在笔记本上 1 分钟内找出最佳的分离配置并生成部署文件,代替在 GPU 上测两天。04 号脚本的搜索都在几秒内完成。
6.4 分层的 KV 内存与传输
KV 块管理器(KVBM)利用数据中心里所有的存储层级:G1 显存、G2 主机内存、G3 本地 SSD、G4 网络存储。把 KV 卸载到下层,命中时再取回——slides:分离部署 + KV 卸载让 TTFT 快 2.2 倍;适用于多轮对话、代码生成、智能体调用、生成式推荐。这正是第 27 讲(LMCache)的主题。
NIXL(NVIDIA Inference Xfer Library):统一的数据传输库。上层(“北向”)给推理框架一套跨节点、跨存储层级的传输接口;下层(“南向”)插接各种后端——UCX、Mooncake 的传输引擎、GPUDirect Storage、POSIX、3FS 等,自动选最好的。
Grove:Kubernetes 上按拓扑优化的调度。分离部署的一个”服务”由多种角色组成(预填充组、解码组、路由器……),各有副本数和最小副本数,需要整体地、按 NVLink / 网络拓扑放置和伸缩——普通的 Deployment / LeaderWorkerSet 表达不了。
6.5 容错
Dynamo 提供三种机制(slides 第 41 页):高可用(路由器的状态共享,挂了自动重启)、请求取消(用户断开就停止计算)、请求迁移(在 token 粒度上迁移已生成的部分回答)。
05 号脚本第 3 部分(LLaMA-3.1-70B,8 × H100;输入 2000):解码节点在生成了 g 个 token 后故障——
| g | 让客户端重试:追上进度要等 | 迁移:停顿 | 迁移:重新预填充的长度 |
|---|---|---|---|
| 50 | 1.17 秒 | 0.117 秒 | 1.02× |
| 200 | 4.34 秒 | 0.125 秒 | 1.10× |
| 800 | 17.0 秒 | 0.158 秒 | 1.40× |
迁移时,前端已经把生成的 token 流给了用户;把”提示 + 已生成的 g 个 token”作为一个新的预填充发给另一个节点,从第 g + 1 个 token 接着生成,代价只是一次稍长的预填充。这和第 24 讲的”抢占后重算”是同一个道理:KV 可以从 token 重新算出来,token 才是真正要保存的状态。
7. 分离的历史与基于分离的系统 · History and systems built on disaggregation
Hao Zhang 讲了一段”分离的趣史”(slides 第 58–59 页):
- 2023 年底:DistServe 在 UCSD 发表并开源;同期有微软的 Splitwise(未开源)。Splitwise 的角度是硬件:解码不需要最新 GPU 的算力,可以放在更便宜、更省电的 GPU 上(论文:同样成本下吞吐高 1.4 倍、成本低 20%,或同样的功耗和成本下吞吐高 2.35 倍)。
- 2024 年:开源社区接入得比连续批处理、PagedAttention 慢——小规模上看不出显著收益。但它悄悄地成了大公司大规模部署的首选架构(字节跳动、Google 等)。
- 2025 年:DeepSeek-V3 用预填充 / 解码分离,两个阶段用不同的并行:预填充 4 路张量并行(带序列并行)+ 8 路数据并行、MoE 32 路专家并行(最小部署单元 32 张卡);解码 4 路张量并行 + 80 路数据并行、MoE 320 路专家并行、冗余专家(最小单元 320 张卡)。同年,NVIDIA 在 GTC 主题演讲里发布 Dynamo。
基于分离的系统:LMCache / CacheGen(KV 可以有损地存储、压缩后在网络上传输——第 27 讲)、DeepSeek-V3 的服务、Dynamo;以及以 KV 缓存为中心的 Mooncake(Kimi 的服务平台,附加讲 A2)。
开放问题(Vikram 的 slides):大规模的异构模型部署几乎没人研究过;智能体任务需要多个模型紧密协作;KV 缓存与数据传输的效率;规模化推理缺乏容错和性能测量工具(AIPerf 是一个尝试;智能体的性能建模、多模态的性能刻画、推理的故障注入框架都还空白);推测解码、扩散式推理模型在服务中的位置;企业级 RAG + 缓存增强生成(CAG);以及——模型变得这么快,软件栈怎样跟上?
8. 小结 · Summary
- 两个阶段性格相反:预填充计算受限(算术强度随提示长度增长,一个几百 token 的提示就能算满 GPU),要小批、快速完成;解码访存受限(每步读一遍权重和全部 KV),要大批,批的上限是显存。
- 两种时延、一个目标:TTFT 由预填充决定,TPOT 由解码决定;goodput = SLO 达成率 ≥ 90% 时的最大到达率,per-GPU goodput 对应每个合格请求的成本。吞吐 ≠ goodput。
- 同置的两个问题:干扰(一次迭代被更慢的那部分拖住、生成卡顿)和耦合(两个阶段共用资源与并行策略)。结果是过度配置。分块预填充只是在 TTFT 与 TPOT 之间交换,消除不了干扰,还带来 O(N²) 的 KV 重复读。
- 分离:预填充实例算完把 KV(+ 第一个 token)交给解码实例。干扰消失,两个阶段各自优化;代价是 KV 传输和一个更复杂的放置问题。最简单的 2P1D 就能让每张 GPU 的 goodput 翻倍(DistServe 实测 2.0–4.6 倍、最多 7.4 倍)。
- 放置:预填充实例 ≈ M/D/1 队列,低负载偏爱张量并行、高负载偏爱流水线并行;KV 传输在 NVLink / 高速 InfiniBand 上可以忽略,在低带宽网络上必须把同一段的预填充和解码放进同一个节点(算法 2)。搜索靠模拟器,几秒到几分钟。
- Dynamo 把这些做成生产系统:分离部署、KV 感知路由(命中与负载的加权)、按 SLA 伸缩的规划器、离线配置搜索、分层 KV 内存(G1–G4)、统一的传输库 NIXL、拓扑感知的调度、请求迁移等容错机制。
9. 常见误区 · Common pitfalls
- “吞吐最高的系统就是最好的服务系统。” 用户在乎的是满足 SLO 的请求;吞吐高的系统可能大部分请求都超时(01 号脚本:到达率 8 时吞吐 4.1 请求 / 秒,达成率 1%)。
- “分块预填充解决了预填充与解码的干扰。” 它把大的卡顿摊成小的、经常的减速,并让 TTFT 变差;TTFT 很紧时甚至比不分块更差(02 号脚本)。
- “分离部署要多传一份 KV,一定更慢。” 在节点内 NVLink 或现代 InfiniBand 上,传一个请求的 KV 比一个解码步还短;而消除干扰、各选并行策略带来的收益大得多。
- “分离后,预填充和解码各用一半 GPU 就好。” 最优比例取决于输入 / 输出长度和两个 SLO(03 号脚本:短输出 4P2D,聊天 3P3D),而且会随流量变化——这就是规划器的作用。
- “张量并行总是比流水线并行好(延迟更低)。” 对预填充实例,高到达率时排队主导,容量更大的流水线并行反而 TTFT 更低(04 号脚本、DistServe 图 4)。
- “分离只是一种部署技巧,跟模型无关。” KV 的大小决定了传输压力:多头注意力的 OPT-175B 每个 token 4.5 MiB,GQA 的 LLaMA-3.1-70B 只有 0.31 MiB;MLA 更小。模型结构和服务架构是一起演化的。
- “KV 感知路由就是把请求发给命中最多的节点。” 只看命中会让热门前缀压垮少数节点(05 号脚本:P90 TTFT 165 ms vs 39 ms)。
- “节点故障时,正在生成的请求只能重来。” 已生成的 token 在前端;把它们和提示一起作为一次预填充迁移到别的节点,停顿只有一次预填充的时间。
10. 练习 · Exercises
- 用屋脊点解释:为什么一个 13B 模型在 A100 上,预填充约 150 个 token 以后就是计算受限的,而解码批大小 256 时仍是访存受限的?
- 一个请求输入 1000、输出 300 个 token,TTFT 0.2 秒、TPOT 40 ms。端到端时延是多少?如果 TPOT 的 SLO 是 50 ms,而有 5% 的解码步因为插入预填充变成 300 ms,这个请求的 TPOT 还达标吗?
- 已知一张卡只做预填充时 goodput 为 6 rps,只做解码时 15 rps。用 xPyD 的整数实例数,每张 GPU 的最佳 goodput 是多少?对应什么比例?
- 用式 (2)、(3) 求:D = 0.4 秒、K = 1.6 时,到达率多大时流水线并行 2 段的平均 TTFT 开始低于张量并行 2 路?
- 分块预填充把一个 4096-token 的提示切成 512 的块。一共要重复读多少个 token 的 KV?换成 1024 的块呢?
- OPT-66B 一个 1000-token 请求的 KV 有多大?在 25 Gb/s 的链路上传要多久?这条链路每秒最多能支撑多少个这样的请求?
- 为什么 DistServe 的低带宽放置算法要求”同一段流水线的预填充与解码在同一个节点”?如果模型不用流水线并行(inter-op = 1),这个约束意味着什么?
- DistServe 用”拉”的方式传 KV。如果改成预填充实例算完就”推”给解码实例,突发流量时会发生什么?
- KV 感知路由的代价 = 排队时间 + w × 预填充时间。w = 0、w = 1、w → ∞ 各对应什么策略?为什么 w = 1 有特别的含义?
- 解码节点在生成第 500 个 token 时故障。对比”客户端重试”和”迁移”两种恢复方式各需要重新计算多少 token;为什么迁移的那次预填充比 500 个解码步快得多?
参考答案
- 预填充 L 个 token 时每个权重复用 L 次,算术强度 ≈ L FLOPs/字节(2 FLOPs × L / 2 字节),L > 153 就超过 A100 的屋脊点。解码批 B 时线性层的强度 ≈ B,但还要读批里所有请求的 KV(每个 token 800 KiB × 上下文长度),这部分只有很少的计算,把整体强度压在 50 左右(01 号脚本),远低于 153。
- 0.2 + 0.04 × 299 = 12.16 秒。卡顿后平均 TPOT = 0.95 × 0.04 + 0.05 × 0.3 = 0.053 秒 > 50 ms,不达标——少数长卡顿就能拉高平均值(P99 的 TBT 更明显)。
- n 张预填充卡、k 张解码卡:min(6n, 15k)/(n + k)。5P2D:min(30, 30)/7 = 4.29;2P1D:min(12, 15)/3 = 4.0;3P1D:min(18, 15)/4 = 3.75。最佳 5P2D,每张 GPU 约 4.29(理想的连续值是 1/(1/6 + 1/15) = 4.29)。
- 令 D + RD²/(4(2 − RD)) = D/K + RD²/(2K(K − RD)),D = 0.4、K = 1.6。逐点算:R = 1:流水线 0.425、张量 0.292;R = 2:0.467、0.375;R = 2.7:0.517、0.510;R = 3:0.550、0.625。交叉点约 R ≈ 2.74 请求 / 秒(张量并行的稳定上限是 K/D = 4,流水线是 2/D = 5)。K 越小,交叉点越低——张量并行越早被超越。
- N = 8 块:512 × (0 + 1 + … + 7) = 512 × 28 = 14,336 个 token(提示长度的 3.5 倍)。1024 的块:N = 4,1024 × 6 = 6,144(1.5 倍)。
- 2.25 MiB × 1000 ≈ 2.36 GB;25 Gb/s = 3.125 GB/s,约 0.75 秒;每秒最多约 1.3 个。
- KV 只在对应的层之间传;同一段的层在同一个节点里,传输就只走 NVLink。inter-op = 1 时每个实例都在一个节点内,意味着一对预填充 + 解码实例必须挤进同一个节点(权重要放两份),大模型(如 175B)根本放不下。
- 突发时预填充实例会一下子把大量 KV 推给解码实例,解码实例的显存放不下就得拒绝、抢占或 OOM;“拉”的方式让预填充实例的显存充当缓冲区,解码实例按自己的容量取,两边各按自己的节奏工作。
- w = 0:只看排队,最空闲优先;w → ∞:只看命中;w = 1:代价恰好是”排队时间 + 预填充时间” = 预计的 TTFT,即贪心地最小化每个请求的 TTFT。
- 重试:重新预填充提示 + 重新解码 500 个 token(还要等它们一个个生成);迁移:一次长度为”提示 + 500”的预填充。预填充一次处理所有 token,是计算受限的矩阵乘,几百毫秒;500 个解码步每步都要读一遍权重,几秒到十几秒。
11. 延伸阅读 · Further reading
- DistServe:Zhong et al. (2024),OSDI;代码 github.com/LLMServe/DistServe。
- Splitwise:Patel et al. (2024),ISCA——按阶段选硬件。
- Sarathi-Serve:Agrawal et al. (2024),OSDI——分块预填充与无卡顿调度。
- TetriInfer:Hu et al. (2024);DéjàVu:Strati et al. (2024)——同期的分离 / KV 流式传输工作。
- DeepSeek-V3 技术报告(2024)§3.4:预填充与解码分离、各自的并行与冗余专家。
- NVIDIA Dynamo:github.com/ai-dynamo/dynamo(KV 路由、规划器、KVBM、NIXL、AIConfigurator、Grove)。
- vLLM 的分离部署示例:docs.vllm.ai 的 disaggregated serving 一节(slides 推荐的学习用例子)。
- Mooncake:Qin et al. (2025),FAST 最佳论文——以 KV 缓存为中心的分离架构(附加讲 A2)。
12. 下一讲预告 · Next lecture
分离部署把 KV 缓存从”GPU 里的临时状态”变成了”在机器之间搬来搬去的数据”。顺着这个方向再走一步:KV 缓存能不能像文件一样存下来、压缩、跨请求跨实例共享,甚至拼接(不是前缀的复用)?第 27 讲是 Junchen Jiang 的嘉宾讲座:CacheGen(把 KV 当视频来编码,压缩 3–10 倍)、CacheBlend(RAG 的多个文档各自缓存、拼起来只重算一小部分 token)和 LMCache(把 KV 缓存从推理引擎里分离出来的开源系统)。