周末你负责操办一场 100 桌的婚宴,每桌要上 10 道菜。一个人显然做不完——灶台不够、人手不够、时间更不够。怎么办?第一种办法:多开几个厨房,每个厨房都按同一本菜谱做,一桌派一个厨房负责。第二种办法:一个厨房内部按上菜顺序切成几个工位——备菜台只管凉菜、炒锅位只管热菜、蒸箱只管汤羹,菜一道接一道流过工位。第三种办法:某一道硬菜,比如一整只烤全羊,单个灶台放不下,干脆让四位厨师围着同一个烤炉,一位切配、一位调味、一位看火、一位装盘,互相递料配合得天衣无缝。 今天的难题是:菜谱有 175 道,每桌还全是硬菜,任何单一办法都不够。真正的大型饭店必须三招齐上:厨房复制、工位切菜、硬菜合做。本文要讲的,就是把“按菜切流程”和“同一道菜分多人做”这两招组合起来的方法——什么菜该给哪个工位、几位厨师怎么配合、料怎么传不打架、闲下来的厨师怎么安排。看完你就能看懂主流大模型训练框架里的并行配置文件了。

岗位关联

学会流水线与张量并行组合,你就能独立设计千亿级模型的多卡训练方案,看懂 3D 并行配置,并在实际训练中快速定位显存与通信瓶颈。

目录
  1. 可视化演示
  2. 组合背景动机
  3. 并行维度对比
  4. 切分组合策略
  5. 通信拓扑设计
  6. 显存计算平衡
  7. 调度分配策略
  8. 工程配置实践
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示BST的插入构建、查找路径和中序遍历过程,切换模式查看核心操作的不同执行流程。

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

BST 插入构建过程 紫色为已构建节点,金色为当前插入路径 左子树<根<右子树 每个新节点从根开始比较,小于根走左子树,大于根走右子树

组合背景动机

回到婚宴的比喻。100 桌宴席、每桌 10 道菜,你发现光有“按菜切流程”还不够:有一道硬菜本身太大,单独一个工位的厨师也端不动。此时必须再叫几位厨师围在同一道菜前,一人切肉、一人配菜、一人掌勺、一人装盘——这就是“同菜分人做”。也就是说,流水线负责把菜谱切成段落,张量并行负责把某一道巨型硬菜再切成小块分给多人。两个维度叠加,才装得下真正的满汉全席。

技术世界里,这一幕每天都在发生。大语言模型的参数从 10 亿涨到 1750 亿(如 GPT-3),单张 A100 显卡只有 80GB 显存,而 1750 亿参数仅权重一项用 FP16 存储就需要约 350GB,单卡连参数都放不下,更别提训练还要存梯度和优化器状态。于是训练框架引入了三种切分:数据并行复制多个完整模型、流水线并行按层切段、张量并行把每一层的权重矩阵切开。当模型大到“层数很多”和“单层很大”两个问题同时出现时,流水线和张量并行就必须组合上场。

组合并行在业界常被称为“3D 并行”,因为三个并行维度像一个三维立方体,每个维度管一件事。要算总共需要多少张显卡,只需把三个维度的并行度乘起来:

回到比喻:饭店最终的总人手,从来不是简单相加,而是“几个厨房 × 每厨房几个工位 × 每工位几位厨师”。理解了这三个维度各自的角色,下面就能看清它们各自负责切什么了。

NGPU=Pdata×Ptensor×Ppipeline\displaystyle N_{GPU} = P_{data} \times P_{tensor} \times P_{pipeline}
这个公式说的是:总厨师人数 = 厨房套数 × 每个厨房工位数 × 每工位厨师数。任何一个维度加大,都需要更多人手。
  1. GPU 总数 N_GPU整个训练集群可用的显卡总数,相当于饭店动用的厨师总人数。
    NGPU\displaystyle N_{GPU}
  2. 数据并行度 P_data把整套宴席复制几份,同时开几套流水线处理不同的客人订单。
    Pdata\displaystyle P_{data}
  3. 张量并行度 P_tensor同一道菜由几位厨师围着同一个灶台协作,对应权重矩阵被切成的份数。
    Ptensor\displaystyle P_{tensor}
  4. 流水线并行度 P_pipeline按上菜顺序把厨房分成几个工位,对应模型按层切成的段数。
    Ppipeline\displaystyle P_{pipeline}

