Pipeline Parallel
Systems · Pipeline Parallel · NeurIPS 2019

Pipeline Parallel:把模型切成段,让 micro-batch 排进流水线

单卡放不下模型时,最直接的办法是按层切成 p 段,段间只传边界 activation。但流水线会空转(bubble),而且 GPipe 的 all-F-all-B 必须同时留住 m 个 micro-batch 的激活。1F1B 不减少气泡,却把激活驻留从 O(m) 压到 O(p);interleaved 1F1B 再用 extra communication 换更小的气泡。

Yanping Huang, Youlong Cheng, Ankur Bapna, Orhan Firat, Mia Xu Chen, Dehao Chen, et al. arXiv 1811.06965v5 ↗ Megatron-Core @ 70f7a26 ↗
TL;DR · 30 秒抓住核心

Pipeline parallelism 不减少总计算量。它把模型按层切成 p 段,把 mini-batch 切成 m 个 micro-batch,让不同段同时开工。它解决的是「模型放不下」,不是「单卡算得慢」。

GPipe 的 schedule 最保守:先把 m 个 micro-batch 的 forward 全做完,再统一做 backward, 梯度累积后一次更新。好处是语义和串行训练等价;代价是首尾各有 p−1 段空转,而且要把 m 个 micro-batch 的激活都留着。

1F1B 把 steady state 改成一 forward 一 backward,在飞的 micro-batch 最多 p 个, 激活显存从 O(m) 降到 O(p);气泡总量不变。Megatron 默认走这条。

Interleaved 1F1B 让每张卡持有 v 个 model chunk,气泡约按 1/v 缩小, 但 p2p 通信约按 v 增加,而且 m 要按 p 分组。三种 schedule 的取舍, 最终由「显存、气泡、互连」三者谁先卡住决定。

17.9%
p=8、m=32 时的气泡占比
(8−1)/(32+8−1) = 7/39
32
K=8 时取 M=32(=4K)
§2.3 实验观察:M ≥ 4K 时气泡可忽略
3.46GB
AmoebaNet-D 重算后的峰值激活显存
未重算为 6.26 GB
01

模型放不下时,切层是最直接的路——但流水线会空转

先把 p、m、F、B 四个量定清楚;后面所有时间线都只用它们。

数据并行(DP)把 batch 切开,每张卡仍要放一份完整模型,显存不降。张量并行(TP)把每层的 GEMM 切开, 每层都要 all-reduce,跨节点带宽吃不消。流水线并行(PP)换一个维度:按层把模型切成 p 段, 每段放一张卡(或一组卡),段间只传边界 activation。

为了让 p 段同时忙起来,PP 把 mini-batch 再切成 m 个 micro-batch。每个 stage 对每个 micro-batch 做一次 forward(F)和一次 backward(B)。stage s 的 F 要等 stage s−1 的输出,B 要等 stage s+1 的梯度——这就是流水线的依赖链。

依赖链的代价是气泡(bubble):开始时要等 p−1 个 stage 依次拿到第一个 activation, 结束时要等 p−1 个 stage 依次把梯度传回去。m 越大,这段固定开销越容易被摊薄;p 越大,首尾空转越长。

第二个代价是激活显存。做 B 的时候要用 F 时留下的 activation。GPipe 把 m 个 micro-batch 的 F 全做完才做 B,所以这 m 份激活都得留着;1F1B 在 steady state 里一 forward 一 backward,最多只留 p 份。

两个成本

气泡决定吞吐,激活决定能不能开得动

气泡:p 段流水线在首尾各有 p−1 段空转;m 越大越不明显。公式见 03 节。

激活:GPipe 需要保留 m 个 micro-batch 的激活;1F1B 只保留至多 p 个。后者是 Megatron 默认 1F1B 的直接原因。

02

GPipe 用同步换正确性,1F1B 用调度换显存

两条路线的分歧不在数学,而在「什么时候做 backward」。

GPipe

all-F → all-B

先把 m 个 micro-batch 的 forward 全部跑完,再按相反顺序做 backward;所有梯度在 mini-batch 结束时累加并更新一次。

优点:同步语义,结果与把整个 batch 放一张卡上训练等价(同样的数值精度下)。论文 §2.2 的原话是 “gradients from all M micro-batches are accumulated and applied to update the model parameters”。

