大模型分布式并行方式详解

系统梳理 DP(含 ZeRO-1/2/3、FSDP)、TP、PP、SP、CP、EP 等所有主流并行方式:切什么维度、每张 GPU 本地维护什么、大家共享什么、通信内容与通信原语、通信发生的时机与规律。

📌 阅读方式
贯穿全文我们用同一套「六问」拆解每种并行:① 切分维度 → ② 本地维护 → ③ 共享/需同步 → ④ 通信内容 → ⑤ 通信原语与时机 → ⑥ 通信量与优缺点。先读 集合通信原语 一节能让后面所有内容更好懂。

🧭 总览:并行的本质是"切一个大问题"

训练/推理一个大模型,本质上要在一批数据上,对一个巨大的计算图反复做前向、反向、参数更新。当单卡放不下(显存不够)或算不快(算力不够)时,就要把这个"大问题"沿着某个维度切开,分给多张 GPU。不同的并行方式,区别就在于"沿哪个维度切"。

一个训练任务的张量大致有这么几个维度,每种并行对应切其中之一:

并行方式切分的维度直觉一句话主要解决
DP数据并行Batch(样本)维度每卡一份完整模型,各算各的数据,最后同步梯度算得更快(吞吐)
ZeRO/FSDPBatch 维度切数据 + 把"模型状态"分片存DP 的省显存版:参数/梯度/优化器状态不再每卡都存整份省显存 + 吞吐
TP张量并行单个权重矩阵的 hidden 维度把一层 Linear/Attention 的矩阵横着/竖着切,多卡合力算一层放下大层 + 降单层延迟
PP流水线并行模型的层(深度)维度把 L 层切成几段,像工厂流水线一样接力放下深模型
SP序列并行序列长度维度(LayerNorm/Dropout 区)TP 的搭档:把 TP 里冗余的激活也沿序列切开省显存省激活显存
CP上下文并行序列长度维度(Attention 全程)超长上下文时,把 seq 切开,注意力用 Ring 交换 K/V超长序列
EP专家并行MoE 的专家(expert)维度把不同专家放到不同卡,token 按路由 All-to-All 分发MoE 扩容
🔑 一个统一的心智模型
所有并行都在回答两个问题:(1) 这张卡上"少存了"什么?→ 决定省多少显存;(2) 因为少存了,需要在什么时候"临时把缺的东西要过来 / 把算出来的东西还回去"?→ 这就是通信。 省显存/省算力和通信量永远在做权衡(trade-off)。

两大类:省算力 vs 省显存

⚡ 提升吞吐类(切数据)
DP、ZeRO、FSDP。每张卡处理不同样本,模型逻辑上完整。扩得越多,单位时间处理的样本越多。
🧩 拆模型类(切模型/激活)
TP、PP、SP、CP、EP。一份数据(或一份数据的一部分)由多张卡协作完成一次前向/反向,用来放下单卡装不下的模型或激活。

🧮 前置知识:训练时显存都花在哪

要理解 ZeRO/FSDP 为什么这么切,必须先知道训练一个模型,显存被哪几块吃掉。设模型有 Ψ 个参数,用混合精度(FP16/BF16 计算 + FP32 主权重)+ Adam 优化器:

显存组成内容大小(以 Ψ 个参数计)是否随 batch/seq 增长
参数 ParametersFP16 权重(前向/反向用)
梯度 GradientsFP16 梯度
优化器状态
Optimizer States
FP32 主权重副本
FP32 momentum(一阶动量)
FP32 variance(二阶动量)
激活 Activations前向每层输出,反向要用∝ batch × seq × layers是(可能很大)
FP16 参数
FP16 梯度
12Ψ
Adam 优化器状态 (4+4+4)
= 16Ψ
「模型状态」合计(不含激活)
⚠️ 关键结论
「模型状态」(参数+梯度+优化器状态) ≈ 16Ψ 字节,其中优化器状态就占 12Ψ(75%)。一个 7.5B 模型光模型状态就要 120GB,远超单卡。
ZeRO 的核心洞察就是:DP 里每张卡都存了一份完全相同的 16Ψ,这是巨大的冗余。把它们分片存,就能省下大量显存。 而「激活」则是 TP/SP/CP/PP 关注的另一块显存。

📡 集合通信原语(所有并行的"字母表")

GPU 之间怎么"交换数据",归根到底就是几种标准的集合通信(Collective Communication)操作,由 NCCL/RCCL 等库实现。看懂这几个原语,后面每种并行的"通信内容"你就能自己推出来。

原语做什么通信量(每卡,N 卡,数据量 M)典型用在
AllReduce 把 N 张卡上各自的张量逐元素求和(或其它 reduce),结果每张卡都拿到一份完整的和。等价于 ReduceScatter + AllGather。 ≈ 2M DP 同步梯度;TP 前向/反向合并部分和
ReduceScatter 把张量求和,但结果切成 N 份,每张卡只拿到其中 1 份(第 i 张卡拿第 i 段的和)。 ≈ M ZeRO/FSDP 归约梯度;SP
AllGather 每张卡各持有 1 份分片,操作后每张卡都拿到拼接起来的完整张量 ≈ M ZeRO/FSDP 临时凑齐参数;SP
All-to-All "转置式"交换:每张卡把自己的数据切成 N 份分别发给 N 张卡,同时从每张卡各收 1 份。像发牌。 ≈ M MoE 专家并行(EP)分发/回收 token
Broadcast 一张卡把数据复制发送给所有卡 ≈ M 参数初始化广播、TP 输入分发
P2P (Send/Recv) 点对点:一张卡把数据发给指定的另一张卡。不是集合操作。 M(单向) PP 跨 stage 传激活/梯度;CP 的 Ring 传 K/V

