LLM分布式计算五大并行策略:从数据并行到专家并行全解析
2026/9/15 7:26:38 网站建设 项目流程

如何从算法视角理解 LLM 分布式计算五大并行策略

先聊个很实际的感受:很多算法同学做模型训练和推理时,说白了就是调用 DeepSpeed、Megatron-LM 或者 vLLM 这些框架,GPU 一跑起来就完事了。一旦遇到显存不够、训练速度上不去、推理延迟下不来,就开始对着报错日志发愁,最后只能把模型尺寸调小或者把 batch size 降下来。问题根源很大程度在于——你对大模型分布式计算的底层并行策略没有建立全局认知。

这篇系列第二篇就专门解决这个痛点。我会用算法同学熟悉的语言,把 LLM 分布式计算中最重要的五类并行策略讲透:数据并行、流水线并行、张量并行、上下文并行、专家并行。这篇文章能帮你搞清楚每种并行的切分维度、通信开销、适用场景、优缺点,以及它们在实际的大模型训练和推理框架里是怎么组合使用的。

不管你是正在做大模型训练调优的算法工程师,还是准备转入 AI Infra 方向的同学,这篇文章都可以帮你建立一套完整的分布式并行知识框架。看完之后,你在面对模型规模上不去的瓶颈时,至少能判断出"问题出在哪种并行维度上",这比盲目调参有意义得多。

1. 背景与动机:为什么算法同学必须补这一课

1.1 模型规模与单卡显存的天然矛盾

算法同学对显存的感知往往停留在"加载模型权重"这个层面。比如 7B 参数的模型用 FP16 存储,权重大约是 14GB,看起来一张 A100 80G 也能放得下。但真实训练场景远不止这点消耗:优化器状态一般要用 FP32 保存,Adam 优化器每个参数会对应两份动量张量,这样光优化器状态就是权重的 8 到 12 倍开销;梯度本身也要 FP32 存储,再加上激活值、中间计算结果、通信缓冲区,一个 7B 模型用单卡训练,实际显存占用轻松超过 120GB。

推理场景也好不到哪去。虽然不需要优化器状态,但 KV Cache 在长上下文场景下会吃掉大量显存,7B 模型配上 32K 上下文,KV Cache 可能比权重本身还大。更不用说模型在解码阶段需要同时保留算子和中间张量。这就是为什么模型做到一定规模,单卡必然是放不下的,分布式并行计算从"可选项"变成了"必选项"。

1.2 算法同学普遍面临的理解断点

我接触过很多算法背景的同事,他们的分布式知识大多停留在"用 nn.DataParallel 或者 DeepSpeed stage 2 把模型放到多卡上"这种程度。一旦要解释清楚这几个问题就卡住了:

  • Megatron 的张量并行为什么要切分 attention 的 QKV 权重矩阵?
  • 数据并行和 ZeRO 优化的本质区别是什么?
  • 为什么 3D 并行配置是 TP=8, PP=4, DP=24 而不是其他组合?
  • 长序列训练里面,Flash Attention 和上下文并行怎么配合?

这些断点的核心原因在于:算法同学熟悉的是 PyTorch 的 Module 抽象,但分布式并行是系统层面的抽象。并行策略决定了张量如何在设备间切分、通信如何编排、计算如何重叠。不理解这一层,就很难真正驾驭大模型训练框架。

1.3 系列文章的定位与目标

这一篇不会停留在概念罗列上。我会把五种并行策略的切分方式、通信模式、显存分布、扩展性上限都展开,并结合实际框架配置说明它们是怎么落地的。你可能会看到一点数学表达,但我会尽量用生活化的语言去解释每个抽象概念。

读懂这篇文章之后,你应该具备三种能力:第一,看到一个模型配置和集群拓扑,能说出为什么这样设计并行方案;第二,训练或推理出现性能问题,能初步定位是通信瓶颈还是负载不均;第三,面试 AI Infra 岗位时,能把并行维度的推导逻辑讲清楚,而不是只背名词。

2. 五大并行策略核心原理全拆解

