大模型分布式并行的实际配合
DP、TP、PP 这三个东西,单独拎出来讲都能讲清楚,但真到看代码或者配集群的时候,经常搞不清哪一层切了什么、通信发生在哪。我之前看 Megatron-LM 的配置文件,三个参数一乘等于总卡数,但背后的数据流其实没完全想明白。
这篇逐个讲清楚,再讲组合方式。
三个维度分别解决什么问题
| 维度 | 切什么 | 解决什么问题 | 通信方式 |
|---|---|---|---|
| DP | 数据 (batch) | 单卡算力不够,需要更多 GPU 一起算 | All-Reduce (梯度) |
| TP | 单层参数 | 单层模型太大,单卡装不下 | All-Reduce (激活) |
| PP | 模型层 | 模型层数太多,单卡装不下 | P2P Send/Recv |
DP 最直观:4 张卡,每张卡算 1/4 的数据,最后同步梯度。和传统的数据并行训练没有本质区别。
TP 稍微复杂一点:单层网络(比如一个 MLP 或 Attention)的参数量超过了单卡显存,需要把矩阵切开。常见的是列切分接行切分:
Input
│
├──► [GPU0] W_0 ──┐
└──► [GPU1] W_1 │──► 聚合
│
... ...
切完之后每张卡只存一部分权重,forward 完需要 all-reduce 把结果拼回来。
PP 是按层切:0-19 层放 GPU0,20-39 层放 GPU1,数据依次经过每个 stage 计算。问题是 GPU1 在等 GPU0 的输出时会空转,产生 pipeline bubble。
3D 怎么叠加
实际部署时,三个维度是正交的,三个维度正交组合,可以唯一确定每张卡负责的数据和参数。
假设有 64 张 GPU:
DP = 4 (4 个数据并行副本)
PP = 4 (每个副本内 4 个流水线 stage)
TP = 4 (每个 stage 内 4 卡张量并行)
4 × 4 × 4 = 64
数据流是这样的:
- Batch 被 DP 切成 4 份,每份给一组 PP 流水线
- 每组 PP 内部,数据流经 4 个 stage
- 每个 stage 内部,单层参数被 TP 切到 4 张卡上
graph TD
subgraph DP["DP = 4"]
direction TB
G0[DP Group 0]
G1[DP Group 1]
G2[DP Group 2]
G3[DP Group 3]
end
subgraph PP["PP = 4"]
direction LR
S0[Stage 0]
S1[Stage 1]
S2[Stage 2]
S3[Stage 3]
S0 --> S1 --> S2 --> S3
end
subgraph TP["TP = 4"]
direction TB
T0[GPU0]
T1[GPU1]
T2[GPU2]
T3[GPU3]
end
G0 --> S0
S0 --> T0
S0 --> T1
S0 --> T2
S0 --> T3
请求 A ──► [DP Group 0]
├── PP Stage 0 (TP: GPU0,1,2,3)
├── PP Stage 1 (TP: GPU4,5,6,7)
├── PP Stage 2 (TP: GPU8,9,10,11)
└── PP Stage 3 (TP: GPU12,13,14,15)
请求 B ──► [DP Group 1]
├── PP Stage 0 (TP: GPU16,17,18,19)
...
通信放在哪很重要
三个维度的通信特征完全不同,放错了位置性能直接崩。
| 并行维度 | 通信类型 | 通信量 | 应该放在哪 |
|---|---|---|---|
| DP | All-Reduce (梯度) | 2 × 模型大小 | 跨节点 (IB/RoCE) |
| TP | All-Reduce (激活) | 每层的激活值 | 单节点内 (NVLink) |
| PP | P2P Send/Recv | 相邻 stage 的激活 | 同节点或相邻节点 |
TP 必须放在单节点内。因为 TP 每层的 forward/backward 都有 all-reduce,通信最频繁,需要最高带宽。如果 TP 跨节点走 IB,通信延迟会把计算收益吃掉。
DP 通信量其实最小:一轮 forward+backward 只需要同步一次梯度,而且是异步的(和下一轮 forward overlap)。所以 DP 可以跨节点甚至跨机架。
PP 介于中间:通信量是相邻 stage 的激活值,比 TP 小,比 DP 频繁。尽量同机架部署。
训练 vs 推理的区别
| 场景 | DP | TP | PP |
|---|---|---|---|
| 训练大模型 | ✅ | ✅ | ✅ |
| 单节点推理 (8卡) | ❌ | ✅ | 可选 |
| 多节点推理 | ✅ | ✅ | ✅ |
| 高并发服务 | ✅ | 可选 | 可选 |
训练时 3D 并行是标配,因为既要装下模型,又要并行算数据。
推理时更灵活。单节点 8 卡推理一个 70B 模型,通常 TP=8 就够了,DP=1。如果是服务化场景(vLLM/SGLang),可能 TP=4 做推理,DP=2 做两个副本,增加并发。
我之前搞混的地方
PP 的 bubble 问题。一开始以为只要增加 micro-batch 数量就能完全消除 bubble,但其实:
gantt
title Pipeline Bubble 示意(4 Stages,4 Micro-batches)
dateFormat X
axisFormat %s
section Stage 0
F0 : 0, 1
F1 : 1, 2
F2 : 2, 3
F3 : 3, 4
section Stage 1
F0 : 1, 2
F1 : 2, 3
F2 : 3, 4
F3 : 4, 5
section Stage 2
F0 : 2, 3
F1 : 3, 4
F2 : 4, 5
F3 : 5, 6
section Stage 3
F0 : 3, 4
F1 : 4, 5
F2 : 5, 6
F3 : 6, 7
Stage 0 算完 F0 后,Stage 1 才开始。Stage 1 开始前,Stage 2 和 Stage 3 都在空等,这就是 bubble。
- GPipe:micro-batch 很多,但内存爆炸(需要同时存很多中间激活)
- 1F1B (One-Forward-One-Backward):内存好了,但 bubble 只是减少,不是消除
没有免费的午餐,PP 的收益始终受限于 bubble 比例。
TP 的切分方向。列并行和行并行的顺序不能乱,MLP 的标准做法是”列切 → 行切”,这样中间的激活不需要 all-reduce,只在最后做一次。如果切反了,通信量翻倍。
总结
3D 并行本质上是在解决”模型太大、算力不够、显存不够”这三个问题,分别用 TP、PP、DP 来应对。实际配置时,最优先保证 TP 在机内,PP 在机架内,DP 可以跨机柜。训练必须 3D 全上,推理根据模型大小和并发需求灵活组合。
写到这感觉比之前看 Megatron 配置文件时清楚多了,至少能一眼看出 tensor_model_parallel_size × pipeline_model_parallel_size × data_parallel_size = world_size 背后,每张卡具体在算什么、和谁通信。