并行策略基础:数据并行、模型并行、流水线并行、混合并行

聊到分布式训练,绕不开的就是这四种并行策略。说实话,我刚入行那会儿,也经常搞混它们之间的区别。后来在项目里踩过几次坑,才真正理解了各自的适用场景。

今天我就把这四种策略掰开揉碎了讲清楚。你想想看,训练一个大模型,就像盖一栋大楼。数据并行是让更多人同时砌墙,模型并行是把墙拆成几段分别砌,流水线并行是让砌墙、刷漆、装窗户形成流水线,而混合并行就是把这些方法组合起来用。

数据并行(Data Parallelism)

数据并行是最直观、也最常用的策略。说白了,就是把训练数据切成多份,每个GPU拿一份,模型参数大家各持一份副本。

每个GPU独立计算梯度,然后通过AllReduce操作把梯度同步一下,再统一更新参数。

核心流程:

  1. 将训练数据切分成N份,分发给N个GPU
  2. 每个GPU持有完整的模型副本
  3. 各GPU独立前向传播、计算损失、反向传播
  4. 通过AllReduce同步梯度
  5. 每个GPU用同步后的梯度更新本地参数

我在项目中遇到过一个问题:当模型大到单个GPU放不下时,数据并行就失效了。因为每个GPU都要存一份完整的模型参数。这时候就得考虑别的策略了。

我的经验:数据并行适合模型能塞进单卡的情况。如果模型参数超过单卡显存的80%,建议换策略。我曾经硬扛过一次,结果OOM了三次才老实。

模型并行(Model Parallelism)

模型并行解决的就是模型太大、单卡放不下的问题。做法是把模型的不同层或不同参数切分到不同GPU上。

举个例子,一个12层的Transformer,可以切成3份,每份4层,分别放在3张卡上。前向传播时,数据依次经过卡1、卡2、卡3,反向传播时反过来。

这里有个坑——计算依赖。因为后一层必须等前一层的输出,所以GPU之间是串行工作的。利用率上不去,说白了就是大部分时间在等数据。

注意:模型并行会导致严重的计算空闲。我曾经试过把BERT-Large切成4份放在4张卡上,结果GPU利用率只有30%左右。大部分时间都在等通信。

我个人的习惯是:除非模型真的放不下,否则尽量不用纯模型并行。实在要用,也得搭配其他策略一起上。

流水线并行(Pipeline Parallelism)

流水线并行是对模型并行的一种改进。它把模型切成多个阶段(Stage),每个阶段放在一张卡上。但不一样的是,它引入了微批次(Micro-batch)的概念。

具体做法:把一个大batch切成多个微批次。第一个微批次进入阶段1计算时,阶段2是空闲的。等微批次1进入阶段2,阶段1就可以处理微批次2了。这样就能让所有GPU同时工作起来。

# 伪代码示意
def pipeline_forward(model_stages, micro_batches):
    # 阶段1处理微批次1
    output = model_stages[0](micro_batches[0])
    
    # 阶段1处理微批次2,同时阶段2处理微批次1
    for i in range(1, len(micro_batches)):
        output = model_stages[0](micro_batches[i])
        output = model_stages[1](output)
    
    # 最后阶段处理剩余微批次
    for stage in model_stages[2:]:
        output = stage(output)
    return output

嗯,这里要注意:流水线并行有个气泡(Bubble)问题。就是刚开始和快结束时,总有些GPU是空闲的。微批次越多,气泡占比越小,但显存占用也越大。这是个trade-off。

避坑指南:我曾经把微批次设得太小(比如2个),结果气泡占了将近一半的时间。后来改成8个微批次,吞吐量直接翻倍。建议微批次数量至少是流水线阶段数的2倍。

混合并行(Hybrid Parallelism)

混合并行就是把上面几种策略组合起来用。现在训练千亿参数的大模型,基本都走这条路。

最常见的组合是:数据并行 + 流水线并行 + 模型并行。比如训练GPT-3,就是用了这种混合方案。

我画了一张图,帮你理解它们之间的关系:

混合并行策略架构图 数据并行(Data Parallelism) GPU 0 ── GPU 1 ── GPU 2 ── GPU 3 流水线并行(Pipeline Parallelism) Stage 1 → Stage 2 → Stage 3 → Stage 4 微批次1 → 微批次2 → 微批次3 → ... 模型并行(Model Parallelism) Layer 1-4 ── Layer 5-8 ── Layer 9-12 ── Layer 13-16 (每个GPU负责部分层) 混合并行 = 数据并行(跨节点) + 流水线并行(节点内) + 模型并行(单卡内)

实际工程中,我一般这样分配:

策略 适用场景 通信开销 我的建议
数据并行 模型能塞进单卡 高(AllReduce) 优先使用,简单可靠
模型并行 模型太大,单卡放不下 极高(逐层依赖) 尽量少用,除非不得已
流水线并行 模型层数多,可切分 中等(点对点通信) 配合数据并行使用效果最佳
混合并行 千亿参数大模型 高(多种通信混合) 需要精细调优,建议先做profiling

重要提醒:混合并行不是简单地把策略堆在一起。我曾经见过一个团队,把数据并行、模型并行、流水线并行全用上,结果通信开销占了训练时间的70%。说白了,策略越多,通信越复杂,调优难度指数级上升。

我个人习惯的做法是:先用数据并行跑通,如果显存不够,再加流水线并行。模型并行是最后的选择。你想想看,能少一种通信就少一种,训练效率往往更高。

实战技巧:如果你用PyTorch,可以试试FSDP(Fully Sharded Data Parallel)。它把模型参数、梯度、优化器状态都分片存储,相当于数据并行和模型并行的结合体。我在训练7B模型时用过,效果不错。

最后说一句:没有银弹。每种策略都有它的适用场景。关键是根据你的模型大小、集群配置、带宽情况来选。我建议你先做个小规模的profiling,看看瓶颈在哪,再决定用哪种策略。


公众号:蓝海资料掘金营,微信deep3321