想象你是一家汽车工厂的厂长。生产线很长——从底盘到总装有上百道工序,可单个车间根本摆不下。于是你把生产线切成四段,分别放进四个车间:1 号车间做底盘,2 号车间装发动机,3 号车间喷漆,4 号车间总装,车间之间只传半成品。这就是 GPipe 干的事:一张 GPU 装不下大模型,就把网络按层切开,分给多张 GPU 各管一段。可问题马上来了:如果每批车都等前一车间整批做完才往后传,后面三间车间就只能干瞪眼——GPU 大量空转,这就是「流水线气泡」。GPipe 的妙招是:把每批车拆成一支支小拖车(微批次),让所有车间同时忙起来;质检(反向传播)则从成品端逐级往回查,统一收齐整改单后再同步调整工艺参数。于是整条流水线又快、又省显存、精度还不打折。

岗位关联

理解 GPipe 是看懂 Megatron、DeepSpeed 等主流流水线并行方案的基石,能帮你在多卡训练时快速定位性能瓶颈、合理制定并行策略,是分布式训练工程师的必备基本功。

目录
  1. 可视化演示
  2. 模型切分:把流水线装进四间车间
  3. 朴素流水线:为什么后面车间总在喝茶
  4. 微批次策略:拆成小队,让流水线转起来
  5. 气泡量化:气泡到底有多大
  6. 权重更新:收齐质检单,统一调工艺
  7. 显存优化:仓库不够,用完就扔
  8. 工程实践:车间怎么分、拖车怎么定
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

动态演示GPipe流水线并行原理的核心机制,通过架构/数据/状态/性能四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

GPipe流水线并行原理:GPip GPipe流水线分阶段原理 微批次填充策略与流水线启动 核心机制可视化 GPipe流水线并行原理整体架构与关键组件

模型切分:把流水线装进四间车间

汽车厂把上百道工序拆成「底盘、发动机、喷漆、总装」四段,各占一个车间,段与段之间只交接半成品。大模型训练也是这个道理:一张 GPU 的显存放不下全部层,那就把网络按层切成 K 个连续的分区(Partition),每个分区完整落在一张 GPU 上。

GPipe 中每个分区装的是「层的连续一段」:假设模型共有 L 层,切成了 K 段,第 i 段拥有 l_i 层。数据从分区 1 进入,经过一段段加工——这就是前向传播;最终在分区 K 上算出损失。求导更新时则反过来,梯度从分区 K 一路传回分区 1,这就是反向传播,相当于质检员从成品端逐级往回排查问题。

分区之间要传两样东西:前向时把最后那层算出的激活值传给下一分区,反向时把梯度回传给上一分区。因为每个分区只在内部做完整的层间计算,单卡只需保存自己那一段的参数与激活,显存压力立刻从「整个模型」降到「模型/K」。

车间之间传的只是半成品和检验单,材料都在车间内部消化——分区边界上的通信量远小于层内计算量,这正是 GPipe 能把整条流水线铺开的底气。

L=∑i=1Kli\displaystyle L = \sum_{i=1}^{K} l_i
模型总层数等于 K 个分区层数之和,这是切分的守恒约束。
  1. 总层数 L整个模型的层数,比如某个 96 层的大模型。
    LL
  2. 分区数 K把模型切成几段,段数通常等于 GPU 卡数。
    KK
  3. 累加符号从第 1 个分区数到第 K 个分区,把每段拥有的层数全部加起来。
    ∑i=1Kli\sum_{i=1}^{K} l_i
  4. 恒等关系所有分区层数加起来必须严格等于总层数 L,不能多也不能少。
    L=∑i=1KliL = \sum_{i=1}^{K} l_i

朴素流水线:为什么后面车间总在喝茶

最省事的排班方式:1 号车间把整批 100 辆车全部加工完,才装车运给 2 号车间。于是前 100 辆车的时间里,2、3、4 号车间全部在喝茶;等 1 号车间放假,2 号车间又开始连轴转。