并行维度对比

假设你现在当上了这家大饭店的厨师长,手里有一本 32 层的菜谱。三种分工方式各有各的切法:按“菜”切,是把第 1 到第 8 道菜分给工位一、第 9 到第 16 道菜分给工位二——这是流水线并行,切的是模型的“层序列”;按“菜里的原料”切,是让四位厨师同时做同一道菜,一个切肉、一个切菜、一个掌勺、一个装盘——这是张量并行,切的是某一层内部的“权重矩阵”;整间厨房复制一份,搬到隔壁再开一套同样的流水线——这是数据并行,切的是“数据的批次”。

从技术上看,三个维度有着本质区别。流水线并行把 Transformer 的层序列切成 P_pp 段,每张卡只持有其中一段的完整参数,工位之间只传递“半成品”(每层的输出激活张量),通信稀疏。张量并行则把每一层内的 QKV 投影矩阵、MLP 全连接矩阵按行或列切开,同一层的多张卡需要频繁同步彼此的中间结果,通信密集。数据并行不减少任何一张卡的模型显存——每个副本都拿着完整模型,它只负责用不同数据喂出梯度再互相平均。

三种切法对显存的影响也可以直接算出来。模型参数被流水线和张量两个维度同时分摊,每张卡存放的模型参数占比为:

一句话回扣:储物柜的存货,被“分段”和“分块”两个动作共同摊薄;但要小心,张量并行摊薄显存的同时,也带来了每层都要发生的沟通成本——这正是下一节要平衡的重点。

M每卡=M模型Ptensor×Ppipeline\displaystyle M_{每卡} = \frac{M_{模型}}{P_{tensor} \times P_{pipeline}}
每张卡的模型显存 = 总模型大小 ÷ 两个切分维度的乘积。数据并行不参与这个除法,因为每个数据副本都持有完整模型。
  1. 每卡模型显存 M_每卡单张 GPU 需要常驻的模型参数量,相当于每位厨师手边的工作原料量。
    M每卡\displaystyle M_{每卡}
  2. 模型总显存 M_模型全部模型参数的存储占用,好比整本菜谱所有食材的总量。
    M模型\displaystyle M_{模型}
  3. 张量并行度 P_tensor权重矩阵被切成的份数,分母里出现它,表示每张卡只拿矩阵的一块。
    Ptensor\displaystyle P_{tensor}
  4. 流水线并行度 P_pipeline模型层被切成的段数,每张卡也只持有其中一段的完整权重。
    Ppipeline\displaystyle P_{pipeline}

切分组合策略

既然两个维度要一起上,先切哪个?回到厨房:你得先确定每道菜属于哪个工位(流水段),再决定这道菜由几个人合作(张量切分)。顺序不能反——如果先让四位厨师围着一道菜切好料,却发现这道菜被分到了两个不同的工位,料就白切了。同理,组合并行必须“先划层、后切权”:先把 N 层模型均匀分给 P_pp 个流水级,再把每个流水级内部的每一层权重矩阵按 P_tp 切块。

具体操作上有两条硬性规则。第一,层的总数必须能被流水线并行度整除,否则各工位分到的层数不均匀,层数少的工位会提前完工空转,层数多的工位拖垮整个流水线。第二,每一层的注意力头数必须能被张量并行度整除,因为多头注意力是按“头”分组的——张量并行把 32 个注意力头分给 4 张卡,每卡负责 8 个头;如果头数除不尽,某些卡就会多分到半个头,权重形状直接对不上。

这两个维度分别决定了“每一级有几层”和“每张卡有几个头”,可以用两个式子同时刻画:

回到比喻:菜单要先排好菜序,再给每道菜分人手;同理,先定流水段边界(哪些层在哪个工位),再在段内做张量切分(矩阵怎么分块)。切分方案落定之后,下一个绕不开的问题是:料怎么传?