2.1 数据并行:最朴素但不简单的切分思路

数据并行应该是大家最早接触的并行方式。它的核心思想很直观:每个设备(GPU)都保存一份完整的模型副本,然后把训练数据切成多份,每个设备用自己的那份数据独立计算前向和反向。梯度计算完成后,通过 AllReduce 通信把大家的梯度汇总平均,再用平均后的梯度去更新所有设备上的模型参数。

这里面有一个很关键的细节值得展开。数据并行的通信量是跟模型规模线性相关的,因为每次迭代都要把完整模型大小的梯度做一次全局聚合。比如一个 7B 模型用 FP32 存梯度就有 28GB,用 8 卡数据并行,每轮通信量就是这个量级的两倍左右。所以模型越大,数据并行的通信压力就越大,这也是为什么分布式并行不能只有数据并行一个维度。

ZeRO 是数据并行的进阶形态,它的核心洞察是:既然每张卡都保存了全量模型副本,其实存在大量冗余(权重、梯度、优化器状态在每张卡上都有一份)。ZeRO 把这三块内容做了分区存储:ZeRO-1 切分优化器状态,ZeRO-2 继续切分梯度,ZeRO-3 连参数也切分掉。通信方式也从 AllReduce 变成了 Reduce-Scatter 加 All-Gather 的组合,这样通信总量不变,但每张卡的平均通信量大幅下降。

数据并行的一个直观类比是:不同小组拿着完全相同的教材(模型),各自做不同的练习题(不同batch的数据),做完之后把各自的错题和答案汇总,提炼出统一的改进方向,下次所有小组按改进后的方法继续做题。

2.2 张量并行:把矩阵切开,从计算图内部动手

如果说数据并行是"每个设备各自为政",那张量并行就是"一个算子的活,多个设备合着干"。以 Transformer 结构中最重要的线性层为例,假设输入 x 的维度是 [batch, seq_len, hidden_size],权重 W 的维度是 [hidden_size, output_size]。张量并行可以在 output_size 这个维度上把 W 切成多块,每个设备只计算自己那一块的输出,最后拼起来。

Megatron-LM 提出了一种非常经典的"行并行+列并行组合"模式,专门用于 Transformer 的 MLP 结构和 Attention 结构。以 MLP 为例,第一个线性层 W1 按列切成多块,每个设备独立计算中间结果;第二个线性层 W2 则按行切分,需要先做一次 AllReduce 把中间结果聚合完全,再做矩阵乘法。这样设计的精妙之处在于:整个 Transformer block 只需要两次 AllReduce 通信,分别位于 attention 输出层和 MLP 输出层附近,通信次数被压缩到理论最小值。

张量并行的核心代价是通信非常密集。矩阵乘法的结果是相互依赖的,切分维度是计算维,必须反复聚合中间结果。因此张量并行要求设备间的通信带宽极高,实践上通常只在单机内部使用 NVLink 连接的 GPU 之间做张量并行。跨节点做张量并行,通信延迟会急剧上升,性能会很难看。

一个直观类比是:把一道大型矩阵乘法想象成一场多人接力。张量并行不是让几个人分别算不同的题目,而是让几个人合作算同一道大题目,每算一步都要把彼此的中间结果汇总同步,这显然要求大家站在同一块黑板前面工作。

2.3 流水线并行:按层切分,让模型像工厂流水线一样跑起来

流水线并行按照 Transformer 层的深度方向切分。假设一个 32 层的模型切成 4 份,每份包含 8 层,放在不同的 GPU 上。数据像流水线上的物料一样,依次经过 GPU0 的前 8 层,再到 GPU1 的 8 层,以此类推。前向传播时从 GPU0 传到 GPU3,反向传播时按相反方向回传梯度。

流水线并行最大的问题是会产生流水线气泡。最朴素的实现方式是每个设备等前一个设备算完才开始,这样同一时刻只有一个 GPU 在干活,其他都在空转,利用率极低。为了解决这个问题,业界提出了微批次(micro-batch)的概念:把一个大 batch 切成 m 个微批次,每个微批次独立流经流水线。这样不同的 GPU 可以同时处理不同的微批次,填满流水线。