对应到训练:把整个 batch 当成一个整体,在 GPU 间依次传递。GPU 1 算完整个 batch 的前向,把结果传给 GPU 2,GPU 2 再接着算……任何时刻只有一张卡在计算,其余 K-1 张都在空等。

这种「分层串行」唯一的好处是显存不爆,代价是设备利用率极低。直觉上,利用率就是「干活的车间数除以总车间数」:

老板很快发现,问题不是车间不够好,而是「货」送得太整。把货拆小、让流水线流动起来,才是治本之策。

Unaive=1K\displaystyle U_{\text{naive}} = \frac{1}{K}
朴素串行时,K 张卡中只有 1 张在工作,利用率仅 1/K。
  1. 利用率 U设备有效计算时间占总时间的比例,越接近 1 越好。
    UU
  2. 正在干活的卡任意时刻只有当前拿到整批数据的那 1 张卡在计算。
    11
  3. 总卡数 K整条流水线一共用了 K 张 GPU。
    KK
  4. 比例结果1 张卡工作、其余 K-1 张等待,利用率是 K 分之一。
    Unaive=1KU_{\text{naive}} = \frac{1}{K}

微批次策略:拆成小队,让流水线转起来

把 100 辆车拆成 10 个小车队,每队 10 辆。1 号车间加工完第一队马上往下传,转头就加工第二队;2 号车间收到第一队立即开工。片刻之后,四个车间手里都各自有小队在做——整条线流动起来了。

GPipe 的微批次(micro-batch)就是这个思路:总批量 B 被切成 M 个等大的小份,每份 b 个样本,依次注入流水线。每个微批次独立走完前向,在最后一层算出损失,随后各自触发反向。

多批微批次前后脚流动,还带来一个关键机制——梯度累积:每个微批次的反向梯度不是立刻更新权重,而是累加在本地梯度缓冲里。当 M 个微批次全部算完,这把梯度就等价于整批数据的梯度,此时才更新一次参数。

拆队之后,车间再也不用等「整批货」,气泡从「几乎所有时间」缩小到「启动和收尾的那一小段」。下一节,我们把气泡算个明白。

B=M×b\displaystyle B = M \times b
总批量 B 被切成 M 个微批次,每个微批次含 b 个样本。
  1. 总批量 B一次权重更新使用的全部样本数,也叫全局批次。
    BB
  2. 微批次数 M把 B 拆成几份,M 越大流水线填得越满。
    MM
  3. 微批次大小 b每个小队的样本数。
    bb
  4. 乘法关系M 份大小为 b 的微批次合起来正好等于 B。
    B=M×bB = M \times b

气泡量化:气泡到底有多大

流水线刚启动时,2 号车间要等 1 号车间的第一队,3 号车间要等两队的时间,4 号车间等得更久;收尾时反向再经历一遍等待。这前后两段「没活干」的时间,就是气泡(bubble)。

GPipe 论文给出了一个优雅的结论:把从第一个微批次开工到最后一个微批次完工看作 M+K-1 个时间段,每个分区平均要空等 K-1 个时间段。于是气泡占比:

从公式能读出两个结论:一是 K 越小气泡越少,但 K 受显存和模型大小约束,不能随便小;二是 M 越大气泡越小,这正是 GPipe 论文里「微批次越多吞吐越高」实验的原因。稳态阶段(气泡之外的时间),每个分区每个时间段都在干活,理论吞吐可以达到理想值的 M/(M+K-1) 倍——拖车越多,车间越闲不下来。