Lstage=NlayersPpipeline,Hlocal=HheadsPtensor\displaystyle L_{stage} = \frac{N_{layers}}{P_{pipeline}}, \qquad H_{local} = \frac{H_{heads}}{P_{tensor}}
每级层数 = 总层数 ÷ 流水线并行度;每卡头数 = 总注意力头数 ÷ 张量并行度。两个商都必须是整数。
  1. 每级层数 L_stage每个流水工位负责的 Transformer 层数量,要求是整数且各工位相等。
    Lstage\displaystyle L_{stage}
  2. 总层数 N_layers 与流水线并行度 P_pipelineN_layers 必须被 P_pipeline 整除,否则无法做到均匀分配,会人为制造瓶颈。
    NlayersPpipeline\displaystyle \frac{N_{layers}}{P_{pipeline}}
  3. 每卡头数 H_local张量并行时每张卡实际负责的注意力头数量,对应每个厨师分到的那几份配料。
    Hlocal\displaystyle H_{local}
  4. 总头数 H_heads 与张量并行度 P_tensor总头数必须被 P_tensor 整除,多头注意力才能按完整“头”为单位切分,不产生残缺计算。
    HheadsPtensor\displaystyle \frac{H_{heads}}{P_{tensor}}

通信拓扑设计

四位厨师围着同一个烤炉做烤全羊,切配师傅每切下一块肉就要递给掌勺师傅,一来一回极其频繁——这是张量并行:同层的多张卡每算一小步就要做一次 All-reduce 全量同步,频率极高。而工位之间传菜就从容得多:备菜台把装好盘的凉菜用手推车推到下一个工位,一桌只推一次——这是流水线并行:只在工位边界传递一次激活张量,频率低得多。

这种频率差异直接决定了硬件选型。张量并行的 All-reduce 发生在每一层的前向和反向中,一个 Transformer 层大约要同步 4 次,而且必须等到同步完才能继续算下一步,属于“卡着算”的同步通信,必须走 GPU 之间的 NVLink 高速互联。因此张量并行度绝不能超过一个节点内的 GPU 数量——节点内 NVLink 是全互联的,跨节点只能走慢得多的 InfiniBand 或以太网。流水线并行则不同:它只在工位边界做点对点传输,单次数据量大但次数少,可以搭配异步流水线做“边算边传”,用通信与计算重叠来隐藏延迟。

把通信量写成公式,能直观看到两者量级差异。设层数为 N_layer,批次、序列长度、隐藏维度乘积为 bsh:

回到厨房画面:调料小碟要放在伸手可及的地方(NVLink 同节点),传菜推车可以走远路(IB 跨节点)。搞清了这个主次关系,下一步就要算一算:分工带来的显存节省,和等人等料造成的空转,到底怎么权衡?

CTPCPP=4×Nlayer2×(Ppp−1)\displaystyle \frac{C_{TP}}{C_{PP}} = \frac{4 \times N_{layer}}{2 \times (P_{pp} - 1)}
张量并行总通信量 ≈ 4 次/层 × 层数;流水线总通信量 ≈ 2 次/边界 × 边界数。层数越深,TP 通信占比越大。
  1. 分子 4 × N_layer每层前向反向各做两次 All-reduce,共 4 次;层数越多,张量并行同步次数线性增长。
    4×Nlayer\displaystyle 4 \times N_{layer}
  2. 分母 2 × (P_pp − 1)流水线有 P_pp − 1 个边界,每个边界前向反向各传一次激活,共 2 次。
    2×(Ppp−1)\displaystyle 2 \times (P_{pp} - 1)
  3. 比值含义分子对应“同灶台递料”次数,分母对应“手推车传菜”次数,比值越大说明组合方案越依赖高速节点内互联。
    CTPCPP\displaystyle \frac{C_{TP}}{C_{PP}}

显存计算平衡

