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 知识体系结构图

下面这张图,帮你理清模型并行的核心逻辑:

模型并行核心逻辑 模型并行 张量并行 (TP) 流水线并行 (PP) 层内拆分:按行/按列切分权重 通信:每层一次 all-reduce 层间拆分:按层数/计算量切分 通信:每段一次 point-to-point 混合使用:单机TP + 多机PP + DP = 3D并行 横向切单层 + 纵向切多层 + 数据并行扩规模

嗯,这张图把核心逻辑都串起来了。你想想看,从左到右,从张量并行到流水线并行,再到混合使用,其实就是从「一层怎么拆」到「多层怎么拆」,最后到「整个系统怎么搭」的过程。

最后说一句:模型并行没有银弹。选哪种策略,取决于你的模型大小、卡的数量、网络带宽。我建议先做个小实验,跑几个 step 看看通信开销,再决定最终方案。

想要更多资料,联系可可