

本章节参考UP主 ↗
分布式训练是为了解决大规模深度学习模型在单设备上无法容纳或训练过慢的问题,通过将计算和存储负载分散到多个 GPU 或机器上协同完成训练。
其中,DP (DataParallel) 是 PyTorch 早期的单进程多线程方案,将一个 batch 的数据分发到多个 GPU 并行前向/反向,但梯度汇总和参数更新由主 GPU(如 cuda:0)完成,存在通信瓶颈且不支持多机,现已基本弃用。 DDP (DistributedDataParallel) 是当前主流的多进程分布式训练方法,每个 GPU 独立运行一个进程,拥有完整的模型副本,通过高效的 All-Reduce 操作同步梯度,支持单机多卡和多机多卡,扩展性好、性能高。 而 DeepSpeed ZeRO(Zero Redundancy Optimizer)则从内存优化角度出发,在数据并行基础上对优化器状态、梯度和模型参数进行分片存储,显著降低显存占用,使百亿甚至千亿参数模型能在普通 GPU 集群上训练。
三者中,DDP 侧重通信效率,ZeRO 侧重内存节省,实践中常结合使用(如 DDP + ZeRO)以兼顾速度与规模。
Data Parallel (DP)#
DP是从硬盘读取数据,通过一个CPU进程将数据集划分成多份,然后分发给每个GPU独立进行前向和反向传播,计算出各自的梯度,然后各GPU将各自的梯度传到GPU0上。GPU0通过对这些梯度求和取平均更新自己模型的参数,然后广播给其他GPU。
如图:
在分布式训练里,可能一半的时间都花在多卡之间的通信上,下面分析一下DP的通信量:
假设模型参数量为,GPU节点数为。 则对于GPU0,传入梯度为:,传出参数为 对于其他GPU:传出梯度为,传入参数为。
存在的问题#
| 问题类别 | 具体描述 |
|---|---|
| 单进程多线程架构 → GIL 和通信瓶颈 | - DP 在一个主进程中启动多个线程,每个线程控制一个 GPU。 - 受 Python GIL(全局解释器锁)限制,CPU 密集型操作无法真正并行。 - 所有梯度必须先汇总到主 GPU(通常是 cuda:0),再由主 GPU 广播更新后的参数。- 导致主 GPU 成为通信与计算瓶颈。 |
| 不支持多机训练 | - DP 仅支持单机多卡,无法跨机器(multi-node)扩展。 - 不能用于大规模分布式集群训练,限制了模型规模和数据吞吐能力。 |
| 显存浪费严重 | - 每个 GPU 都保存完整的模型副本(与 DDP 相同)。 - 主 GPU 额外存储所有其他 GPU 的梯度副本用于聚合。 - 例如:8 卡训练时,主 GPU 显存占用 ≈ 其他卡的 2 倍,极易发生 OOM(Out-Of-Memory)。 |
| 同步效率低 | - 每个 batch 的前向/反向完成后,必须等待所有 GPU 完成计算,才能在主 GPU 上聚合梯度。 - 采用同步阻塞式通信,任一“慢卡”(straggler)会拖慢整体训练速度。 - 虽使用 NCCL 或 CUDA P2P 通信,但数据路径必须经过主 GPU,导致带宽利用率低下。 |
Distributed Data Parallel(DDP)#
介绍DDP之前,先了解一下Ring-AllReduce:Ring-AllReduce 是一种高效的分布式梯度同步算法,广泛应用于大规模深度学习训练中(如 PyTorch DDP、Horovod 等)。其核心思想是将参与训练的多个 GPU(或节点)组织成一个逻辑环形拓扑(ring),每个设备只与左右两个邻居通信,通过多轮分块传输逐步完成全局梯度的聚合。
整个过程分为两个阶段:Scatter-Reduce(各设备将本地梯度分块,沿环传递并累加,最终每块由一个设备持有完整和)和 All-Gather(将累加后的梯度块沿环广播,使所有设备获得完整的全局梯度)。
相比传统的中心化聚合(如 Parameter Server 或 DP 的主 GPU 汇总),Ring-AllReduce 消除了单点瓶颈,通信量与设备数无关(带宽最优),且能充分利用多设备间的并行通信能力。尤其在高速互联网络(如 NVLink、InfiniBand)下,它能实现接近线性的扩展效率,是现代大模型分布式训练的通信基石。