储物柜和灶台的矛盾来了。分工越细,每位厨师手边要放的原料越少(显存越省),但互相递料的次数越多(通信越贵);流水段切得越多,工位之间传菜越频繁,而且一道菜在上下游之间的等待时间也越长——这就是“气泡”。气泡指的是流水线上某些工位没活干、干等着的时间,好比婚宴上宾客还没吃完凉菜,热菜工位的厨师只能站在灶台边看着火发呆。

更精确地说,显存账单由三部分构成:模型参数与梯度、优化器状态、激活值(每层的中间输出,相当于做菜过程中的半成品)。流水线和张量并行组合能显著压缩第一部分——参数按两个维度分摊。但第二部分激活值有个特点:它不随张量并行度线性下降(LayerNorm、Dropout 等算子仍然需要全量激活),而是主要和“同时有多少个微批次在流水线上”成正比。至于第三部分,优化器状态也要按参数占比同步分摊。

于是工程上真正要权衡的是“流水线级数”和“微批次数量”对气泡的影响。当有 P_pp 个流水级、把一个大批次切成 M 个微批次时,气泡占比有一个经典的估算公式:

一句话回扣:省下来的储物柜空间,有一部分被“等人等料”的时间悄悄吃掉了。为了不让空转太难看,业界发明了错峰上菜的调度办法——这正是下一节的主角。

ηbubble=Ppp−1Ppp+M−1\displaystyle \eta_{bubble} = \frac{P_{pp} - 1}{P_{pp} + M - 1}
气泡率 = 首尾阶段的空闲槽位数 ÷ 总时间段数。级数越大气泡越大,微批次越多气泡越小。
  1. 分子 P_pp − 1流水线刚启动和即将结束时,首尾工位必须等待的空闲槽位数。
    Ppp−1\displaystyle P_{pp} - 1
  2. 分母 P_pp + M − 1完成 M 个微批次理想上需要经历的总时间段数,等于级数加批次数减一。
    Ppp+M−1\displaystyle P_{pp} + M - 1
  3. 微批次 M 的作用M 越大,分母越大,气泡占比越小;但 M 太大又会撑爆激活值的显存。
    M\displaystyle M
  4. 级数 P_pp 的作用级数越大,分子分母同时变大,气泡率整体上升——多切段省了参数显存,却赔了利用率。
    Ppp\displaystyle P_{pp}

调度分配策略

婚宴上菜不能等第 1 桌的 10 道菜全部做完再上第 2 桌,那样第 2 桌客人要饿到半夜。聪明的厨师长会错峰:第 1 桌的凉菜刚端出去,备菜台就开始备第 2 桌的凉菜;第 1 桌的热菜下锅时,第 2 桌的凉菜已经上桌。训练大模型也是这个道理,把一个大的训练批次切成 M 个微批次(micro-batch),让它们像流水一样一个接一个涌进流水线。

最早的 GPipe 方案是“先全部前向、再全部反向”:所有微批次先从前到后做完前向,攒下一大堆激活值,然后再从后到前做反向。这样做实现简单,但所有微批次的中间结果必须同时存活,显存峰值极高——相当于把 100 桌的半成品菜全堆在厨房台面上。后来提出的 1F1B(One-Forward-One-Backward)调度则规定:每个工位做完一个微批次的前向,只要条件允许就立刻做反向,前向反向交替进行。这样流水线上同时存活的微批次数量被压到大约一个流水线级数 P_pp,显存峰值大幅下降。

1F1B 的显存占用可以概括为:常驻的参数、梯度、优化器状态(M_参数),加上最多 P_pp 份微批次激活值:

回到宴席:1F1B 的精髓是“随做随清”,不让半成品堆满整个后厨。前向、反向交错推进,显存峰值被压住,气泡也被微批次填掉大半——调度方案定好了,最后一步就是把这一切写进训练框架的配置文件。