GPipe 是最早的一版实现,它在每个微批次完成后做一次全局梯度同步,简单可靠,但气泡占比依然比较高。之后的 PipeDream 系列和 1F1B 策略解决了启动阶段和收尾阶段的气泡问题。1F1B 的关键是让一个微批次的反向计算尽可能早地开始,而不是等所有微批次前向做完再统一反传,这样显存占用降低不少,效率也大幅提升。

流水线并行的一个关键优势是通信量很小。每个 Transformer 层之间只需要传递 hidden_states 和梯度,这些张量的大小远小于模型权重总量。而且流水线并行可以跨节点部署,甚至不同层可以运行在不同型号的 GPU 上。很多公司用混合硬件构建异构集群,流水线并行就是核心依赖的并行方式。

流水线的类比很直观:汽车工厂有冲压、焊装、涂装、总装四个车间,一辆车依次经过每个车间完成生产。如果每个车间等前一个车间完全处理好一辆车再接手,那么只有一辆车在工厂里流动,效率极低。优化方案就是多辆车同时在不同车间流动,每辆车就是一个小微批次。

2.4 上下文并行:长序列时代的专用解法

上下文并行是最近随着长文本模型大火而进入大家视野的并行策略。它的核心思想是在序列长度这个维度上做切分,而不是在数据、层数或参数上做切分。为什么需要这样?因为 Transformer 的自注意力机制有一个关键的平方复杂度问题:序列长度是 32K 时,注意力矩阵就是 32K×32K,单单这一个中间矩阵用 FP16 存储就有 2GB 以上。而序列长度到 128K,这个矩阵直接膨胀到 32GB,单卡基本扛不住。

上下文并行的核心做法是:把序列的一段分配给一个设备,每个设备只处理自己这一段 token 的 QKV 投影计算。但是注意力计算是全局的,每个 token 的 attention 都要关注序列所有位置。这就引出两个研究方向:一是 Ring Attention,让 KV 在每个设备之间循环轮转,每个设备依次拿到其他设备的 K/V 分块做局部注意力计算,最终得到完整的注意力输出;二是 DeepSpeed Ulysses 方案,在计算 attention 之前做一次 All-to-All 通信,把序列切分的布局转化为头数维度的切分,让每个设备拥有完整序列但只负责一部分 attention head。

上下文并行和张量并行经常被一起比较使用。张量并行在处理超长序列时也会遇到问题——只有 QKV 投影矩阵和输出投影层可以被切分,注意力计算的核心部分依然需要完整序列数据,通信次数很多。上下文并行则直接针对注意力部分做了专门的切分策略,所以长序列场景下往往比张量并行更高效。

不过需要特别强调一点:上下文并行的通信量跟序列长度相关,而且通信频繁度很高,实际应用时通常会把上下文并行控制在较小的规模内,比如 2 到 8 个设备,再配合其他并行策略去扩展整体规模。

2.5 专家并行:MoE 模型专属的分布方案

Mixture-of-Experts 架构在最近两年非常火,其核心结构是:原本的 FFN 层不再是单一的全连接层,而是被替换成多个专家网络和一个门控路由。每个 token 经过门控网络路由,只会激活少数几个专家(比如 top-2),这样就实现了"用更少的计算量,拥有巨量参数"的效果。

专家并行的核心思想是:把不同的专家网络放置在不同的 GPU 上,token 通过路由机制被发送到对应的专家所在的设备上去计算。因为每个 token 只需要访问少数专家,所以大部分计算是局部的。但这也带来一个很棘手的问题:token 在设备之间通信的频率非常高,每个 Transformer 层都要做一次 All-to-All 通信。一个 token 从 GPU0 出发,它的专家可能分布在 GPU2 和 GPU5 上,那么它就必须被发送到这两个设备去完成计算,再送回来。

