如果你第一次看到 TP、DP、PP、CP、EP 这五个缩写,大概率会有点头大。我第一次接触大模型分布式训练时就是这样:模型本身在单卡上明明能跑通,为什么到了集群上还要拆成这么多维度?后来帮算法团队调试一个 70B 参数的训练任务,被 Infra 同学追问“TP 打算开几?PP 呢?梯度累积设多少?”,我才真正意识到,这几个字母背后其实是同一套显存与通信经济学。这篇是“算法同学学 Infra”系列的第二篇,我尽量不讲论文公式,全用直觉和工程经验把这件事讲清楚,目标是让你看完之后,别人再说 TP=8、PP=4、DP=8,你能立刻知道集群长什么样、每张卡的显存都花到哪去了、哪个环节会成为瓶颈。
1. 先建立直觉:五种并行到底在解决什么问题
1.1 单卡训练为什么走不通
先说显存墙。拿一个 7B 模型举例,BF16 精度下权重就要 14GB,单卡 80GB 的 A100/H100 看起来放得下对吧?但训练不是只有权重。你还要存梯度,又是 14GB;用 Adam 优化器做混合精度训练时,还要额外保存 FP32 主权重、一阶动量、二阶动量,这部分每参数大约 12 字节,7B 参数就是 84GB。光这几项加起来已经超过 110GB,还没算前向计算过程中那些激活值。所以 7B 模型想在单卡上用 Adam 做全参数训练,80GB 的卡根本不现实。
再说算力墙。训练一个 token 大约需要 6N 次浮点运算,N 是模型参数量。7B 模型就是每个 token 大约 42 GFLOPs,100B tokens 的数据量就是 4.2e21 FLOPs。一张 H100 的 BF16 稠密算力大概 1e15 FLOPs/s 量级,单卡纯算就要四五十天,更不用说数据搬运、通信同步、低效利用这些现实损耗。所以数据并行、模型并行这些手段不是“加分项”,而是在大模型训练里必须做的事。
1.2 一张表看穿五种并行的本质区别
这五个缩写经常放在一起,容易让人以为是一套体系的五个组件,实际上它们各自切的维度完全不同,解决的问题也完全不同。我习惯用一张表把“切什么、收益是什么、代价是什么”说清楚:
| 并行策略 | 切分对象 | 核心收益 | 主要代价 | 典型场景 |
|---|---|---|---|---|
| DP 数据并行 | 训练数据 batch | 吞吐量随卡数近似线性提升 | 每步梯度同步产生通信 | 模型单卡放得下时最简单的扩展方式 |
| TP 张量并行 | 层内的矩阵权重 | 单层权重显存变小、单层计算变快 | 每层都有高频 all-reduce,极吃网络延迟 | 单层矩阵太大、GPU 放不下时 |
| PP 流水线并行 | Transformer 层 | 每卡只放一部分层,显存压力大减 | 跨 stage 传激活值,存在气泡 | 层数很深、模型整体放不下时 |
| CP 上下文并行 | 序列长度 / KV 状态 | 支持超长上下文训练与推理 | 注意力需要环形传输,通信复杂 | 32K、128K 以上超长序列 |
| EP 专家并行 | MoE 网络里的专家 | 只激活部分专家,计算量大降 | all-to-all 通信,负载可能不均 | MoE 架构的大规模训练 |
做个人味很重的类比:DP 是“同一张卷子复印几份,几个人各做各的再做答案对齐”;TP 是“一道题本身太大,切两半,一人算一半再拼答案”;PP 是“试卷按大题拆开,第一个人做第一题,第二个人做第二题,串成流水线”;CP 是“卷子太长,每个人只负责看一段,但段落之间要互相传递”;EP 是“请了一批专家,每道题找对口的专家做,做完再传回来”。不管哪种,本质都是把显存和算力分散到多张卡上,再用通信把它们重新粘起来,区别只在于从哪个维度切。
2. 数据并行 DP:最符合直觉的并行方式
2.1 从“每个人看不同题目”讲起
数据并行是算法同学最容易接受的一种并行方式,因为它完全不打乱模型结构。假设你有 4 张卡、一个全局 batch size 是 32,那就每张卡分到 8 条样本,每张卡上都放一份完整的模型副本,各自做前向、各自算梯度。
但这里有个关键问题:模型参数是所有卡共享的。如果各卡拿着自己的 8 条样本梯度直接更新参数,那 4 张卡更新四次,参数早就飞了。所以每步反向算完梯度之后,必须先做一次梯度同步,也就是 all-reduce,把 4 份梯度求平均,得到全局梯度后再统一更新。这就是 PyTorch DDP 在做的事。
这套逻辑很像一个团队分工做题:每个人独立做完自己的那部分,再对答案,最后每个人按统一答案订正。它对模型的精度几乎没有任何影响,因为从数学上讲,并行计算结果和非并行计算的全局梯度是一致的,前提是用了同一个随机种子初始化、同一个 batch 划分。
2.2 梯度同步的代价:为什么 DP 不是免费的
DP 的通信成本不算小。一次 all-reduce 的通信量只跟模型大小有关,跟 batch size 没有关系。7B 模型用 BF16 存梯度,每步梯度同步要汇总的数据就是 14GB,8 卡时每张卡都要参与广播规约。这个通信发生在每个训练 step 里,模型越大、step 越多,累积的时间就越夸张。
所以 DP 在实际应用中有一个经验判断:模型能放进单卡显存时,DP 是最佳选择;但模型太大、单卡放不下时,纯 DP 就不够用了,因为模型副本本身已经撑爆显存。这时候就需要往模型内部切,或者用 FSDP 这种分片方案。
2.3 显存不够时:FSDP 和 ZeRO 在干什么
FSDP(Fully Sharded Data Parallel)和 DeepSpeed 的 ZeRO 思路很像:既然 DP 每张卡都放完整模型太浪费,那就把参数、梯度、优化器状态都分片到所有卡上,每张卡只存 1/N。
但模型前向的时候,每张卡又需要完整的参数来做矩阵运算,怎么办?答案是 all-gather 临时取回。比如在某个 Transformer 层前向之前,把该层参数从所有卡收集回来,算完这一层再释放。等反向的时候再按需取回,计算完立即把梯度归位。这本质是“用通信换显存”:训练时不需要的权重和数据不长期占用显存,要用时再传。
这个方案很香,代价是通信量比纯 DDP 更大,因为除了梯度同步,还要频繁做参数收集。所以 FSDP 在小规模集群上不一定比纯 DP 快,但它在单卡显存逼近极限的时候能保住训练跑起来。
3. 张量并行 TP:把矩阵运算本身切碎
3.1 一个线性层怎么切成两半
张量并行是 Megatron-LM 那套思路,它不再按数据分,而是按矩阵内部维度分。一个线性层 Y = XW,X 的形状是 [batch, in],W 的形状是 [in, out],其中 in 和 out 在 Transformer 里通常就是 hidden size 的倍数。TP 有两种切法。
第一种叫列切(column parallel),把 W 按输出维度切成 W1 和 W2,各自形状是 [in, out/2]。X 完整复制给两张卡,两张卡分别算 XW1 和 XW2,得到 Y1 和 Y2,最后拼起来得到完整的 Y。前向拼接,反向的时候要把梯度 split 回两张卡。
第二种叫行切(row parallel),把 W 按输入维度切成两半,X 也按列切开,两张卡各自算一半矩阵乘法的累加结果,最后需要一次 all-reduce 把两块部分和加起来,才能得到最终的 Y。反向的时候同样要把梯度广播回去。
如果只写公式会很抽象,伪代码长这样:
# 列切:W1 [in, out/2], W2 [in, out/2] Y1 = X @ W1 # 卡 0 Y2 = X @ W2 # 卡 1 Y = torch.cat([Y1, Y2], dim=-1) # 行切:W1 [in/2, out], W2 [in/2, out] X1 = X[:, :in//2] X2 = X[:, in//2:] Y = X1 @ W1 + X2 @ W2 # 需要一次 all-reduce3.2 通信频率决定了 TP 必须绑在高速网络
TP 的显存收益很明显:参数、梯度、优化器状态都按 TP 维度切成 1/TP,矩阵运算也被拆小,单卡显存压力大幅下降。但代价是通信极其密集。Transformer 每个 attention 块和 MLP 块里通常有两处 all-reduce,前向一次、反向又是一轮,模型的层数又深,算下来每个训练 step 里多层反复通信。
这就是为什么 TP 几乎必须在一台机器内部、依赖 NVLink 这类高带宽低延迟的互联来做。跨节点做 TP 不是不行,但普通万兆网或 IB 网络的延迟和带宽很难撑住每层的高频 all-reduce,性能会掉得非常难看。实际排布时,TP 通常优先放在同一节点内的 8 张卡上,跨节点的并行维度交给 PP 和 DP。
3.3 Transformer 里的 QKV 投影与输出投影怎么配合
在标准 Transformer 层里,TP 的切法是有讲究的。attention 部分的 QKV 三个线性层适合用列切,这样不同的 head 被分到不同卡上,天然的布局就是每个 GPU 负责一部分注意力头;attention 后面的输出投影适合用行切,把各个卡的局部注意力结果 all-reduce 合并。
MLP 也类似:第一个全连接层做列切,中间的激活函数每个卡独立算,第二个全连接层做行切,最后 all-reduce。整体上可以理解成“一个算子被物理拆分后,前向和反向都有一对 f 和 g 操作来保证跨卡数据流动正确”。框架里用 Megatron-LM 或相关库时,这些细节已经被封装了,但理解这一层你会更清楚为什么 TP 的通信会出现在这些位置。
3.4 别把 TP 里的序列并行当 CP
和 TP 经常一起出现的还有个词叫序列并行(SP,Sequence Parallel)。它做的事情是把 LayerNorm、Dropout 这些沿着 token 维度有独立状态的算子的激活值,也按序列切到各卡,从而省掉一部分激活显存。它和 CP 不是一回事:SP 通常和 TP 配合使用,只是进一步把非矩阵类的算子也拆开;CP 则专门针对超长序列的注意力部分,后面会单独讲。读框架代码时看到“sequence parallel”先确认它是不是在 TP 语境内,别和长序列并行混了。
4. 流水线并行 PP:切模型的层,而不是切矩阵
4.1 按层分配,每个 GPU 负责一段
PP 的思路更贴近直觉:模型有几十上百层,按层切成几段,每张卡只负责其中一段。比如一个 32 层模型,4 张卡做 PP,卡 0 负责第 1 到 8 层,卡 1 负责 9 到 16 层,依此类推。
数据流是依次往下传的:卡 0 算完第 8 层的输出,传给卡 1 继续算第 9 到 16 层,这个过程很像工厂流水线。因为每张卡只需要保存自己那部分层的权重、梯度和优化器状态,显存压力比完整模型副本小很多。同时,模型层数越深,PP 的扩展性越好,因为并行粒度是“层”,不是矩阵里的每一个维度。
4.2 micro-batch 与 1F1B 调度
但朴素的流水线有个大问题:同一时刻可能只有一张卡在计算,其他卡都在等。比如卡 0 算完第一个 batch 传给卡 1 之后,如果卡 0 没有新 batch 可算,它就只能空转。为了解决这个问题,生产上几乎都用 micro-batch:把一个大的 batch 再切成很多小 batch,依次灌进流水线。这样卡 0 算完第一个 micro-batch 传给卡 1 后,立刻开始算第二个 micro-batch,流水线里的每一段都能持续运转。
最常用的调度叫 1F1B(one forward one backward):每个 stage 交替执行一个 micro-batch 的前向和反向,这样能把显存峰值控制得更低。读者不需要手写调度,但要知道一点:micro-batch 数量直接影响气泡率和显存峰值,它是一个实实在在的调优参数。
4.3 气泡率计算:PP=4 的代价
PP 存在一个不可避免的“气泡”:流水线刚启动和最后排空时,总有 stage 在等待,这个空档比例大概可以写成气泡率 = (P-1) / (M+P-1),P 是流水线 stage 数,M 是 micro-batch 数。
举个例子,PP=4、micro-batch 数=16,气泡率约 15.8%;如果把 micro-batch 提到 32,气泡率降到约 8.6%。所以生产环境里通常会把 M 设得比较大,把气泡压下去。但 M 变大会增大激活显存,所以实际配置时往往要和 activation checkpointing、梯度累积一起权衡。这里给算法同学一个直觉:PP 不是免费的,它的成本是气泡和调度复杂度,但换来的显存收益非常可观。
5. 上下文并行 CP:为超长序列量身定做
5.1 长序列到底难在哪
当模型要支持 32K、128K 甚至更长的上下文时,单卡根本放不下整个序列的激活值和 KV cache。比如处理一条 128K 长度的序列,纯 attention 的复杂度虽然是线性注意力优化后能缓解,但 KV 在内存里依然很大,而且 MHA 的中间结果本身就占显存。如果只是硬塞进一张卡,很快会 OOM。
CP 的切入点是序列维度:把一条长序列切成 C 段,每张卡负责其中一段 token 的 query 和 key/value 计算。每张卡只需要保存 1/C 的 KV 状态,显存压力瞬间降下来。这是它和 TP、PP 最本质的区别:切的是“时间轴/序列轴”,不是层内矩阵,也不是模型的层。
5.2 Ring Attention:让 KV 在卡间转圈
标准 attention 里,每个 query 要和所有 key 做计算。如果每张卡只留了自己那一段 KV,query 就只能看到局部信息,算出来的 attention 不完整。所以 CP 必须让 KV 在卡之间转起来,最典型的实现叫 Ring Attention。
大概流程是:每张卡保存自己那段的 query 和 KV,初始状态各算各的局部注意力;然后把自己的 KV 块传给下一张卡,同时接收上一张卡的 KV 块,再和本地 query 继续算注意力。这样经过 C 轮之后,每个 query 都能和所有 key 算过一遍。听起来像“卷子轮流改”:每张桌子拿一部分卷子,卷子轮流转一圈,每个人都看过所有卷子。代价是纯计算量不变,但通信增加了,而且通信模式是点对点环形传输。
5.3 CP 和 TP 的常见配合方式
实际部署里 CP 很少单独用。它一般和 TP 组合在一起:TP 负责把层内矩阵计算切到多卡,CP 负责把序列长度切到多卡。比如一个节点 8 张卡,设 TP=4、CP=2,那相当于 4 张卡组合做矩阵切分,再分成两组各处理一半序列。这样既能压住大矩阵的显存,又能扛住超长序列。
这个组合在推理时也特别重要。长上下文服务场景下,KV cache 是显存大头,CP 能显著降低单卡 KV 占用。目前很多推理引擎里已经支持类似配置,算法同学做长上下文模型服务时,建议优先考虑 TP+CP 而不是只堆 TP。
6. 专家并行 EP:MoE 模型的必经之路
6.1 MoE 让参数变多、计算变少
MoE(Mixture of Experts)是当前大模型卷参数量的一个重要手段。它的特点是:模型总参数很大,但因为稀疏激活,每个 token 只经过路由网络选择出的少数几个专家 FFN。比如一个 MoE 层里有 64 个专家,每个 token 只激活 2 个专家,那么总参数量虽然很大,实际计算量并不随专家总数线性增长。
这就带来一个新问题:专家的权重到底放在哪张卡上?如果每个 GPU 都复制全部专家权重,那 MoE 的大参数优势就没意义了,显存会爆炸。EP 的做法是:把不同专家分布到不同 GPU 上,每个 GPU 只持有部分专家。token 从哪个卡发出不重要,重要的是它能被送到“拥有对应专家”的卡上完成计算,算完再送回原来的卡。
6.2 all-to-all:token 在不同 GPU 之间“串门”
EP 的核心通信模式是 all-to-all。普通的 DP 通信是 all-reduce,所有卡拿着同样大小的数据做规约;TP 也是 all-reduce 居多。但 EP 里,每张卡上都有一些 token 要发给专家所在的不同卡,发件方和收件方是多对多的关系,而且每张卡发往不同目标的数据量还不一样。
这个模式很像物流分拣中心:一批包裹要从各个网点送到不同的处理仓库,每个仓库只处理特定类别的货,所有网点之间需要互相派送。all-to-all 的高峰通信量会非常大,所以 EP 对网络带宽和拓扑非常敏感。实际工程里,EP 通常会优先把专家放在同一个节点内,尽量减少跨节点 all-to-all 的数据量。
6.3 负载均衡是 EP 最大的坑
EP 最让人头疼的问题不是显存,而是负载不均衡。如果某几个专家特别“热门”,大量 token 都路由过去,那几张卡的算力会打满,其他拿着冷门专家的卡却在空转。这也是为什么 MoE 训练里要额外加负载均衡 loss,目的就是让 token 分布尽量均匀。
除了负载均衡 loss,工程上还可以做专家分组、token drop、动态调整路由策略等手段。算法同学在评估 MoE 效果时,不能只看模型精度,还要看训练吞吐是否被不均衡拖累。如果你发现 GPU 利用率忽高忽低、每 step 时间波动很大,优先怀疑 EP 的负载均衡。
7. 组合拳:一套接近生产的并行配置怎么定
7.1 一个 64 卡集群的配置实例
实际大规模训练很少只用一种并行,最常见的是 DP + TP + PP 组合,有时再加上 CP 或 EP。先牢记一个基本公式:总 GPU 数 = DP × TP × PP × CP(如果 EP 存在,它一般是在某个维度内部再展开)。
假设你在 8 节点 × 8 卡 = 64 卡集群上训练一个 70B 模型,典型排法有两种思路:
| 方案 | 配置 | 特征 |
|---|---|---|
| A | TP=8,PP=8,DP=1 | 每卡只放 70B/(8×8)≈1.1B 参数,显存最省;但 TP 大、PP 气泡压力大,调度复杂 |
| B | TP=4,PP=4,DP=4 | 保留 4 份模型副本,显存稍重;但 DP 的梯度同步能让 batch 更大,总体吞吐通常更稳 |
实际操作中,节点内的 8 卡优先给 TP,跨节点的维度用 PP 或 DP。配置时要把 TP 设置在 NVLink 域内,PP 尽量让相邻 stage 之间有较高带宽,DP 对网络要求最低,可以放得比较远。EP 在 MoE 模型里通常会打断这个整齐公式,比如一个 DP=4 × EP=8 的配置里,4 份模型副本各自并行,但每份模型内部又按专家维度切成 8 份,所有副本共享同一套专家布局。
7.2 推理场景和训练场景为什么不一样
训练和推理的并行配置不能直接照搬。训练时优化器状态、梯度、激活值占掉大量显存,所以需要 PP 来切层、FSDP/ZeRO 来分片优化器状态。推理时模型已经固定,没有梯度、没有优化器状态,显存大头是权重和 KV cache,所以很多时候只需要 TP 和 CP。
比如跑一个长上下文推理服务,权重放不下就加 TP,KV cache 放不下就加 CP,很少需要把 PP 开到很大,因为推理的 batch 通常比较小,流水线气泡会非常难看。你如果直接把训练时的 TP=8、PP=8 配到推理服务上,你会发现第一 token 延迟变得很高,吞吐反而上不去,因为流水线深、通信多,但计算量远没有训练时候那么大。
7.3 主流框架分别覆盖哪些并行
- PyTorch DDP 和 FSDP:主要覆盖 DP 和分片式数据并行。
- DeepSpeed:ZeRO 分片、流水线并行(PP)都支持,也被广泛用在 DP 为主的大规模训练里。
- Megatron-LM:TP、PP、CP、EP 支持最全,是目前很多大模型训练底座。
- vLLM、SGLang 等推理引擎:主要支持 TP、PP、CP,对长上下文服务越来越重视。
不同框架对并行维度的命名和组合约束有差异,但底层通信原语都差不多:all-reduce 对应 DP/TP 的梯度或结果规约,all-gather 对应 FSDP/ZeRO 的参数收集,all-to-all 对应 EP 的 token 路由,send-recv 对应 PP 的 stage 间传输。看框架文档时先定位四个原语,就不容易被 API 绕晕。
8. 算法同学最常踩的坑与快速决策清单
8.1 卡数多不等于跑得快
最容易踩的坑是盲目加大 TP 或 PP。TP 从 4 提到 8,单个矩阵计算量确实变小了,但每层 all-reduce 的通信次数变多,如果 batch 或 sequence 不够长,计算时间不足以掩盖通信时间,整体反而更慢。PP 从 2 提到 4,气泡率上升,micro-batch 不够多时吞吐直接往下掉。
判断并行配置好不好,不要看单卡利用率这一个指标,要看 samples/s 或 tokens/s 的实际吞吐。我见过不少算法同学盯着 nvidia-smi 看到 GPU 利用率 90% 就觉得没问题,但实际上大量时间花在通信等待上,利用率指标会骗人。最直接的办法是用同一个模型、同一个小数据集,分别在不同配置下跑 50 个 step,比较每秒处理样本数。
8.2 显存 OOM 时的排查顺序
训练时遇到 OOM,第一个反应往往是减小 batch size,这没错,但它是最后手段,不是最优手段。我建议按这个顺序排查:
- 权重 + 梯度 + 优化器状态是否已经接近卡的上限。比如 70B 全参数训练配 TP=4,每卡权重加优化器状态大概率超过 40GB,需要继续加 TP、PP 或开 FSDP/ZeRO。
- 激活值是否过大。激活值反而不容易一眼看出来,试试打开 activation checkpointing,通常能省掉一大块显存。
- 框架的通信缓冲区是不是在占显存。Megatron 之类框架会预留一部分显存给通信 buffer,配置 message buffer size 可以调整。
- 最后再考虑减小 batch size 或梯度累积,因为这会影响训练效率和模型收敛行为。
8.3 一份决策清单解决 80% 的选型问题
- 模型单卡能放下,想扩大吞吐:优先 DP 或 FSDP,简单直接。
- 模型权重已经超过单卡显存:优先 TP,并且让 TP 待在节点内。
- 层数特别深、TP 已经尽量大了还不够:加 PP,把层切成多段。
- 要处理 32K 以上超长序列:加 CP,优先配合 TP。
- 模型是 MoE 架构:加 EP,配合 DP 或 TP。
- 网络条件一般、没有高速互联:少用大 TP,多用 DP + PP。
这条清单不是绝对公式,但能解决大部分初期选型困惑。真正深入之后你会发现,每个并行维度之间还会互相影响,比如梯度累积和 DP 组合会改变有效 batch size,改变学习率预期;EP 和 PP 组合时各 stage 的计算量可能不再均匀。把这些关系摸一遍,才算真正把五个字母吃透了。
最后说一点个人感受。算法同学学这些东西,最难的不是记缩写,而是建立“每个字母背后都是成本”的意识:DP 是通信换吞吐,TP 是用高速互联换显存,PP 是用气泡换显存,CP 是用环形传输换长上下文,EP 是用 all-to-all 换稀疏计算。我后来每次调并行配置,都会先问自己三个问题:模型到底多大?节点内和跨节点的网络带宽大概多少?我真正要压的是显存、吞吐还是长序列支持?把这三个问题的答案写在纸上,再去看框架文档,基本不会选错。希望这篇能把你的直觉也建立起来。