M1F1B=Mparam+Ppp×Mact\displaystyle M_{1F1B} = M_{param} + P_{pp} \times M_{act}
1F1B 的显存峰值 = 参数类显存 + 级数 × 单微批次激活显存。级数越大,允许同时“挂”在流水线上的半成品越多。
  1. 参数类显存 M_param当前卡持有的模型权重、梯度和优化器状态,数值由 TP×PP 分摊决定。
    Mparam\displaystyle M_{param}
  2. 单微批次激活显存 M_act一个微批次在一层中产生的中间激活值,相当于一份菜在某个工位的半成品台面。
    Mact\displaystyle M_{act}
  3. 流水级数 P_pp1F1B 下同时存活的微批次数量约为 P_pp,因此激活显存要乘上这个系数。
    Ppp×Mact\displaystyle P_{pp} \times M_{act}
  4. 对比 GPipeGPipe 需要同时保留全部 M 个微批次的激活,若 M 远大于 P_pp,其显存峰值会远高于 1F1B。
    MGPipe=Mparam+M×Mact\displaystyle M_{GPipe} = M_{param} + M \times M_{act}

工程配置实践

现在你已经是这家大饭店的厨师长了,手里攥着一份排班表。最流行的开源框架 Megatron-LM 里,排班表就两行参数:--tensor-model-parallel-size 控制张量并行度(几位厨师合做一道菜),--pipeline-model-parallel-size 控制流水线并行度(几个工位接力)。下面是一个 TP=8、PP=8 的典型配置:

配置时记住三个口诀。第一,“TP 不出节点”:张量并行度上限是单节点 GPU 数,8 卡的节点最多 TP=8,因为张量通信必须走节点内 NVLink。第二,“PP 受整除约束”:流水线并行度必须能整除模型层数,32 层模型可以 PP=8(每级 4 层),但不能 PP=12。第三,“DP 最后补足”:确定 TP 和 PP 后,用数据并行度把剩余 GPU 填满,即 N_GPU = DP × TP × PP。经验上,训练千亿模型常用 TP=8、PP=8、DP 视集群规模取 2 到 16。

排查问题时也按这个顺序来:先看整除约束是否满足,再看 TP 是否跨了节点,最后用性能分析工具(如 PyTorch Profiler、Nsight Systems)看通信时间占比。两个整除约束是配置文件能跑起来的前提:

回到厨房:排班表最终要回答三个问题——几个厨房(DP)、几个工位(PP)、每工位几个人(TP)。口诀“TP 不出节点、PP 看层数、DP 补位”就是所有大模型训练团队的共同经验。组合并行的原理与配置,到此闭环。

Nlayer mod Ppp=0,Hhead mod Ptp=0\displaystyle N_{layer} \bmod P_{pp} = 0, \qquad H_{head} \bmod P_{tp} = 0
层数必须能被流水线并行度整除,注意力头数必须能被张量并行度整除——这两条不满足,训练直接报错或结果错误。
  1. 层数整除 PP流水线按整层切分,每级分到整数层,否则有的工位多一层,变成全局最短木板。
    Nlayer mod Ppp=0\displaystyle N_{layer} \bmod P_{pp} = 0
  2. 头数整除 TP按整“头”切分注意力,头数除不尽就会产生无法合并的残块。
    Hhead mod Ptp=0\displaystyle H_{head} \bmod P_{tp} = 0
  3. 工程意义这两个条件相当于排班表的基本纪律——先满足“每工位层数一致、每厨师头数一致”,再谈优化吞吐。
     mod \displaystyle \bmod
bash4 行
--tensor-model-parallel-size 8 \--pipeline-model-parallel-size 8 \--micro-batch-size 1 \--global-batch-size 256

AI 应用场景

场景 1:千亿参数 LLM 的预训练部署

阶段:模型训练(预训练)

解决的问题:175B 参数的模型单卡根本放不下,仅权重 FP16 就需要约 350GB 显存,传统单卡训练直接 OOM。通过 TP=8、PP=8、DP=N 的组合,把权重、梯度、优化器状态摊到几十上百张卡上,显存问题迎刃而解。

具体案例:某团队用 64 张 A100-80G 训练类 GPT-175B 模型。8 卡一个节点,节点内 NVLink 全互联,因此 TP=8 限制在节点内;8 个节点之间用 InfiniBand 组流水线,PP=8;节点间再叠加数据并行。配置 --tensor-model-parallel-size 8 --pipeline-model-parallel-size 8 --micro-batch-size 1 --global-batch-size 512,配合 1F1B 调度与激活重计算,最终将每卡显存控制在 70GB 以内。