Bubble=K−1M+K−1\displaystyle \text{Bubble} = \frac{K-1}{M+K-1}
气泡占比等于填充等待除以总时长。当 M 远大于 K 时约等于 (K-1)/M,这是工程上最爱用的近似。
  1. 气泡 Bubble所有设备空闲等待的时间占总时间的比例,越小越好。
    Bubble\text{Bubble}
  2. 分子 K-1最后一个分区要等前 K-1 个分区完成填充才能开工,这段等待构成启动气泡。
    K−1K-1
  3. 分母 M+K-1从第一个微批次开工到最后一个微批次完工的总时间段数。
    M+K−1M+K-1
  4. 极限行为固定 K 而让 M 趋向无穷大,分母远大于分子,气泡趋近于零。
    lim⁡M→∞K−1M+K−1=0\lim_{M \to \infty} \frac{K-1}{M+K-1} = 0

权重更新:收齐质检单,统一调工艺

流水线还有个纪律问题:如果 1 号车间拿到第一批质检单就立刻改工艺,而 4 号车间还在按旧参数生产,那么下一批车用的就是「两套标准」,质量没法保证。

GPipe 的纪律叫同步更新(synchronous update):M 个微批次都完成反向、梯度都累积到本地之后,所有分区在同一道屏障(barrier)后同时按下更新按钮。由于每个分区持有模型的不同层段,参数天然彼此独立,更新时不需要把模型参数广播给别人——只需要保证大家在同一时刻、基于完整的整批梯度做更新。

这个设计保证了与数据并行等价的收敛语义:M 个微批次的梯度求平均,就等于整批数据的梯度。第 i 个分区的参数更新公式如下:

统一收尾意味着每一批车都按同一套最新工艺生产。同步更新是 GPipe 能保证训练精度不因切分而下降的关键设计。

θi(t+1)=θi(t)−η⋅1M∑m=1Mgi,m(t)\displaystyle \theta_i^{(t+1)} = \theta_i^{(t)} - \eta \cdot \frac{1}{M} \sum_{m=1}^{M} g_{i,m}^{(t)}
每一步更新时,每个分区用自己的参数减去「学习率 × M 个微批次梯度的平均」。
  1. 更新前的参数分区 i 在第 t 步开始时的权重。
    θi(t)\theta_i^{(t)}
  2. 单个微批次梯度第 m 个微批次反向传播后,在分区 i 上算出的梯度。
    gi,m(t)g_{i,m}^{(t)}
  3. 求平均把 M 个梯度加起来除以 M,得到整批数据的平均梯度。
    1M∑m=1Mgi,m(t)\frac{1}{M}\sum_{m=1}^{M} g_{i,m}^{(t)}
  4. 学习率 η步长超参数,控制参数每次迈多大步子。
    η\eta
  5. 一步更新从旧参数沿梯度反方向走一步,得到新参数。
    θi(t+1)=θi(t)−η⋅1M∑m=1Mgi,m(t)\theta_i^{(t+1)} = \theta_i^{(t)} - \eta \cdot \frac{1}{M} \sum_{m=1}^{M} g_{i,m}^{(t)}

显存优化:仓库不够,用完就扔

反向质检需要用到前向时的中间结果(激活值)。如果每一层都把这些半成品堆在仓库里,模型一深仓库直接爆掉——这在大模型里是比参数更致命的显存杀手。

GPipe 的解法叫激活重计算(re-materialization):前向时只保留传到下一个车间的「边界激活值」,层内中间激活用完即扔;反向需要某一层的激活时,从边界激活值重新做一遍前向把它算回来。

效果立竿见影。设每个分区有 L/K 层、共 M 个微批次,重算前要存 M×(L/K) 份激活;重算后只存 M 份边界激活和一份临时前向空间:

代价是反向时要多算一遍前向,训练时间大约增加三成。但相比显存爆掉直接跑不了,用 30% 的时间换数倍的显存,这笔买卖在工程上太划算了——这也是后续几乎所有大模型训练框架都默认开启重计算的原因。