🎬 动画:四种核心原语在做什么

下面四个动画自动循环播放,用 4 张 GPU(蓝/绿/橙/紫代表 4 段数据)演示数据如何流动。

AllReduce · 跨卡求和,人人拿到完整结果 循环播放
DP 同步梯度、TP 合并部分和都用它。
① 初始:4 张卡各持一个本地值(如各自算出的梯度)
GPU0
3
GPU1
1
GPU2
2
GPU3
4
② AllReduce:所有卡的值逐元素求和
Σ = 3 + 1 + 2 + 4 = 10
③ 结果:每张卡都得到完整的和(≈2M 通信量)
GPU0
10
GPU1
10
GPU2
10
GPU3
10
Step 1各卡本地值
Step 2逐元素求和
Step 3各卡同得总和
ReduceScatter · 求和后切开,每卡只拿 1 段 循环播放
ZeRO/FSDP 归约梯度用它,通信量只有 AllReduce 的一半(≈M)。
① 初始:每卡都持有完整向量的一份(分 4 段 a b c d)
GPU0
abcd
GPU1
abcd
GPU2
abcd
GPU3
abcd
② ReduceScatter:按段求和,第 i 卡只负责收第 i 段
段 a→GPU0,段 b→GPU1,段 c→GPU2,段 d→GPU3
③ 结果:每卡只保留自己那 1 段的和(1/N 显存)
GPU0
Σa
GPU1
Σb
GPU2
Σc
GPU3
Σd
Step 1各卡完整向量
Step 2按段求和散射
Step 3各卡只留 1 段
AllGather · 各持 1 段,收集成完整 循环播放
ZeRO/FSDP 临时凑齐参数用它(≈M)。它和上面的 ReduceScatter 合起来正好 = AllReduce。
① 初始:每卡各持有 1 个分片
GPU0
S0
GPU1
S1
GPU2
S2
GPU3
S3
② AllGather:各卡把自己的分片广播给其他所有卡
互相收集 S0 · S1 · S2 · S3
③ 结果:每卡都拿到拼接好的完整张量
GPU0
S0S1S2S3
GPU1
S0S1S2S3
GPU2
S0S1S2S3
GPU3
S0S1S2S3
Step 1各卡 1 个分片
Step 2互相收集
Step 3各卡得完整
All-to-All · "转置式"交换(发牌) 循环播放
MoE 专家并行(EP)分发/回收 token 用它。每卡把自己的数据按目的地拆开发出去。
① 初始:每卡持有发往 4 个目的地的数据块(→0 →1 →2 →3)
GPU0
→0→1→2→3
GPU1
→0→1→2→3
GPU2
→0→1→2→3
GPU3
→0→1→2→3
② All-to-All:每块飞向它的目的地卡(行列转置)
GPU_i 的 →j 块 ⇄ GPU_j 的 →i 块
③ 结果:每卡收到来自所有卡、发给自己的块(0· 1· 2· 3 表来源)
GPU0
0123
GPU1
0123
GPU2
0123
GPU3
0123
Step 1按目的地分块
Step 2转置交换
Step 3按来源收齐
💡 最重要的一条等式
AllReduce = ReduceScatter + AllGather
这就是 ZeRO/FSDP 相比普通 DP 不额外增加通信总量的根本原因:DP 用一次 AllReduce(≈2M)同步梯度;ZeRO 把它拆成 ReduceScatter(≈M) 归约梯度 + AllGather(≈M) 凑参数,总量还是 ≈2M,却省下了巨量显存。

通信量的两个直觉

DP · 数据并行 Data Parallelism

最基础的并行。每张卡放一份完整的模型,各自处理不同的数据小批次,反向后用一次 AllReduce 同步梯度,保证所有卡的模型始终一致。

① 切分维度

Batch(样本)维度。全局 batch 拆成 N 份(micro-batch),第 i 张卡拿第 i 份数据。模型本身完全不切。

② 每张 GPU 本地维护什么

GPU 0(数据 shard 0)
完整参数 2Ψ 完整梯度 2Ψ 完整优化器状态 12Ψ 本卡 micro-batch 的激活
GPU 1(数据 shard 1)
完整参数 2Ψ 完整梯度 2Ψ 完整优化器状态 12Ψ 本卡 micro-batch 的激活
GPU N-1(数据 shard N-1)
完整参数 2Ψ 完整梯度 2Ψ 完整优化器状态 12Ψ 本卡 micro-batch 的激活

🔵 蓝色 = 每张卡都存一份"完整且相同"的副本 → 这就是 DP 的显存冗余:16Ψ × N,浪费严重。只有激活是各卡不同的。

💾 本地维护(各卡独有)
  • 本卡数据分片的激活值(不同)
🔗 共享 / 需保持一致
  • 参数:每步更新后必须完全一致(靠梯度同步保证)
  • 梯度:需跨卡求平均
📨 通信内容
  • 反向得到的梯度(大小 = 2Ψ)

③④⑤ 通信内容、原语与时机

各卡独立前向
(各算各的数据)
各卡独立反向
得到本地梯度
AllReduce 梯度
(求和/平均)
各卡用相同
平均梯度更新参数
🎬 动画:DP 一个训练步 循环播放
3 张卡各喂不同数据,各自算梯度 g0/g1/g2;AllReduce 求平均后,参数保持一致。
① 前向:每卡拿不同的数据批次,模型参数 W 相同
GPU0 · 数据A
W
GPU1 · 数据B
W
GPU2 · 数据C
W
② 反向:各卡得到不同的本地梯度
GPU0
g0
GPU1
g1
GPU2
g2
③ AllReduce:跨卡对梯度求平均(唯一的通信)
ḡ = (g0 + g1 + g2) / 3
④ 更新:各卡用相同的 ḡ 更新 → 参数始终一致
GPU0
W'
GPU1
W'
GPU2
W'
Step 1各卡不同数据前向
Step 2各卡本地梯度
Step 3AllReduce 求平均
Step 4同步更新参数