场景 2:多节点集群的算力规划与选型

阶段:算力规划与成本预算

解决的问题:租用云上 GPU 集群时,面对不同实例规格(8 卡节点、16 卡节点),不知道该怎么组合并行参数才能既满足显存、又不浪费带宽。组合并行的约束(TP ≤ 节点卡数、层数整除 PP)能直接指导选型。

具体案例:某公司要微调 70B 模型,有 8 卡 A100 节点。先按整除约束枚举可行方案:70B 模型 80 层、80 注意力头,TP 可取 8,PP 可取 8(80/8=10 层每级、80/8=10 头每卡),剩余 GPU 用 DP 填满。最终选定 TP=8 + PP=8 + DP=2 共 128 卡,气泡率约 7%,比盲目选 TP=4+PP=16 的方案吞吐高 18%。

场景 3:训练过程中的显存 OOM 排障

阶段:训练调优与故障排查

解决的问题:TP+PP 组合训练时频繁出现 OOM,传统做法是盲目缩小 batch 导致吞吐骤降。正确排查顺序是:先用显存账单公式判断是参数、优化器还是激活值超限,再针对性调整 TP、PP 或微批次数量。

具体案例:某 13B 模型训练时 OOM,通过 profiler 发现激活值占显存 60%。团队将微批次从 4 降到 2、开启 1F1B 和激活重计算,显存占用立刻下降 40%,吞吐只损失 5%;若当初直接增大 TP 反而会因为跨节点通信更慢。此排查流程已成为该团队的标准化排障手册。

Python 演示

python45 行
"""Demo of 3D parallel plan enumeration: check divisibility and bubble ratio"""  class ParallelConfig:    def __init__(self, layers, heads, gpus_total, gpus_per_node,                 n_microbatches):        self.layers = layers        self.heads = heads        self.gpus_total = gpus_total        self.gpus_per_node = gpus_per_node        self.n_microbatches = n_microbatches     def find_plans(self):        """Enumerate feasible (data_parallel, tensor_parallel, pipeline_parallel)"""        plans = []        for p_tp in (1, 2, 4, 8):            if self.heads % p_tp != 0:                continue            if p_tp > self.gpus_per_node:                continue            for p_pp in (1, 2, 4, 8, 16):                if self.layers % p_pp != 0:                    continue                if self.gpus_total % (p_tp * p_pp) != 0:                    continue                p_data = self.gpus_total // (p_tp * p_pp)                plans.append((p_data, p_tp, p_pp, self.bubble_ratio(p_pp)))        return plans     def bubble_ratio(self, p_pp):        """Bubble ratio = (pp - 1) / (pp + microbatches - 1)"""        return (p_pp - 1) / (p_pp + self.n_microbatches - 1)  def main():    config = ParallelConfig(layers=32, heads=32, gpus_total=64,                            gpus_per_node=8, n_microbatches=32)    print("64 GPUs / 8 per node: feasible 3D parallel plans")    for p_data, p_tp, p_pp, bubble in config.find_plans():        print("  data=%2d  tensor=%d  pipeline=%2d  bubble=%.1f%%"              % (p_data, p_tp, p_pp, bubble * 100))  if __name__ == "__main__":    main()
目的:枚举满足整除约束的 (DP, TP, PP) 组合,并估算各方案的流水线气泡率,帮助工程师在做训练方案时快速筛选可行配置。
  1. if self.heads % p_tp != 0:注意力头数必须能被张量并行度整除,否则多头注意力无法按整“头”均分。
  2. if p_tp > self.gpus_per_node:张量并行度不能超过单节点卡数,否则 All-reduce 要走慢速跨节点网络。
  3. if self.layers % p_pp != 0:层数必须能被流水线并行度整除,保证每个工位分到的层数一致。
  4. p_data = self.gpus_total // (p_tp * p_pp)用总卡数反推数据并行度,凑满 N_GPU = DP × TP × PP 的 3D 组合。
  5. return (p_pp - 1) / (p_pp + self.n_microbatches - 1)气泡率公式:级数越大气泡越大,微批次越多气泡越小(但显存会涨)。
  6. for p_data, p_tp, p_pp, bubble in config.find_plans():遍历所有可行方案并打印气泡率,便于对比选型。

