专栏大模型笔记·并行2 / 5
4 min学习

流水线并行:1F1B、虚拟段与气泡

把模型按层切成 P 段之后,micro-batch 怎么排才能让每段都有活干。从 GPipe 到 1F1B 到 interleaved 1F1B 到 Zero Bubble,每一步只改一件事。推气泡占比 (P−1)/m、每个 rank 在途激活 P−r 份、虚拟段的显存代价,配一个可以拖的时间线。

目录5 节

流水线并行把 LL 层切成 PP 段,每段放一张卡,一个 batch 切成 mm 个 micro-batch 依次流过去。它是几种并行里通信最省的:段与段之间只传一个 S×dS\times d 的激活。代价是另一种东西:后面的段在等前面的段送前向,前面的段在等后面的段送反向。这段等待叫气泡。这一篇讲气泡从哪来、怎么算、四代调度各拿什么去换它。

先把时间线画出来

约定前向记 1 个时间单位,反向记 2 个(反向要算两个矩阵乘,对输入的梯度和对权重的梯度)。下面是一个可以拖的时间线,默认是 1F1B 调度,P=4P = 4m=8m = 8

前向(1 单位)反向(2 单位)气泡:这个 rank 在等别人ViT 计算

用同一个 micro-batch 的深浅表示它在流水线里的先后; 时颜色的明暗区分同一 rank 上的不同虚拟段。1F1B 的气泡时间占比理论值是 ,通常近似写成 ;读数里给的是 rank 0 的模拟值。

斜线阴影是气泡。rank 0 在开头做完几个前向之后要停下来,等第一个 micro-batch 的反向从 rank 3 一路传回来;rank 3 在开头什么都做不了,要等第一个 micro-batch 的前向一路传过来。两头的等待加起来就是流水线的固有代价。

四代调度,每代改一件事

改顺序切细段拆反向GPipe先做完全部前向,再做全部反向每 rank 攒份激活1F1Bwarmup 之后一前一反交替在途激活最多份,气泡不变interleaved 1F1B每 rank 持段不连续的层气泡缩到,通信乘Zero Bubble反向拆成对输入和对权重两半用对权重的那一半填气泡
四代流水线调度,每一步只改一件事:1F1B 改执行顺序压显存;interleaved 把每段切细压气泡;Zero Bubble 把反向拆成两半,用不依赖下游的那一半把剩下的气泡填掉。

GPipe 最直接:mm 个 micro-batch 的前向全部做完,再做全部反向。气泡就是流水线灌满和排空的时间,占比 (P1)/(m+P1)(P-1)/(m+P-1)。问题在显存:反向开始之前,每个 rank 要为全部 mm 个 micro-batch 保存激活,mm 一大就放不下,而气泡偏偏要靠大 mm 来摊薄。

1F1B 改执行顺序。每个 rank 先做几个前向(warmup),之后每做一个前向就紧接着做一个反向,最后把剩下的反向做完。关键效果是:一个 micro-batch 的反向一做完,它的激活就能释放,所以每个 rank 同时攒着的激活份数有了上界。这个上界下一节推。要注意的是,1F1B 没有减少气泡,两头的灌满和排空还在,它换的是显存。

Interleaved 1F1B 减气泡。让每个 rank 不持有连续的 L/PL/P 层,而是持有 VV 段不连续的层(虚拟段),micro-batch 在流水线里转 VV 圈。每圈的前向只有原来的 1/V1/V 长,灌满和排空的时间也缩到 1/V1/V。代价是段间通信次数乘 VV,以及 warmup 变长带来的更多在途激活。把上面的 VV 拉到 2 或 3 能直接看到气泡缩小。

Zero Bubble 拆反向。反向的两个矩阵乘里,对输入的梯度必须马上传给上一段,对权重的梯度谁也不等,可以推迟。把后者从关键路径上摘下来填进气泡,理论上能把气泡填到零。这一篇不展开。

推导一:气泡占比

以 1F1B、rank 0 为对象。它一共要做 mm 次前向和 mm 次反向,忙碌时间 m(tf+tb)m(t_f + t_b)。它在 warmup 做完 P1P-1 个前向后必须等第一个反向回来,这段等待是第一个 micro-batch走完剩下 P1P-1 段前向再走完 P1P-1 段反向的时间,(P1)(tf+tb)(P-1)(t_f+t_b)。所以

气泡占比=(P1)(tf+tb)m(tf+tb)+(P1)(tf+tb)=P1m+P1P1m.\text{气泡占比} = \frac{(P-1)(t_f+t_b)}{m(t_f+t_b) + (P-1)(t_f+t_b)} = \frac{P-1}{m+P-1} \approx \frac{P-1}{m}.

时间线的读数里给的是模拟值和这个理论值,两者一致。interleaved 把分子里的 P1P-1 段换成 1/V1/V 长的段,占比变成 (P1)/(mV+P1)(P-1)/(mV+P-1)。减气泡的两条路都在这个式子里:增大 mm,或者增大 VV

推导二:每个 rank 攒着几份激活

1F1B 的 warmup 里,rank rr 先做 P1rP-1-r 个前向再开始交替。交替阶段每次先前向后反向,所以峰值时它攒着 P1r+1=PrP-1-r+1 = P-r 份 micro-batch 的激活:rank 0 攒 PP 份,rank P1P-1 只攒 1 份。切到时间线的「在途激活」视图,右侧的柱子就是这个数。

这个阶梯是流水线的固有形状:显存压力集中在前面的 rank,后面的 rank 有大量空闲。很多显存优化就是针对这个阶梯的,比如把前面 rank 的激活搬到后面 rank 的显存里。

虚拟段让阶梯更陡。V>1V > 1 时 rank rr 的 warmup 前向数是 2(P1r)+(V1)P2(P-1-r) + (V-1)P(Megatron 的公式),每一份是原来的 1/V1/V 大。以 P=8P = 8V=2V = 2 为例,rank 0 峰值攒 2×7+8+1=232\times 7 + 8 + 1 = 23 个 chunk,折合 11.5 份完整激活,比 V=1V = 1 时的 8 份多了近一半。这是 interleaved 用显存换气泡的账,拉时间线的 VV 能看到柱子怎么涨。

一张表收尾

调度气泡占比rank 0 峰值在途激活段间通信
GPipe(P1)/(m+P1)(P-1)/(m+P-1)mm每 micro-batch 2 次
1F1B(P1)/(m+P1)(P-1)/(m+P-1)PP每 micro-batch 2 次
Interleaved 1F1B(P1)/(mV+P1)(P-1)/(mV+P-1)(2(P1)+(V1)P+1)/V\big(2(P-1)+(V-1)P+1\big)/V每 micro-batch 2V2V
Zero Bubble趋近 0与 1F1B 同量级每 micro-batch 2 次

气泡和在途激活是流水线的两条账,四代调度都是在这两条账之间腾挪。气泡本身也不必浪费:只要有不依赖流水线顺序的计算,比如多模态模型的视觉编码器,就可以塞进去。Kimi K3 的系统篇里有这个用法的一个真实例子。