专家并行和数据并行在实际中通常是结合使用的。对于 MoE 模型,主流做法是采用 EP 加 DP 的组合:注意力等密集计算部分走数据并行,每个设备有完整副本;MoE 的专家部分则做专家并行切分,分布在多个设备上。这样既能利用数据并行的简单高效,又能发挥专家并行的参数扩展能力。

专家并行的核心难题是负载均衡。如果某些专家特别热门,大量 token 都被路由到同一台设备上,那这个设备就成了性能瓶颈,其他设备却在空转。DeepSpeed 的做法是做动态负载均衡,根据每个专家的实时流量来调整专家副本和放置位置;Switch Transformer 则通过辅助负载均衡损失,在训练阶段就引导门控网络尽量均匀分配 token。

3. 并行策略的选型逻辑与组合方案

3.1 每种并行策略的通信与扩展性对比

不同的并行策略在通信开销、显存分布、扩展边界上差异很大。我整理一张表方便横向对比。

并行维度切分对象主要通信模式通信量特征单卡显存节省效果主要应用场景
数据并行训练数据AllReduce / Reduce-Scatter + All-Gather与模型大小相关无(每卡全量副本)小模型训练、超大批量训练
张量并行权重矩阵AllReduce与激活尺寸相关权重、激活按切分比例下降单机多卡训练与推理
流水线并行Transformer 层Point-to-Point与单层激活大小相关激活、权重按层数切分跨节点训练、异构集群
上下文并行序列长度Ring / All-to-All与序列长度相关注意力中间值大幅降低超长序列训练与推理
专家并行专家网络All-to-All与 token 交换量相关专家参数按设备分布MoE 模型训练与推理

数据并行和专家并行是"无切分"或"少量切分"的并行方式,通信模式比较简单;张量并行和上下文并行是切入到单个算子内部的细粒度并行,通信非常频繁;流水线并行则是粗粒度的设备间切分,通信最少但气泡问题需要优化。

3.2 实际框架中如何组合使用这些并行策略

单独使用任何一种并行策略都很难支撑起千亿级模型的训练。业界已经形成了几个主流的组合模式:CCR的术语叫"3D 并行",即数据并行、张量并行、流水线并行三者叠加。Megatron-LM 提出并验证的经典做法是:DP 在最外层,PP 在中层,TP 在最内层。假设有 64 张 GPU,可以配置为 TP=4(4 卡做张量并行)、PP=4(4 组流水线)、DP=4(4 路数据并行),那么总 GPU 数 = TP×PP×DP = 4×4×4 = 64 张,每个参数都有 64 个维度的组合关系。

实际选型时要遵循一个基本原则:高通信需求的并行维度应该约束在高速通信域内,低通信需求的并行维度可以尽量扩大。GPT-3 175B 训练时在单个 DGX-A100 节点(8 卡 NVLink 互联)内部做 TP=8,然后 8 个节点之间(通过 IB 网络)做 PP=8,最外层再用 DP 做多副本并行。这样保住了通信性能,也实现了大集群规模扩展。

DeepSpeed 在这个框架里继续叠加 ZeRO 优化。对于模型规模特别大的场景,ZeRO-3 可以配合流水线并行和张量并行一起使用。这样同一个参数可能同时在三个维度上做切分——张量维度、层维度、以及参数区间的维度。理解起来确实有点绕,但核心思路只有一个:每一维度切分解决一个特定的显存瓶颈点。

推理侧的并行策略选择跟训练侧差异很大。训练侧重吞吐量,尽量全部设备同步执行前反向;推理侧重延迟和并发。vLLM 中常用的配置是张量并行加数据并行:张量并行做单请求的加速,数据并行做多请求的并发承载。对于超长上下文的推理场景,会再叠加上下文并行来降低单卡的 KV Cache 压力。

3.3 确定并行配置的几个关键公式与计算方法

在实际工作场景中,我们经常需要根据模型大小和显存需求来确定并行配置。把关键的计算方法整理一下:

第一步是计算总参数显存。FP16 或 BF16 的权重每 10 亿参数约占 2GB。7B 模型就是大约 14GB,175B 模型大约 350GB。如果训练,还要加上梯度和优化器状态。用 Adam 优化器时,权重、梯度和优化器状态三者的显存占比大约是:参数 1 份,优化器状态 2 份(一二阶动量都用 FP32),梯度 1 份。实际每 10 亿参数训练显存大约需要 16GB 左右。

第二步是计算分到单卡后是否放得下。以 70B 模型 BF16 训练为例,参数加梯度加优化器状态总计约 70×16 = 1120GB,如果单卡是 80GB,那么权重分片后必须保证每卡占比不超过 70%(还要留激活值空间),也就是大约 56GB,这意味着总切分份数至少为 1120/56 = 20 份。结合上一节的通信约束,可能选择 TP=8、PP=4,那么权重和优化器状态的切分是 32 份,已经超出了需求,可以降低 ZeRO 的等级,或者扩大 batch size 来提升效率。

第三步是估算激活值显存。激活值的计算公式相对复杂,一般粗略估算为每层每 token 大约是 hidden_size 的 10 到 20 倍。序列长度 4096、hidden_size 8192 的情况下,单层激活大约几十 MB 到几百 MB。层数多了之后,激活显存总量会很可观。这也是为什么激活重计算、上下文并行会成为长序列训练的必需方案。

实际配置时大家也不用太纠结精确计算,可以参考 Hugging Face 和 DeepSpeed 提供的显存估算工具,先粗算,再实测。

4. 训练和推理场景中的实操落地与配置详解

4.1 从零配置一个 70B 模型的分布式训练方案

光讲概念不够,我拿一个实际配置案例来推演整个过程。假设手里有 32 张 A100 80G GPU,希望训练一个 70B 参数的模型,上下文长度 4096。

70B 模型训练显存需求大约 70×16=1120GB,32 张卡总共显存 2560GB,理论上够放。但我们要考虑的并不是总显存够不够,而是单卡显存是否够用。梯度同步通常要求所有卡结构一致,所以每张卡的参数切分需要均匀。

如果选择 TP=8、PP=4、DP=1 的组合,那么张量并行 8 卡把权重切成 8 份,流水线 4 层再把层数分成 4 份,总共切分 32 份。每张卡承载的参数显存是 1120/32 = 35GB,加上激活值和通信缓冲区,单卡 80GB 完全够用。但因为 DP=1,梯度同步和参数更新只在一组 TP+PP 内进行,无法通过增加数据并行来提升吞吐。

如果换成 TP=8、PP=2、DP=2,那么总切分 32 份不变,但 DP=2 意味着有两份模型副本,可以同时处理两份不同的数据。缺点是因为 PP=2,每张卡的层数更多,激活值占用更高一些。对于 70B 模型 4K 上下文,TP=8、PP=2、DP=2 通常在吞吐和稳定性上表现比 TP=8、PP=4、DP=1 更好。

用 Megatron-LM 启动时的核心配置参数如下:

python -m torch.distributed.launch --nproc_per_node=8 pretrain_gpt.py \ --tensor-model-parallel-size 8 \ --pipeline-model-parallel-size 2 \ --num-layers 80 \ --hidden-size 8192 \ --num-attention-heads 64 \ --seq-length 4096 \ --micro-batch-size 1 \ --global-batch-size 32 \ --use-flash-attn \ --recompute-activations

这里 recompute-activations 是激活重计算,用少量额外计算换显存——每个 Transformer 层的前向激活不保留所有中间值,反向时重新算一遍。micro-batch-size 1 配合 PP=2 使用,global-batch-size 32 表示数据并行维度上是 16 个微批次×2 个流水线内的微批次拆分。这个参数组合经过实测稳定性和精度都符合预期。

4.2 长序列训练中如何配置上下文并行

长序列训练(比如 128K 上下文)是目前大模型迭代的主战场之一。假设同样用 32 卡 A100,目标序列长度是 128K,hidden_size 8192。单纯靠 TP 和 PP 很难搞定,因为注意力矩阵的显存是平方级别增长的,哪怕单卡只放很少的 batch,attention 矩阵本身也会爆显存。这时候就要上上下文并行。