代价:激活驻留 O(m),首尾气泡集中。论文 §2.3 说气泡时间是 O((K−1)/(M+K−1))。

1F1B

warmup → steady → cooldown

首 stage(rank 0)的 warmup 做 p−1 个 forward;steady state 每步一 forward 一 backward;cooldown 再做 p−1 个 backward。一般 stage 的 warmup/cooldown 数为 p−rank−1(见 04 节)。

优点:在飞的 micro-batch 最多 p 个,激活从 O(m) 降到 O(p);steady state 里每个 stage 交替干活,PipeDream §3.3 称其在 balanced pipeline 中“no GPU is idle in steady state”。

代价:气泡总量不变。SC21 §2.2.1 明确说 “The time spent in the bubble is the same for this new schedule”,省下来的是显存。Megatron 用的是带 batch 末尾 flush 的同步版本(PipeDream-Flush)。

三种 schedule 的差别可以压成一张表(据 GPipe §2.3、PipeDream §3.3、SC21 §2.2 与 Megatron 源码整理;不是原文表格):

schedule执行顺序气泡激活驻留语义 / 用在
GPipeall-F → all-B(p−1)/(m+p−1)m 个 micro-batch同步;GPipe
1F1B首 stage:warmup p−1 F → steady F/B → cooldown p−1 B与 GPipe 相同≤ p 个同步 flush;Megatron 默认
Interleaved 1F1B每卡 v 个 chunk,按 chunk 交错约 1/v与非交错相当同步;Megatron VPP

这里要区分 PipeDream 原版和 Megatron 的 1F1B:PipeDream 用 weight stashing 让异步流水线的权重版本一致 (PipeDream §3.4,按 arXiv 1806.03377v1 的编号;SOSP 版章节号可能不同),而 Megatron 的 1F1B 在每个 batch 末尾做 flush,保持同步语义(SC21 §2.2.1 称之为 PipeDream-Flush)。本页的时间线按后者画。

03

气泡的公式只有一行,难的是把它排进时间线

调 p 和 m,直接看气泡怎么被摊薄;再切换 GPipe/1F1B,看同样的气泡被放在哪里。

bubble fraction = (p − 1) / (m + p − 1)
GPipe §2.3:气泡时间 O((K−1)/(M+K−1)),本页用 p、m 代替 K、M;假设每个 F/B 各占 1 个时间单位

p 是 stage 数,m 是 micro-batch 数。总时间线长度是 2(m+p−1) 个时间单位:m 个 F + m 个 B + 首尾各 p−1 个空转。理想时间是 2m;差值 2(p−1) 就是气泡。m 相对 p 越大,气泡占比越低。

论文的实验观察是 M ≥ 4×K 时气泡开销可忽略(§2.3)。这是吞吐实验的结论;上面的 1:1 公式给的是 时间线占比,两者不是同一把尺子推断(§2.3 同时给出公式与实验观察,未说明二者如何换算)。 公式用来比较 p、m 的相对影响,不用来复现论文的实测吞吐。

自己动手:拖 p 和 m,切换 GPipe / 1F1B

下面是最小时间线模拟:每个 stage 一行,横轴是时间;绿色是 forward,紫色是 backward,斜线格是 bubble。 切换 schedule 只改变 F/B 的顺序,不改变总计算量。

forward backward bubble
时间线与气泡
气泡占比27.3%
总时长22
理想时长16
气泡格6
有效计算72.7%
激活驻留(micro-batch 数,示意)
GPipe8
1F1B4
手算对账:两组参数与上方读数逐位一致

口径:F 和 B 各占 1 个时间单位;p 个 stage;m 个 micro-batches;忽略通信与重算。气泡比例 = (p−1)/(m+p−1)。

① p=4, m=8
气泡 = (4−1)/(8+4−1) = 3/11 = 0.272727… → 27.3%
总时长 = 2×(8+4−1) = 22;理想 = 2×8 = 16;气泡格 = 22−16 = 6
有效计算 = 16/22 = 0.727272… → 72.7%
激活驻留:GPipe 8;1F1B min(4,8)=4。

② p=8, m=32
气泡 = (8−1)/(32+8−1) = 7/39 = 0.179487… → 17.9%
总时长 = 2×(32+8−1) = 78;理想 = 2×32 = 64;气泡格 = 78−64 = 14
有效计算 = 64/78 = 0.820512… → 82.1%
激活驻留:GPipe 32;1F1B min(8,32)=8。