常见误区

误区“TP 切得越细,每卡显存越小,那我把 TP 调到 32 甚至 64,不就能训练更大的模型了吗?”

正解TP 的通信发生在每一层、且必须等 All-reduce 完成才能继续计算;TP 超过单节点卡数后,通信从 NVLink 降级到 InfiniBand,带宽下降一个数量级,算力利用率可能跌到 50% 以下。TP 的上限由通信拓扑决定,而不是由显存决定。就像四位厨师围一个灶台还能递料,32 位厨师围着同一个灶台只会互相撞手,传料的功夫比做菜还久。

为何常见:人们直觉认为“切得越细分摊的人越多”,却忽略了合作本身的同步开销随人数超线性增长。

误区“流水线只是把层分到不同卡上,每张卡各算各的,应该不需要通信吧?”

正解每个微批次在流水级之间传递的是完整激活张量(hidden states),前向和反向各传一次;对 175B 模型,一批数据的激活就有几百 MB,单次传输量很大。此外还有等待上下游产生的“气泡”时间开销。准确说法是:流水线通信是“低频率、大包”,张量并行是“高频率、中包”,两者都有通信成本。

为何常见:流水线的切分单位是“层”,看起来层级之间天然独立,人们容易忽略层与层之间必须传递中间结果的数据依赖。

误区“气泡公式里微批次数量在分母上,切得越多气泡越小,那把 1 个 batch 切成 1000 份不就好了?”

正解微批次越多,同时“挂”在流水线上的激活值越多(1F1B 下约为 P_pp 份),激活显存随之上涨;且微批次过小会导致每个 GPU kernel 的矩阵太小,算力利用率下降。实践中微批次数量取流水线级数的 1~2 倍即可,例如 PP=8 时取 16~32,让气泡和显存同时处于可接受区间。

为何常见:只盯着气泡公式的单调递减性质,忽略了公式背后的激活显存和计算效率约束。

误区“反正都是把模型拆开,先设 TP 还是先设 PP,结果应该一样吧?”

正解正确顺序是“先定 TP(受单节点卡数约束),再定 PP(受层数整除约束),最后用 DP 补足 GPU 数量”。TP 要求同节点内的 NVLink 高速互联,PP 可以跨节点。如果先把 PP 定得很大、再发现 TP 必须跨节点,通信就会频繁穿越慢速网络,性能断崖式下跌。三个维度在公式里地位对称,但在硬件拓扑中完全不对称。

为何常见:公式 N_GPU = DP × TP × PP 里三个维度看起来是纯乘法关系,让人误以为它们互相独立、顺序随意。

面试题

Q1. 流水线并行切分模型的基本单位是?

单选题 · easy

  • A.按词的维度切开词表
  • B.按层序列切分给各级← 正确答案
  • C.按样本批次切分数据
  • D.按优化器状态切分

答案:B。流水线并行把模型按“层”为基本单位切成连续的多段,每段分配给一个流水级;词表切分属于张量并行,样本切分属于数据并行,优化器状态切分不属于模型结构切分。

Q2. 某模型注意力头数为 40,下列哪个值能作为张量并行度?

单选题 · mid

  • A.TP=4 可整除 40← 正确答案
  • B.TP=6 可整除 40
  • C.TP=9 可整除 40
  • D.TP=11 可整除 40

答案:A。张量并行按整“头”切分注意力,头数必须能被 TP 整除。40 只能被 4 整除(40÷4=10),6、9、11 均不能整除 40,所以答案为 A。

Q3. 与 GPipe 相比,1F1B 调度最大的优势是?

单选题 · mid

  • A.参数显存占用减半
  • B.激活显存峰值更低← 正确答案
  • C.通信总次数降为零
  • D.不需要切分微批次

