做分布式训练绕不开流水线并行。大模型参数一多单卡放不下数据并行又解决不了显存问题流水线并行就成了必须掌握的手段。这篇是“图解分布式训练”系列的第一篇只聚焦一件事流水线并行的核心逻辑以及真正落地时的实践要点。我尽量用图和表把调度过程讲清楚让两类人都能读进去。一类是刚接触分布式训练想搞明白流水线并行到底是什么、和别的并行方式有什么区别另一类是已经在跑训练但发现显存、效率、收敛总出问题想弄明白背后的原理。读完你应该能建立起一个完整的认知框架并且知道实际配置里该动哪些参数、按什么顺序排查问题。1. 流水线并行到底在解决什么问题1.1 单卡放不下的模型先算一笔显存账训练大模型最先撞上的墙是显存。以百亿参数10B模型为例用半精度bf16存权重每个参数占 2 字节100 亿参数光权重就要 20GB。但训练时远不止权重本身前向反向要存梯度和激活值优化器还要维护额外的状态。一个常用的粗略估算口径是用 Adam 这类自适应优化器训练时每个参数大约需要 1216 字节的显存预算包括权重、梯度、动量和方差。按 12 字节算10B 模型的参数相关开销就是 120GB单卡 80GB 的 H 系列显卡根本塞不下。如果你觉得“那我把模型切小一点跑不就行了”还是绕不开同一个问题。模型的层数翻倍显存需求不是线性增长而是被激活值缓存和临时计算空间成倍放大。所以只要模型规模上去单卡放不下是必然的这是流水线并行出现的第一个理由把模型拆开让每张卡只承担一部分。1.2 三种并行各管一段数据并行、张量并行、流水线并行面对显存不够直觉上就是把多块 GPU 拼起来一起干活这其实就是并行策略的出发点。目前主流的思路大体分三类我习惯用一个表格把它们的分工说清楚并行方式切的是什么通信特点解决什么问题数据并行把 batch 切成多份每卡一份每步做梯度全归约通信随卡数增长吞吐量上不去卡多算得快张量并行把一层的矩阵运算切开多卡协作算同一层每层都要做全归约通信量大单层放不下或者想压缩单卡显存流水线并行把模型按层切成多段每卡负责一段只传递层间激活值和梯度通信量小模型整体放不下需要跨卡部署用一句话概括数据并行把数据切开张量并行把层切开流水线并行把模型按层切开。三者不互斥大模型训练里经常组合使用。流水线并行的定位是用相对较低的通信成本先解决显存放不下的问题。它和另外两种并行最明显的区别在于GPU 之间不需要每层都同步一次只需要在段与段之间交换数据这让它可以跨机器部署也不至于被通信拖垮。2. 核心逻辑切层、微批与气泡2.1 按层切开装配线上的工位流水线并行的基本操作是把模型从中间切开。假设一个 Transformer 模型有 48 层平均分成 4 段每段 12 层放到 4 张卡上这就是 4 个 stage流水线段。数据流动的方向是从前往后的第一张卡读入输入 batch完成第 112 层的前向计算把输出的 hidden state 发给第二张卡第二张卡接着算第 1324 层再往后传最后一张卡算完最后一段并计算 loss。反向传播时顺序反过来从最后一张卡逐段往前传梯度。这个过程非常像汽车装配线每个工位只负责自己那一段工序做完就把半成品交给下一个工位。为什么能这样切因为 Transformer 层之间天然存在清晰边界——上一层的输出 hidden state 就是下一层的输入除此之外没有别的依赖。这个边界是流水线并行成立的前提。如果你用的模型结构里层与层之间有跨层连接、共享权重之类的复杂依赖切分就没这么简单了得先处理依赖关系。2.2 朴素切分的致命浪费气泡如果你真的按上面方式直接训练效率会非常难看。假设整批数据一次性传进去第一张卡要算完一整批的所有层第二张卡才开始干活第一张卡算完就没事干了。后续阶段依次接力任意时刻都只有 1 张卡在计算其他卡都在等着。4 张卡的时候利用率是 25%切得越多浪费越严重这显然不能接受。我管这个空闲等待叫“气泡”bubble。气泡的本质是流水线里下游工位必须等上游工位产出才能开工。就像装配线一次只造一辆车下一道工序永远在等上一道大部分工位闲置。气泡是流水线并行最核心的效率敌人后面讲的所有调度方案本质上都是在压缩它。2.3 微批处理拆小件才能不断料解决办法说穿了也不复杂不要把 batch 当成一个整体而是把一个 batch 拆成多个更小的 micro-batch微批一个接一个地送进流水线。还是拿 4 个 stage 举例。假设一个 batch 有 64 条样本拆成 8 个 micro-batch每个 8 条。第一张卡算完第 1 个 micro-batch 后立刻传给第二张卡自己接着算第 2 个 micro-batch。于是第二张卡在第一张卡算第 2 个 micro-batch 的同时开始算第 1 个 micro-batch。流水线很快就被填满所有卡都在连续工作。这就是“微批”的精髓把装配线从一次只产一辆车改成连续下料产线上始终有车在流动每个工位都能忙起来。微批数量越多流水线越不容易断档气泡占比就越小。但微批数量不是越大越好它直接影响显存里的激活值数量也影响全局 batch size 的语义后面实操部分会展开讲。3. 调度策略演进从填满排空到 1F1B 再到交错式3.1 经典填满排空调度简单但吃显存微批解决了“填满流水线”的问题但前向和反向怎么排列不同方案差别很大直接决定显存占用。最早的经典做法是两阶段式先把所有 micro-batch 的前向全部算完填满再统一做反向排空。以 P4、M4 为例调度长这样F 表示一个 micro-batch 的前向B 表示反向点表示空闲时间: t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 t13 t14 S1 F1 F2 F3 F4 . . . . . B1 B2 B3 B4 . S2 . F1 F2 F3 F4 . . . . B1 B2 B3 B4 . S3 . . F1 F2 F3 F4 . . B1 B2 B3 B4 . . S4 . . . F1 F2 F3 F4 B1 B2 B3 B4 . . .这个方案的好处是简单、容易实现坏处是前向过程中产出的每个 micro-batch 的激活值都得存着等反向阶段用。micro-batch 一多显存立刻爆炸。它是理解流水线并行最好的入门模型但实际工程里很少直接用。3.2 1F1B 稳态调度把显存省下来后来业界普遍改用一种更聪明的调度方式一个前向接一个反向工程里常叫 1F1Bone forward one backward。前面先做一段预热等流水线进入稳态后每张卡交替执行前向和反向算完一个 micro-batch 的反向就释放它对应的激活值不必把全部 micro-batch 的激活值一直保留到排空阶段。相比填满排空同等配置下显存占用大幅下降这也是它在实践中能支持更大 M 的原因。代价是实现的复杂度上去了。因为反向依赖的前向结果是交错的调度器必须仔细安排每个 micro-batch 的先后顺序。另一个隐藏问题是不同 stage 可能基于不同版本的权重做计算梯度和权重版本对不上会带来收敛层面的偏差。工程上通常通过延迟更新、版本对齐等手段处理这也是流水线并行“跑通容易、跑稳难”的原因之一。3.3 交错式切分用通信换气泡1F1B 缓解了显存问题但气泡还在。于是又出现了交错式流水线每个 stage 不再只负责一段而是把整个模型切成更细的多段让每张卡负责多段。比如 4 张卡把 48 层切成 8 段每张卡拿 2 段。这样流水线里“在途”的干活单元变多了装配线上同时流动的车更多气泡被进一步压缩。好处是效率更高坏处是切段越多GPU 之间来回传递激活值的次数越多通信开销上升。而且切得太碎会让每个计算块变小GPU 的算力不一定能充分用起来。所以交错式的切分数是一个折中并不是越多越好。我的建议是先用常规切分跑通再根据实测利用率决定要不要上交错式。3.4 气泡占比的估算公式气泡占比可以用一个简单的公式做预估。假设 stage 数量为 Pmicro-batch 数量为 M经典调度下气泡占比 ≈ (P - 1) / (M P - 1)手动算一遍就清楚了Pstage 数M微批数理论气泡占比2420%4827.3%4328.6%83217.9%可以看到M 越大气泡越小但收益递减。交错式调度相当于给分母乘上一个约等于每卡段数的因子气泡进一步压低同时通信成本上升。这个公式最大的用处不是精确定量而是建立直觉当你发现 GPU 利用率上不去时先算算当前 M 对应的气泡理论值。如果理论值已经很低那瓶颈就不在气泡而在通信或者负载不均排查方向完全不一样。4. 实操配置要点与参数选择4.1 切多少个 stage先按显存缺口算实践中第一个要定的参数是 stage 数量 P它基本等于你打算用多少张卡来跑纯流水线。P 不是越大越好stage 越多单卡显存压力越小但气泡变大、通信次数变多。而且每个 stage 至少要有几层才有意义层太少GPU 之间传激活值的开销会吃掉计算收益。我的建议是从显存缺口算起先统计单卡能放下多少层再按“总层数 ÷ 单卡可容纳层数”向上取整得到 P。比如每张卡能装 8 层模型有 32 层那 P 至少取 4。之后用监控看实际利用率和显存余量再决定要不要增加。直接上十几二十个 stage 通常不是最优解除非模型真的巨大且通信条件极好。4.2 微批数量 M经验起点与调整方向M 的经验起点是至少 4 倍于 P再根据显存余量往上加。M 太小气泡占比高GPU 闲着M 太大激活显存暴涨。如果你用的是 1F1B 这类省显存的调度M 的上限会比经典方案宽松不少所以选调度模式要结合 M 一起想。不管用什么方案记住一个关键的恒等式全局 batch size M × micro_batch_size × 数据并行度也就是说M 不只是调度参数它直接影响你的有效 batch size 和学习率设置。改 M 之后要重新考虑学习率和 warmup 步数否则收敛节奏会漂。很多人在流水线并行的坑里栽跟头不是并行逻辑没搞懂而是把这个恒等式忘了。4.3 负载均衡的坑头尾层不按常规出牌很多人切模型时按层数均分这是最容易踩的坑。一个 Transformer 模型并不是每层参数量都一样embedding 层和最后的输出头往往特别大。如果简单地把 48 层平均切成 4 段很可能第 1 段和第 4 段显存明显高于中间段结果中间卡算完等着两头还在忙整个流水线被最慢的 stage 拖住。因为整个流水线的速度等于最慢 stage 的速度负载不均的损失是全局性的。我建议切分前先做一次简单的统计用下面的伪代码把每层参数量打出来# 统计每个 Transformer 层的参数量辅助负载均衡 for idx, layer in enumerate(model.layers): n sum(p.numel() for p in layer.parameters()) print(flayer {idx}: {n / 1e6:.1f}M params)统计完再分配层数embedding 层和输出头所在的 stage 少放几层中间层多的 stage 多放几层。宁可多花十分钟统计也别让某一个 GPU 成为瓶颈。4.4 与数据并行、张量并行怎么组合流水线并行一般不单独使用。常见的组合是先把 GPU 分组组内各卡承担不同 stage形成一条流水线然后让多条流水线复制每条流水线拿不同的 batch 做数据并行。这样显存问题和吞吐问题同时解决。张量并行可以放进每个 stage 内部当某一段的单个层还是放不下时在 stage 内再用张量并行把矩阵切开。三个维度各有分工数据并行管吞吐流水线并行管层间显存张量并行管层内显存。配置时先决定组的大小和数量再决定每段层数和微批数顺序别搞反。还要留意通信拓扑尽量让流水线相邻 stage 落在同一机架内跨机架的段数越少通信受带宽抖动的影响越小。5. 常见问题与排查实录5.1 利用率低先分清气泡、负载和通信症状是直接观察到的用监控工具看 GPU 利用率出现周期性的大段空闲或者整体利用率普遍不到 50%。先按公式估算气泡理论值。如果理论气泡就很高优先提高 M。如果 M 已经比较大但实际利用率还是低那要考虑负载不均或者通信在等带宽。我实际调参时的顺序是先确认 M ≥ 4P再用性能剖析工具看每个 stage 的实际计算时长若某 stage 明显偏长调整层分配一切正常但还是闲才怀疑通信问题。不要一上来就盲目加大 M防止激活显存先炸。5.2 显存 OOM三个高频原因OOM 常见原因有三个。一是 M 太大激活值缓存超出预算改用 1F1B 或者降低 M。二是头尾 stage 因 embedding 和输出头占满显存调整层分配。三是激活值参数和调度方式不匹配前向溢出导致某个 stage 临时缓存偏多。处理激活值还有一招开启激活检查点activation checkpointing用重计算换显存通常能让显存占用下降一大截。但要注意它会让前向变慢所以只在 OOM 压力大时开并配合调整 M 一起做。开了之后一定要重新测一次端到端吞吐别只盯显存。5.3 训练卡死从最小复现开始排查卡死是分布式训练里最让人头疼的问题。常见原因有两个收发不匹配——某个 stage 在等一批数据但上游发送的微批数量、顺序和你配置的对不上通信组配置错误——数据发到了错误的 rank。另一个原因是前向反向交错时某一步的条件分支没走对导致一个 stage 永远在等一个不会来的 tensor。排查思路先用最小配置2 个 stage、1 个 micro-batch跑通在阶段入口打日志核对每个 rank 实际收到的 tensor shape 和顺序再逐步加大 M。分布式调试的黄金法则是缩小复现单机能复现的不要上多机单卡能复现的不要上多卡。5.4 收敛异常有效 batch 与权重版本流水线并行改变了权重更新和梯度的时间关系收敛问题常在。常见表现是 loss 在某个点突然暴涨或者训练曲线比单卡更抖。原因往往和两个因素有关一是 M 变大导致有效 batch 变大学习率没有相应调整二是调度带来的权重版本滞后问题更新时用的梯度和当前权重的匹配关系变差。解决手段提高前期的 warmup把学习率峰值适度降低优先使用带版本对齐的同步更新必要时在同一组参数下多跑几步看趋势不要因为一两步的抖动就改参数。重点是把流水线并行的“等效 batch 变化”这个因素纳入调参考虑。5.5 一套实用的排查顺序最后总结一套我自己惯用的排查顺序先跑单卡小模型确认模型本身的正确性。用 2 个 stage、1 个 micro-batch 跑通流水线链路确认收发和切层正确。逐步增加 M观察利用率和显存变化找到拐点。再考虑交错式切分和激活检查点把效率和显存调到可接受范围。最后才动数据并行度、学习率这类外层参数。这套顺序的原则是每次只改一个变量确保每一步的异常都能定位到具体原因。常见问题可以先对照下面的速查表定位症状常见原因处理方向GPU 周期性空闲、利用率低微批数太少气泡占比高提高 M确认 M ≥ 4P训练慢但利用率看着还行某 stage 负载过重成为瓶颈重新分配层数平衡每段参数量显存 OOM 频繁M 过大 / 调度模式吃显存 / 头尾层太多降低 M换 1F1B开激活检查点训练中途卡死收发不匹配、通信组配置错缩小复现规模打日志核对收发loss 异常抖动或暴涨有效 batch 变化、权重版本滞后调整学习率与 warmup使用同步更新6. 一点个人经验把流水线并行真正跑顺我发现最值钱的经验反而是“别把它想得太高级”。它本质上就是装配线。装配线要顺畅依赖的是工件大小均匀、每道工序耗时接近、物料供给不断档。对应到训练里就是负载均衡、M 够大、通信不堵。很多优化方案其实都是从这个朴素道理推出来的。我个人的习惯是新模型结构先在单卡上反复验证前向反向一致再用两条流水线做冒烟测试。这个流程多花不了多少时间却能把后面排查问题的周期大幅缩短。另一个习惯是动手配置前先手动画一遍调度图标清楚每个 stage 在哪个时间点收谁的数据、发谁的数据。这个系列叫“图解”就是因为我觉得把调度图画出来很多模棱两可的设计选择会立刻变得清晰。分布式训练的水很深如果你也有流水线并行的踩坑经历欢迎一起交流。