O(M⋅LK)⟶O(M+LK)\displaystyle O\left(M \cdot \frac{L}{K}\right) \quad \longrightarrow \quad O\left(M + \frac{L}{K}\right)
激活显存从「与微批次数和每分区层数都成正比」降为「两者线性相加」。
  1. 重算前M 个微批次各自保存本分区内 L/K 层的中间激活,显存随乘积增长。
    O(M⋅LK)O\left(M \cdot \frac{L}{K}\right)
  2. 重算后只存 M 个边界激活值,加当前分区一层的前向临时空间。
    O(M+LK)O\left(M + \frac{L}{K}\right)
  3. 箭头方向从左到右表示显存占用大幅下降。
    ⟶\longrightarrow
  4. 代价反向时每层要多算一次前向,训练时间通常增加约 30%。
    时间↑≈33%\text{时间} \uparrow \approx 33\%

工程实践:车间怎么分、拖车怎么定

回到厂长视角:流水线图纸已经齐了,真正落地还差两个关键决策——怎么分段?拖车(微批次)数量取多少?

分段要均衡:只看层数是不行的。embedding、注意力、FFN 的计算量差异很大,最稳妥的做法是按每段实测耗时切分,让各分区负载接近,避免某张 GPU 成为木桶短板。

拖车数量由气泡公式指导:想让气泡占比小于 ε,微批次数至少要:

同时记住两个约束:固定总批量 B 时,M 越大每个微批次越小,单卡算力利用率下降、通信次数上升;M 取 K 的 3~5 倍通常就有不错的性价比,再往上收益趋平。经验总结成一句话:分段均衡、拖车够用、重算开启,流水线就能又快又稳。

M≥⌈K−1ϵ⌉\displaystyle M \ge \left\lceil \frac{K-1}{\epsilon} \right\rceil
由气泡近似公式 (K-1)/M 反推微批次数量下界,工程上足够安全。
  1. 目标气泡 ε能接受的最大气泡占比,比如 0.2 表示 20%。
    ϵ\epsilon
  2. 分子 K-1沿用气泡公式的分子,近似场景下气泡 ≈ (K-1)/M。
    K−1K-1
  3. 除法把 K-1 除以 ε,得到 M 的下界。
    K−1ϵ\frac{K-1}{\epsilon}
  4. 向上取整M 必须是整数,用天花板函数取整。
    ⌈⋅⌉\left\lceil \cdot \right\rceil
  5. 实践结论例如 K=4、目标 20% 时,M 取 15 左右比较稳妥。
    M≥⌈K−1ϵ⌉M \ge \left\lceil \frac{K-1}{\epsilon} \right\rceil

AI 应用场景

千亿参数大模型预训练

阶段:分布式训练 · 模型并行策略选型

解决的问题:单卡显存(如 80GB)装不下千亿参数模型的权重、优化器状态和激活。GPipe 把模型按层切到多卡,用微批次填满流水线,让「模型太大导致单卡放不下」不再等于「大部分卡闲着」;同时开启激活重计算,把激活显存再压低一个量级。

具体案例:某团队用 32 张 A100 训练 175B 规模模型,按 4 层一组切成 8 个分区,M=64 个微批次,气泡占比约 10%;配合重计算,每卡激活显存被压到 60GB 以下,训练吞吐达到单纯数据并行方案的 2.6 倍。

多模态大模型训练

阶段:模型并行 · 异构模块协同训练

解决的问题:视觉 encoder、文本 encoder 与融合层的计算特性差异大,天然适合分层切分;视频类长序列输入的激活值极大,普通数据并行方案显存直接溢出。GPipe 按模态切分,并在 encoder 与融合层之间做流水线,配合重计算解决长序列激活爆炸。

具体案例:训练 10B 多模态视频理解模型时,把视频 encoder 放前 4 卡、文本 encoder 放中间 2 卡、融合 transformer 放后 2 卡,M=32 个微批次,端到端吞吐相比纯数据并行提升约 2.8 倍,显存峰值下降 45%。

大模型 LoRA 微调与推理服务

阶段:微调与推理部署

解决的问题:微调时冻结的底座模型仍需完整前向与反向,激活显存峰值依旧;推理阶段长 prompt 的 KV cache 同样吃显存。复用 GPipe 的层切分与重计算思路,可以把底座摊到多卡,让 48GB 的卡也能跑 70B 量级的微调任务。

