本文是分布式训练并行系列的一篇。前置背景可参考:
1. 为什么需要流水线并行
单机模型并行(Naïve Model Parallel)把模型不同层放在不同 GPU,同一时刻只有一张卡在算,其余全部空等——GPU 利用率极低。
GPipe [1] 提出用 micro-batch 填充流水线空隙:把一个 mini-batch 切成 $m$ 份 micro-batch,各阶段可以流水线化执行。但 GPipe 的 “全 F 全 B” 策略导致:
- 显存峰值高:设备 0 要把所有 $m$ 个 micro-batch 的激活值全存到反向传播结束;
- 气泡(Bubble)时间大:前向全部跑完才开始反向,等待时间约 $(p-1)$ 个 stage 的前向 + 反向时间。
1F1B(One Forward One Backward)调度[2] 是 GPipe 的工程替代方案,也是 Megatron-LM 的默认 PP 实现,显著改善了这两个问题。
2. 1F1B 调度
2.1 核心思想
不等全部前向完成,尽量早开始反向。具体规则:
- Warmup 阶段:阶段 $i$(从 0 开始)先跑 $p - i$ 个 micro-batch 的前向(其中 $p$ 是 stage 总数),填充流水线;
- Steady state:此后每做完一个 micro-batch 的前向,立刻做一个(更早的)micro-batch 的反向,保持 in-flight 激活数量恒为 $p$;
- Cooldown 阶段:流水线排空,只做反向。
时间轴(p=4, m=8):
Device 0: F0 F1 F2 F3 F4 F5 F6 F7 B7 B6 B5 B4 B3 B2 B1 B0
Device 1: F0 F1 F2 F3 F4 F5 F6 F7 B7 B6 B5 B4 B3 B2 B1 B0
Device 2: F0 F1 F2 F3 F4 F5 F6 F7 B7 B6 ...
Device 3: F0 F1 F2 F3 F4 F5 F6 F7 B7 ...
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^
Steady State(1F1B) Cooldown
2.2 气泡比率(Bubble Ratio)
GPipe(All-F-then-All-B)的气泡时间约为 $2(p-1)$ 个单元时间(前向 + 反向各 $(p-1)$ 个 stage 等待)。
1F1B(PipeDream-Flush)的气泡比率:
\[\text{bubble ratio} = \frac{p - 1}{m}\]其中 $p$ 是 pipeline stage 数,$m$ 是 micro-batch 数。
推导:
- Warmup 阶段,设备 $p-1$(最后一台)等了 $(p-1)$ 个前向时间才接到数据;
- Cooldown 阶段,设备 $0$(第一台)在最后 $(p-1)$ 个反向里无法叠上新前向;
- 总气泡时间为 $(p-1) \cdot (t_f + t_b)$,理想总时间为 $m \cdot (t_f + t_b)$。
因此,当 $m \gg p$ 时气泡可以压得很低。实践中通常要求 $m \geq 4p$。
2.3 内存分析
| 方案 | 最大 in-flight 激活 | 显存主要瓶颈 |
|---|---|---|
| Naïve MP | 整个 batch | 严重 OOM |
| GPipe | $m$ 个 micro-batch 全部 | 随 $m$ 线性增长 |
| 1F1B | $p$ 个 micro-batch | 固定为 $p$,与 $m$ 无关 |
1F1B 的关键优势:把内存峰值从 $O(m)$ 压到 $O(p)$。
3. 关键实现细节
3.1 激活值的生命周期
1F1B 中,每个 stage 在 steady state 需要同时持有最多 1 个 micro-batch 的激活值供反向使用(其它的要么已释放,要么还未产生)。实际实现中会用 double buffering 存两个激活以隐藏通信延迟:做 micro-batch $k$ 的反向时,把 micro-batch $k+1$ 的激活通过 P2P 通信提前 prefetch 进来。
3.2 P2P 通信
相邻 stage 间通过 send_forward / recv_forward / send_backward / recv_backward 四类 P2P 操作传激活和梯度:
# Megatron-LM schedules.py(简化版)
# 前向:从上游 stage 接收激活,计算,发给下游
input_tensor = recv_forward(recv_tensor_shapes, config)
output_tensor = forward_step(input_tensor, ...)
send_forward(output_tensor, config)
# 反向:从下游接梯度,反向计算,发梯度给上游
output_tensor_grad = recv_backward(send_tensor_shapes, config)
input_tensor_grad = backward_step(input_tensor, output_tensor, output_tensor_grad)
send_backward(input_tensor_grad, config)
Megatron-LM 使用非阻塞(isend/irecv)实现通信计算重叠。
3.3 梯度累积与 Optimizer Step
多个 micro-batch 的梯度在同一个 pipeline stage 上累积,只在整个 batch(所有 $m$ 个 micro-batch)的反向都完成后才做一次 optimizer.step(),确保参数更新同步(PipeDream-Flush 中无 weight staleness 问题)。
4. 配置建议
Megatron-LM 开启 PP 的核心参数:
--pipeline-model-parallel-size 4 # p 值
--num-micro-batches 8 # m 值,建议 m >= 4*p
组合并行下的经验法则:
- PP 用于节点间(通信量小,只传激活值,带宽敏感度低);
- TP 用于节点内(每层都有 AllReduce,依赖 NVLink 高带宽);
- DP 在剩余 GPU 上扩展吞吐。
5. 局限与改进方向
- 气泡无法为零:1F1B 的气泡比率 $(p-1)/m$ 在 $p$ 大时仍可观。更大的 $m$ 可以降低气泡,但会增加显存(每个 stage 持有更多 micro-batch 的激活)。
- Warmup/Cooldown 不对称:激活内存在 warmup 阶段快速积累,到 steady state 才稳定。
- 改进方向:
- VPP(Interleaved PP):通过让每个设备负责多个不连续层,进一步降低气泡比率(见下篇);
- DualPipe(DeepSeek-V3 [3]):双向流水线,实现近零气泡,同时不增加激活内存。
- ZeroBubble [4]:通过将权重梯度计算(
dW)从激活梯度计算(dX)中分离,实现真正零气泡。
参考
[1] Huang, Y., et al. GPipe: Efficient Training of Giant Neural Networks using Pipeline Parallelism. NeurIPS 2019. arxiv:1811.06965
[2] Narayanan, D., et al. Efficient Large-Scale Language Model Training on GPU Clusters Using Megatron-LM. SC 2021. arxiv:2104.04473
[3] DeepSeek-AI. DeepSeek-V3 Technical Report. 2024. arxiv:2412.19437
[4] Qi, P., et al. Zero Bubble Pipeline Parallelism. ICLR 2024. arxiv:2401.10241