数据并行(Data Parallel)
核心问题:如何用更多 GPU 加速训练?¶
数据并行(Data Parallel, DP)是最直观的并行策略:每个 GPU 处理不同 batch 的数据,同步梯度后更新参数。这样可以线性扩展训练速度(理想情况下)。
基本思想: - 每个 GPU 有完整模型副本 - 每个 GPU 处理不同 batch 的数据 - 所有 GPU 的梯度同步(All-Reduce) - 每个 GPU 独立更新参数(梯度相同,更新结果一致)
数据并行的工作流程¶
初始化:
GPU 0: 模型副本 A(从 rank 0 广播初始状态)
GPU 1: 模型副本 B(从 rank 0 广播初始状态)
GPU 2: 模型副本 C(从 rank 0 广播初始状态)
GPU 3: 模型副本 D(从 rank 0 广播初始状态)
训练循环:
Step 1:
GPU 0: 处理 batch 1 → 梯度 g1
GPU 1: 处理 batch 2 → 梯度 g2
GPU 2: 处理 batch 3 → 梯度 g3
GPU 3: 处理 batch 4 → 梯度 g4
Step 2: 梯度同步(All-Reduce)
所有 GPU 的梯度求和并分发:
g_avg = (g1 + g2 + g3 + g4) / 4
每个 GPU 都得到 g_avg
Step 3: 参数更新
GPU 0: 用 g_avg 更新模型副本 A
GPU 1: 用 g_avg 更新模型副本 B
GPU 2: 用 g_avg 更新模型副本 C
GPU 3: 用 g_avg 更新模型副本 D
为什么每个 GPU 独立更新参数: - All-Reduce 后所有 GPU 的梯度相同(已平均) - 每个进程独立执行优化器逻辑,灵活性更高 - 不需要参数广播步骤,减少通信开销
核心挑战¶
通信开销¶
数据并行的核心瓶颈是梯度同步。每次反向传播后需要 All-Reduce 所有 GPU 的梯度:
问题: - 模型越大,梯度越大,通信时间越长 - GPU 越多,通信拓扑越复杂,延迟越高 - 如果通信时间超过计算时间,GPU 会空闲等待
性能优化技术¶
梯度分桶(Gradient Bucketing)¶
问题:逐参数同步梯度会导致频繁的小消息通信,带宽利用率低。
解决方案:将多个参数的梯度组织到一个 bucket 中,按 bucket 同步。
参数:[p1, p2, p3, p4, p5, p6, p7, p8]
梯度:[g1, g2, g3, g4, g5, g6, g7, g8]
Bucket 0: [g1, g2, g3, g4](25MB)
Bucket 1: [g5, g6, g7, g8](25MB)
同步时按 bucket All-Reduce,而非逐参数同步
权衡: - Bucket 越大:通信次数越少,带宽利用率越高,但通信延迟越长 - Bucket 越小:通信延迟越短,但通信次数越多,带宽利用率低
默认配置:25MB bucket,在延迟和带宽之间取得平衡。
梯度计算与梯度通信重叠¶
问题:如果等待所有梯度计算完毕后再同步,GPU 会空闲等待通信完成。
解决方案: 在反向传播过程中,当某个 bucket 的梯度都准备好时,立即触发异步 All-Reduce,GPU 继续计算其他层的梯度。
时间 →
GPU 0: 计算梯度 g1, g2, g3, g4(bucket 0 就绪)→ 触发异步 All-Reduce → 继续计算 g5, g6, g7, g8
GPU 1: 计算梯度 g1, g2, g3, g4(bucket 0 就绪)→ 触发异步 All-Reduce → 继续计算 g5, g6, g7, g8
注意:这是 DP 特有的"梯度计算与梯度通信重叠",与 PP 的"不同阶段计算重叠"不同
效果:通信时间被计算时间掩盖,整体训练速度提升。
梯度累积¶
问题:小 batch size 导致梯度噪声大,但增大 batch size 会超出显存。
解决方案: - 多次小 batch 的梯度累积到同一个 bucket - 累积到目标次数后再同步,减少通信频率
目标 batch size = 256
单卡 batch size = 32
累积步数 = 256 / 32 = 8
前 7 步:梯度累积到 bucket,不同步
第 8 步:触发 All-Reduce,更新参数
权衡: - 减少通信次数,但增加显存占用(需要保存累积梯度)
梯度压缩¶
问题:梯度通信量大,成为瓶颈。
解决方案: - 在 All-Reduce 前对梯度进行压缩(如 PowerSGD 低秩分解) - 通信后再解压缩
权衡: - 减少通信量,但增加计算开销(压缩/解压缩)
常见实现¶
PyTorch DDP(DistributedDataParallel)¶
特点: - 进程级并行(每个进程对应一个 GPU) - 使用 NCCL/Gloo 后端进行通信 - 自动梯度分桶和计算通信重叠 - 通过 autograd hook 拦截梯度计算
适用场景:模型能放入单卡显存,需要加速训练。
技术细节:详见 PyTorch 官方文档1。
FSDP(Fully Sharded Data Parallel)¶
特点: - 参数、梯度、优化器状态都分片到不同 GPU - 动态重分片参数(需要时才拉取) - 显存占用远低于 DDP
适用场景:超大模型,单卡放不下。
ZeRO(Zero Redundancy Optimizer)¶
特点: - DeepSpeed 提出的优化器状态分片策略 - 三个阶段:分片优化器状态(ZeRO-1)、分片梯度(ZeRO-2)、分片参数(ZeRO-3) - ZeRO-3 等价于 FSDP
适用场景:超大模型,单卡放不下。
与其他并行方式的对比¶
| 并行策略 | 模型切分 | 数据切分 | 显存占用 | 通信内容 | 适用场景 |
|---|---|---|---|---|---|
| 数据并行(DP) | 完整副本 | 不同 batch | 高 | 梯度同步 | 模型能放入单卡 |
| 流水线并行(PP) | 按层切分 | 微批次流水线 | 中 | 激活值传递 | 模型层数多 |
| 张量并行(TP) | 层内切分 | 相同 batch | 低 | 中间结果 | 注意力矩阵大 |
| 混合并行(3D) | DP + PP + TP | 组合策略 | 可调 | 多种通信 | 超大规模模型 |
常见问题¶
为什么数据并行比模型并行更常用?¶
数据并行实现简单,通信模式固定(All-Reduce),容易优化。模型并行需要根据模型架构设计切分策略,通信模式复杂。
什么时候使用数据并行?¶
- 模型能放入单卡显存
- 需要加速训练(更多 GPU)
- batch size 可以线性扩展
什么时候需要其他并行策略?¶
- 模型太大,单卡放不下(使用 PP 或 TP)
- batch size 受限,无法线性扩展(使用 PP 或 TP)
- 通信带宽受限(使用 PP 或 TP 减少通信量)
参考资料¶
-
Li et al. PyTorch Distributed: Experiences on Accelerating Data Parallel Training. 2020. https://arxiv.org/pdf/2006.15704 ↩