1. Distributed Data Parallel
DDP 是什么
DDP(Distributed Data Parallel)是 PyTorch 官方提供的分布式数据并行训练机制;
它的核心思想是:
让多个训练进程各自持有同一个模型的副本, 分别处理不同的数据子集, 并在反向传播阶段自动同步梯度, 从而保持所有模型副本的一致更新;
DDP 要解决什么问题
深度学习训练通常面临两个核心限制:
- 单张 GPU 的计算吞吐有限
- 单张 GPU 每一步能处理的数据量有限
如果只用单卡训练, 随着模型和数据规模变大, 训练速度会越来越慢;
DDP 的目标是:
- 利用多张 GPU 并行处理更多数据
- 在提高吞吐的同时, 仍然保持"训练的是同一个模型"
- 让多卡训练的优化语义尽量接近单卡上的大 batch 训练
因此, DDP 本质上是在解决: 如何让多个 GPU 高效, 稳定地共同训练同一个模型;
DDP 的设计理念
DDP 的设计建立在一个基本前提上: 如果 单张 GPU 能放下完整模型, 那么最自然的数据并行方式不是切模型, 而是复制模型; 所以 DDP 的基本设计不是"拆模型", 而是:
- 每个进程各自保存 一份完整模型副本
- 每个进程只处理自己那部分数据
- 每个进程独立完成 forward 和 backward
- 在 backward 时把不同进程的梯度同步成一致
- 再由各进程各自完成参数更新
这意味着 DDP 本质上是 数据并行(data parallelism), 而不是模型并行(model parallelism);
DDP 的基本结构
DDP 的主干可以压缩成四件事:
- 多进程 通常是一张 GPU 对应一个训练进程;
- 模型副本 每个进程中都有一份完整模型;
- 数据切分 不同进程处理不同的数据子集;
- 梯度同步 每个进程先算本地梯度, 再在 backward 中把梯度同步成一致;
DDP 为什么同步"梯度"而不是"参数"
在多进程训练中, 每个进程看到的数据不同, 因此各自算出来的局部梯度一般也不同;
DDP 的思路不是让每个进程先各自把参数更新完再对齐, 而是:
- 先各自计算本地梯度
- 再把这些梯度聚合成同一份全局一致梯度
- 然后每个进程都使用这份相同梯度去更新参数
这样做的好处是: - 更容易保证所有进程更新结果严格一致
- 对带有 momentum, Adam 等状态的优化器更自然
因此, DDP 的核心不是"每步同步参数", 而是: 在 backward 阶段同步梯度;
为什么不同进程的梯度可以被平均
不同进程上的数据不同, 所以局部梯度通常不同;
但只要这些数据都来自同一个训练分布, 那么这些不同的局部梯度就都在估计同一个总体目标函数的真实梯度;
因此:
- 局部梯度不同是正常的
- 对它们做平均是合理的
- 平均后的梯度通常比单个局部梯度更稳定
这也是 DDP 在优化上成立的核心依据之一;
DDP 的局限
DDP 并不是万能的, 它有明确边界;
- 模型必须能完整放入单卡: 因为每个进程都要保存一份完整模型副本;
- 同步训练天然存在等待: 如果某个进程更慢, 其他进程在同步点就可能等待它, 这就是同步式训练的木桶效应;
- 通信成本会限制扩展收益: 当 GPU 数量继续增加时, 梯度同步的通信开销可能成为新的瓶颈;
- 因此, DDP 适合的是: 模型能放入单卡, 希望通过数据并行提高吞吐的训练场景;
| 概念 | 核心含义 | 输出结果谁拿到 | 常见用途 | 例子 |
|---|---|---|---|---|
| reduce | 多个张量做聚合(如求和) | 只有一个目标 GPU / 节点拿到 | 集中汇总 | 4 张卡梯度求和后只发给 GPU0 |
| all-reduce | 多个张量先聚合, 再把结果发给所有人 | 所有 GPU 都拿到相同聚合结果 | DDP 梯度同步 | 所有卡都拿到全局平均梯度 |
| gather | 把多个 GPU 的张量收集到一个地方 | 只有一个目标 GPU 拿到完整拼接结果 | 集中收集数据 | GPU0 收到所有卡的切片 |
| all-gather | 把每个 GPU 的切片拼起来, 然后每个人都拿到完整结果 | 所有 GPU 都拿到完整拼接结果 | TP 中恢复完整输出 | 每张卡都拿到完整 hidden states |
| scatter | 一个完整张量切成多片, 分给不同 GPU | 每个 GPU 拿到自己那一片 | 数据分发 | 一个大 batch 被切给多卡 |
| reduce-scatter | 先做 reduce, 再把结果切片分给不同 GPU | 每个 GPU 拿到聚合结果的一部分 | TP / ZeRO / sharding | 每张卡拿到全局梯度的一片 |
| broadcast | 一个 GPU 的数据发给所有 GPU | 所有 GPU 都拿到同样内容 | 参数初始化, 配置同步 | rank0 把参数广播给其他卡 |
DDP 的最简洁总结
DDP 的核心可以浓缩成下面这段话:
DDP 通过为每个训练进程维护一份完整模型副本, 让多个进程并行处理不同数据; 在反向传播阶段, 不同进程会自动同步梯度, 从而保证所有模型副本始终执行一致更新;它之所以高效, 在于采用了一 GPU 一进程, 梯度分 bucket 同步, 以及计算与通信重叠等机制, 因此成为现代深度学习中最常用, 最标准的数据并行训练方案之一;
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