答案:B。GPipe 要等所有微批次前向完成再统一反向,激活值全部存活;1F1B 前向反向交替,同时存活的微批次约为流水级数 P_pp,激活显存峰值显著下降。参数显存由 TP/PP 决定,通信不会归零,微批次仍然要切。

Q4. 关于张量并行与流水线并行的通信特征,正确的是?

单选题 · mid

  • A.张量通信稀疏且包小
  • B.流水线通信每层发生
  • C.张量通信每层多次发生← 正确答案
  • D.两者频次完全相同

答案:C。张量并行在每一层的前向和反向都要做多次 All-reduce,因此“每层多次”;流水线通信只发生在工位边界,不是每层都发生,频次远低于 TP,两者完全不同。

Q5. 单节点有 8 张 A100 且节点内 NVLink 互联,设置张量并行度时推荐上限是?

单选题 · easy

  • A.2(照顾小 batch)
  • B.4(折中显存与带宽)
  • C.8(充分利用 NVLink)← 正确答案
  • D.64(跨节点扩展)

答案:C。张量并行通信密集,必须限制在 NVLink 节点内。单节点 8 卡时 TP 上限就是 8;超过 8 就要跨节点走慢速网络,All-reduce 延迟暴增,因此 D 是错误选项。

Q6. 常见的 3D 并行包含下列哪些维度?

多选题 · mid

  • A.数据并行(复制完整模型)← 正确答案
  • B.张量并行(切分权重矩阵)← 正确答案
  • C.流水线并行(按层切分)← 正确答案
  • D.算子融合并行(合并计算)

答案:ABC。3D 并行 = 数据并行 + 张量并行 + 流水线并行,对应文章开头的“厨房复制、工位切菜、硬菜合做”。算子融合是单卡上的计算优化手段,不属于并行维度。

Q7. 流水线训练中“气泡(bubble)”出现的根本原因是?

单选题 · easy

  • A.显存不足触发换页
  • B.前后级工位忙闲不均← 正确答案
  • C.网络延迟导致丢包
  • D.学习率设置不合理

答案:B。流水线启动和收尾阶段,首尾工位没有输入数据只能空等;即使稳态运行时,级数越多,启动/收尾等特时间占比越高,这就是气泡。它源自前后级之间的依赖与等待,与显存、丢包、学习率无关。

Q8. 设计流水线与张量并行组合方案时,必须满足哪些约束?

多选题 · mid

  • A.层数能被流水线并行度整除← 正确答案
  • B.注意力头数能被张量并行度整除← 正确答案
  • C.张量并行度不超过单节点卡数← 正确答案
  • D.微批次必须大于总层数

答案:ABC。A 保证每级层数均匀,B 保证多头注意力按整“头”切分,C 保证 TP 通信走 NVLink。微批次数量只需能整除全局批次即可,与总层数没有必须的大小关系,D 错误。

Q9. 模型参数共 70B(不含梯度与优化器),TP=8、PP=2、DP=4 时每卡模型参数约为?

单选题 · hard

  • A.约 8.75B(仅按 TP 分摊)
  • B.约 4.38B(TP 与 PP 分摊)← 正确答案
  • C.约 35B(仅按 PP 分摊)
  • D.约 1.09B(三维全分摊除)

答案:B。模型参数只被 TP 和 PP 两个维度分摊,DP 复制完整模型不参与分摊。所以每卡参数 = 70B ÷ (8 × 2) = 4.375B,选 B。D 把 DP 也除了进去是常见错误,DP 的每份副本都持有完整 70B。

Q10. 下列哪些做法可以有效缓解 TP+PP 组合训练的通信压力?

多选题 · hard

  • A.计算与通信相互重叠调度← 正确答案
  • B.把张量并行度调至 32
  • C.梯度压缩并延迟同步← 正确答案
  • D.删除流水线边界传输

答案:AC。通信与计算重叠能隐藏大部分传输延迟,梯度压缩则减少 All-reduce 的数据量,两者都有效。把 TP 调到 32 会迫使通信跨节点走慢速网络,反而加剧压力;流水线边界传输是数据依赖的必经之路,不能删除。