NCCL 的 All-to-All 通信在这种场景下发挥关键作用。DeepSpeed Ulysses 的实现流程大致是:QKV 投影在张量并行维度上正常计算,然后通过 All-to-All 把序列切分换成头数切分,让每个设备拥有一段完整序列的若干 attention head,做局部 attention 计算,再通过一次 All-to-All 把计算结果还原成序列切分的布局。这样每个设备上的注意力矩阵长度从 128K 降到了 128K/CP,显存压力大大降低。

使用 DeepSpeed Ulysses 时,注意 CP 的值一般不超过 attention head 的数量,而且 CP×TP 要能整除 head 数。例如 head 数为 64,如果 TP=4,那么 CP 可以选择 2 或 4,但不能是 8 或 16。这是很多配置报错的隐含前提。

上下文并行的通信开销不能完全忽略。虽然每个设备只做局部 attention,但 All-to-All 通信需要把每个 token 的 K/V 广播到所有设备。实际测试中,CP=4 时通信开销占总时长的比例大约在 10% 到 20%,与序列长度和数据规模相关。如果测试发现通信占比过高,优先考虑增大 TP 而不是继续提升 CP。

4.3 MoE 模型训练时专家并行的配置策略

目前主流的 Mixtral、DeepSeek-MoE 这类模型,专家并行的配置策略跟密集型模型有明显差异。以 Mixtral 8x7B 为例,每层有 8 个专家,每个 token 激活 2 个专家,专家参数量大约占模型总参数的 80% 以上。

没有使用专家并行时,8 个专家的权重在每个数据并行副本中都要完整保存,这会造成显存浪费。使用专家并行后,可以把不同专家分配到不同的 GPU 上,每个 GPU 只保存一部分专家参数。比如把 8 个专家分布在 4 张卡上,每张卡只保存 2 个专家。

配置时有个关键点:专家并行的度一般要匹配总 GPU 数量。假设有 32 卡集群做 MoE 训练,可以用 EP=4、TP=4、PP=2 再加 DP=1 的组合。TP=4 处理注意力部分的张量切分,EP=4 切分专家,PP=2 按层切分。组合后的总 GPU 数 4×4×2=32。

但 MoE 训练有一个隐藏的显存问题:专家经过 EP 切分后每个设备只保留部分专家参数,但路由机制需要把 token 从各个设备汇总到专家所在的设备。如果 token 分布极不均衡,个别设备收到的 token 可能特别多,显存和算力双高。实践中需配合 load balancing loss,同时限制 batch size 不要过大,避免单卡的 token 数量超过处理上限。

4.4 推理部署时并行策略的选择差异

推理场景和训练场景在并行选型上有一个核心差异:推理的 batch size 往往很小,显存压力主要来自权重和 KV Cache。这意味着梯度同步的通信需求不复存在了,并行配置的目标从"降低每卡显存、提升吞吐"变成"降低单请求延迟、提升并发能力"。

推理时张量并行依然是最常用的维度。一个 70B 模型在 BF16 下权重约 140GB,单卡 A100 80G 根本无法加载,TP=2 之后每卡 70GB,TP=4 之后每卡 35GB,这样可以完全放进显存。对于 7B 甚至 13B 模型,通常不需要张量并行,单卡直接加载即可,因为多卡通信引入的延迟可能比计算延迟还高。

长上下文推理场景下,KV Cache 的管理成为并行选型的重点。用 vLLM 这类推理框架时,KV Cache 是动态分配的,PagedAttention 技术把 KV Cache 切分成物理块,按需分配。上下文并行在推理中主要用来解决"单卡放不下超长上下文的 KV Cache"的问题,切分序列维度后,每卡的 KV Cache 压力线性下降。

