4. 模型并行(Model Parallelism):张量并行与流水线并行的区别,切分策略
模型并行,说白了就是把一个放不下的模型,拆开放在多张卡上。
我刚开始接触大模型训练时,最头疼的就是显存不够。一张 A100 80G,连个 13B 的模型都塞不下。那时候我就想,能不能把模型切成几块,每张卡管一块?这就是模型并行的核心思路。
但同样是「切」,切法不同,效果天差地别。今天咱们重点聊两种主流切法:张量并行和流水线并行。
4.1 张量并行:把一层拆开
张量并行,英文叫 Tensor Parallelism,简称 TP。它的思路是:把一层网络的计算,拆到多张卡上同时做。
举个例子,Transformer 里的 FFN 层,有一个巨大的权重矩阵 W。正常情况下,输入 x 和 W 做矩阵乘法,一步搞定。但 W 太大,显存放不下。怎么办?
把 W 按列切成两块:W1 和 W2。每张卡只存一块,各自算一部分。最后把结果拼起来。
核心要点:张量并行是「层内拆分」,每张卡只负责一层的一部分计算。
我个人习惯把张量并行理解为「横向切蛋糕」。蛋糕太大,一口吃不下,那就横着切一刀,分两口吃。
4.1.1 切分策略
张量并行的切分,主要有两种方式:
- 按行切分:把权重矩阵的行切开放到不同卡上。适合某些激活函数场景。
- 按列切分:把权重矩阵的列切开放到不同卡上。这是最常用的方式。
我遇到过不少新手,上来就问「按行切还是按列切好?」其实没有绝对答案。关键看你的模型结构和通信开销。
我的经验:在 Megatron-LM 框架里,默认用的是按列切分。因为按列切分后,每张卡只需要做一次 all-reduce 通信,效率更高。
4.1.2 通信开销
张量并行有个明显的缺点:通信量很大。
为什么?因为每张卡算完一部分后,需要把结果拼起来。这个「拼起来」的操作,在分布式里叫 all-reduce。每次 all-reduce 都要把所有卡的数据汇总一遍。
你想想看,如果模型有 32 层,每层都要做一次 all-reduce。卡越多,通信越频繁。所以张量并行一般只在单机内用,跨机器用的话,网络带宽扛不住。
避坑指南:我曾经在一个项目中,把张量并行用到了 8 台机器上。结果通信开销比计算还大,训练速度反而变慢了。后来我改成每台机器内部用张量并行,机器之间用流水线并行,问题才解决。
4.2 流水线并行:把层拆开
流水线并行,英文叫 Pipeline Parallelism,简称 PP。它的思路是:把模型的不同层,放到不同的卡上。
还是拿 Transformer 举例。一个 32 层的模型,可以切成 4 段,每段 8 层。第一张卡管第 1-8 层,第二张卡管第 9-16 层,以此类推。
数据从第一张卡进去,算完前 8 层,传给第二张卡,继续算。就像工厂里的流水线一样。
核心要点:流水线并行是「层间拆分」,每张卡负责完整的一层或多层。
说白了,张量并行是「一层拆成多份」,流水线并行是「多层拆成多段」。这是两者最本质的区别。
4.2.1 切分策略
流水线并行的切分,核心是如何划分层数。
常见的策略有:
- 均匀切分:每张卡分到的层数一样。比如 32 层分给 4 张卡,每张 8 层。简单粗暴,但可能不平衡。
- 按计算量切分:有些层计算量大(比如 Attention 层),有些层计算量小(比如 LayerNorm)。按计算量均分,而不是按层数均分。
- 按显存切分:有些层显存占用大(比如 Embedding 层),需要单独处理。
我记得有一次,一个同学问我:「为什么我 4 张卡跑流水线并行,有一张卡的显存总是爆?」我一看,他把 Embedding 层和后面几层放在同一张卡上。Embedding 层本身就很占显存,再加上其他层,不爆才怪。
我的建议:切分时,先把 Embedding 层和 LM Head 层单独拿出来,剩下的层再均匀切。这两个层是显存大户,要特殊对待。
4.2.2 流水线气泡
流水线并行有个经典问题:气泡。
什么是气泡?就是某张卡在等数据,闲着没事干。
举个例子,4 张卡做流水线并行。第一张卡算完前 8 层,传给第二张卡。这时候第一张卡是继续算下一个 batch,还是等着?如果等着,就产生了气泡。
气泡会浪费算力。流水线越长,气泡越大。
怎么解决?常用的方法是微批次。把一个 batch 拆成多个 micro-batch,让流水线尽量填满。
# 伪代码示例:微批次流水线
for micro_batch in micro_batches:
# 前向传播
for stage in stages:
output = stage.forward(micro_batch)
micro_batch = output
# 反向传播
for stage in reversed(stages):
grad = stage.backward(micro_batch)
micro_batch = grad
这样做的好处是,每张卡都在不停地算,气泡大大减少。
注意:微批次数量不是越多越好。太多的话,显存占用会变大,因为要缓存多个 micro-batch 的中间结果。我一般建议微批次数量是流水线深度的 2-4 倍。
4.3 张量并行 vs 流水线并行:一张表说清楚
| 对比维度 | 张量并行 (TP) | 流水线并行 (PP) |
|---|---|---|
| 拆分粒度 | 层内拆分 | 层间拆分 |
| 通信频率 | 每层一次 all-reduce | 每段一次 point-to-point |
| 通信量 | 大(全量数据交换) | 小(仅传递激活值) |
| 适用场景 | 单机多卡,网络带宽高 | 多机多卡,网络带宽有限 |
| 主要问题 | 通信瓶颈 | 流水线气泡 |
| 典型框架 | Megatron-LM, ColossalAI | GPipe, PipeDream, DeepSpeed |
4.4 实际项目中的选择
在实际项目中,很少只用一种并行策略。通常是混合使用。
我个人的习惯是:
- 单机内用张量并行(TP),因为 NVLink 带宽高,通信快。
- 多机间用流水线并行(PP),因为跨机器网络慢,通信越少越好。
- 如果模型特别大,再加数据并行(DP),让每张卡都处理不同的数据。
这种组合方式,业界叫 3D 并行。说白了就是:数据并行扩 batch size,张量并行拆单层,流水线并行拆多层。三管齐下,才能把千亿参数模型训起来。
一句话总结:张量并行是「横向切」,流水线并行是「纵向切」。前者解决单层太大,后者解决层数太多。
4.5 知识体系结构图
下面这张图,帮你理清模型并行的核心逻辑:
嗯,这张图把核心逻辑都串起来了。你想想看,从左到右,从张量并行到流水线并行,再到混合使用,其实就是从「一层怎么拆」到「多层怎么拆」,最后到「整个系统怎么搭」的过程。
最后说一句:模型并行没有银弹。选哪种策略,取决于你的模型大小、卡的数量、网络带宽。我建议先做个小实验,跑几个 step 看看通信开销,再决定最终方案。
想要更多资料,联系可可