流水线并行:1F1B、虚拟段与气泡
把模型按层切成 P 段之后,micro-batch 怎么排才能让每段都有活干。从 GPipe 到 1F1B 到 interleaved 1F1B 到 Zero Bubble,每一步只改一件事。推气泡占比 (P−1)/m、每个 rank 在途激活 P−r 份、虚拟段的显存代价,配一个可以拖的时间线。
流水线并行把 层切成 段,每段放一张卡,一个 batch 切成 个 micro-batch 依次流过去。它是几种并行里通信最省的:段与段之间只传一个 的激活。代价是另一种东西:后面的段在等前面的段送前向,前面的段在等后面的段送反向。这段等待叫气泡。这一篇讲气泡从哪来、怎么算、四代调度各拿什么去换它。
先把时间线画出来
约定前向记 1 个时间单位,反向记 2 个(反向要算两个矩阵乘,对输入的梯度和对权重的梯度)。下面是一个可以拖的时间线,默认是 1F1B 调度,,:
用同一个 micro-batch 的深浅表示它在流水线里的先后; 时颜色的明暗区分同一 rank 上的不同虚拟段。1F1B 的气泡时间占比理论值是 ,通常近似写成 ;读数里给的是 rank 0 的模拟值。
斜线阴影是气泡。rank 0 在开头做完几个前向之后要停下来,等第一个 micro-batch 的反向从 rank 3 一路传回来;rank 3 在开头什么都做不了,要等第一个 micro-batch 的前向一路传过来。两头的等待加起来就是流水线的固有代价。
四代调度,每代改一件事
GPipe 最直接: 个 micro-batch 的前向全部做完,再做全部反向。气泡就是流水线灌满和排空的时间,占比 。问题在显存:反向开始之前,每个 rank 要为全部 个 micro-batch 保存激活, 一大就放不下,而气泡偏偏要靠大 来摊薄。
1F1B 改执行顺序。每个 rank 先做几个前向(warmup),之后每做一个前向就紧接着做一个反向,最后把剩下的反向做完。关键效果是:一个 micro-batch 的反向一做完,它的激活就能释放,所以每个 rank 同时攒着的激活份数有了上界。这个上界下一节推。要注意的是,1F1B 没有减少气泡,两头的灌满和排空还在,它换的是显存。
Interleaved 1F1B 减气泡。让每个 rank 不持有连续的 层,而是持有 段不连续的层(虚拟段),micro-batch 在流水线里转 圈。每圈的前向只有原来的 长,灌满和排空的时间也缩到 。代价是段间通信次数乘 ,以及 warmup 变长带来的更多在途激活。把上面的 拉到 2 或 3 能直接看到气泡缩小。
Zero Bubble 拆反向。反向的两个矩阵乘里,对输入的梯度必须马上传给上一段,对权重的梯度谁也不等,可以推迟。把后者从关键路径上摘下来填进气泡,理论上能把气泡填到零。这一篇不展开。
推导一:气泡占比
以 1F1B、rank 0 为对象。它一共要做 次前向和 次反向,忙碌时间 。它在 warmup 做完 个前向后必须等第一个反向回来,这段等待是第一个 micro-batch走完剩下 段前向再走完 段反向的时间,。所以
时间线的读数里给的是模拟值和这个理论值,两者一致。interleaved 把分子里的 段换成 长的段,占比变成 。减气泡的两条路都在这个式子里:增大 ,或者增大 。
推导二:每个 rank 攒着几份激活
1F1B 的 warmup 里,rank 先做 个前向再开始交替。交替阶段每次先前向后反向,所以峰值时它攒着 份 micro-batch 的激活:rank 0 攒 份,rank 只攒 1 份。切到时间线的「在途激活」视图,右侧的柱子就是这个数。
这个阶梯是流水线的固有形状:显存压力集中在前面的 rank,后面的 rank 有大量空闲。很多显存优化就是针对这个阶梯的,比如把前面 rank 的激活搬到后面 rank 的显存里。
虚拟段让阶梯更陡。 时 rank 的 warmup 前向数是 (Megatron 的公式),每一份是原来的 大。以 、 为例,rank 0 峰值攒 个 chunk,折合 11.5 份完整激活,比 时的 8 份多了近一半。这是 interleaved 用显存换气泡的账,拉时间线的 能看到柱子怎么涨。
一张表收尾
| 调度 | 气泡占比 | rank 0 峰值在途激活 | 段间通信 |
|---|---|---|---|
| GPipe | 份 | 每 micro-batch 2 次 | |
| 1F1B | 份 | 每 micro-batch 2 次 | |
| Interleaved 1F1B | 约 份 | 每 micro-batch 次 | |
| Zero Bubble | 趋近 0 | 与 1F1B 同量级 | 每 micro-batch 2 次 |
气泡和在途激活是流水线的两条账,四代调度都是在这两条账之间腾挪。气泡本身也不必浪费:只要有不依赖流水线顺序的计算,比如多模态模型的视觉编码器,就可以塞进去。Kimi K3 的系统篇里有这个用法的一个真实例子。