这个时间线简化了什么?不是 Megatron 的完整实现

覆盖:GPipe 的 all-F→all-B 全局屏障;1F1B 的 warmup/steady/cooldown 顺序;stage 间 F/B 依赖; 两种 schedule 在同样 p、m 下的总时长与空转格数。

未覆盖:真实 F/B 时间不相等(通常 backward 更贵);p2p 通信与计算的重叠;re-materialization; 负载不均衡;interleaved 的 virtual chunks;MoE、CP 等额外并行轴。

因此这里只模拟「调度顺序」对气泡位置和总量的影响,不模拟吞吐绝对值。真实实现见 Megatron schedules.py 与 p2p_communication.py。

Interleaved 1F1B:把气泡再除以 v,但通信乘上 v

每张卡不再只持有一段连续层,而是持有 v 个 model chunk(例如 4 层变成 2 个 chunk,每块 2 层)。 调度时按 chunk 交错,设备在等某个 chunk 的梯度时可以先算另一个 chunk 的 forward。SC21 §2.2.2 给的近似是: 气泡时间变为原来的 1/v,通信量增加 v 倍。

代价是 m 必须按 p 分组:SC21 要求 m 是 p 的整数倍;Megatron 的实现允许最后一组有余数,但余数必须为 0 或 ≥ p,否则会报 “dependency bubbles” 错误(schedules.py:1164-1174)。

04

Megatron 把三种 schedule 写成一个入口

看代码怎么选 schedule、怎么算 warmup、怎么发 p2p。

入口是 get_forward_backward_func(schedules.py:53)。非 MultiModule 路径下,它只看两个配置:pipeline parallel size 和 virtual pipeline size。pp_size > 1 时,有 virtual pipeline 就走 interleaved,否则走 non-interleaved 1F1B;pp_size = 1 走 no_pipelining(schedules.py:161-167)。

if pp_size > 1:
    if vp_size is not None:
        forward_backward_func = forward_backward_pipelining_with_interleaving
    else:
        forward_backward_func = forward_backward_pipelining_without_interleaving
else:
    forward_backward_func = forward_backward_no_pipelining
schedules.py:161-167(154-155 是 MultiModule 早退、157-159 是 None fallback;去掉类型标注与注释)

warmup 怎么算。non-interleaved 的 warmup 数是 total_stages - current_stage - 1(schedules.py:2301),也就是 p−rank−1; steady loop 跑 m - warmup 次「一 forward 一 backward」(schedules.py:2409), 最后 cooldown 把 warmup 阶段留下的 backward 做完(schedules.py:2488)。

interleaved 的 warmup 数更复杂: (pp_size - rank - 1) * 2 + (num_model_chunks - 1) * microbatch_group_size_per_vp_stage (schedules.py:958-959)。代码里前一项固定乘 2;后一项按 (num_model_chunks - 1) * microbatch_group_size_per_vp_stage 填充。schedule 的 (microbatch, chunk) 顺序由 get_schedule_table 生成(schedules.py:983),warmup/steady/cooldown 的主循环分别起于 schedules.py:1567、:1727、:1965(对应的段起始注释在 1536、1725、1948)。

p2p 怎么发。P2PCommunicator(p2p_communication.py:148)封装 pp group; _batched_p2p_ops(:22)用 torch.distributed.batch_isend_irecv 批量提交 isend/irecv;steady state 用 send_forward_recv_backward(:521)和 send_backward_recv_forward(:554)把两个方向的重叠起来。通信只发生在 stage 边界, 传的是 activation / gradient 的张量,不传整层参数。

激活怎么省。non-interleaved 循环里维护 input_tensors 和 output_tensors 两个列表:warmup append 见 schedules.py:2393-2394;steady append + pop(0) 见 schedules.py:2453-2461,cooldown 继续 drain。列表长度由在飞 micro-batch 数决定, 不会增长到 m。这就是 1F1B 在代码层面的省显存机制。

TP

层内切 GEMM

每层两次 all-reduce;适合节点内 NVLink;切得太细会让 GEMM 变小。

PP

层间切 stage

段间只传边界 activation;可以跨节点;代价是 bubble 与激活驻留。

DP

复制模型切 batch

每张卡一份完整模型;梯度 all-reduce;实现简单,显存不降。