具体案例:对 70B 模型做 LoRA 微调,8 卡切成 8 个分区,M=24 个微批次,把激活显存从每卡 72GB 压到 40GB,成功塞进 48GB 显存的 A6000,吞吐约为 8 卡数据并行的 1.7 倍。

Python 演示

python58 行
"""模拟 GPipe 流水线调度的耗时与气泡, 对比朴素串行方案."""  class PipelineSimulator:    """用 K 个分区、M 个微批次描述一条训练流水线."""     def __init__(self, stages, micro_batches):        self.K = stages        self.M = micro_batches     def gpipe_time(self, unit=1.0):        """GPipe 总耗时: 填充 K-1 段加上 M 段稳态计算."""        return (self.M + self.K - 1) * unit     def naive_time(self, unit=1.0):        """朴素方案: 每个分区完整处理整批后才传给下一个分区."""        return self.K * self.M * unit     def bubble_ratio(self):        """理论气泡占比: 空闲时间段数除以总时间段数."""        return (self.K - 1) / (self.M + self.K - 1)     def speedup(self):        """相对朴素串行的加速比."""        return self.naive_time() / self.gpipe_time()     def schedule(self):        """生成调度表: 元素为 (分区号, 开始时刻, 结束时刻)."""        cells = []        for s in range(self.K):            for m in range(self.M):                cells.append((s, s + m, s + m + 1))        return cells     def report(self):        cells = self.schedule()        makespan = max(end for _, _, end in cells)        print("K=%d 个分区, M=%d 个微批次" % (self.K, self.M))        print("朴素串行耗时: %.1f 个时间单位" % self.naive_time())        print("GPipe 耗时  : %.1f 个时间单位" % self.gpipe_time())        print("调度校验    : 仿真最大完工时刻 = %.1f" % makespan)        print("加速比      : %.2f 倍" % self.speedup())        print("气泡占比    : %.2f%%" % (self.bubble_ratio() * 100))  def show_bubble_trend():    """固定分区数, 观察微批次增多时气泡如何被压下."""    K = 4    print("\n固定 K=%d, 增大 M 的气泡变化:" % K)    for M in [1, 4, 8, 12, 16, 32, 64]:        sim = PipelineSimulator(K, M)        print(" M=%-3d -> 气泡 %6.2f%%" % (M, sim.bubble_ratio() * 100))  if __name__ == "__main__":    demo = PipelineSimulator(stages=4, micro_batches=12)    demo.report()    show_bubble_trend()
目的:用纯 Python 仿真 GPipe 的流水线调度,验证气泡公式 (K-1)/(M+K-1) 与加速比,并直观观察增大微批次数量对气泡的抑制效果。
  1. def gpipe_time(self, unit=1.0):定义 GPipe 总耗时计算函数,参数 unit 是每个时间段长度。
  2. return (self.M + self.K - 1) * unit流水线经 K-1 段填充后进入稳态,总时段数为 M+K-1。
  3. def naive_time(self, unit=1.0):朴素方案没有流水线,每个分区完整处理完整批。
  4. return self.K * self.M * unit每张卡都串行处理 M 份工作,总耗时 K×M 个时间段。
  5. return (self.K - 1) / (self.M + self.K - 1)气泡公式的直接翻译,分子是填充等待,分母是总时长。
  6. cells.append((s, s + m, s + m + 1))微批次 m 进入分区 s 的时刻恰为 s+m,加工一个时间段后完工。
  7. makespan = max(end for _, _, end in cells)取所有单元格的最大结束时刻,用来校验公式与仿真一致。
  8. for M in [1, 4, 8, 12, 16, 32, 64]:用一组递增的 M 观察气泡占比单调下降的趋势。
  9. if __name__ == "__main__":保证模块被 import 时不自动执行,直接运行脚本时才打印报告。

