流水线并行 PP 与 interleaved schedule
8.1 为什么 TP 之外还要 PP
TP 的痛点:
- 受限于单节点 NVLink 域,TP 度 ≤ 8;
- 跨节点 TP 性能会塌方;
- 对 175B 这种级别的模型,TP=8 还不够(每卡仍要 22B 的状态)。
PP 把模型的不同层放到不同 GPU 上,每张卡只算一段。175B 经典配置 PP=16,每张卡装 6 层,配上 TP=8 就 8×16=128 卡支撑一份完整模型。
8.2 PP 的核心问题:bubble
如果只把一个 batch 送进 pipeline,会出现严重空闲:
每条 stage 在 forward / backward 大部分时间都在等别人。这就是 pipeline bubble。
8.3 解决之一:micro batch 流水
把一个 global batch 拆成 $m$ 个 micro batch,让它们像水管里的水一样接连进入 pipeline:
bubble 比例:
$$ \text{bubble ratio} = \frac{S - 1}{m + S - 1} $$
$S$ = stage 数,$m$ = micro batch 数。$m$ 越大 bubble 越小,但单步显存压力越大(要存 $m$ 份激活)。
8.4 1F1B:让显存可控的调度
"all-forward-all-backward"(GPipe 风格)虽然简单,但每个 stage 要存 $m$ 份激活,显存爆炸。Megatron 用 "1F1B(one-forward-one-backward)"调度:前向到第 $S$ 个 micro batch 后开始穿插反向。
每张卡同时只持有 ~$S$ 份激活(不是 $m$ 份)。
8.5 Interleaved(virtual pipeline):进一步降 bubble
1F1B 已经把激活控制住了,但 bubble 仍存在。Megatron 提出 virtual pipeline(也叫 interleaved 1F1B):让每张物理卡持有多段不连续的层。
例如 PP=4,每张卡原本装 $L/4$ 层;启用 virtual pipeline=2 后,每张卡装 两段 $L/8$ 层,分别对应 virtual stage 0 和 4。这样一个 micro batch 在 pipeline 里转两圈,bubble 变小:
$$ \text{bubble ratio}_\text{interleaved} = \frac{1}{v} \cdot \frac{S - 1}{m + S - 1} $$
$v$ 是 virtual pipeline 大小。代价是通信次数变 $v$ 倍,所以对网络带宽要求更高。
8.6 在 Megatron 里怎么打开
# 基本 PP
--pipeline-model-parallel-size 16
# Interleaved(virtual pipeline)
--pipeline-model-parallel-size 16 \
--virtual-pipeline-model-parallel-size 2 \
--num-layers-per-virtual-pipeline-stage 3 # 96 层 / (16 × 2) = 3 层每 vstage
# 让 embedding 单独占一个 stage(loss balancing)
--standalone-embedding-stage
8.7 layer 怎么分配到 stage
Megatron 提供几种 layer layout 策略:
| flag / 字段 | 说明 |
|---|---|
--num-layers-per-pipeline-stage | 固定每 stage 层数 |
--account-for-embedding-in-pipeline-split | 把 embedding/lm_head 当成"半层"算进 split |
--account-for-loss-in-pipeline-split | loss 层也算 |
PipelineParallelLayerLayout | 自定义 layout 类(高级用法) |
8.8 关键源码
| 文件 | 角色 |
|---|---|
megatron/core/pipeline_parallel/schedules.py | 核心:1F1B / interleaved 调度实现 |
megatron/core/pipeline_parallel/p2p_communication.py | NCCL p2p 通信封装(send / recv hidden) |
megatron/core/pipeline_parallel/combined_1f1b.py | combined 1F1B(更新版) |
megatron/core/pipeline_parallel/utils.py | 工具函数 |
megatron/core/pipeline_parallel/hybrid_cp_schedule.py | PP + CP 混合调度 |
8.9 forward_backward_func:用户可见入口
外部代码(包括 pretrain_gpt.py)调的入口是:
from megatron.core.pipeline_parallel.schedules import get_forward_backward_func
forward_backward_func = get_forward_backward_func()
losses = forward_backward_func(
forward_step_func=forward_step_func,
data_iterator=data_iterator,
model=[model],
num_microbatches=num_microbatches,
seq_length=seq_length,
micro_batch_size=micro_batch_size,
forward_only=False,
)
这个函数返回的不是单一 forward / backward,而是整套 1F1B 调度的执行结果。算法工程师只要传 forward_step_func 这一个 callback,剩下都交给 schedule。
8.10 PP 通信量
每个 micro batch 跨 stage 的 p2p 通信 = $B_\mu \cdot S \cdot H \cdot 2\,\text{bytes}$(bf16)。
全 step 通信量约 $2 m \cdot B_\mu \cdot S \cdot H \cdot 2$(前向 + 反向)。
相比 TP 的 all-reduce,PP 通信量小很多,所以 PP 能跨节点(infiniband 即可),TP 不行(要 NVLink)。
8.11 PP 跨节点 vs TP 跨节点
| 并行 | 每层通信 | 能跨节点? |
|---|---|---|
| TP | 每层 2 次 all-reduce | 不行(需要 NVLink) |
| PP | 每 micro batch 一对 p2p send/recv | 可以(infiniband 足够) |
| DP | 每步 1 次 all-reduce 梯度 | 可以 |
经典放置法:单节点内 TP=8,跨节点 PP=16,再跨更大范围 DP。
8.12 PP 的典型坑
| 现象 | 原因 | 修复 |
|---|---|---|
| loss 只在最后 stage 有效 | 正常,loss 计算只在末尾 stage | 用 finalize_model_grads 等接口跨 rank 同步 |
| num_layers 不能被 PP 整除 | — | 调 num_layers 或用 num-layers-per-pipeline-stage 显式分配 |
| m (micro batch 数) 太小 | bubble 大 | 升 global_batch_size 或降 DP |
| interleaved 通信极慢 | 跨节点 + 高 v 值 | 降 virtual_pipeline_size 到 2 |
| 某 stage OOM 别 stage 没事 | layer layout 不均 | 用 --standalone-embedding-stage 或自定义 layout |
| 第 1 stage embedding 显存高 | embedding 大 | 同上,或 TP 切 vocab |
8.13 schedules.py:三个 forward_backward 函数
所有 PP 调度集中在 megatron/core/pipeline_parallel/schedules.py(104KB)。入口和三种调度:
| 行号 | 函数 | 触发条件 |
|---|---|---|
| 48 | get_forward_backward_func() | dispatcher:根据 pipeline_model_parallel_size 和 virtual_pipeline_model_parallel_size 选择 |
| 592 | forward_backward_no_pipelining | PP=1(退化为纯 DP/TP) |
| 896 | forward_backward_pipelining_with_interleaving | VP>1 时的 interleaved 1F1B(最复杂) |
| 2039 | forward_backward_pipelining_without_interleaving | PP>1, VP=1 普通 1F1B |
另外新文件 pipeline_parallel/hybrid_cp_schedule.py(2025-Q2)专门为 PP + CP 联合 设计——超长序列下原 1F1B 的 buffer 数与 chunk 模型不再够用。
8.14 ParallelState 关键 API
调试 PP 时一定会用到,megatron/core/parallel_state.py(1790 行)的核心几个:
| 行号 | 函数 | 返回 |
|---|---|---|
| 1621 | get_tensor_model_parallel_world_size() | TP 组大小 |
| 1629 | get_pipeline_model_parallel_world_size() | PP stage 数 |
| 1649 | get_tensor_model_parallel_rank() | 当前 TP rank |
| 1657 | get_pipeline_model_parallel_rank() | 当前 PP stage |
| 1710 | get_virtual_pipeline_model_parallel_rank() | 当前 VP chunk index |
| 1733 | get_tensor_model_parallel_src_rank() | TP 组里负责广播的 rank |
| 1749 | get_data_parallel_src_rank() | DP 组里负责加载数据的 rank |
| 1503 | get_context_parallel_group() | ★ CP 组(2025 新) |
| 1526 | get_hybrid_data_context_parallel_groups() | ★ DP×CP 复合组 |
整个文件有约 53 个 get_*_world_size / get_*_rank,覆盖所有并行维度(TP/PP/DP/CP/EP/MoE 内 DP/MoE 内 EP/...)。
8.15 这章你需要带走什么
- PP 把不同层放到不同 GPU;
- 1F1B 调度把显存占用从 $m$ 份激活降到 $S$ 份;
- bubble 比例 = $(S{-}1)/(m{+}S{-}1)$,调大 micro batch 数 $m$ 来降;
- Interleaved(virtual pipeline)进一步降 bubble,代价是通信变多;
- PP 可跨节点,TP 不行;
forward_backward_func是用户可见入口。