Megatron 用 parallel_state 里的 group 函数把三维并行拼起来: get_tensor_model_parallel_world_size(parallel_state.py:2151)、 get_pipeline_model_parallel_world_size(:2159)、 get_pipeline_model_parallel_rank(:2187)、 get_virtual_pipeline_model_parallel_world_size(:2257)、 get_data_parallel_world_size(:2321)。PP 只是其中一个轴。

论文 §3 把 TP 描述为 “orthogonal and complementary to pipeline model parallelism”;Appendix B 讲的是 model+data parallel 的 group 划分。论文的 8.3B / 512 GPU 实验是 8-way TP + 64-way DP,不含 PP; 论文 §6 明确说更大的模型需要把 intra-layer 和 inter-layer model parallelism 叠加起来。

05

同一组 p、m,两条时间线的差别在哪

空格就是 bubble;GPipe 把气泡堆在首尾,1F1B 把它切碎摊开。

GPipe 与 1F1B 的流水线时间线对比 4 个流水线 stage、8 个 microbatches。绿色为 forward,紫色为 backward,斜线格为 bubble。两者总时长都是 22 个时间单位,每行都有 6 个 bubble 格;GPipe 的 bubble 集中在首尾,1F1B 的 bubble 被打散。 forwardbackwardbubble GPipe · all-F → all-B 前 8 个 micro-batch 全部 forward,再全部 backward S0 S1 S2 S3 0 5 10 15 20 t 1F1B · warmup → steady → cooldown 先做 p−1 个 forward,之后一 forward 一 backward S0 S1 S2 S3 0 5 10 15 20 t
本页重绘(非原文 Figure):p=4、m=8、F/B 各 1 个时间单位。斜线格 = bubble;绿色 = forward,紫色 = backward。

GPipe 的时间线是两块三角形:前 8 个 F 依次填满 4 个 stage,最后 8 个 B 再依次退出。每个 stage 的空转都集中在首尾, 共 6 个时间单位。1F1B 的时间线是锯齿:stage 0 先做 3 个 warmup F,再做 1 个 steady F,然后等梯度做 B……空转被切碎, 但每个 stage 仍然是 6 个时间单位。两者的总时长都是 22。

真正的差别不在时间线长度,而在激活:GPipe 要同时保留 m=8 个 micro-batch 的激活,1F1B 最多 p=4 个。Interleaved 没有画进来:它会把每个 stage 的工作再切成 v 份,气泡缩小,但 p2p 消息变多; 时间线更碎,不改变总计算量。

为什么 1F1B 的空转看起来更分散,但总数一样?把每个 stage 的空转格数相加

1F1B 在 warmup 之后保持「一 F 一 B」的节奏;某个 stage 等上游梯度时,其他 stage 可能正在算。空转没有被消除, 只是从连续的两段变成了分散的格子。

在这个 4×8 的例子里,每个 stage 有 2×8=16 个计算格、22−16=6 个空转格;4 个 stage 合计 24 个空转格, 占 4×22=88 个格子的 27.3%。GPipe 和 1F1B 的这两个总数相同,差别只在空转的位置。

06

吞吐几乎线性,但只在大 m 时成立

GPipe 的两张吞吐表,和 Megatron 的 3D 组合数据。

Table 2 是 GPipe 在 TPU 上的归一化训练吞吐:每个分区一张 accelerator,batch size 按显存调整。行是 micro-batch 数 M,列是分区数 K。M=1 表示没有 pipeline parallelism。

TPUAmoebaNet K=2K=4K=8Transformer K=2K=4K=8
M=111.131.3811.071.3
M=41.071.261.721.73.24.8
M=321.211.843.481.83.46.3

Table 2:归一化训练吞吐(TPU;batch size 按显存调整)

M=1 时,K 从 2 到 8,AmoebaNet 只从 1 到 1.38,Transformer 从 1 到 1.3:只有一张卡在算,pipeline 没有并行度。 M=32 时,Transformer 在 K=8 达到 6.3,是 K=2 时 1.8 的 3.5 倍(6.3/1.8;论文 §3 也写 3.5× speedup); AmoebaNet 从 K=2 的 1.21 到 K=8 的 3.48,只有 3.48/1.21≈2.9×,低于 4× 线性,论文归因于各层参数量/计算量不均衡。

