跳到主要内容
推理优化

6.1 推理并行策略总览

从显存账本和延迟/吞吐目标出发,对比推理场景下 TP、PP、DP、EP 四种切法的存储方式、通信模式与适用场景,给出选择并行度的顺序和一个可运行的估算脚本

分布式推理 张量并行 流水线并行 数据并行 专家并行

前五章都默认一件事:模型放得进一张 GPU。到了 70B 级别的 Dense 模型或 DeepSeek-V3 这样的 MoE,这个前提就不成立了,引擎必须把模型拆到多张卡、甚至多台机器上。

拆法不止一种。按层内切、按层间切、整份复制、按专家切,四种方式各自解决不同的问题,通信代价也差很多。本节先算清”为什么要拆”,再把四种切法放在一起比较,最后给出选择并行度的顺序。后面 6.2–6.4 节分别展开每一种。

⏰ 信息时效:原理部分与框架无关;涉及 vLLM 的建议与参数基于 v0.32.0(2026-10-18 发布)文档。

📑 目录


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:

mKV/token=2×nlayers×nkv×dhead×b=2×80×8×128×2 B=320 KiBm_{\text{KV/token}}=2\times n_{\text{layers}}\times n_{\text{kv}}\times d_{\text{head}}\times b =2\times80\times8\times128\times2\ \text{B}=320\ \text{KiB}

一个 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:

4 张 GPU 上的四种并行方式:TP 每卡存所有层的四分之一,每层两次 AllReduce;PP 每卡存连续 20 层,只在段间传激活;DP 每卡存完整模型,前向无通信;EP 每卡一份注意力、8 个专家分到 4 张卡,每个 MoE 层两次 All-to-All

📊 方式切什么每卡存的权重前向中的通信主要收益
TP每层的权重矩阵,按头或中间维切约 1/t1/t每层 2 次 AllReduce每卡读的权重变少,单请求延迟下降
PP层,按顺序切成若干段约 1/p1/p段与段之间点对点传激活通信少,适合跨节点扩容
DP不切,整份复制完整一份Dense 模型无通信吞吐随副本数增长
EPMoE 层的专家专家约 1/e1/e,注意力与共享部分另算每个 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 里一共有 nn 个 Token(Decode 时就是并发请求数),隐藏维为 hh,激活每个元素占 bb 字节。

4.1 TP:次数多,每次不大

每层有 2 次 AllReduce,每次规约的张量大小是:

STP=n×h×bS_{\text{TP}}=n\times h\times b

LL 层的模型每步共 2L2L 次。用 Ring 算法时,每张卡每次大约发送 2t−1tS2\frac{t-1}{t}S 字节。以 Llama-3.1-70B、TP=4、Decode 时 64 个请求为例:

  • 每次 AllReduce:64×8192×2 B=1 MiB64\times8192\times2\ \text{B}=1\ \text{MiB}
  • 每步 160 次,每张卡共发送约 160×1.5 MiB=240 MiB160\times1.5\ \text{MiB}=240\ \text{MiB}

按 H100 NVLink 单向 450 GB/s 算,纯传输约 0.56 ms,和十几 ms 的 TPOT 相比不算大。但 160 次通信每次都要所有卡对齐一遍,消息又小,固定的启动和同步延迟占了大头。这就是 TP 在节点内依赖 NVLink、在节点间很难用好的原因(6.2 节)。

4.2 PP:次数少,只在段边界

每个段边界传一次激活,每步共 p−1p-1 次点对点发送。vLLM 的 Llama 实现在段间传 hidden_states 和 residual 两个张量,同样 64 个请求时每个边界约 2 MiB。次数从 160 次降到 3 次,对网络的要求低得多。

PP 的代价不在通信量,而在气泡:一个 Batch 走到第 2 段时,第 1 段在等。6.3 节专门讲怎么用多个 Batch 交错填满流水线。

4.3 EP:总量大,而且不均匀

每个 MoE 层先 Dispatch、再 Combine。每个 Token 要发到它选中的 kk 个专家所在的卡,每份 h×bh\times b 字节,Combine 时再收回来:

SEP/layer≈2×n×k×h×bS_{\text{EP/layer}}\approx2\times n\times k\times h\times b

以 DeepSeek-V3 为例(h=7168h=7168,每个 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=4160 次 AllReduce1 MiB低延迟、高带宽,基本限于 NVLink 域内
PP=43 次 P2P约 2 MiB普通 RDMA 网络即可
DP0—无
EP每个 MoE 层 2 次 All-to-All与 kk、路由分布有关跨节点时需要 RDMA,并依赖专门的通信 Kernel

5. 选择并行度的顺序

vLLM 文档对单个副本给出的建议可以概括为三档:

  1. 单卡装得下:不做分布式。
  2. 单节点多卡装得下:用 TP,TP 大小取需要的卡数。
  3. 单节点装不下:TP 取每个节点的卡数,PP 取节点数,即 TP 在节点内、PP 跨节点。

文档还提到一个例外:模型在单节点内装得下,但卡数不能整除模型结构(例如头数)时,可以用 PP 代替 TP,因为 PP 按层切,允许各段层数不等。

在这三档之上,再按目标做调整:

🎯 情况做法原因
一个副本已经满足延迟,吞吐不够加 DP 副本副本之间无通信,扩展近乎线性
TPOT 不达标增大 TP(在 NVLink 域内)每卡读的权重更少
吞吐优先、延迟要求宽松用较小的 TP,多开副本TP 的同步开销随卡数上升,同样的卡数,多个小副本的总吞吐往往更高
KV Cache 不够、并发上不去增加卡数或用 PP;GQA 模型注意 TP 超过 KV 头数后 KV 不再缩小见 6.2 节
MoE 模型注意力 DP + 专家 EP6.4 节
跨节点TP 不出节点,节点间用 PP 或 DP/EPTP 的通信次数太多

⚠️ 注意:并行度还受模型结构约束。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 头数后的变化

📚 参考资料