想象这样一个场景:一家小作坊接到一张“打造一万台精密仪器”的大订单。老板发现两件事搞不定:第一,整个车间塞不下全部零件(显存墙);第二,就算塞得下,一个人手工打造一万台也得干到退休(算力墙)。于是老板决定开一家三层结构的工厂:先把整条生产线按工序分成若干个车间——这是流水线并行(PP);每个车间里再安排几个师傅围着同一件半成品分工打磨——这是张量并行(TP);最后整套车间和师傅复制好几份,同时加工不同批次的原料——这是数据并行(DP)。用三种方式层叠扩产,就是大模型训练里的“3D并行”。 3D并行把数据并行、张量并行、流水线并行三种策略叠加在一个集群上,让千亿参数模型既“装得下”又“跑得快”。本文从工厂视角逐层拆解三个维度的分工逻辑、层叠时的配置公式、通信开销的平衡技巧,以及真正落地调优时容易踩的坑。
对AI从业者来说,3D并行是训练千亿级大模型的必修课:掌握了它,你才能真正榨干多卡集群的算力,从“会跑模型”进阶到“会设计训练系统”。
目录
可视化演示
动态演示大模型3D并行的核心机制,通过概览/流程/对比/趋势四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
大模型3D并行:数据并行 数据并行DP的原理与AllRe 张量并行TP的层内权重切分机制 核心机制可视化 大模型3D并行整体架构与关键组件
并行基础概念:为什么单卡练不动千亿模型
一个人做满汉全席,会撞上两堵墙:厨房案板放不下几十道菜的食材,一双手也来不及在开席前做完。训练超大模型也一模一样——“显存墙”和“算力墙”。一块顶配的 H100 显卡只有 80GB 显存,而一个 100B 参数的模型仅权重就要约 200GB(FP16),直接塞不进去;就算硬塞,单卡训练也要跑上几百年。想翻墙,唯一的办法是“把活分出去”。
怎么分?业界总结出三条基本路子:数据并行(DP)复制整套模型、把数据切块分给各副本;张量并行(TP)把每一层的权重矩阵切成几块分给多卡;流水线并行(PP)把模型按层切成若干连续阶段。用工厂语言说:先分车间(PP),再给车间配多名分工的工人(TP),最后复制一整条生产线(DP)。
三层叠加,就是 3D 并行。三个维度互相垂直,GPU 总数恰好是三者乘积:
回到小作坊的比喻:只复制生产线(DP)解决不了单条线放不下零件的问题;只分工(TP)解决不了要同时处理海量订单的问题;只分车间(PP)每个环节仍然太慢。三层错落叠加,才把“装不下”和“跑得慢”一起攻破——显存墙靠 TP 拆权重、PP 拆层数,算力墙靠 DP 多线并行。
- 三个并行度DP、TP、PP 分别代表数据副本数、层内切块数、流水线段数,三者是三个正交的坐标轴
- GPU总数每个 DP 副本内部都要有 PP×TP 张卡负责一段模型的一个分块,所以总卡数是三者乘积
- 实例说明DP=2、TP=3、PP=3 时,总共需要 18 张卡
数据并行DP:复制整条生产线
蛋糕店接到一万个订单,一个师傅做到天亮也做不完。最简单的办法是什么?复制十套“师傅+烤箱+配方”,每套认领不同的原料批次去烤,最后大家把成品摆到一起卖。数据并行就是这个思路:每张 GPU 都保存一份完整模型,训练时把一次迭代的数据批平均切成 \( N \) 份,每卡领一份做前向和反向传播。各卡算出的梯度方向略有差异,所以需要在 DP 组内“对齐口径”——把所有梯度求和取平均,再统一更新。
这个对齐动作就是经典的 AllReduce,公式非常朴素:
DP 最大的优点是好实现、扩展性强;但两个特点必须记牢:一是每张卡都要装下完整模型加优化器状态,显存一点没省;二是每一轮迭代都要同步一次全量梯度,通信量与参数量成正比。所以 DP 适合“模型单卡放得下、数据量大得吓人”的场景;一旦模型大到单卡放不下,就得靠 TP 和 PP 来给显存“减负”。
回到蛋糕店:复制十条生产线解决的是“量”的问题,但每套厨房还是得备齐全部烤箱和食材。当一套设备本身大到一间屋子都塞不下时,光复制没用——下面轮到 TP 出场。
- 本地梯度第 \( i \) 张卡拿着自己那份数据算出梯度 \( g_i \)
- AllReduce 求和所有副本把梯度累加成一个总和
- 取平均总和除以副本数 \( N \),得到平均梯度
- 统一更新所有副本用同一个平均梯度更新参数,保证模型处处一致
张量并行TP:车间里围着半成品分工
巨型钢板要加工成精密零件,一台机床塞不下。老师傅的做法是:把钢板画上格子切开,几个师傅各磨一块,最后严丝合缝拼起来。张量并行 TP 干的就是这事——把神经网络每一层的权重矩阵切成若干块,分散到多张 GPU 上,每卡只算自己那一块,算完再拼回完整输出。
以全连接层 \( Y = XW \) 为例:把权重 \( W \) 按行切成上下两块,输入 \( X \) 相应地按列切,两张卡各算局部乘积,最后用 AllReduce 把两部分相加,精确还原出完整 \( Y \):
TP 在 Transformer 上的应用同样直接:Attention 的 Q、K、V 权重可以按“头”切分,每个 GPU 只负责几个注意力头;MLP 的两层大矩阵则按行列切分。代价是每一层前向和反向都要做一次集体通信,通信量在三种并行里最密集,必须依赖 NVLink 这类卡间高速互联;收益是权重和优化器状态被均分到多卡,显存压力大幅下降。所以实践中有一个铁律:TP 组必须放在同一台物理节点内,贴着 NVLink 交换机,别让数据“绕远路走网络”。
回到车间:围着大工件分工的师傅们,每道工序之间都要互相递工具、对尺寸,所以必须站得近、喊得应——这就是 TP 必须绑在 NVLink 高速互连环上的原因。
- 原式输入 \( X \) 与权重 \( W \) 做矩阵乘,得到输出 \( Y \)
- 切矩阵把 \( W \) 按行切成上下两块,\( X \) 按列切成左右两块,保证形状匹配
- 局部计算GPU0 算 \( X_1W_1 \),GPU1 算 \( X_2W_2 \),互不等待
- 求和归并用 AllReduce 把两块卡的结果相加,还原完整输出
流水线并行PP:把厂房改成流水线
菜太多一道一道炒来不及,聪明的后厨把工序拆开:第一组只管洗菜切菜,第二组只管下锅颠勺,第三组只管装盘出菜。模型训练也一样,把几十上百层网络按连续层切成多个阶段(stage),每个阶段由一组 GPU 负责。数据像半成品一样依次流过所有阶段,这就是流水线并行 PP。
但流水线有个天生的毛病:开工时后半段车间空等原料,收工时前半段车间空等下游。这段浪费叫“气泡”(bubble)。怎么治?把一个大 batch 拆成 \( m \) 个微批次(micro-batch),像传送带上多放几件半成品,让前后车间的等待被摊薄。气泡占比有明确的公式:
PP 的显存收益很明显:每卡只保存一部分层。但它也有自己的通信需求——阶段边界要传递“半成品”(激活值和梯度),跨节点传输依赖高速网络。为了进一步压气泡,工程上还有 1F1B 调度(一个前向紧跟一个反向)、虚拟流水线(把阶段再微切)等进阶招数。
回到比喻:流水线段数越多,工人分工越细,但最后车间等原料等得越久;在传送带上多放几件半成品(增大微批 \( m \)),气泡自然被摊薄。不过要小心:微批太多,激活显存也会水涨船高。
- 阶段数流水线被切成的段数 \( p \),\( p \) 越大等待链条越长
- 微批次数每个阶段要依次处理的微批数量 \( m \)
- 理想总槽位\( p \) 个阶段处理 \( m \) 个微批,理想时间对应 \( m+p-1 \) 个槽位
- 气泡槽位启动和排空阶段共浪费 \( p-1 \) 个槽位
- 气泡占比两者相除即气泡率
层叠配置策略:三层工厂怎么盖
三层工厂的施工图有讲究:不是随便复制几条线、切几刀就能高效运转。布局顺序、通信管道、显存负担三件事都得算清楚。
先说规模:GPU 总数 = DP × TP × PP。总卡数固定时,三个维度怎么分配直接决定通信模式。再看物理拓扑:TP 通信最密集,必须放进同一节点 NVLink 域;PP 次之,跨节点但要高速网络(IB 或高带宽以太网);DP 通信最稀,每迭代才同步一次,可以放心跨节点跨可用区。通信量排序大约是 TP > PP > DP,谁的网络占用高,谁就要离高速互联更近。
显存也要三块一起规划:权重和优化器状态被 TP 均分,激活则被 PP 按段摊薄。单卡显存的粗略估算公式:
实际配置有个通行口诀:TP 先喂满节点内 NVLink,PP 分出阶段控制层深,DP 收尾复制吃数据吞吐。比如 8 卡一台的节点,可以先定 TP=8 占满单节点 NVLink,再定 PP 跨 8 个节点,最后用 DP 乘一个倍数把整个集群铺满。
回到比喻:车间怎么排工位(TP)、车间之间传送带怎么连(PP)、一共复制几条产线(DP),是施工图上三件套。排错了,工人们大半时间在等料,而不是干活。
- 权重分片权重显存 \( M_w \) 被 TP 切成 \( T \) 块,每卡只存 \( 1/T \)
- 优化器状态Adam 等优化器每参数要额外存约 4 倍状态(动量与方差),同样被 TP 均分
- 激活分片激活显存 \( M_a \) 按 PP 段数 \( P \) 摊分,每阶段只存自己那部分
- 总和三者相加就是规划单卡显存时的参考值
调优部署经验:盯住堵点再调参
三层工厂投产之后,活儿还没完:今天这条线堵料、明天那台机器罢工,都得有章法地应对。大模型训练的调优也一样在细节里。
第一招,Profiling 定位瓶颈。用 Nsight、NCCL 等工具看每个 GPU 的计算利用率、通信等待时间和显存水位。发现 TP 通信占比超过 30%,就减少 TP、把算力预算让给 PP 或 DP;发现流水线气泡大,就增加微批次数。别拍脑袋调参,让数据说话。
第二招,梯度累积配合。DP 通信太频繁拖慢训练时,可以攒几轮梯度再统一 AllReduce,等效训练批量随之变大:
第三招,混合精度叠算:3D 并行中普遍开启 FP16/BF16,权重和激活显存再省一半,但要注意 TP 组内的通信量也要按半精度重新核对带宽。第四招,容错检查点:每训练一段保存完整检查点,TP/PP 任一卡掉线时,从最近检查点整体恢复——毕竟 3D 并行是全集群协同作战,一个钉子坏一辆车,没有检查点就全盘重来。
回到比喻:好厂长不靠蛮干,而是盯仪表盘(Profiling)、错峰攒货(梯度累积)、轻装上阵(混合精度)、备好预案(检查点)——训练集群持续高效运转,靠的正是这套“开厂经验”。
---
- 单卡微批大小每张卡一次前向传播的样本数 \( b \)
- DP 副本数整套模型被复制成了几条数据生产线
- 累积步数攒 \( s \) 步梯度再统一做 AllReduce
- 等效批量三者相乘,就是一次参数更新真正看过的样本数
AI 应用场景
场景1:千亿参数大模型集群训练
阶段:分布式训练与集群部署
解决的问题:单块 H100 只有 80GB 显存,一个 100B 参数模型仅权重就需约 200GB(FP16),单卡既装不下也训不完;传统“一台机器多卡”方案没有规划卡间通信路径,GPU 利用率极低。
具体案例:某算法团队用 8 台 8 卡 A100 节点(共 64 卡)训练 65B 模型,按 3D 并行配置 DP=4、TP=4、PP=4。每个节点内 TP=4 用 NVLink 全互联,PP=4 把 64 层模型切成 4 段跨节点流动,DP=4 复制 4 份吃不同数据。配合梯度累积与 BF16 混合精度,模型算力利用率(MFU)从初版 18% 提升到 43%,训练周期从 32 天压缩到 9 天。
场景2:云端弹性训练资源编排
阶段:资源调度与训练平台
解决的问题:云上 GPU 集群节点间网络质量参差不齐(NVLink、IB、千兆以太网混存),若随机分配 3D 并行角色,TP 组可能被拆到跨节点,通信直接爆炸。
具体案例:MLOps 团队为共享 K8s 集群设计拓扑感知调度器:申请 64 卡任务时,TP=4 必须落进同一节点,PP=4 分配到 IB 互联的相邻节点,DP=2 跨可用区复制。调度后集群整体利用率提升约 35%,任务排队时间缩短近一半。
Python 演示
class ParallelConfig: def __init__(self, dp, tp, pp): self.dp = dp self.tp = tp self.pp = pp self.total_gpus = dp * tp * pp def rank_to_coord(self, rank): """把全局GPU编号映射到(DP, PP, TP)三维坐标""" tp = rank % self.tp pp = (rank // self.tp) % self.pp dp = rank // (self.tp * self.pp) return (dp, pp, tp) def allreduce_group(self, rank): """返回与该卡共享DP梯度同步组的所有GPU编号""" coord = self.rank_to_coord(rank) return [d * self.tp * self.pp + coord[1] * self.tp + coord[2] for d in range(self.dp)] def pp_downstream(self, rank): """返回流水线下游阶段的GPU编号(最后一阶段返回None)""" coord = self.rank_to_coord(rank) if coord[1] == self.pp - 1: return None return coord[0] * self.tp * self.pp + (coord[1] + 1) * self.tp + coord[2] def simulate_3d_parallel(config, num_microbatches=4): print("=== 3D并行模拟:DP=%d × TP=%d × PP=%d ===" % ( config.dp, config.tp, config.pp)) print("总GPU数:%d" % config.total_gpus) print() for rank in range(config.total_gpus): coord = config.rank_to_coord(rank) group = config.allreduce_group(rank) downstream = config.pp_downstream(rank) dest = str(downstream) if downstream is not None else "无(最后一阶段)" print("GPU%d 坐标(DP=%d, PP=%d, TP=%d)" % ( rank, coord[0], coord[1], coord[2])) print(" 梯度同步组:%s" % group) print(" 流水线下游:%s" % dest) print() print("--- 流水线调度(按时间步观察活跃GPU)---") for step in range(num_microbatches + config.pp - 1): active = [] for rank in range(config.total_gpus): coord = config.rank_to_coord(rank) mb_id = step - coord[1] if 0 <= mb_id < num_microbatches: active.append(rank) print("时间步%d:活跃GPU=%s" % (step, active)) if __name__ == "__main__": config = ParallelConfig(dp=2, tp=2, pp=2) simulate_3d_parallel(config, num_microbatches=4)
self.total_gpus = dp * tp * ppGPU 总数等于三个并行度相乘,这是配置的总约束rank_to_coord把全局 GPU 编号拆成 (DP, PP, TP) 三维坐标,对应三条正交的并行轴tp = rank % self.tp先取 TP 坐标:维度最低,组内卡号连续pp = (rank // self.tp) % self.pp再取 PP 坐标:按 TP 大小跳号取段dp = rank // (self.tp * self.pp)最后取 DP 坐标:每跨一个副本,编号平移整个 PP×TP 块allreduce_group返回同一 DP 坐标下、PP 和 TP 坐标相同的所有卡,它们组成梯度同步组pp_downstream返回同 DP、同 TP 坐标的下一 PP 阶段卡号,最后一阶段返回 Nonefor step in range(num_microbatches + config.pp - 1)时间步从流水线启动覆盖到排空,共 m+p-1 步mb_id = step - coord[1]当前时间步该阶段应处理第几个微批,像传送带上走到的半成品序号if 0 <= mb_id < num_microbatches只有微批落在有效区间内的 GPU 才处于活跃状态
常见误区
误区张量并行能省显存,那就把 TP 设成 8 甚至 16,省下的显存不就能装更大模型了吗?
正解TP 每层前向和反向都要做 AllReduce/AllGather 同步,通信量随切块数快速增长。TP 超过单节点 NVLink 域后要跨交换机走网络,通信延迟会直接吞掉省显存换来的算力。正确做法是让 TP 恰好占满单节点的 NVLink 域(如 8 卡节点设 TP=8),不要盲目追加。
为何常见:直观上“显存不够就多切几块”,却忽略了碎片越碎、拼接成本越高——好比钢板切八瓣再焊起来的时间远大于直接磨。
误区DP 每张卡都要存完整模型,显存一点没省,那不如把卡都拿去做 TP 和 PP。
正解DP 是三种并行中唯一与数据吞吐直接挂钩的维度。训练速度的核心是“单位时间看多少样本”:有了 DP=N,相当于 N 张卡同时消费 N 份数据,吞吐近似线性提升。显存不够靠 TP/PP,速度不够靠 DP,三者管的事不同、不可互相替代。
为何常见:把“省显存”当成并行的唯一目标,忘了训练还要追求吞吐和收敛速度。
误区模型层数多,那把 PP 设成 16 甚至 32,每卡只存几层,显存肯定轻松了。
正解由气泡率公式 \( \frac{p-1}{m+p-1} \) 可知,\( p \) 增大分子变大,气泡率必然上升;想压气泡就得同步增大微批次数 \( m \),这又会放大激活显存。实践中 PP 通常控制在 4~16,并配 1F1B 调度、虚拟流水线,阶段边界还会增加跨节点传输点。
为何常见:只看到每卡层数变少的好处,忽略了流水线“启动—排空”空等的时间和跨段通信成本。
误区DP=2×TP=8×PP=4=64 和 DP=4×TP=8×PP=2=64 反正都 64 卡,效果差不多。
正解三个维度的通信密集度完全不同:TP 每层都同步、PP 阶段边界同步、DP 每迭代才同步一次。维度分配必须匹配物理拓扑:TP 放 NVLink 域、PP 靠近网络骨干、DP 可跨区。同样 64 卡,配错了通信等待可能占掉训练时间一半以上。
为何常见:把 GPU 想象成可任意连线的抽象节点,忽略了真实集群有 NVLink、PCIe、IB、以太网的带宽层级差异。
面试题
Q1. 数据并行(DP)中,每张GPU保存的模型参数状态是?
- A.完整模型的一份独立副本← 正确答案
- B.整个模型按层各存一段
- C.只保存梯度不保存权重
- D.每卡只存某一层的参数
答案:A。DP 是“复制生产线”,每张卡都持有一份完整模型副本,只是吃不同批次的数据。B 是 PP 的按层切段,C 和 D 与训练实际不符。
Q2. 流水线并行(PP)把模型按什么粒度切开?
- A.按权重矩阵的行列
- B.按连续的层分成阶段← 正确答案
- C.按注意力头逐个切开
- D.按激活函数的种类
答案:B。PP 好比流水线车间,按连续层切成阶段,每个阶段由一组 GPU 负责。A 是 TP 的切法,C 是 TP 切 Attention 的方式之一,D 不是并行切分依据。
Q3. 3D并行中,GPU总数与三个并行度的关系是?
- A.三者的最大值决定所需GPU数
- B.三者之和等于所需GPU数
- C.三者乘积等于所需GPU数← 正确答案
- D.只需要DP与TP的乘积
答案:C。三个维度互相正交,每个 DP 副本内都要有完整的 PP×TP 布局,所以总卡数 = DP×TP×PP。A、B、D 都漏掉了至少一个维度。
Q4. 关于PP气泡率 bubble=(p-1)/(m+p-1),下列说法正确的是?
- A.阶段数p越大气泡率越低
- B.微批次数m越大气泡率越低← 正确答案
- C.气泡率与微批次数无关
- D.阶段数p越小气泡率越高
答案:B。\( p \) 增大分子上升,气泡率升高;\( m \) 增大分母上升,气泡率下降。A、D 说反了,C 与公式冲突。
Q5. 为什么TP组的通信在三种并行中最密集?
- A.因为每个数据迭代只同步一次梯度
- B.因为每层前向和反向都要同步矩阵乘积← 正确答案
- C.因为每个微批只在阶段边界传激活
- D.因为DP副本各自独立不用通信
答案:B。TP 把一层切成多卡,前一层的输出必须汇总拼接才能进入下一层,所以每一层的前向和反向都伴随集体通信。A 说的是 DP,C 说的是 PP,D 与事实相反。
Q6. DP组内的梯度同步使用哪种通信模式?
- A.AllReduce梯度全体规约← 正确答案
- B.Broadcast广播到全局
- C.AlltoAll全交换权重
- D.ReduceScatter单点聚合
答案:A。DP 副本算完梯度后用 AllReduce 求和求平均,再回发给所有副本保证模型一致。B、C、D 都不是标准的梯度同步方案。
Q7. 3D并行配置时,正确的策略有哪些?
- A.TP组尽量占满单节点NVLink域← 正确答案
- B.PP阶段跨节点使用高速网络← 正确答案
- C.DP副本可分散到不同可用区← 正确答案
- D.三个维度可任意交换不影响性能
答案:ABC。TP 通信最密必须贴着 NVLink;PP 次之但要高速跨节点链路;DP 通信最稀可跨区容灾。D 完全错误——交换维度会显著改变通信总量和训练性能。
Q8. 配置 DP=4、TP=4、PP=8 时,总GPU数和单个DP梯度同步组包含的卡数分别是?
- A.总128卡,各组16卡
- B.总128卡,各组32卡← 正确答案
- C.总256卡,各组16卡
- D.总256卡,各组32卡
答案:B。总卡数 = 4×4×8 = 128。一个 DP 梯度同步组包含的是同 DP 坐标下、所有 PP×TP 组合的卡,即 PP×TP = 4×8 = 32 张。A 的 16 卡少算了一维,C、D 基数算错。
Q9. 下列哪些手段可以降低PP流水线的气泡率?
- A.增大微批次数m← 正确答案
- B.换成1F1B调度← 正确答案
- C.使用虚拟流水线切分← 正确答案
- D.增大流水线阶段数p
答案:ABC。由气泡率公式,增大 \( m \) 使分母变大直接降气泡;1F1B 和虚拟流水线是业界成熟的流水线调度优化。D 增大 \( p \) 反而抬高气泡率。
Q10. 在3D并行训练中做调优部署时,下列哪些做法是合理的?
- A.用Profiling发现TP通信占比过高时减少TP← 正确答案
- B.用梯度累积增大等效批量但不改变DP← 正确答案
- C.开启混合精度后直接按半精度核对显存← 正确答案
- D.检查点只保存DP维度某一副本的参数
答案:ABC。Profiling 定位瓶颈后动态减 TP,合理;梯度累积在不加 DP 副本的前提下拉大等效批量,合理;混合精度让权重和优化器状态减半,核对合理。检查点必须覆盖 TP/PP 的全部分片,只存一个 DP 副本会丢失分片信息、无法恢复,D 错误。