☰
大模型分布式训练全解析:数据并行、张量并行与流水线并行
2026/10/20 13:57:27 网站建设 项目流程

大模型分布式训练这六个字,很多人一听就头大。搞过训练的人都知道,单卡已经满足不了实际需求了。70B、405B这种量级的参数量,光BF16权重就是140GB起步,一张加速卡显存再大,也装不下完整模型,更别提训练时还要额外维护梯度、优化器状态。于是“拆数据”和“拆模型”就成了绕不开的两条路。这篇内容准备把这个话题彻底讲透——数据并行怎么拆、模型并行怎么切、张量并行和流水线并行分别解决什么问题、真实训练大模型时怎么组合,以及我实际踩过的坑。适合正在从单卡转向多卡训练、或者已经在跑分布式但对并行策略还不太有底的同学。

1. 大模型分布式训练全景:先搞清楚到底在拆什么

1.1 为什么单卡训不动大模型

很多刚接触大模型的同学以为“显存不够”只是模型放不下,其实这只是第一层瓶颈。一个70B的模型,按BF16存储,权重就是70×10^9×2字节,等于140GB。目前单张主流加速卡通常是80GB左右,权重都塞不进去,更不知道把梯度、优化器状态放在哪里了。

更重要的是算力。训练过程中,每处理一个token都要对全部参数做一次前向和反向计算,计算量跟模型参数量和token数量直接成正比。单卡算力再强,面对几十亿甚至上千亿token的训练语料,也会慢到没有工程意义。所以分布式训练本质上解决两件事:一是让模型拆得下,二是让算力堆得上。前者靠模型并行,后者靠数据并行,还有一堆辅助手段,比如混合精度、梯度累积、激活重计算。

1.2 拆数据和拆模型,到底差在哪里

“拆数据”的思路很直白:每张卡复制一份完整模型,然后把训练数据切成很多份,每张卡吃一小份,最后把梯度汇总同步。这种方式实现简单,可是模型一旦大到一张卡装不下,就直接失效。

“拆模型”的思路则是把模型本身切成几块,分给不同的卡去算。可以按层切,做成流水线并行(Pipeline Parallelism,PP);也可以把某一层的矩阵运算切开,做成张量并行(Tensor Parallelism,TP)。模型拆开之后,每张卡只需要管理一部分权重,显存压力自然降下来,但卡和卡之间需要频繁通信来交换中间结果。

实际训练中几乎不会只用某一种。现在业界常说“3D并行”,就是把数据并行DP、流水线并行PP、张量并行TP混在一起,形成一个三维的并行组合。至于怎么组合、每组并行度配多少,要看模型结构、集群拓扑、通信带宽,以及卡上显存预算。后面我会给出具体配置案例。

2. 数据并行:先把数据拆开吃透

2.1 数据并行到底是怎么转起来的

数据并行(Data Parallelism,DP)是分布式训练最基础的形态。逻辑上,每个进程都有一份完整的模型副本和优化器副本。训练时,先把一个全局batch按进程数量切成若干份,每个进程用自己分到的数据做一次完整的前向、反向,求出本地梯度。

关键的一步在反向之后:所有进程需要通过集合通信把本地梯度加起来求平均,得到的平均梯度才是这一轮真正用来更新参数的梯度。这个“求平均”在工程上不是简单Post过程,而是通过AllReduce操作完成的。AllReduce会把所有rank的梯度张量汇总,然后把结果广播回每个rank。因为每个rank都用平均梯度更新一模一样的模型副本,所以所有rank的权重始终保持一致,不会出现模型分裂。

现在的框架里,分布式数据并行(DDP)模块把这些细节封装得很干净。每个进程在启动时分配到一个全局rank,框架自动把模型参数、梯度张量的通信关系建立好。反向传播过程中,梯度一旦产生,框架就按参数分组发起异步AllReduce,让通信和计算部分重叠,利用率提升明显。你实际要操心的主要是两件事:数据加载时每个rank不能重复读取同一个样本,以及全局batch的计算方式。

2.2 全局batch和学习率的关系

数据并行一上,batch的意义就变了。假设单卡micro-batch为4,一共32张卡,那么每个step实际训练样本是4×32=128。如果没用梯度累积,这个128就是一个全局batch。如果用了梯度累积,比如累积16步,那么全局batch变成4×32×16=2048。