Table 3 在没有 NVLink 的 GPU 上,M=32、K 从 2 到 8:AmoebaNet 2.7×,Transformer 3.3×。GPipe 只在分区边界传 activation,所以 PCIe 也能跑出接近线性的趋势;论文 §3 说 communication bandwidth 不再是 model parallelism 的瓶颈。

GPUAmoebaNet K=2K=4K=8Transformer K=2K=4K=8
M=3211.72.711.83.3

Table 3:无 NVLink 的 GPU 归一化吞吐(M=32)

Table 1 未完整转录,只取关键行;其余分区/显存行未转录。AmoebaNet-D 从 82M(Naive-1)到 318M(Pipeline-1,重算后), 峰值激活 6.26 GB → 3.46 GB,减少 2.80 GB(相对减少 44.7%);同一张表里,AmoebaNet 用 8 个分区到 1.8B, Transformer 用 128 个分区到 83.9B。表头条件:AmoebaNet 行是 8GB NVIDIA GPU,Transformer 行是 16GB TPUv3。 这里的 82M / 318M / 1.8B / 83.9B 都是 Table 1 的直接读数,不需要二次换算。

反直觉

1F1B 不减少气泡,interleaved 才减少

1F1B 的卖点是显存:在飞 micro-batch 从 m 降到 p,时间线总长不变(SC21 §2.2.1)。真正缩短气泡的是 interleaved 1F1B,代价是通信量约乘 v(SC21 §2.2.2)。如果只看到「1F1B」三个字就以为气泡没了,选型会跑偏。

Megatron 1909.08053 的 8.3B / 512 GPU 结果是 8-way TP + 64-way DP,15.1 PFLOPS、76% scaling efficiency; 论文没有把这个实验说成 PP 的结果,而是明确说 TP 与 PP 正交,>16B 需要把两者叠加(§6)。

07

什么时候别用 pipeline

PP 解决的是「放不下」,不是「算得快」。

边界一

模型能单卡放下时,PP 只会更慢

PP 增加 bubble 和 p2p,不减少单卡计算量。GPipe 的 Table 2 里 M=1 这一行就是证据:K 从 2 到 8, 归一化吞吐只到 1.13 / 1.38(AmoebaNet)和 1.07 / 1.3(Transformer),因为没有 micro-batch 可以填满流水线。

边界二

m 太小,气泡吃不掉

气泡比例是 (p−1)/(m+p−1);m 接近 p 时,首尾空转占比很高。论文的实验观察是 M ≥ 4K 时气泡可忽略(§2.3);interleaved 还要求 m 按 p 分组,最后一组余数必须为 0 或 ≥ p,否则 Megatron 直接报 dependency bubbles(schedules.py:1164-1174)。

边界三

p 受互连和负载均衡限制

PP 的通信量只和边界 activation 有关,比 TP 更适合跨节点;但 p2p 延迟、stage 间负载不均衡都会变成空转。 GPipe 用 cost estimator 尽量把计算均分到各 partition;AmoebaNet 的 sub-linear 吞吐就是参数分布不均的结果 (Table 2 与 §3)。

边界四

1F1B 不是免费降气泡

1F1B 省的是激活,不是气泡;如果瓶颈是 bubble,应该增大 m、减少 p,或者上 interleaved。interleaved 的 代价是通信量约乘 v,网络没有余量时收益会被吃掉(SC21 §2.2.2)。

边界五

顺序层图假设

PP 假设模型可以表达成顺序的层序列;分支、多模态 tower、非均匀层需要自定义 partition 和 schedule。 Megatron 的 multi-module 支持能处理一部分,但这已经是额外的工程复杂度,不是开箱即用。

把上面的条件压成一张决策表(据 GPipe §2.3、SC21 §2.2 与 Megatron 源码整理;不是原文表格):

情况建议
单卡放得下不用 PP;DP 或 TP 更直接
放不下,节点内带宽高TP + PP;TP 放节点内,PP 跨节点
激活显存是瓶颈1F1B(激活 O(p))
气泡是瓶颈,网络有余量interleaved 1F1B(气泡约 1/v,通信约 v×)
batch 很小先增大 m 或减少 p;否则 bubble 占比很高

一句话:p 决定能放多大,m 决定气泡被摊多薄,schedule 决定激活留多少。先看哪个成本在卡你, 再调对应的旋钮;不要默认「上了 PP 就一定更快」。