单卡放不下模型时,最直接的办法是按层切成 p 段,段间只传边界 activation。但流水线会空转(bubble),而且
GPipe 的 all-F-all-B 必须同时留住 m 个 micro-batch 的激活。1F1B 不减少气泡,却把激活驻留从
O(m) 压到 O(p);interleaved 1F1B 再用 extra communication 换更小的气泡。
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 的取舍,
最终由「显存、气泡、互连」三者谁先卡住决定。
先把 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 的直接原因。
两条路线的分歧不在数学,而在「什么时候做 backward」。
先把 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))。
首 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 | 执行顺序 | 气泡 | 激活驻留 | 语义 / 用在 |
|---|---|---|---|---|
| GPipe | all-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)。本页的时间线按后者画。
调 p 和 m,直接看气泡怎么被摊薄;再切换 GPipe/1F1B,看同样的气泡被放在哪里。
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 的相对影响,不用来复现论文的实测吞吐。
下面是最小时间线模拟:每个 stage 一行,横轴是时间;绿色是 forward,紫色是 backward,斜线格是 bubble。 切换 schedule 只改变 F/B 的顺序,不改变总计算量。
口径: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。
覆盖: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。
每张卡不再只持有一段连续层,而是持有 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)。
看代码怎么选 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)。
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 在代码层面的省显存机制。
每层两次 all-reduce;适合节点内 NVLink;切得太细会让 GEMM 变小。
段间只传边界 activation;可以跨节点;代价是 bubble 与激活驻留。
每张卡一份完整模型;梯度 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 叠加起来。
空格就是 bubble;GPipe 把气泡堆在首尾,1F1B 把它切碎摊开。
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 在 warmup 之后保持「一 F 一 B」的节奏;某个 stage 等上游梯度时,其他 stage 可能正在算。空转没有被消除, 只是从连续的两段变成了分散的格子。
在这个 4×8 的例子里,每个 stage 有 2×8=16 个计算格、22−16=6 个空转格;4 个 stage 合计 24 个空转格, 占 4×22=88 个格子的 27.3%。GPipe 和 1F1B 的这两个总数相同,差别只在空转的位置。
GPipe 的两张吞吐表,和 Megatron 的 3D 组合数据。
Table 2 是 GPipe 在 TPU 上的归一化训练吞吐:每个分区一张 accelerator,batch size 按显存调整。行是 micro-batch 数 M,列是分区数 K。M=1 表示没有 pipeline parallelism。
| TPU | AmoebaNet K=2 | K=4 | K=8 | Transformer K=2 | K=4 | K=8 |
|---|---|---|---|---|---|---|
| M=1 | 1 | 1.13 | 1.38 | 1 | 1.07 | 1.3 |
| M=4 | 1.07 | 1.26 | 1.72 | 1.7 | 3.2 | 4.8 |
| M=32 | 1.21 | 1.84 | 3.48 | 1.8 | 3.4 | 6.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 的瓶颈。
| GPU | AmoebaNet K=2 | K=4 | K=8 | Transformer K=2 | K=4 | K=8 |
|---|---|---|---|---|---|---|
| M=32 | 1 | 1.7 | 2.7 | 1 | 1.8 | 3.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 的卖点是显存:在飞 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)。
PP 解决的是「放不下」,不是「算得快」。
PP 增加 bubble 和 p2p,不减少单卡计算量。GPipe 的 Table 2 里 M=1 这一行就是证据:K 从 2 到 8, 归一化吞吐只到 1.13 / 1.38(AmoebaNet)和 1.07 / 1.3(Transformer),因为没有 micro-batch 可以填满流水线。
气泡比例是 (p−1)/(m+p−1);m 接近 p 时,首尾空转占比很高。论文的实验观察是 M ≥ 4K 时气泡可忽略(§2.3);interleaved 还要求 m 按 p 分组,最后一组余数必须为 0 或 ≥ p,否则 Megatron 直接报 dependency bubbles(schedules.py:1164-1174)。
PP 的通信量只和边界 activation 有关,比 TP 更适合跨节点;但 p2p 延迟、stage 间负载不均衡都会变成空转。 GPipe 用 cost estimator 尽量把计算均分到各 partition;AmoebaNet 的 sub-linear 吞吐就是参数分布不均的结果 (Table 2 与 §3)。
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 就一定更快」。