全局batch从1变成128,学习率还维持原样,往往会收敛得不够好。业界常用一个启发式规则:全局batch翻几倍,学习率大致也按比例放大,同时需要更长的warmup来稳住训练初期。但这不是无限的,batch过大以后,梯度噪声变小,优化器反而容易跑进尖锐局部最优。所以很多人更愿意用warmup加余弦退火来控制学习率曲线,而不是简单粗暴地把学习率顶上去。

另一个容易忽略的问题:数据并行时每个rank的数据顺序必须保持独立且随机,但又要保证整个训练过程中的随机性可复现。常见的做法是每个rank一个独立的随机数生成器,按rank号错开数据起点。失手把每个rank的随机种子设成一样,会导致每个rank读到的样本完全相同,全局batch根本没变大,只是把同一批数据重复算了几遍,这是很典型的“并行度上去了,效果却没上去”的翻车现场。

2.3 通信量很快会变成天花板

数据并行有一个很要命的特性:每个step都要同步完整梯度和完整模型参数,通信量跟模型参数量成正比,跟batch大小无关。以7B模型为例,BF16梯度约14GB。做一次Ring AllReduce,每张卡要收发的数据量大约是完整梯度字节数的两倍,也就是每张卡要经手28GB左右的通信量。如果节点间网卡是10Gbps,算下来光同步梯度就要几十秒;如果用400Gbps的高带宽网络,一次同步才不到一秒。

所以数据并行不是无脑加卡就行。单机8卡内部用高速互联基本没什么压力,跨节点之后通信带宽就成了命门。很多团队跑大模型时发现,加机器数量之后每秒处理token数不涨反跌,多半就是卡在跨节点通信上。GPU利用率曲线里能看到大量时间停在通信等待上。

2.4 零冗余优化:数据并行下的显存自救

纯粹的DDP有一个巨大的浪费:每张卡都保存一份完整的优化器状态。用带两个动量的自适应优化器(比如Adam系列)训练一个70B模型,粗略算下来每个参数要占18字节左右:2字节权重、4字节梯度、4字节主权重,再加上两个4字节的动量状态。7B模型就是126GB,70B模型就是1260GB。这个量级单卡根本扛不住,所以数据并行必须配合参数切分。

业内有一类方案叫零冗余优化(常简称为ZeRO),核心思路是:梯度、优化器状态、模型权重,这些副本本来有DP张数那么多份,能不能只在所有卡上各保留一份,用到的时候再临时拼起来?

按这个思路分了三档。第一档切分优化器状态,通信量跟普通DP几乎一样,显存立刻降一大截;第二档再把梯度也切分,显存继续降,通信量略微增加;第三档连参数也切分,每个rank只持有全部参数的一部分,前向算到哪层,就临时把哪层的参数通过通信收集齐全。第三档的通信开销明显变大,但显存占用已经低到可以拿8张80G卡勉强训练70B模型。代价是通信时间变长,训练吞吐反而不一定比用更多卡更优。我自己用过之后的一个判断标准:能用更多卡堆过去,优先堆卡;卡数实在有限,再考虑把零冗余优化开到第三档。

3. 模型并行:把模型一层层切开

3.1 张量并行:把一层切成两半再算

张量并行和流水线并行虽然都叫“模型并行”,但切法完全不同。张量并行是在“层内部”切。以Transformer里最常见的一个线性层为例,权重矩阵的形状是[输出维度,输入维度]。把这块矩阵沿输出维切分成两块,两张卡各拿一半权重,各自算出一半的中间结果,最后通过AllGather把结果拼起来,这是“列并行”。

反过来,也可以沿输入维切开,两张卡各自拿一半的输入数据去算部分和,最后通过AllReduce把部分和累加出完整输出,这就是“行并行”。单独看一种都会带来频繁通信,但社区里有个很经典的配对技巧:把相邻两个线性层配对,前一层用列并行,后一层用行并行。这样前一层做完列并行后,中间结果天然已经按照输入维切好了,后一层可以直接喂给不同的卡,原本两次通信合并成一次AllReduce,通信量几乎减半。

这个方案对硬件最敏感。张量并行的通信发生在每一层内部,属于典型的低延迟高带宽需求。所以一般情况下,张量并行组必须放在同一台机器内部,靠高速互联连接,TP规模通常取4到8之间。一旦把TP跨到不同机器,哪怕走的是200Gbps网卡,延迟和带宽也都撑不住,卡间等待时间会淹没计算收益。很多“一上多机TP就变慢”的问题,就是因为把TP组跨了节点。