推理的另一个考量点是连续批处理与动态调度。这种调度方式下,模型权重在每张卡上是完整加载的,只需要用数据并行去承载多个并发请求。多个模型副本各自独立处理请求,请求进来后由调度器分配到合适的 GPU 上执行。因此大多数在线推理服务的首选是"单机 TP + 多机 DP"的组合:单机内用张量并行降低单请求延迟,多机间用数据并行提升吞吐。

5. 常见问题与故障排查实战

5.1 通信瓶颈:GPU 利用率不高但吞吐上不去

这是我在实际项目里遇到最多的情况。训练时 nvidia-smi 看 GPU 的算力利用率(compute utilization)在 40% 到 60% 之间徘徊,但训练速度怎么也上不去。这类问题的根源往往是通信等待时间太长,GPU 在跑一步、等一步。

排查第一步是检查通信时间占比。打开 Megatron-LM 或者 DeepSpeed 的 profiler 日志,或者直接用 NVIDIA Nsight Systems 抓一轮迭代的时间分布。如果 AllReduce 和 All-to-All 通信耗时占到迭代总时长的 30% 以上,基本可以确定是通信瓶颈。

排查第二步是检查并行配置是否合理。最常见的问题是张量并行的跨节点部署。比如用两台 8 卡机器做 TP=16,张量并行通信全走 IB 网络,带宽远低于 NVLink,通信延迟成倍增长。解决办法是把 TP 收敛到单机 8 卡以内,再用 PP 或 DP 扩展集群规模。

排查第三步是检查通信和计算是否做了重叠。PyTorch 自带的多流通信机制会自动把 AllReduce 和反向计算重叠,但有时会因为模型结构和梯度累积方式导致重叠失效。调试时可以尝试调整 micro-batch size,让流水线更饱和,或者开启通信 overlap 的开关。

5.2 显存不足:OOM 并不总是切分不够导致的

很多人见到显存不足(Out of Memory)的第一反应是把所有并行度都调大。但 OOM 有几种完全不同的成因,处理方式也不一样。

如果是权重和优化器状态导致的 OOM,把 TP 或 ZeRO 等级调大确实有效。但如果是因为激活值累积导致的 OOM,调大 TP 效果有限,更好的方案是开启激活重计算、减少 micro-batch size、或者加大 PP。如果是因为 KV Cache 导致的 OOM,那是推理场景专用的显存类型,可以通过调整调度策略、限制最大序列长度、或启用上下文并行来解决。

实际调试 OOM 时,建议先看 PyTorch 的显存分配器统计:torch.cuda.memory_summary() 会打印详细的分配明细。重点看"Reserved memory"和"Allocated memory"之间的差距,如果差距很大,说明显存碎片化严重,可以尝试设置环境变量 PYTHONMALLOC=malloc 并启用 PyTorch 的 expandable_segments。

5.3 流水线并行中的气泡与负载不均问题

流水线并行的气泡问题,最直接的表现是 GPU 利用率呈现锯齿状分布。用 nvidia-smi dmon 监控每个 GPU 的利用率,会发现有的 GPU 忙到 95%,有的 GPU 只有 30%。气泡率可以通过公式计算:PP=4 时理论气泡上界大约是 (PP-1)/(PP-1+micro-batch数)。micro-batch 越大,气泡占比越低,但显存占用也会上升。

负载不均的另一个来源是模型层大小不一致。比如 embedding 层和最后的输出层往往比其他任意一层都大,如果它们单独占了一个流水线阶段,那个阶段就更容易成为瓶颈。解决思路是使用不均匀的层分配:第一阶段放 embedding 加一层 transformer,后面几个阶段放更多的层,保证每个阶段的参数量大致接近。

MoE 模型的负载不均是另一类问题:专家路由不平衡会导致某些设备过热。调试时通过日志查看每个专家的 token 数量分布,如果波动超过 20%,就要调整负载均衡损失的权重,或者考虑增加专家副本(同一个专家在多个设备上放置副本,路由时可以选择最空闲的那个)。

5.4 精度问题:混合精度并行下结果不稳定

分布式并行的精度问题排查起来更隐蔽。并行策略本身不会引入数值错误,但分布式的梯度聚合和混合精度计算组合在一起,很容易出现精度损失。