首先将GPU0的参数传给GPU1,GPU1的参数传给GPU2,GPU2的参数传给GPU0,形成一个闭环,然后GPU与各自传入的参数相加,得到下面的结果。

然后将GPU0的参数传给GPU1,GPU1的参数传给GPU2,GPU2的参数传给GPU0,得到下面的结果。

以此类推,最终得到:

从而实现了所有GPU上都有聚合后的模型参数,在这个过程中,每个GPU同时发送和接收,可以最大限度利用每个显卡的上下行带宽。
与DP不同,DDP是多进程的,每个进程为自己的GPU准备数据并和其他GPU通信,每个GPU用自己的模型进行前向和反向传播,因为每个GPU的数据都不同,所以梯度也不同,最后通过Ring-AllReduce实现梯度的同步与聚合,每个GPU通过更新梯度来更新模型:
可以更深入理解DDP,在反向传播过程中,越后面的层数,梯度最先得到,但是每计算一个梯度就进行Ring-AllReduce会大幅增加开销,所以会使用桶,计算的梯度会保存在桶中。具体来说,会对每个层注册监听器(为了让DDP框架知道哪些梯度计算好了),当所有GPU的同一个桶都装满了,会执行一次Ring-AllReduce同步,在这个过程中GPU还在计算其他的梯度,当所有的桶都梯度同步了,每个GPU会调用自己的优化器进行更新:
下面分析一下DDP通信量:
假设模型参数量为,GPU节点数为。
对于每一个GPU进程:
Scatter-Reduce阶段传入/传出:
AllGather阶段传入/传出:
总传入/传出:
与集群大小无关
总结#
| 问题类别 | 具体描述 |
|---|---|
| 优点 | - 基于多进程架构,避免 Python GIL 限制,训练更稳定高效。 - 使用高效的 All-Reduce(如 NCCL)进行梯度同步,通信带宽利用率高。 - 支持单机多卡和多机多卡训练,扩展性强。 - 各 GPU 显存占用均衡,无主 GPU 瓶颈。 - 与 DistributedSampler、torchrun 等标准工具链无缝集成,是 PyTorch 官方推荐方案。 |
| 缺点 | - 每个进程需加载完整模型副本,显存开销大,难以直接训练超大规模模型(如百亿参数以上)。 - 需要手动处理 checkpoint 加载/保存、数据分片、随机种子同步等细节,代码复杂度高于 DP。 - 对网络拓扑和通信库(如 NCCL)依赖较强,在低带宽或多机环境下可能成为瓶颈。 - 不具备内置的显存优化机制(如参数分片),需结合 ZeRO 或 FSDP 才能进一步节省内存。 |
DeepSpeed ZeRO#
DeepSpeed ZeRO(Zero Redundancy Optimizer)是微软提出的一种显存优化技术,旨在消除传统数据并行训练中的内存冗余。它通过将优化器状态、梯度和模型参数在多个 GPU 之间进行分片存储,显著降低每个设备的显存占用。ZeRO 分为三个阶段:ZeRO-1 分片优化器状态,ZeRO-2 增加梯度分片,ZeRO-3 进一步分片模型参数,使得百亿甚至千亿参数的大模型能在普通 GPU 集群上高效训练。该技术可与分布式训练框架(如 PyTorch DDP)结合使用,在几乎不损失训练速度的前提下,大幅提升模型规模的可扩展性。
正常情况下每个GPU上要有Data、模型参数和梯度以及优化器的参数和梯度,下面使用的优化器是Adam,所以要存储一阶动量和二阶动量:

ZeRO-1#
由上图可知,占用显存最多的就是优化器状态,因为它是FP32精度,并且每个GPU都需要存储,所以ZeRO-1对优化器状态进行了分片。
这里假设有一个9层的网络结构,每个分片优化器负责管理3层网络结构。当反向传播完成了倒数前3层的梯度计算时,此时GPU0和GPU1并没有计算这些层的优化器,所以需要传给GPU2的优化器进行梯度的求和取平均,当所有的GPU都拿到对应层的梯度时,将梯度值转换为FP32梯度(防止下溢),然后更新对应层的参数。最后,每个GPU将自己更新的那部分层的参数广播给其他GPU,这样就完成了一个epoch的训练。
通讯量分析#
假设模型参数量为,GPU节点数为。 对于每一个GPU进程: 梯度收集阶段传入/传出: 参数广播阶段传入/传出: 总传入/传出: 与DDP通讯量相同。
ZeRO-2#
ZeRO-2的思路更ZeRO-1一样,只是将梯度进行了分片。
在ZeRO-1的基础上,每个GPU只维护了自己负责层的梯度值,其他操作都是一致的,进一步节省了显存。
通讯量分析#
假设模型参数量为,GPU节点数为。 对于每一个GPU进程: 梯度收集阶段传入/传出: 参数广播阶段传入/传出: 总传入/传出: 与DDP通讯量相同。
ZeRO-3#
ZeRO-3 进一步分片模型参数。在前向传播过程中,由于每个GPU维护不同层的参数,前向传播开始时,GPU1和GPU2没有前3层的参数,所以需要通过GPU0广播分发参数给他们进行计算,GPU1和GPU2计算完之后就把这些参数丢弃,从而节省了显存。
前向传播结束后,进行反向传播。同样的,反向传播开始时,GPU0和GPU1没有最后3层的参数,所以需要通过GPU2广播分发参数给他们进行计算,计算完之后就丢弃,节省缓存。

通讯量分析#
假设模型参数量为,GPU节点数为。 对于每一个GPU进程: 梯度收集阶段传入/传出: 参数广播阶段传入/传出: 总传入/传出: 是DDP传输量的1.5倍。
对比#

| 特性 / 阶段 | ZeRO-1 | ZeRO-2 | ZeRO-3 |
|---|---|---|---|
| 分片对象 | 优化器状态(如 Adam 的动量、方差) | 优化器状态 + 梯度 | 优化器状态 + 梯度 + 模型参数 |
| 显存节省效果 | 显存占用 ≈ 原始 DP 的 1/N(仅优化器部分) | 进一步降低,梯度不再全量存储 | 最大节省:每个 GPU 仅存 1/N 的参数、梯度、优化器状态 |
| 适用场景 | 中等规模模型训练(如 1B~10B) | 大模型训练(如 10B~30B) | 超大模型训练(如 70B+) |
| 通信开销 | 总通信量 ≈ 2ψ(与 DDP 相同) | 总通信量 ≈ 2ψ(与 DDP 相同) | 总通信量 ≈ 3ψ(比 DDP 高 50%) |
| 计算流程复杂度 | 低:仅在优化器更新时通信 | 中:反向后需聚合梯度 | 高:前向/反向均需动态收集/释放参数 |
| 是否支持超大模型 | ❌ 单卡仍需存完整模型 | ❌ 单卡仍需存完整模型 | ✅ 单卡只需存模型的一部分 |
| 主要优点 | - 简单易用 - 显著减少优化器显存(占大头) - 通信开销不变 | - 在 ZeRO-1 基础上进一步省显存 - 适合更大 batch size | - 实现极致显存压缩 - 可在普通 GPU 上训练千亿模型 - 支持模型并行替代方案 |
| 主要缺点 | - 模型参数仍全量复制,无法突破单卡模型容量限制 | - 同上,参数仍需全量存储 | - 通信开销显著增加 - 参数频繁 gather/scatter 引入延迟 - 实现复杂,对调度要求高 |