3.2 序列并行:顺手把激活显存也省了

张量并行还有一个变体,叫序列并行(Sequence Parallelism,SP)。常见实现是跟张量并行配合使用,针对LayerNorm、Dropout这类按token独立操作的计算,把输入按序列长度维度切分到不同设备上,每张卡只处理一部分token。

这样做带来的最大收益是激活显存大幅下降。之前做张量并行时,激活值常常在每个rank上重复保存一份完整副本,长序列下这部分内存非常可观。引入序列并行之后,那些逐token运算不需要复制完整激活,每一段只有一份,激活显存几乎能降到原来的张量并行度分之一。对于训练长序列模型的场景,效果非常明显。这个方案不是替代张量并行,而是在张量并行基础上再补一刀,进一步压低显存峰值。

3.3 流水线并行:按层切,像工厂流水线一样跑

流水线并行则是把模型按“层”切开。第0层到第k层放在第0号设备,第k+1层到第2k层放在第1号设备,依次类推。数据前向传播时,从第0号设备流到第1号设备,再流到第2号设备,这个过程很像工厂流水线。

如果不做任何优化,每张卡同一时刻只有一张卡在计算,其他卡全在等,利用率极其惨淡。解决手段是引入“micro-batch”微批次:把一个大batch切成一串小batch依次灌进流水线。第0号设备算完第一个微批次,马上传给第1号设备,同时自己接着算第二个微批次,这样多张卡能同时处理不同微批次,实现并行。

但流水线并行天然有气泡(bubble)问题:最开始流水线还没填满时,后半部分设备是空的;收尾阶段前半部分设备先空闲下来。这个气泡占比可以用一个简化公式估算:(PP-1)/(PP-1+微批次数量)。PP越大,气泡越严重;微批次数量越多,气泡占比越小。所以工程上会尽量把微批次数量放大,但这会抬高激活显存,于是又出现了一种叫1F1B的交错调度方式——一个前向配一个反向交错执行,既压气泡,又控制显存峰值。现在主流训练方案基本都用这种交错调度。

3.4 张量并行和流水线并行,互相能替代吗

经常有人问我:既然都是拆模型,张量并行和流水线并行选一个不就行了?答案是很难替代。张量并行能分担单个大矩阵的显存和计算,但通信频率高,依赖设备间低延迟;流水线并行通信频率很低,每个微批次只传递一次层间输出,但它要求每个设备上能放下一个完整Stage,如果单层因为超大hidden size放不下,流水并行也救不了你,只能靠张量并行继续切。

实际使用中有一个很常见的组合:一层超大,先拿张量并行切开,再把不同层按流水线并行分到多张卡。比如在训练超大模型时,经常看到TP取8,PP取4到几十,DP再叠一层。三种并行策略各管一段:TP管单层太大,PP管整体层数太多,DP管剩下的横向扩展。

4. 混合并行实战:从配置到踩坑一次说清

4.1 三维并行怎么布:TP卡同机,PP跨机可,DP最外层

真正训练一个70B模型,单靠某一种并行肯定不行。我先给出一套很通用的布局口诀:张量并行组优先放在同一节点内,流水线并行组可以跨节点,数据并行的通信量最大,放在最外层扩规模。

为什么这样排?因为数据并行每步要做一次全量梯度AllReduce,跨节点的通信压力最大,但它的通信模式比较规律,适合在大规模集群上展开。流水线并行跨节点时,节点之间只传每一层的边界激活值,通信量比较小。张量并行对延迟最敏感,宁可让TP组占据同一节点内的高带宽互联,也别为了一点并发数把它拆到多台机器上。

以32张卡训70B模型为例,一个常见配置是:TP=4、PP=4、DP=2,或者TP=8、PP=2、DP=2。两者我都跑过。TP=8时单层切得更细,显存压力更小,但层内通信占用的时间更长;TP=4时通信更轻松,但每卡要装更大块的权重和激活。具体选哪种,要看节点的互联能力和单卡显存。原则上先满足显存,再看通信会不会拖后腿。

4.2 显存是怎么算出来的:别等OOM了才拍脑袋

