Pipeline Parallel

流水线并行

核心解释

Pipeline Parallel 按模型深度切分,把连续的层放到不同阶段。一个 microbatch 的前向依次经过各阶段,反向再沿相反方向传回梯度。不同 microbatch 可以同时处于不同阶段。[1]

设模型由 pp 个阶段组成。第 ss 个阶段接收前一阶段的激活,计算自己的层,再把结果发送给下一阶段。每个阶段保留自己的参数与优化器状态。

hs=fs(hs−1;θs),s=1,…,ph_s=f_s(h_{s-1};\theta_s),\qquad s=1,\ldots,p

流水线开始时要逐步填满,结束时要逐步排空。这些阶段没有工作的时间形成气泡。增加 microbatch 数可以摊薄气泡,但也会改变单次计算的大小和调度开销。

GPipe 先集中执行前向,再执行反向。1F1B 在预热后交替执行前向和反向,让激活更早释放。交错调度把每张卡上的层再分成多个模型块,增加调度机会,也增加跨块交接。[2]

更进一步的调度会拆开反向中的输入梯度与权重梯度,或让不同方向的工作重叠。判断收益时,需要把依赖、显存和通信放到同一条时间线上。[3]

资料

QA

气泡率的分母应该用哪段时间?

两者的分母不同。设流水线有 pp 个均衡阶段,每步有 mm 个 microbatch。用同步 GPipe 的简化模型,忽略通信,且各阶段的前向、反向耗时分别相同。气泡时间相对于理想工作时间的比例是 (p−1)/m(p-1)/m;气泡占总时间的比例是 (p−1)/(m+p−1)(p-1)/(m+p-1)。

例如 p=8,m=16p=8,m=16,两种口径分别约为 43.8% 和 30.4%。引用一个气泡比例时,先确认它除以的是哪段时间。

TbubbleTideal=p−1mTbubbleTtotal=p−1m+p−1\begin{aligned}\frac{T_{\mathrm{bubble}}}{T_{\mathrm{ideal}}}&=\frac{p-1}{m}\\[4pt]\frac{T_{\mathrm{bubble}}}{T_{\mathrm{total}}}&=\frac{p-1}{m+p-1}\end{aligned}

1F1B 的气泡没有消失,为什么仍然很有用?

GPipe 先完成一批前向,再统一做反向,需要同时保存多个 microbatch 的激活。1F1B 在预热后交替执行前向和反向,让较早进入的 microbatch 更早释放激活。

在阶段均衡的同步、非交错调度中,1F1B 与 GPipe 的理想气泡相同。它的直接收益是减少同时存活的激活,进而容纳更大的 microbatch 或减少重计算。

Zero Bubble 为什么可以把一部分反向计算放到后面?

一个线性层的反向至少包含输入梯度和权重梯度。上游阶段要等输入梯度,权重梯度则只需在对应的优化器更新前准备好。调度器因此可以先计算输入梯度,把权重梯度安排到后续空隙。

延后计算会延长部分激活的存活时间。能填多少气泡,取决于各项实际耗时、显存预算和跨阶段通信;论文中的不同调度正是在这些条件之间取舍。

流水线一轮训练到底传多少数据?

先看非交错流水线。设有 pp 个阶段、mm 个 microbatch,每个边界传递的激活和反向梯度都为 SS 字节。统计所有阶段的发送量之和,一次前向加反向为 2m(p−1)S2m(p-1)S。

例如 p=8,m=16,S=16 MiBp=8,m=16,S=16\,\mathrm{MiB},合计发送 3.5 GiB。它是所有边界的总发送量,每份传输只计一次;再把接收量相加会得到两倍。

真实模型的边界形状可能不同。此时逐边界求和。交错调度增加了模型块之间的交接,也需要重新统计,不能继续套用原来的阶段数。

Vsend,total=m∑j=1p−1(Sjforward+Sjbackward)V_{\mathrm{send,total}}=m\sum_{j=1}^{p-1}\left(S_j^{\mathrm{forward}}+S_j^{\mathrm{backward}}\right)

把 microbatch 数量加大,为什么不一定更快?

固定全局 batch 时,增加 microbatch 数量会缩小每个 microbatch。流水线更容易填满,但 GEMM 也会变小,启动和通信次数会上升。

固定 microbatch 大小时,增加数量则会改变一次更新处理的样本数。比较速度时,要同时记录全局 batch、microbatch 大小、阶段数和数据并行度。

用了 1F1B,就意味着梯度来自旧权重吗?

1F1B 描述前向与反向的执行次序。训练语义还取决于什么时候更新权重。Megatron 的同步流水线会在本轮 microbatch 的梯度准备好后更新参数,因此本轮使用同一版本的权重。

PipeDream 等异步方案允许不同批次跨更新边界运行,需要处理权重版本和梯度陈旧性。读调度图时,要把优化器更新的位置也标出来。

推理只有前向,PP 还能降低单条请求的延迟吗?

单个 token 仍要按层经过所有阶段。把层分开本身没有缩短这条依赖链,还增加了阶段间传输。PP 的主要作用是容纳模型,并让不同请求或 microbatch 在不同阶段同时运行。

因此服务吞吐和单请求延迟可能朝不同方向变化。Decode 时还要考虑每条序列的逐 token 依赖,以及调度器能否持续为各阶段提供工作。

层数均分以后,怎样定位真正拖慢流水线的阶段?

待解

Embedding、输出层、MoE 路由和重计算会让阶段耗时不同。怎样在固定负载下记录计算、等待、通信及显存峰值,再调整切分?