6.1 推理并行策略总览
从显存账本和延迟/吞吐目标出发,对比推理场景下 TP、PP、DP、EP 四种切法的存储方式、通信模式与适用场景,给出选择并行度的顺序和一个可运行的估算脚本
前五章都默认一件事:模型放得进一张 GPU。到了 70B 级别的 Dense 模型或 DeepSeek-V3 这样的 MoE,这个前提就不成立了,引擎必须把模型拆到多张卡、甚至多台机器上。
拆法不止一种。按层内切、按层间切、整份复制、按专家切,四种方式各自解决不同的问题,通信代价也差很多。本节先算清”为什么要拆”,再把四种切法放在一起比较,最后给出选择并行度的顺序。后面 6.2–6.4 节分别展开每一种。
⏰ 信息时效:原理部分与框架无关;涉及 vLLM 的建议与参数基于 v0.32.0(2026-10-18 发布)文档。
📑 目录
- 1. 两个出发点:装不下,或者不够快
- 2. 推理并行与训练并行的目标不同
- 3. 四种切法放在一起看
- 4. 通信账本:每一步要传多少
- 5. 选择并行度的顺序
- 6. 动手估算:一张卡装不装得下
- 总结
- 自我检验清单
- 参考资料
1. 两个出发点:装不下,或者不够快
1.1 装不下:权重和 KV Cache 一起算
以 Llama-3.1-70B 为例。它有 80 层,隐藏维 8192,64 个 Q 头、8 个 KV 头(GQA),每个头 128 维。BF16 下仅权重就约 70.6B × 2 B ≈ 141 GB,单张 80 GB 的 H100 放不下。
KV Cache 的账在第 1 章算过。每个 Token 在每层存一份 K 和一份 V:
一个 32K Token 的请求要 10 GiB,8 个这样的请求并发就是 80 GiB。权重装下以后,剩下的显存决定了能同时服务多少请求。
所以”装得下”至少有两层意思:
- 权重装得下:每张卡分到的权重小于显存,这是能启动的前提。
- KV Cache 够用:扣掉权重、激活、CUDA Graph 等开销后,剩下的空间能容纳目标并发和上下文长度。很多时候卡数是被这一条推高的。
1.2 不够快:单卡带宽是 Decode 的上限
第 1 章的 Roofline 分析说过,小 Batch 的 Decode 受访存限制:每生成一步,都要把全部权重从 HBM 读一遍。H100 SXM 的 HBM3 带宽约 3.35 TB/s,假设 70B 模型能在单卡放下,读一遍 141 GB 也要 42 ms,TPOT 不可能低于这个数。
把权重切到 4 张卡上,每张卡每步只读约 35 GB,四张卡同时读,理论上每步的访存时间降到约 ¼。这是”拆开以后变快”的来源,代价是卡之间要同步。
另一种”不够快”是吞吐不够:单个副本的延迟已经满意,但请求太多。这时不需要切模型,多放几个副本就行。
📌 关键点:先分清是哪种问题。权重装不下或 KV 不够,需要切模型;单请求延迟太高,需要让多张卡同时算一个请求;总吞吐不够,需要加副本。三种问题对应的并行方式不同。
2. 推理并行与训练并行的目标不同
模块三讲过的 TP、PP、DP、EP,名字和推理里的一样,切分方式也基本相同。差别在于两边要优化的东西不同:
| 📊 维度 | 训练 | 推理 |
|---|---|---|
| 显存里装什么 | 权重 + 梯度 + 优化器状态 + 激活 | 权重 + KV Cache,几乎没有梯度和优化器状态 |
| 主要目标 | 单位时间处理的样本数,最终收敛 | TTFT、TPOT 的延迟分位数,以及满足 SLO 时的吞吐 |
| 一步的工作量 | 一个大 Batch 的前向 + 反向 | Decode 时每个请求只有 1 个新 Token,工作量小且随负载波动 |
| DP 要同步什么 | 每步 AllReduce 梯度 | 副本之间不同步(Dense 模型) |
| PP 的气泡 | 用大量 Micro-batch 摊薄 | Decode 的一步依赖上一步的采样结果,需要多个 Batch 交错才能填满 |
| ZeRO / FSDP | 常用,用通信换显存 | 很少用:每步都要重新收集权重,Decode 的通信量受不了 |
关于最后一行:FSDP 把权重分片存,计算前临时 AllGather 拼回完整参数。训练时一步的计算量大,拼参数的通信可以被掩盖;推理 Decode 一步只算几十个 Token,却要把整份权重在网络上搬一遍,通信时间远大于计算。所以推理侧切权重用 TP 和 PP,不用 ZeRO 式的分片。
💡 提示:训练关心”平均每秒多少样本”,推理关心”每个用户等多久”。同一种并行,在推理里评价它要看对 TTFT 和 TPOT 的影响,以及在延迟约束下能服务多少并发。
3. 四种切法放在一起看
先用后厨打个比方。一桌菜由一组厨师做:
- TP:同一道工序几个人一起干,每人切一部分菜,切完必须凑到一起才能下锅。快,但每道工序都要碰头。
- PP:按工序排成流水线,一个人备料、一个人炒、一个人装盘。每人只管自己那段,交接一次就行,但一桌菜进来,前面的人做完就闲着,得有好几桌同时在做才不浪费人手。
- DP:开几家一模一样的分店,各接各的客人,互不打扰。
- EP:按菜系分档口,川菜师傅只做川菜。每道菜要先送到对应档口,做完再端回来。
对应到模型上,下图用 4 张卡和一个 80 层模型画出四种切法,颜色代表格子所在的 GPU:
| 📊 方式 | 切什么 | 每卡存的权重 | 前向中的通信 | 主要收益 |
|---|---|---|---|---|
| TP | 每层的权重矩阵,按头或中间维切 | 约 | 每层 2 次 AllReduce | 每卡读的权重变少,单请求延迟下降 |
| PP | 层,按顺序切成若干段 | 约 | 段与段之间点对点传激活 | 通信少,适合跨节点扩容 |
| DP | 不切,整份复制 | 完整一份 | Dense 模型无通信 | 吞吐随副本数增长 |
| EP | MoE 层的专家 | 专家约 ,注意力与共享部分另算 | 每个 MoE 层 2 次 All-to-All | 装下专家总量巨大的 MoE |
几点补充:
- TP 和 PP 都能降低每卡的权重显存,但只有 TP 让多张卡同时处理同一个 Token,所以只有 TP 能直接降低单请求的 TPOT。PP 下一个 Token 依次走过各段,单请求的延迟不会变短。
- DP 不省显存。它的前提是一个副本已经装得下,可以和 TP、PP 组合,例如两个副本、每个副本 TP=4。
- EP 只作用于 MoE 层。注意力层仍然需要用 TP 或 DP 来安排,vLLM 的常见做法是注意力走 DP、专家走 EP(6.4 节)。
4. 通信账本:每一步要传多少
记本步 Batch 里一共有 个 Token(Decode 时就是并发请求数),隐藏维为 ,激活每个元素占 字节。
4.1 TP:次数多,每次不大
每层有 2 次 AllReduce,每次规约的张量大小是:
层的模型每步共 次。用 Ring 算法时,每张卡每次大约发送 字节。以 Llama-3.1-70B、TP=4、Decode 时 64 个请求为例:
- 每次 AllReduce:
- 每步 160 次,每张卡共发送约
按 H100 NVLink 单向 450 GB/s 算,纯传输约 0.56 ms,和十几 ms 的 TPOT 相比不算大。但 160 次通信每次都要所有卡对齐一遍,消息又小,固定的启动和同步延迟占了大头。这就是 TP 在节点内依赖 NVLink、在节点间很难用好的原因(6.2 节)。
4.2 PP:次数少,只在段边界
每个段边界传一次激活,每步共 次点对点发送。vLLM 的 Llama 实现在段间传 hidden_states 和 residual 两个张量,同样 64 个请求时每个边界约 2 MiB。次数从 160 次降到 3 次,对网络的要求低得多。
PP 的代价不在通信量,而在气泡:一个 Batch 走到第 2 段时,第 1 段在等。6.3 节专门讲怎么用多个 Batch 交错填满流水线。
4.3 EP:总量大,而且不均匀
每个 MoE 层先 Dispatch、再 Combine。每个 Token 要发到它选中的 个专家所在的卡,每份 字节,Combine 时再收回来:
以 DeepSeek-V3 为例(,每个 Token 激活 8 个路由专家),BF16 下每个 Token 每个 MoE 层的往返约 229 KB,其中落在本卡的部分不需要过网络。和 TP 比,EP 的通信次数相当(每个 MoE 层 2 次,Dense 层没有),但单次数据量大,并且取决于路由:热门专家所在的卡收得多,整层要等最忙的那张卡(6.4 节)。
4.4 DP:Dense 模型零通信
各副本独立处理自己的请求,前向中不需要任何同步。MoE 模型开 DP + EP 时情况不同:所有 DP rank 必须一起进入 MoE 层做 All-to-All,即使某个 rank 当前没有请求,也要跑一次空的前向陪着同步。
| 📊 方式 | 每步通信次数 | 每次大小(64 个 Decode Token) | 对互联的要求 |
|---|---|---|---|
| TP=4 | 160 次 AllReduce | 1 MiB | 低延迟、高带宽,基本限于 NVLink 域内 |
| PP=4 | 3 次 P2P | 约 2 MiB | 普通 RDMA 网络即可 |
| DP | 0 | — | 无 |
| EP | 每个 MoE 层 2 次 All-to-All | 与 、路由分布有关 | 跨节点时需要 RDMA,并依赖专门的通信 Kernel |
5. 选择并行度的顺序
vLLM 文档对单个副本给出的建议可以概括为三档:
- 单卡装得下:不做分布式。
- 单节点多卡装得下:用 TP,TP 大小取需要的卡数。
- 单节点装不下:TP 取每个节点的卡数,PP 取节点数,即 TP 在节点内、PP 跨节点。
文档还提到一个例外:模型在单节点内装得下,但卡数不能整除模型结构(例如头数)时,可以用 PP 代替 TP,因为 PP 按层切,允许各段层数不等。
在这三档之上,再按目标做调整:
| 🎯 情况 | 做法 | 原因 |
|---|---|---|
| 一个副本已经满足延迟,吞吐不够 | 加 DP 副本 | 副本之间无通信,扩展近乎线性 |
| TPOT 不达标 | 增大 TP(在 NVLink 域内) | 每卡读的权重更少 |
| 吞吐优先、延迟要求宽松 | 用较小的 TP,多开副本 | TP 的同步开销随卡数上升,同样的卡数,多个小副本的总吞吐往往更高 |
| KV Cache 不够、并发上不去 | 增加卡数或用 PP;GQA 模型注意 TP 超过 KV 头数后 KV 不再缩小 | 见 6.2 节 |
| MoE 模型 | 注意力 DP + 专家 EP | 6.4 节 |
| 跨节点 | TP 不出节点,节点间用 PP 或 DP/EP | TP 的通信次数太多 |
⚠️ 注意:并行度还受模型结构约束。TP 大小要能整除注意力头数;vLLM 允许 TP 超过 KV 头数,代价是复制 KV 头。PP 不受头数限制,但每段的层数不均时,最慢的一段决定整条流水线的速度。
6. 动手估算:一张卡装不装得下
下面的脚本按 Llama-3.1-70B 的结构,估算不同 TP × PP 组合下每张卡的权重、每个 Token 的 KV 大小,以及剩下的空间能放多少 Token。纯标准库,可以直接运行:
GiB = 1024**3
# Llama-3.1-70B(来自 config.json)
MODEL = dict(params=70.6e9, layers=80, kv_heads=8, head_dim=128)
GPU_MEM = 80 * 1e9 # H100 80GB,按 10^9 计
UTIL = 0.90 # 对应 vLLM 的 --gpu-memory-utilization
RESERVED = 4 * GiB # 激活、CUDA Graph、通信缓冲等,假设值
DTYPE_BYTES = 2 # BF16 权重与 KV
def per_gpu(tp, pp, m=MODEL):
weights = m["params"] * DTYPE_BYTES / (tp * pp)
kv_heads_local = max(1, m["kv_heads"] // tp) # TP > KV 头数时每卡至少 1 个头
kv_per_token = 2 * (m["layers"] / pp) * kv_heads_local * m["head_dim"] * DTYPE_BYTES
free = GPU_MEM * UTIL - weights - RESERVED
return weights, kv_per_token, free
print(f"{'TP×PP':>6} {'权重/卡':>9} {'KV/Token/卡':>12} {'KV 可用/卡':>11} {'可容纳 Token':>12} {'32K 请求数':>10}")
for tp, pp in [(1, 1), (2, 1), (4, 1), (8, 1), (16, 1), (8, 2), (4, 2)]:
w, kv, free = per_gpu(tp, pp)
if free <= 0:
print(f"{tp:>3}×{pp:<2} {w / GiB:>8.1f}G {'—':>12} {'装不下':>11}")
continue
tokens = int(free // kv)
print(f"{tp:>3}×{pp:<2} {w / GiB:>8.1f}G {kv / 1024:>10.0f}Ki {free / GiB:>10.1f}G "
f"{tokens:>12,} {tokens // 32768:>10}")
输出:
TP×PP 权重/卡 KV/Token/卡 KV 可用/卡 可容纳 Token 32K 请求数
1×1 131.5G — 装不下
2×1 65.8G — 装不下
4×1 32.9G 80Ki 30.2G 395,569 12
8×1 16.4G 40Ki 46.6G 1,222,046 37
16×1 8.2G 40Ki 54.8G 1,437,500 43
8×2 8.2G 20Ki 54.8G 2,875,001 87
4×2 16.4G 40Ki 46.6G 1,222,046 37
从表中可以看出:
- TP=2 仍然装不下:两张卡各分到约 66 GiB 权重,加上预留空间就超了。最少要 4 张卡。
- 从 TP=4 到 TP=8,并发能力约变成 3 倍:卡数翻倍,每卡权重减半,KV 也减半,两项叠加。很多部署选 TP=8 的原因在这里,而不只是延迟。
- TP=16 比 TP=8 只多了约 18% 的 Token:KV 头只有 8 个,TP=16 时每卡仍存 1 个头,KV 不再变小,多出来的只是权重省下的空间。同样 16 张卡,TP=8 × PP=2 能放下 2 倍的 Token。
- 4×2 和 8×1 的容量一样:这两种都是 8 张卡、每卡 ⅛ 的权重和 KV。区别在延迟:TP=8 每个 Token 由 8 张卡同时算,4×2 每个 Token 要依次过两段 TP=4。
这只是容量估算:权重按参数量平均分配,没有算 Embedding 与 LM Head 在 PP 首尾段的不均衡,预留的 4 GiB 也是假设值。实际可用的 KV 空间以 vLLM 启动日志里打印的 KV Cache 容量为准。
📝 总结
- 切模型有两个动机:权重或 KV Cache 装不下,以及单卡带宽限制了 Decode 速度。吞吐不够时加副本即可,不必切模型。
- 推理不用 ZeRO/FSDP 式的权重分片:Decode 每步计算量小,临时收集权重的通信掩盖不住。
- TP 每层 2 次 AllReduce,能降低单请求延迟,但依赖 NVLink;PP 只在段间传激活,适合跨节点,代价是气泡;DP 在 Dense 模型上零通信;EP 每个 MoE 层两次 All-to-All,数据量受路由影响。
- vLLM 的基本规则:单卡不切,单节点用 TP,多节点 TP 取每节点卡数、PP 取节点数。
- GQA 模型的 TP 超过 KV 头数后,每卡 KV 不再缩小,加卡换来的并发明显变少。
🎯 自我检验清单
- 能算出一个模型的 BF16 权重大小和每 Token 的 KV Cache 大小
- 能区分”装不下""单请求太慢""吞吐不够”三种问题,并对应到切模型、TP、DP
- 能说明推理场景很少使用 FSDP/ZeRO 分片权重的原因
- 能写出 TP 每步 AllReduce 的次数和每次的数据量
- 能解释 PP 为什么不能降低单请求的 TPOT
- 能按 vLLM 的三档规则为单节点、多节点部署选出 TP 和 PP
- 能用本节的脚本估算某个模型在不同 TP × PP 下的 KV 容量,并解释 TP 超过 KV 头数后的变化
📚 参考资料
- Megatron-LM: Training Multi-Billion Parameter Language Models Using Model Parallelism
- GPipe: Efficient Training of Giant Neural Networks using Pipeline Parallelism
- GShard: Scaling Giant Models with Conditional Computation and Automatic Sharding
- Efficiently Scaling Transformer Inference
- The Llama 3 Herd of Models
- NVIDIA H100 Tensor Core GPU
- vLLM 文档:Parallelism and Scaling