最常见的问题是大规模 AllReduce 时浮点溢出。FP16 的动态范围有限,当梯度值很小或者很大时,直接做 AllReduce 求和可能精度丢失。解决办法是使用 BF16 替代 FP16,BF16 的指数位和 FP32 相同,只是尾数位少,几乎不会出现溢出问题。

另一个常见问题是梯度累积与并行策略叠加时,数值行为会改变。如果开了梯度累积,又同时用数据并行做梯度同步,要确保累积的是同一份梯度,而不是不同步的乱序累加。实现上需要用no_sync上下文管理器抑制中间同步,只在最后一次微批次完成时触发梯度同步。

5.5 关键教训汇总与排查建议

在实际接触大量训练集群和推理服务之后,我总结出几条非常实用的排查经验,分享给大家参考。

第一,出现问题时先看拓扑约束。高通信的并行维度优先绑定在同一台机器内,低通信的并行维度再跨节点扩展。违反了这个原则,即便代码和参数都对,性能也不会好。

第二,不要盲目追求大并行度。并行度越高,通信开销和调度复杂度就越大。能用单卡解决的事情用单卡,能上数据并行解决的事情不要上张量并行,这个原则几乎所有规模场景都适用。

第三,从日志和监控数据入手定位。不要凭感觉猜测是通信问题还是计算问题。NVIDIA Nsight Systems 和 PyTorch Profiler 是定位分布式瓶颈最有效的两个工具。

第四,配置文件一定要保留记录。每次修改并行配置都记录当时的吞吐、loss 曲线、显存占用和通信时间,慢慢积累出适合自己的配置数据库,这是团队最宝贵的资产。

6. 从算法视角看并行计算的本质与未来

作为算法工程师,我们一开始很容易陷入"把模型调好就行"的思维,觉得分布式是 Infra 团队的职责。但大模型时代,这个界限已经模糊了。模型的设计和并行方案从一开始就要一起考虑。比如模型在哪个层级放 MoE 专家,每个专家多大,路由机制怎么设计,这些决策同时影响算法效果和系统性能。如果算法同学不理解并行计算的基本原理,就很容易设计出"模型效果达标但根本跑不动"的结构。

并行计算本质上就是在回答两个问题:数据怎么分配,计算怎么协调。数据分配决定了每张卡存什么,计算协调决定了每张卡什么时候跟谁通信。TP 是算同一个矩阵的不同部分,PP 是算同一个模型的不同层,DP 是算不同数据,CP 是算序列的不同段,EP 是算不同专家。理解了这个统一的框架,所有并行策略都能在脑子里串起来。

未来较长一段时间内,分布式训练和推理的核心挑战都会集中在通信效率和显存效率上。算法侧的新进展比如稀疏注意力、MoE 路由优化、量化等,最终都要落到并行计算框架里面才能兑现实际收益。这也是我写这个系列的初衷:作为算法同学,不需要成为最顶级的 Infra 专家,但至少要有一张并行计算的"地图",知道问题出在哪个区域,去找对应的工具和方案。

我自己在复盘这些并行策略时最强烈的感受是:并行计算并没有特别高深莫测,它本质上就是把大问题拆成小问题再组合起来。每个策略都是在维度切分、通信开销、显存占用这三者之间做取舍。理解并接受"没有最优的并行配置,只有最适合当前场景的配置"这个理念,比记住任何具体的配置都更重要。

我也强烈建议看完这篇内容的读者,找一台 4 卡甚至 8 卡的机器,搭一个小的 GPT 模型,亲手把 TP=1/2/4、PP=1/2、DP=1/2/4 的组合都跑一遍,记录吞吐和显存。纸上得来终觉浅,动手跑一圈,你对并行计算的理解会扎实很多。

希望这篇文章对你有用,下一篇我计划接着写分布式训练框架的落地细节,包括 Megatron、DeepSpeed 和 vLLM 的资源调度与性能调优的深入实践。

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

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

立即咨询