我见过太多同学上大模型训练时,第一步就是被OOM打懵。与其反复试错,不如先算账。用常见的混合精度加自适应优化器方案,每个参数大概要占这么些空间:权重2字节,梯度4字节,优化器里的主权重、一阶动量、二阶动量加起来12字节,总得说就是大约18字节。70B模型就是约1260GB。

如果开零冗余优化,把优化器状态和梯度沿数据并行维度切分,再加上TP和PP对权重的切分,可以用一个简化公式估算每卡压力:模型总参数量×18字节÷(TP×PP×DP)。32卡TP4PP4DP2的情况下,大约是70×18÷32,约40GB,还没算激活值和通信缓冲区。所以实践中70B模型配32张80G卡,再开激活重计算,属于“能跑但有点紧”;配到64张卡就从容很多。

激活内存更反直觉。它不跟参数量直接挂钩,而是跟batch大小、序列长度、hidden size、层数绑定。长序列大batch下,激活值常常比模型权重还大。应对办法就是激活重计算,也叫检查点重计算:前向时不保存中间激活,反向要用的时候临时重算一遍。这是用算力换显存,速度会掉一点,但能救命。

4.3 从配置到启动:一份示例配置和流程

我习惯把一个训练任务的并行配置写成下面这样,一目了然:

{ "parallelism": { "tp_size": 4, "pp_size": 4, "dp_size": 2, "micro_batch_size": 1, "gradient_accumulation_steps": 16, "zero_stage": 3, "activation_checkpoint": true } }

这个配置对应的全局batch是micro_batch_size × dp_size × gradient_accumulation_steps,也就是1×2×16=32。注意,这里dp_size不是直接配出来的,而是总卡数除以TP×PP,也就是32÷(4×4)=2。global batch只有32,对70B模型来说偏小,实际训练时通常把micro_batch_size调到2到4,或者把累积步数加大。千万记住,改并行配置时,全局batch是否被改变要单独算清楚,否则训练节奏完全不是你以为的那样。

启动流程上,第一步是让调度器把32个进程分到4台机器,每个进程拿到一个global rank和一个local rank。第二步是根据rank计算进程组:前4个rank组成TP组,接着每4个组成一个TP组;前16个rank组成PP组,后16个组成另一个PP组;最后根据剩余维度确定DP组。这些进程组建立好之后,框架会自动安排通信原语,不需要你手动给每张卡写通信代码。业务侧要做的只是在每个step里跑forward和backward,最后调用优化器更新。

真正容易出错的是数据加载。DP维度会分发数据,TP和PP维度里的进程通常应该看到完全相同的数据。如果偷懒用“每个rank一个不同的种子”来切数据,很容易把TP和PP组内的数据搞乱,导致不同并行维度在同一模型上算着不同样本,最终梯度对不上。

4.4 断点续训与训练中期稳定性

大模型训练动不动跑几周,中途宕机几乎必然发生。所以断点续训不是可选项,是基础配置。保存checkpoint时至少要包含四样东西:模型权重、优化器状态、学习率调度器的step、还有每个rank的随机数生成器状态。很多同学只保存权重和优化器,恢复训练后loss曲线突然变形,就是随机状态没还原。

跨节点训练的机器时钟也经常出问题。时钟偏差会让集合通信库超时,报错还不直观。我在实际项目中遇到过两次类似情况,排查到最后都是节点间时间不同步。训练前把所有节点时间对齐一下,能省掉大量莫名其妙的通信错误。

训练中还要每隔一段时间检查每个rank的GPU利用率。如果大部分卡利用率正常,只有某张卡经常掉到很低,基本可以断定是数据加载不均衡,或者那张卡所在的机器上跑着别的任务抢占了CPU和带宽。大模型训练是木桶效应,一张慢卡拖住整个集群,所以监控必须覆盖到单卡粒度。

5. 分布式训练避坑指南:那些坑我替你踩过了

5.1 常见故障速查表

