Chapter 08

流水线并行 PP 与 interleaved schedule

📌 源码引用基于 megatron/core/pipeline_parallel/ · commit 432d76b2b8bf 。

8.1 为什么 TP 之外还要 PP

TP 的痛点:

PP 把模型的不同层放到不同 GPU 上,每张卡只算一段。175B 经典配置 PP=16,每张卡装 6 层,配上 TP=8 就 8×16=128 卡支撑一份完整模型。

8.2 PP 的核心问题:bubble

如果只把一个 batch 送进 pipeline,会出现严重空闲:

sequenceDiagram participant S0 as Stage 0 participant S1 as Stage 1 participant S2 as Stage 2 participant S3 as Stage 3 S0->>S0: fwd S0-->>S1: hidden S1->>S1: fwd Note over S0: idle S1-->>S2: hidden S2->>S2: fwd Note over S0,S1: idle S2-->>S3: hidden S3->>S3: fwd Note over S0,S2: idle S3-->>S3: bwd S3-->>S2: grad Note over S0,S1: idle S2-->>S2: bwd S2-->>S1: grad Note over S0: idle S1-->>S1: bwd S1-->>S0: grad S0-->>S0: bwd

每条 stage 在 forward / backward 大部分时间都在等别人。这就是 pipeline bubble。

8.3 解决之一:micro batch 流水

把一个 global batch 拆成 $m$ 个 micro batch,让它们像水管里的水一样接连进入 pipeline:

gantt dateFormat X axisFormat %s section S0 μ1 fwd :a1, 0, 1 μ2 fwd :a2, 1, 2 μ3 fwd :a3, 2, 3 μ4 fwd :a4, 3, 4 μ1 bwd :a5, 7, 8 μ2 bwd :a6, 8, 9 μ3 bwd :a7, 9, 10 μ4 bwd :a8, 10, 11 section S1 μ1 fwd :b1, 1, 2 μ2 fwd :b2, 2, 3 μ3 fwd :b3, 3, 4 μ4 fwd :b4, 4, 5 μ1 bwd :b5, 6, 7 μ2 bwd :b6, 7, 8 μ3 bwd :b7, 8, 9 μ4 bwd :b8, 9, 10 section S2 μ1 fwd :c1, 2, 3 μ2 fwd :c2, 3, 4 μ3 fwd :c3, 4, 5 μ4 fwd :c4, 5, 6 μ1 bwd :c5, 5, 6 μ2 bwd :c6, 6, 7 μ3 bwd :c7, 7, 8 μ4 bwd :c8, 8, 9

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 后开始穿插反向。

gantt dateFormat X axisFormat %s title 1F1B 调度(S=4 stages, m=8 micro batches) section S0 F1 :0, 1 F2 :1, 2 F3 :2, 3 F4 :3, 4 F5/B1 :4, 5 F6/B2 :5, 6 F7/B3 :6, 7 F8/B4 :7, 8 B5 :8, 9 B6 :9, 10 B7 :10, 11 B8 :11, 12 section S3 F1 :3, 4 F2/B1 :4, 5 F3/B2 :5, 6 F4/B3 :6, 7 F5/B4 :7, 8 F6/B5 :8, 9 F7/B6 :9, 10 F8/B7 :10, 11 B8 :11, 12

每张卡同时只持有 ~$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.pyPP + 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 计算只在末尾 stagefinalize_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_sizevirtual_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 行)的核心几个:

行号函数返回
1621get_tensor_model_parallel_world_size() TP 组大小
1629get_pipeline_model_parallel_world_size() PP stage 数
1649get_tensor_model_parallel_rank() 当前 TP rank
1657get_pipeline_model_parallel_rank() 当前 PP stage
1710get_virtual_pipeline_model_parallel_rank() 当前 VP chunk index
1733get_tensor_model_parallel_src_rank() TP 组里负责广播的 rank
1749get_data_parallel_src_rank() DP 组里负责加载数据的 rank
1503get_context_parallel_group() ★ CP 组(2025 新)
1526get_hybrid_data_context_parallel_groups() ★ DP×CP 复合组

整个文件有约 53 个 get_*_world_size / get_*_rank,覆盖所有并行维度(TP/PP/DP/CP/EP/MoE 内 DP/MoE 内 EP/...)。

8.15 这章你需要带走什么