⑥ 优缺点

✅ 优点
  • 实现最简单,扩展性好
  • 通信少(每步一次 AllReduce,可与反向重叠)
  • 提升训练吞吐几乎线性
❌ 缺点
  • 显存零节省:每卡都存完整的 16Ψ,装不下大模型
  • 模型必须能塞进单卡
这就是 ZeRO 要解决的问题
DP 的致命伤是那 16Ψ × N 的冗余。既然每张卡的参数/梯度/优化器状态都一样,为什么每张卡都要存完整的一份?ZeRO 说:不用,切开分着存。

ZeRO-1 / 2 / 3 · 零冗余优化器 Zero Redundancy Optimizer

ZeRO(DeepSpeed)是 DP 的"省显存进化版"。核心思想:数据并行照旧(各卡不同数据),但把那份冗余的 16Ψ 模型状态沿参数维度切成 N 份,每张卡只存 1/N。谁需要完整的谁临时 AllGather 拿过来,用完就丢。 逻辑上等价于 DP,数学结果完全一致,只是把"空间换成了通信"。

三个渐进阶段:切得越多,越省显存,通信越多

阶段分片的内容每卡模型状态显存相比 DP 省额外通信
ZeRO-1 Pos只切优化器状态(12Ψ)4Ψ + 12Ψ/N~4× (N大时)无(通信量同 DP)
ZeRO-2 Pos+g优化器状态 + 梯度2Ψ + 14Ψ/N~8×无(通信量同 DP)
ZeRO-3 Pos+g+p优化器状态 + 梯度 + 参数16Ψ/N~N× (线性!)+50%(多一次参数 AllGather)

下标含义:os = optimizer states,g = gradients,p = parameters。ZeRO-3 是"全都切",显存随卡数线性下降——这也是 FSDP 的等价物。


ZeRO-1:只切优化器状态

GPU 0
完整参数 2Ψ 完整梯度 2Ψ 优化器状态 [第0段] 12Ψ/N
GPU 1
完整参数 2Ψ 完整梯度 2Ψ 优化器状态 [第1段] 12Ψ/N
GPU N-1
完整参数 2Ψ 完整梯度 2Ψ 优化器状态 [第N-1段] 12Ψ/N

🟢 绿色 = 分片存储(每卡只存 1/N)。参数和梯度仍是完整副本。

ZeRO-2:再切梯度

GPU 0
完整参数 2Ψ 梯度 [第0段] 2Ψ/N 优化器状态 [第0段] 12Ψ/N
GPU 1
完整参数 2Ψ 梯度 [第1段] 2Ψ/N 优化器状态 [第1段] 12Ψ/N
GPU N-1
完整参数 2Ψ 梯度 [第N-1段] 2Ψ/N 优化器状态 [第N-1段] 12Ψ/N

ZeRO-3:连参数也切(=FSDP)

GPU 0
参数 [第0段] 2Ψ/N 梯度 [第0段] 2Ψ/N 优化器状态 [第0段] 12Ψ/N
GPU 1
参数 [第1段] 2Ψ/N 梯度 [第1段] 2Ψ/N 优化器状态 [第1段] 12Ψ/N
GPU N-1
参数 [第N-1段] 2Ψ/N 梯度 [第N-1段] 2Ψ/N 优化器状态 [第N-1段] 12Ψ/N

🟢 三样东西全部分片,每卡稳定只占 16Ψ/N —— 显存随 GPU 数线性下降,这是"零冗余"的完全体。

ZeRO-3 的运行机制(重点:参数是"临时凑齐、用完就扔")

因为参数也被切了,任何一层要做前向/反向计算时,本地并没有该层的完整权重。于是:

要算第 L 层前
本地只有 1/N 权重
AllGather
凑齐第 L 层完整权重
算完第 L 层前向
立即释放完整权重
下一层重复……
🎬 动画:ZeRO-3 / FSDP 逐层「聚合 → 计算 → 释放」 循环播放
平时每卡只存参数的 1/N(彩色分片)。算某层时临时 AllGather 凑齐完整权重,算完立刻丢掉,回到 1/N。
① 平时:GPU 只保存本层参数的 1/N 分片(省显存)
GPU0
W₀
GPU1
W₁
GPU2
W₂
GPU3
W₃
② 计算前:AllGather 临时凑齐第 L 层完整权重
AllGather → 每卡暂得完整 W = [W₀W₁W₂W₃]
③ 计算:用完整权重做该层前向/反向
GPU0
W₀W₁W₂W₃
GPU1
W₀W₁W₂W₃
GPU2
W₀W₁W₂W₃
GPU3
W₀W₁W₂W₃
④ 算完立即释放完整权重,回到 1/N(反向再 ReduceScatter 梯度)
GPU0
W₀
GPU1
W₁
GPU2
W₂
GPU3
W₃
Step 1只存 1/N 分片
Step 2AllGather 凑齐
Step 3用完整权重计算
Step 4释放,回到 1/N
📨 ZeRO-3 通信内容
  • 前向:参数 AllGather
  • 反向:参数 AllGather + 梯度 ReduceScatter
🕐 通信时机
  • 贯穿整个前向和反向,每层/每 bucket 一次(而非每步一次)
  • 可与计算重叠:预取下一层参数
📈 通信量
  • ≈ 3M(前向 AllGather + 反向 AllGather + ReduceScatter)
  • 比 DP 的 2M 多约 50%
