并行策略基础:数据并行、模型并行、流水线并行、混合并行
聊到分布式训练,绕不开的就是这四种并行策略。说实话,我刚入行那会儿,也经常搞混它们之间的区别。后来在项目里踩过几次坑,才真正理解了各自的适用场景。
今天我就把这四种策略掰开揉碎了讲清楚。你想想看,训练一个大模型,就像盖一栋大楼。数据并行是让更多人同时砌墙,模型并行是把墙拆成几段分别砌,流水线并行是让砌墙、刷漆、装窗户形成流水线,而混合并行就是把这些方法组合起来用。
数据并行(Data Parallelism)
数据并行是最直观、也最常用的策略。说白了,就是把训练数据切成多份,每个GPU拿一份,模型参数大家各持一份副本。
每个GPU独立计算梯度,然后通过AllReduce操作把梯度同步一下,再统一更新参数。
核心流程:
- 将训练数据切分成N份,分发给N个GPU
- 每个GPU持有完整的模型副本
- 各GPU独立前向传播、计算损失、反向传播
- 通过AllReduce同步梯度
- 每个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,就是用了这种混合方案。
我画了一张图,帮你理解它们之间的关系:
实际工程中,我一般这样分配:
| 策略 | 适用场景 | 通信开销 | 我的建议 |
|---|---|---|---|
| 数据并行 | 模型能塞进单卡 | 高(AllReduce) | 优先使用,简单可靠 |
| 模型并行 | 模型太大,单卡放不下 | 极高(逐层依赖) | 尽量少用,除非不得已 |
| 流水线并行 | 模型层数多,可切分 | 中等(点对点通信) | 配合数据并行使用效果最佳 |
| 混合并行 | 千亿参数大模型 | 高(多种通信混合) | 需要精细调优,建议先做profiling |
重要提醒:混合并行不是简单地把策略堆在一起。我曾经见过一个团队,把数据并行、模型并行、流水线并行全用上,结果通信开销占了训练时间的70%。说白了,策略越多,通信越复杂,调优难度指数级上升。
我个人习惯的做法是:先用数据并行跑通,如果显存不够,再加流水线并行。模型并行是最后的选择。你想想看,能少一种通信就少一种,训练效率往往更高。
实战技巧:如果你用PyTorch,可以试试FSDP(Fully Sharded Data Parallel)。它把模型参数、梯度、优化器状态都分片存储,相当于数据并行和模型并行的结合体。我在训练7B模型时用过,效果不错。
最后说一句:没有银弹。每种策略都有它的适用场景。关键是根据你的模型大小、集群配置、带宽情况来选。我建议你先做个小规模的profiling,看看瓶颈在哪,再决定用哪种策略。
公众号:蓝海资料掘金营,微信deep3321