现象可能原因处理思路
只有某张卡OOM,其他卡正常激活分配不均、TP跨节点、序列并行未开启开序列并行和激活重计算,检查TP组是否都落在同一节点内
Loss变成NaN学习率过大、混合精度loss scale异常、数据里有异常值调小学习率,检查loss scale是否被不断拉低,排查数据预处理
训练吞吐远低于理论值网络拓扑不合理、网卡速率过低、集合通信和计算未重叠用通信压测工具确认带宽,调整TP/PP/DP布局,检查网卡型号和驱动
某张卡利用率经常掉零数据加载阻塞、CPU预处理跟不上、机器被其他任务抢占增加数据预取线程,换独立的CPU/内存资源,定位抢占进程
重启后loss和原来偏离很大checkpoint缺优化器状态或RNG状态,数据顺序变化完整保存四类状态,恢复时严格加载,固定数据打乱顺序的种子
并行度一加,速度反而下降通信时间超过计算时间,并行开销大于收益降低TP或ZeRO档位,检查跨节点通信带宽,适当放大micro-batch

5.2 排查OOM的思路不能只看总显存

OOM排查时,很多人只看“剩余显存还有多少”,这其实没用。分布式训练里,最危险的是“峰值显存”瞬间爆掉。激活重计算开没开、序列并行开没开、微批次大小是多少,都会显著改变峰值。我给一个排错顺序:先把微批次调到1,如果还爆,说明模型权重和优化器状态本身就放不下;如果不爆了,说明是激活显存峰值问题,优先开激活重计算和序列并行。之后再慢慢调大微批次,找到临界点。

另外,TP布局错了也会导致单卡OOM,比如TP组跨节点,模型被切了,但某张卡因为通信等待而临时累计了多个batch的激活,显存直接爆掉。检查一下TP组内是否都在同节点,往往比反复调参更快。

5.3 训练越来越慢:内存碎片和通信长尾

有一个很容易被忽视的问题:训练前期速度快,跑了几万步后越来越慢,重启后又恢复正常。这种情况八成不是模型出了问题,而是显存碎片化。

分布式训练时,每个step都会分配和释放大量临时显存,长期运行后高速显存被切成碎片,新的大块分配失败,框架只能等待或者走更慢的路径。解决办法是给框架的显存分配器设置更大块的分块大小,或者定期重启。如果是跨节点场景,还有一种“长尾效应”:某个节点的网卡因为散热、PCIe链路降速等原因变慢,整个训练被拖累。大量慢节点的共性问题,需要通过硬件监控提前发现。

5.4 可复现性:分布式下“没有完全一样”

很多同学会疑惑:为什么分布式训练的结果和单卡训练不一样?为什么两次多卡训练结果也不完全一样?这是正常的。跨设备通信时浮点累加顺序不同、异步AllReduce时机不同,都会带来极细微的数值差异。

如果追求严格复现,只能尽量做到三件事:固定每个rank的数据顺序、固定随机数种子、固定通信顺序。但即便这样,不同框架和不同驱动版本下结果也可能有细微差别。所以做实验对比时,不要纠结两次训练loss差0.001,而是关注趋势和大局。真正要警惕的是“明显偏大”的不可复现,那通常是数据加载或参数初始化搞错了。

5.5 从仿真到真跑的平滑迁移

最后给个很实际的经验:不要一上来就按最终规模配置集群。我习惯先在1张卡上用小模型把代码逻辑调通,再把模型换成目标规模、DP拉大验证梯度同步正确,最后才加TP和PP。没验证过就直接上TP=8、PP=8,出了问题既可能是模型代码,又可能是并行配置,排查难度完全不一样。

并行度不是越高越好。在某些配置下,TP从4升到8,通信开销增长反而让吞吐下降。判断一个配置好不好,不要只看“显存装下了吗”,要看“每秒处理token数”和“GPU有效计算占比”。这指标上不去,并行度再高也只是让机器忙起来,而不是让训练快起来。

最后分享几点个人体验

分布式训练做得久了,我有一个比较深的感受:这个领域难点不在某个高深算法,而在“系统性”。单卡代码可能没问题,一旦放大到几百张卡,数据、通信、显存、稳定性、监控,每一环都可能成为瓶颈。最怕的不是出错,而是出错了不知道从哪一层开始查。

所以我自己的习惯是,不管项目多急,都先做小规模的全流程验证,再上大规模。训练过程中,每卡维度的大盘监控一定要开,通信和计算的比例要能实时看到。多花半天搭监控,往往能省下后面几天的排障时间。

这套并行策略不只用于大模型预训练,后面的持续训练、指令微调、甚至推理侧的并行服务,底层思路都是同一套:拆数据、拆模型,再在中间找到通信和计算的最佳平衡点。理解到这里,框架层面的配置参数对你就不再是一堆冷冰冰的数字了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询