⚖️ ZeRO-3 的权衡
用"每层都要 AllGather 参数"的更频繁、更细粒度的通信,换来了显存随卡数线性下降(能训超大模型)。所以 ZeRO-3 更依赖高带宽互联,并大量依赖"计算/通信重叠 + 参数预取"来隐藏通信延迟。另外 ZeRO 还有 Offload(把优化器状态/参数卸载到 CPU 内存甚至 NVMe)进一步省显存,代价是更慢。

FSDP · 全分片数据并行 Fully Sharded Data Parallel

FSDP 是 PyTorch 官方实现的"ZeRO-3 等价物"。思想与 ZeRO-3 几乎相同:把参数、梯度、优化器状态全部沿数据并行组分片,计算时临时 AllGather 凑齐、算完释放。 差别主要在工程实现与分片粒度上,不在核心原理上。

FSDP ≈ ZeRO-3,那为什么单独讲?
因为它是 PyTorch 生态的原生方案(torch.distributed.fsdp),分片单位是用户定义的 FSDP Unit(通常按 Transformer Block 包裹),而 ZeRO-3 是按参数扁平化桶(bucket)自动切。理解 FSDP 的"Unit + Flat-Parameter"机制,是用好它的关键。FSDP2(per-parameter 分片)进一步改进了这一点。

① 切分维度

与 ZeRO-3 相同:数据并行切 batch;同时把每个 FSDP Unit 内的参数/梯度/优化器状态沿 DP 组切成 N 份。

② 每张 GPU 本地维护什么

GPU 0
各 Unit 的参数分片 [0] 各 Unit 的梯度分片 [0] 各 Unit 的优化器状态 [0] 当前 Unit 临时 AllGather 的完整参数(用完即释放)
GPU 1
各 Unit 的参数分片 [1] 各 Unit 的梯度分片 [1] 各 Unit 的优化器状态 [1] 当前 Unit 临时完整参数
GPU N-1
各 Unit 的参数分片 [N-1] 各 Unit 的梯度分片 [N-1] 各 Unit 的优化器状态 [N-1] 当前 Unit 临时完整参数

③④⑤ 运行流程(一个 FSDP Unit 的生命周期)

进入 Unit 前向 AllGather 该 Unit 全部参数 前向计算 释放完整参数
(只留分片)
反向再 AllGather 一次参数 算梯度 ReduceScatter 梯度
🔗 共享 / 需同步
  • 逻辑上一份完整模型,物理上分片
  • 更新后各卡持有各自 1/N 的最新参数
📨 通信内容
  • 前向:参数 AllGather
  • 反向:参数 AllGather + 梯度 ReduceScatter
🕐 通信时机 / 规律
  • 每进入/离开一个 FSDP Unit 就通信一次
  • 相邻 Unit 的通信与计算重叠(prefetch)
维度ZeRO-3 (DeepSpeed)FSDP (PyTorch)
核心原理完全相同:参数/梯度/优化器状态全分片,临时 AllGather
分片单位参数扁平化后按 bucket 自动切用户定义的 FSDP Unit(常按 Transformer Block);FSDP2 支持 per-parameter
通信原语AllGather + ReduceScatterAllGather + ReduceScatter(相同)
生态DeepSpeed,配置驱动PyTorch 原生,代码包裹模块
OffloadZeRO-Offload / Infinity(CPU/NVMe)CPU offload
分片范围(sharding group)通常全局 DP 组可配 HYBRID_SHARD:机内分片 + 机间复制
💡 HYBRID_SHARD(值得记住的实用点)
FSDP 支持"机内全分片 + 机间数据并行"的混合:在一台机器的 8 卡内做 ZeRO-3 式全分片(走高速 NVLink),机器之间只做普通梯度 AllReduce(走较慢的机间网络)。这样把最频繁的 AllGather 限制在机内,大幅降低跨机通信压力——是超大规模训练常用的折中。

TP · 张量并行 Tensor Parallelism

TP(Megatron-LM)把单个层内的权重矩阵切开,分给多张卡。多张卡合力完成同一层的计算——它们处理同一份数据,各算矩阵的一部分,再通过通信把部分结果合并。与 DP 的"各算各的数据"截然不同。

① 切分维度

权重矩阵的 hidden 维度。核心技巧是把 Transformer 里连续的两个 Linear(MLP 的 up/down,或 Attention 的 QKV/O)分别用"列切"和"行切"配对,使得中间只需要一次通信。

Megatron 的两种切法

列并行 Column Parallel
权重 A 按切:A = [A₁ | A₂]。输入 X 完整复制到每卡。各卡算 Y_i = X·A_i,得到输出的不同列。前向无需通信(输出天然是拼起来的)。用于 MLP 第一层、QKV 投影。
行并行 Row Parallel
权重 B 按切:B = [B₁; B₂]。输入已经是按列切的 Y_i。各卡算 Z_i = Y_i·B_i 得到部分和,需要 AllReduce 求和得到完整输出。用于 MLP 第二层、Attention 输出投影。

MLP 块的经典配对(column→row,中间不通信)

输入 X
(每卡完整)
列并行 Linear+GeLU
各卡得 Y_i (输出的一部分列)
行并行 Linear
各卡得部分和 Z_i
AllReduce
合并得完整输出 Z

妙处:列并行的输出(按列切)正好是行并行需要的输入(按行切),所以中间那步不用通信,只在最后 AllReduce 一次。整个 MLP 块前向只需 1 次 AllReduce。