常见误区

误区气泡公式说明 M 越大气泡越小,那我把 M 设成 1 万,气泡不就趋近于 0 了吗?训练速度肯定起飞。

正解M 增大会带来三笔代价:微批次间通信次数线性增长、显存要保存的边界激活变多、每个微批次过小导致单卡算力利用率下降。工程上 M 取 K 的 3~5 倍即可,继续加大收益趋近于零而开销持续上升。就像小拖车虽然能填满流水线,但拖车太碎,车间门口排队交接的时间反而变长。

为何常见:只盯着公式里的单一变量,忽略了公式没有建模的通信与调度开销。

误区流水线并行和数据并行一样,每张卡都要把梯度广播给其他卡,不然参数不一致怎么训练?

正解GPipe 中每个分区持有模型的不同层段,参数彼此独立,第 i 分区只用自己算出的梯度更新自己的参数,不需要交换模型梯度。真正需要的是「时间上的同步」——所有分区都累积完 M 个微批次梯度后,同时做一次更新,保证模型版本一致。这就像每个车间只改自己工序的工艺参数,但必须等质检单全部收齐再一起动手。

为何常见:习惯性把「并行训练=梯度同步」套用过来,没意识到切分方式不同导致参数归属完全不同。

误区反向后马上更新权重,参数更新越频繁,收敛不是越快吗?为什么要攒着?

正解如果每个微批次都更新,后面的微批次前向时模型参数已经变过了,梯度不在同一版模型上计算,整批的梯度语义就被破坏了;而且不同分区更新节奏不一致会破坏流水线的同步纪律。GPipe 要求 M 个微批次梯度全部累积完毕再统一更新一次,才能保证与整批更新数学上等价。这就像先收齐全班作业再统一改错,而不是改一道交一道。

为何常见:把「梯度累积」误当成「每步更新」,没理解累积的目的是恢复整批梯度。

误区模型被拆到好几张卡上训练,精度肯定比不拆差,毕竟结构都散了。

正解只要微批次切分合理、梯度累积后在同一个同步点更新,GPipe 得到的梯度总和与单卡整批训练数学上等价(浮点误差除外),收敛曲线可以对齐。精度下降通常来自 batch size 调整不当,或激活重计算带来的数值差异(用 fp32 可忽略)。流水线拆得再细,每道工序的工艺参数和原厂完全一样,生产出的车质量就不会变。

为何常见:混淆了「模型结构被改」与「模型被分配到不同设备」,没意识到梯度累积保持了数学等价性。

面试题

Q1. GPipe 将大模型切分到多张 GPU 上的基本单位是什么?

单选题 · easy

  • A.按层切分模型← 正确答案
  • B.按神经元切分
  • C.按注意力头切分
  • D.按训练步数切分

答案:A。GPipe 把神经网络按连续层区间切分成 K 个分区,每个分区部署到一张 GPU,就像汽车厂把工序切成几段放进不同车间。按神经元或注意力头切分是其他并行技术的思路,按训练步数切分则完全不是模型切分的概念。

Q2. 朴素流水线(整批串行)最主要的缺陷是什么?

单选题 · easy

  • A.显存占用过高
  • B.设备利用率太低← 正确答案
  • C.通信带宽不足
  • D.模型精度降低

答案:B。朴素做法把整个 batch 依次在 GPU 间传递,任意时刻只有一张卡在计算,其余 K-1 张卡全部空转,利用率只有 1/K。这就像只有 1 号车间干活、后面车间都在喝茶。显存、通信、精度都不是这种方案的主要矛盾。

Q3. GPipe 引入微批次(micro-batch)最直接的作用是什么?

单选题 · easy

  • A.减少卡间通信
  • B.增大模型容量
  • C.填充流水线气泡← 正确答案
  • D.提高单卡主频

答案:C。把整批拆分后,各个分区间可以同时处理不同微批次,流水线进入稳态,设备不再长时间空等,气泡大幅缩小。切成小队并不是为了减少通信(通信次数反而变多),也不改变模型容量,更与单卡主频无关。

Q4. 设分区数 K=4、微批次数 M=12,按气泡公式计算,气泡占比约为多少?

单选题 · mid

  • A.33.3%
  • B.20%← 正确答案
  • C.25%
  • D.10%

答案:B。代入公式 (K-1)/(M+K-1) = 3/(12+4-1) = 3/15 = 20%。把 4 个车间、12 支小队代入想象:最后一个车间要等前 3 份工作完成填充,而总时段是 15 份,占比正好 20%。

Q5. GPipe 中反向传播的梯度流动方向是?

单选题 · mid

  • A.从第一分区到K分区
  • B.从最后一分区到第一分区← 正确答案
  • C.只在最后一个分区计算
  • D.在所有分区同时随机

答案:B。损失在最后一个分区(总装车间)产生,梯度需要沿着前向的逆方向逐级回传,从第 K 分区一路传回第 1 分区。这就像质检从成品端反向排查每道工序,而不是从第一道工序往后查。

Q6. 在固定总批量 B 的前提下,增大微批次数 M,下列哪些说法正确?

多选题 · mid

  • A.流水线气泡占比下降← 正确答案
  • B.每个微批次粒度变小← 正确答案
  • C.分区边界通信次数增加← 正确答案
  • D.训练总时间严格减少

答案:ABC。M 增大时,(K-1)/(M+K-1) 变小,气泡占比下降;B 被拆成更多份,每份更小;传输次数随 M 线性增加。但总时间不会严格减少——通信和调度开销上升,实际耗时往往先降后升,所以 D 错。

Q7. 关于 GPipe 的同步权重更新,以下正确的是?

多选题 · mid

  • A.各分区模型版本保持一致← 正确答案
  • B.需要引入全局同步屏障← 正确答案
  • C.每分区更新全量模型参数
  • D.梯度来自全部微批次累积← 正确答案

答案:ABD。GPipe 在 M 个微批次全部完成反向后,所有分区在同一 barrier 处同时更新,保证版本一致;每个分区只更新自己所拥有的那一段参数,而不是全量模型;使用的梯度是所有微批次累积后的整批梯度。C 与「分区参数独立」的事实矛盾。

Q8. GPipe 激活重计算(re-materialization)的核心动作是什么?

单选题 · hard

  • A.前向不保存中间激活
  • B.反向用到时重算前向← 正确答案
  • C.丢弃所有边界激活
  • D.直接丢弃反向梯度

答案:B。前向时只保留边界激活、丢弃层内中间激活(A 只是手段),关键动作是反向需要某一层激活时,从边界激活重新做一遍前向把它算回来。边界激活必须保留,反向梯度更不能丢,所以 C、D 都错。

Q9. 关于流水线气泡,下列哪些描述正确?

多选题 · hard

  • A.填充与排空阶段产生气泡← 正确答案
  • B.微批次趋于无穷时气泡趋零← 正确答案
  • C.分区数越多气泡占比越高← 正确答案
  • D.气泡出现时所有卡都空闲

答案:ABC。气泡来自流水线启动填充和收尾排空时部分设备等待;由公式可见 M→∞ 时气泡趋近于 0,K 增大时分子变大、气泡变高。气泡是「某些设备」在等上游,不是所有卡同时闲着,D 错。

Q10. 使用 8 张卡训练,希望气泡占比严格小于 20%,按精确公式 (K-1)/(M+K-1),微批次数 M 最少取多少?

单选题 · extreme

  • A.M=29← 正确答案
  • B.M=35
  • C.M=20
  • D.M=14

答案:A。解不等式 7/(M+7) < 0.2,即 7 < 0.2M + 1.4,得到 M > 28,所以最小整数是 29。35 是近似公式 (K-1)/ε 给出的安全上界,满足条件但不是「最少」;20 和 14 都使气泡大于 20%。