🎬 动画:TP 两张卡合算一个 MLP 块 循环播放
同一份输入 X 送到两卡;权重按列/行切开;各算一部分,最后 AllReduce 合并部分和。
① 输入:同一份 X 复制到两卡;W1 按切、W2 按
GPU0
X
W1ᴸW2ᵀ
GPU1
X
W1ᴿW2ᴮ
② 列并行:各算输出的一部分列(前向无需通信)
GPU0
Y₀ = X·W1ᴸ
GPU1
Y₁ = X·W1ᴿ
③ 行并行:各得部分和 Z₀、Z₁(还不完整)
GPU0
Z₀(部分和)
GPU1
Z₁(部分和)
④ AllReduce:把部分和相加 → 完整输出 Z(通信的是激活
Z = Z₀ + Z₁  →  两卡都得到完整 Z
Step 1X 复制 + 权重切分
Step 2列并行算部分列
Step 3行并行得部分和
Step 4AllReduce 合并

② 每张 GPU 本地维护什么

GPU 0(TP rank 0)
QKV 权重 [列切片 0] O 权重 [行切片 0] MLP W1 [列切片 0] MLP W2 [行切片 0] 对应的梯度/优化器状态分片 同一份输入数据的激活
GPU 1(TP rank 1)
QKV 权重 [列切片 1] O 权重 [行切片 1] MLP W1 [列切片 1] MLP W2 [行切片 1] 对应的梯度/优化器状态分片 同一份输入数据的激活

🟠 橙色 = 每卡只存权重矩阵的一部分(沿 hidden 切)。注意:TP 组内各卡处理的是同一份数据,但注意力头也被分到不同卡(每卡算不同的 heads)。

💾 本地维护
  • 权重矩阵的 1/N 切片(列或行)
  • 对应切片的梯度与优化器状态
  • 注意力的一部分 heads
🔗 共享 / 需同步
  • 输入/输出激活:块的边界处需要是完整的、各卡一致的
  • LayerNorm 等在 TP 中是冗余复制的
📨 通信内容
  • 激活值(部分和),不是梯度也不是参数!
  • 大小 ∝ batch × seq × hidden

④⑤ 通信内容、原语与时机

⑥ 优缺点

✅ 优点
  • 能放下单卡装不下的超大层(如巨大的 FFN、词表)
  • 降低单层计算延迟(多卡分摊)
  • 参数/优化器/梯度也天然被切(省显存)
❌ 缺点
  • 通信频繁且在关键路径,强依赖 NVLink,难以跨机扩展
  • 激活在块边界仍是完整复制的 → 催生了 SP
  • 实现侵入模型代码

PP · 流水线并行 Pipeline Parallelism

PP 沿模型的层(深度)方向切:把 L 层分成几个连续的 stage,每个 stage 放一张(组)卡上。数据像在工厂流水线上一样,一个 stage 算完把中间激活传给下一个 stage。

① 切分维度

模型的层数(深度)。例如 32 层分成 4 个 stage,每 stage 8 层。Stage 之间是串行依赖的(后一段要等前一段的输出)。

② 每张 GPU 本地维护什么

GPU 0 · Stage 0
第 1–8 层的完整参数 这些层的梯度/优化器状态 这些层的激活(多个 micro-batch)
GPU 1 · Stage 1
第 9–16 层 梯度/优化器状态 激活
GPU 2 · Stage 2
第 17–24 层 梯度/优化器状态 激活
GPU 3 · Stage 3
第 25–32 层 梯度/优化器状态 激活

🟣 紫色 = 每卡只持有连续若干层的完整参数(该层的权重是完整的,不像 TP 那样切碎)。这是 PP 与 TP 的关键区别:PP 切"哪些层归谁",TP 切"同一层的矩阵"。

💾 本地维护
  • 本 stage 那几层的完整参数、梯度、优化器状态
  • 在途 micro-batch 的激活(要留到反向)
🔗 共享 / 需传递
  • stage 边界的激活(前向)与激活梯度(反向)
📨 通信内容
  • 前向:把本 stage 输出激活传给下一 stage
  • 反向:把输入梯度传回上一 stage

④⑤ 通信原语与时机:P2P 点对点

核心难题:流水线气泡(Bubble)与 micro-batch

朴素做法(一次喂整个 batch)会让流水线严重空转:Stage 3 必须等 Stage 0→1→2 都算完才能开工,反向时反过来。解决办法是把 batch 切成很多 micro-batch 灌进流水线,让各 stage 尽量都有活干。

GPipe 调度(先全部前向,再全部反向)

灌入 micro-batch 1,2,3,4… 所有 stage 逐步填满(warmup 气泡) 稳态:各 stage 并行处理不同 micro-batch 排空(drain 气泡)

1F1B(One-Forward-One-Backward,PipeDream)

更优的调度:进入稳态后,每个 stage 交替做一次前向、一次反向。好处是能尽早释放激活显存(一个 micro-batch 反向做完就丢掉它的激活),显存占用大幅降低。Interleaved 1F1B(Megatron)进一步把每个 stage 再拆成多个不连续的层块,交错调度,把气泡进一步压小。

🎬 动画:4 个 micro-batch 在 4 个 stage 上流动 循环播放
彩块是 micro-batch,依次进入 Stage0→1→2→3(前向)。注意左上角和右下角的空档=气泡:流水线填充/排空时部分 GPU 在空转。
Stage 0
Stage 1
Stage 2
Stage 3
↖ 填充气泡
排空气泡 ↘
mb1
mb2
mb3
mb4

气泡比例 ≈ (stage 数 − 1) / micro-batch 数。所以要用远多于 stage 数的 micro-batch 来摊薄空转;1F1B 调度还能让反向尽早做、及时释放激活。

⚠️ 气泡率
气泡(GPU 空转)比例约为 (p − 1) / m,p = stage 数,m = micro-batch 数。所以 micro-batch 数 m 要远大于 stage 数 p 才能摊薄气泡。这是 PP 调参的核心。

⑥ 优缺点

✅ 优点
  • 通信少(只在 stage 边界 P2P),能跨机扩展
  • 每卡只放几层,显存友好
  • 与 TP/DP 正交,易组合
❌ 缺点
  • 流水线气泡浪费算力,需要足够多 micro-batch
  • 负载均衡难(各 stage 层数/计算要均匀)
  • 调度实现复杂(1F1B、interleave)

SP · 序列并行 Sequence Parallelism

SP 是 TP 的搭档(Megatron-SP)。它解决 TP 遗留的一个问题:在 TP 里,LayerNorm、Dropout、残差这些"逐元素"操作没法按 hidden 切,于是在 TP 组的每张卡上都冗余保存了完整的激活。SP 把这些区域的激活沿序列长度维度切开,消除这块激活冗余。

先分清:两种"SP"
(A) Megatron 语境的 SP:与 TP 配套,只切 LayerNorm/Dropout 区的激活,用来省激活显存。本节主要讲这个。
(B) 早期/广义的 SP:把整个序列切到不同卡上独立算,注意力处再想办法通信——这条路线后来发展成了 CP(上下文并行)。所以你会看到 SP 和 CP 概念上有重叠。

① 切分维度

在 TP 已经切了 hidden 维的基础上,SP 把 Transformer 中"非 TP 区域"(LayerNorm、Dropout、残差)的激活沿 sequence 维度切成 N 份,每卡只存 1/N 的序列。

② 每张 GPU 本地维护什么(TP+SP 联合视角)

GPU 0(TP=SP rank 0)
权重矩阵 [切片 0] LN/Dropout 区激活 [序列前半] Attention/MLP 区激活(TP 切分)
GPU 1(TP=SP rank 1)
权重矩阵 [切片 1] LN/Dropout 区激活 [序列后半] Attention/MLP 区激活(TP 切分)

🌸 SP 区:激活按序列切;🟠 TP 区:激活按 hidden 切。同一批激活在"进入/离开 TP 区"时要在这两种切分方式之间转换——这就是 SP 的通信来源。

③④⑤ 妙处:把 TP 的 AllReduce 拆成 AllGather + ReduceScatter,通信量不变

在纯 TP 里,进出 TP 区各要一次 AllReduce。SP 把序列切分和 hidden 切分衔接起来后,这两次操作变成:

SP 区
激活按 seq 切
AllGather
(沿 seq 凑齐进入 TP 区)
TP 区
激活按 hidden 切
ReduceScatter
(回到 SP 区,按 seq 切)

⑥ 优缺点

✅ 优点
  • 几乎免费地省下 LN/Dropout 区激活显存(通信量不增加)
  • 与 TP 无缝配合,是长序列训练标配
❌ 缺点
  • 只能和 TP 一起用,不独立
  • 同样受限于机内高带宽
一句话记住 SP
SP = 给 TP 打补丁,把 TP 区之外那份"冗余的完整激活"也沿序列切掉,代价只是把 AllReduce 换成等价的 AllGather+ReduceScatter。

CP · 上下文并行 Context Parallelism

CP 专门为超长上下文(几十万甚至上百万 token)而生。它把序列长度维度切开,让每张卡只负责一段 token 的全部计算(含注意力)。难点在于注意力是"全局"的——每个 token 要看到所有 token 的 K/V,所以 CP 的核心是用 Ring 通信在卡之间轮转 K/V

CP vs SP 的关键区别
Megatron-SP 只把 LayerNorm/Dropout 区的激活按序列切,注意力仍需完整序列(配合 TP)。CP 把整个网络(包括注意力)都按序列切,注意力靠通信补齐远端 K/V。CP 是为长序列扩展设计的,SP 是为省 TP 激活设计的。代表实现:Ring Attention、Megatron-CP、DeepSpeed-Ulysses(用 All-to-All 变体)。

① 切分维度

序列长度 seq。序列 S 切成 N 段,第 i 张卡持有第 i 段 token 的 Q/K/V 和激活。参数则在每张卡上是完整(或与 TP/DP 组合)。

② 每张 GPU 本地维护什么

GPU 0(CP rank 0)
完整层参数 本段 token [0 ~ S/N] 的 Q/K/V 本段 token 的激活 轮转中暂存的远端 K/V 块
GPU 1(CP rank 1)
完整层参数 本段 token [S/N ~ 2S/N] 本段激活 轮转中的远端 K/V 块
GPU N-1
完整层参数 本段 token 尾段 本段激活 轮转中的远端 K/V 块

🔵 CP 区:每卡只持有自己那段序列的 Q/K/V 与激活;参数完整。激活显存随 CP 数线性下降 → 这才能撑起超长序列。

③④⑤ 核心机制:Ring Attention(环形轮转 K/V)

要算注意力 softmax(QKᵀ)V,本卡的 Q 需要和所有 token 的 K/V 做内积。CP 不把所有 K/V 收集到一张卡(那会爆显存),而是让 K/V 块在环上一站一站传递,每收到一块就和本地 Q 算一次局部注意力,用 online-softmax 累加:

本地 Q 先和本地 K/V 算局部注意力 P2P 把本地 K/V 发给
环上下一张卡,同时收上一张的 K/V
用收到的远端 K/V 再算一块,累加进 softmax 重复 N-1 次,遍历全序列
🎬 动画:Ring Attention 的 K/V 环形轮转 循环播放
4 张卡各持一段序列(固定不动)。蓝色 K/V 块沿环一站一站传递;每停一站,本地 Q 就和它算一块局部注意力并累加。转满一圈=看遍全序列。
GPU0
seq 0
GPU1
seq 1
GPU2
seq 2
GPU3
seq 3
K/V 绕环传递
online-softmax 累加
K/V₀
K/V₁
K/V₂
K/V₃

通信原语是最近邻 P2P(环形),通信量与计算量都随序列线性,且可与注意力计算重叠——所以能撑起超长上下文,也能跨机。

⑥ 优缺点

✅ 优点
  • 激活显存与注意力显存随序列线性切开,支持超长上下文
  • Ring P2P 通信可与计算重叠,扩展性好
  • 与 TP/PP/DP 正交组合
❌ 缺点
  • 实现复杂(online softmax、因果掩码下的负载均衡)
  • 短序列时收益不大、反而多通信
  • 参数未被切(需配合 ZeRO/TP 省参数显存)

EP · 专家并行 Expert Parallelism

EP 专为 MoE(Mixture of Experts,混合专家) 模型设计。MoE 的 FFN 层有很多个"专家"(expert,各自是一个 FFN),每个 token 只被路由到少数几个专家(如 top-2)。EP 把不同的专家放到不同的 GPU 上,token 按路由结果用 All-to-All 送到对应专家所在的卡计算,再送回来。

① 切分维度

专家(expert)维度。假设有 64 个专家、8 张卡做 EP,则每卡放 8 个专家。注意力等非 MoE 部分照常用 DP/TP/PP,只有 MoE 的 FFN 部分走 EP。

② 每张 GPU 本地维护什么

GPU 0(EP rank 0)
注意力等共享层参数(复制) 专家 0 ~ 7 的权重 路由到本卡专家的 token 激活
GPU 1(EP rank 1)
注意力等共享层参数(复制) 专家 8 ~ 15 的权重 路由到本卡专家的 token 激活
GPU N-1(EP rank N-1)
注意力等共享层参数(复制) 专家 …… 的权重 路由到本卡专家的 token 激活

🔴 EP 区:每卡只存一部分专家的权重(专家总参数被切)。非专家部分(注意力、路由门控)仍是复制或另行并行。

💾 本地维护
  • 本卡负责的那批专家的完整权重、梯度、优化器状态
  • 共享层(注意力等)参数
🔗 共享 / 需同步
  • 共享层参数在 EP 组内需一致(走 DP 式同步)
  • token 需被送到"正确的专家"
📨 通信内容
  • token 的激活向量(分发去 + 收回来)

③④⑤ 核心机制:两次 All-to-All(Dispatch → Compute → Combine)

每张卡先本地算注意力和门控(router),得到"本卡这些 token 各自要去哪个专家"。由于目标专家可能在任何一张卡上,需要一次"重新分发":

本地 router 决定
每个 token 去哪个专家
All-to-All Dispatch
把 token 送到目标专家所在卡
各卡用本地专家
计算收到的 token
All-to-All Combine
把结果送回 token 原来的卡
🎬 动画:MoE 的 token 路由(两次 All-to-All) 循环播放
每卡有一批 token,router 决定各自去哪个专家;专家分布在不同卡上,所以要 All-to-All 把 token 送过去,算完再送回来。
① 路由:本地 router 给每个 token 标注目标专家(颜色=目标专家所在卡)
GPU0 的 token
→E1→E2→E0
GPU1 的 token
→E0→E3→E1
② All-to-All Dispatch:把每个 token 送到其目标专家所在的卡
token 按目标专家重新分发到各卡
③ 专家计算:每卡只用本地专家处理收到的 token
GPU0 · E0
算 E0 的 token
GPU1 · E1
算 E1 的 token
④ All-to-All Combine:把结果按原路送回 token 来源卡
结果送回 → 每个 token 回到它原来的位置
Step 1router 决定去向
Step 2All-to-All 分发
Step 3本地专家计算
Step 4All-to-All 回收
⚠️ EP 的独有难题:负载不均(load imbalance)
如果路由让某些"热门专家"收到远多于平均的 token,那些卡就成了瓶颈,其余卡空等。因此 MoE 训练要用 辅助负载均衡损失(aux loss)容量因子(capacity factor)丢弃/填充 token 等手段,尽量让各专家收到的 token 数均匀。这直接影响 All-to-All 的效率。

⑥ 优缺点

✅ 优点
  • 让总参数量暴涨(更多专家)而单 token 计算量几乎不变(稀疏激活)
  • 专家权重天然分片,省显存
❌ 缺点
  • All-to-All 通信重,对网络敏感
  • 负载均衡难,需要额外机制
  • 只适用于 MoE 结构

🧩 混合并行:3D / 4D / 5D 并行怎么组合

真实的大模型训练几乎从不用单一并行,而是把它们组合成多维网格(每张 GPU 同时属于多个并行组)。这些维度是正交的:可以把 GPU 想象成一个多维数组,每个维度对应一种并行。

组合的黄金法则:按"通信频率"匹配"物理带宽"

并行通信频率通信量放在哪一层网络
TP / SP极高(每层多次)大(激活)机内 NVLink(同一节点 8 卡内)
EP高(每 MoE 层 All-to-All)中(token)机内或高速互联多机
CP中(注意力内 Ring,可重叠)中(K/V)机内或近邻多机
PP低(stage 边界 P2P)小(激活)跨机(机间网络)
DP / ZeRO / FSDP低(每步一次,可重叠)大(梯度/参数)但不在关键路径最外层,可跨多机
🏆 典型的 "3D 并行" 排布(Megatron / 训练超大稠密模型)
TP 放机内(8 卡 NVLink)→ PP 跨若干机 → DP/ZeRO 在最外层跨所有机。 例如 512 张卡 = TP8 × PP8 × DP8。这样最频繁的 TP 走最快的链路,最省心的 DP 走最慢的链路。加上 SP 省激活、CP 撑长序列、EP 处理 MoE,就是现代 4D/5D 并行。

一张 GPU 在混合并行里的"多重身份"

假设 TP=2, PP=2, DP=2(共 8 卡)。GPU #5 可能同时是:

  • 某个 TP 组的 rank 1 —— 和另一张卡合存一层的权重矩阵,每层 AllReduce 激活
  • 某个 PP stage 1 —— 持有后半段的层,与前一 stage P2P 传激活
  • 某个 DP 组的 rank 1 —— 和另一份副本处理不同数据,每步 AllReduce/ReduceScatter 梯度

每种"身份"对应一个独立的通信组(NCCL communicator),互不干扰地在各自维度上通信。这就是分布式训练框架(Megatron-LM、DeepSpeed)要维护的"进程组拓扑"。

📊 横向对比总表

把前面所有并行方式的核心问题浓缩到一张表里,方便速查:

并行切什么维度每卡本地维护通信内容通信原语通信时机主要目的
DP Batch(数据) 完整模型 + 完整状态(冗余) 梯度 AllReduce 每步反向后 1 次 吞吐
ZeRO-1/2 Batch + 优化器状态(/梯度)分片 完整参数 + 1/N 状态(/梯度) 梯度 + 更新后参数 ReduceScatter+AllGather 每步(同 DP 总量) 省显存+吞吐
ZeRO-3/FSDP Batch + 参数/梯度/状态全分片 各 1/N(临时凑齐当前层参数) 参数(前向+反向) + 梯度 AllGather+ReduceScatter 每层/每 Unit 显存线性下降
TP 权重矩阵 hidden 维 权重的 1/N 切片(同份数据) 激活(部分和) AllReduce 每层前向/反向各 2 次 放下大层/降延迟
PP 模型层(深度) 连续若干层的完整参数 边界激活 / 激活梯度 P2P 每 micro-batch 过 stage 边界 放下深模型
SP 序列维(LN/Dropout 区) + 1/N 的 LN/Dropout 激活 激活 AllGather+ReduceScatter 进出 TP 区(同 TP 频率) 省激活显存
CP 序列维(全网络含注意力) 本段序列的 Q/K/V + 激活 K/V 块(反向传梯度) 环形 P2P / All-to-All 每层注意力内 N-1 步 超长上下文
EP MoE 专家维 一部分专家的完整权重 token 激活(分发/回收) All-to-All ×2 每个 MoE 层 MoE 稀疏扩容

通信的"三种东西"一句话总结

通信【参数/梯度】
DP、ZeRO、FSDP。量 ∝ 模型大小 Ψ,与 batch/seq 无关;每步一次或每层一次;可攒着与计算重叠。
通信【激活】
TP、SP、PP、CP。量 ∝ batch×seq×hidden,与数据强相关;在前向/反向关键路径上,越频繁越吃带宽。
通信【token】
EP。量 ∝ 路由 token 数;All-to-All 全局交换;受负载均衡影响大。

⚡ 推理场景的特别说明

前面很多内容以训练为主。推理(inference / serving)没有反向传播,也没有梯度和优化器状态,因此并行的侧重点不同。这里单独说明。

推理与训练的关键差异

❌ 不需要的
  • 梯度、优化器状态(无反向)
  • DP 的梯度 AllReduce(推理里"DP"= 多副本负载均衡,各副本独立服务不同请求,几乎不通信)
  • ZeRO 的优化器状态分片(无优化器)
✅ 更关心的
  • KV Cache 显存(随并发/序列长度暴涨)
  • 延迟(TTFT / TPOT)而非纯吞吐
  • Prefill 与 Decode 两阶段特性不同

各并行在推理中的角色

并行推理中的作用通信特点
TP最常用。把大模型的每层切开放到多卡,降低单次前向延迟,并让 KV Cache 也按 head 分摊。每层 AllReduce 激活,走 NVLink;KV Cache 随 TP 切分
PP放下超大模型(单机放不下时跨机)。但对延迟不友好(stage 串行),更适合吞吐导向的批处理。stage 边界 P2P 传激活
EPMoE 模型推理必备(如 DeepSeek/Mixtral)。专家分卡,token All-to-All。每 MoE 层两次 All-to-All
CP / SP长上下文推理(尤其 prefill 阶段)把长序列切开,降低单卡激活/KV 压力。注意力内 Ring 传 K/V
DP推理里通常指多实例副本:N 份完整模型各自服务请求,用负载均衡器分发,副本间基本不通信。几乎无(仅 MoE 的 attention DP + EP 组合时有)
🗄️ 推理的核心新变量:KV Cache
自回归解码时,每生成一个 token 都要缓存之前所有 token 的 K/V,避免重复计算。KV Cache 显存 ∝ batch × seq × layers × hidden × 2,在长上下文/高并发下可能比模型权重还大。TP 会把 KV Cache 按注意力头切分到各卡;CP/SP 会按序列切分。这是推理并行相比训练最不同的关注点。
(此外还有 PagedAttention、连续批处理、Prefill/Decode 分离部署等推理专属优化,超出"并行方式"范畴,这里不展开。)

🎯 选型速查:我该用哪种并行?

  • 模型能塞进单卡,只想训得更快DP,再不够上 ZeRO-1/2 省点显存。
  • 模型塞不进单卡(几十亿~百亿)FSDP / ZeRO-3(最易用),或机内 TP
  • 超大稠密模型(百亿~千亿)TP(机内) + PP(跨机) + DP/ZeRO(最外) 三件套,加 SP 省激活。
  • 超长上下文(数十万 token) → 在上面基础上加 CP
  • MoE 模型 → 在上面基础上给 FFN 部分加 EP
  • 纯推理TP(降延迟) + 多副本 DP(扩吞吐),MoE 加 EP,长序列加 CP