DDP(DistributedDataParallel)
2026/7/23大约 4 分钟
DDP 的全称是 DistributedDataParallel(分布式数据并行)。它是 PyTorch 原生提供的、整个深度学习界最经典、最基石,也最稳如老狗的多卡分布式训练框架。
用一句话概括它干的事:每个 GPU 都在做完全一样的填空题(相同的模型结构),但它们分到了不同的试卷(不同的数据 Batch),最后大家通过 NCCL 交流答案,保持步调一致。
DDP 是怎么协同工作的?(经典单步循环)
你可以把 DDP 的运行流程拆解为以下四个干净利落的步骤:
- 带薪复刻 (Initialization & Replication):
在训练开始前,DDP 会在底层启动多个独立的 Python 进程(通常是一个 GPU 绑定一个进程,彻底摆脱 Python GIL 全局解释器锁的折磨)。然后,它把整份模型权重完整地复制(Replicate)到每一张 GPU 的显存里。 - 各吃各的数据 (Forward Pass):利用
DistributedSampler,DDP 把海量的训练数据切开。比如当前的 Global Batch Size 是 128,有 4 张卡,那么每张卡只拿 32 个样本。各个 GPU 独立跑自己的前向传播,算出各自的 Loss。 - 边算边对答案 (Backward Pass & Overlap):每张卡开始反向传播算梯度。重点来了:因为大家吃的数据不一样,算出来的梯度自然也不同。
为了让模型权重保持一致,GPU 之间必须调用底层的 NCCL 发起AllReduce通信,把所有卡上的梯度加起来求一个平均值。 - 同步更新 (Optimizer Step):拿到全局平均梯度后,每张卡上的优化器(如 AdamW)各自执行
optimizer.step()更新自己卡上的权重。因为起点的权重一样,中间对过答案的梯度也一样,更新完后,所有卡上的模型依旧保持高度同步,迎接着下一个 Batch。
站在 SRE 视角:DDP 强在哪里?为什么不用传统的 DP?
很多刚接触 PyTorch 的同学会看到两个极其相似的东西:DataParallel (DP) 和 DistributedDataParallel (DDP)。在生产环境中,SRE 会无条件勒令算法同学把 DP 统统改写成 DDP。
原因有两点极其硬核的技术代差:
1. 进程模型:单进程多线程 (DP) vs 多进程 (DDP)
- DP(过时的垃圾): 它是单进程多线程的。由于 Python GIL 锁的存在,CPU 实际上在不断地在多个线程间切换,根本无法真正并行。更痛苦的是,它有一个“主卡”的概念,主卡要负责分发数据和收集结果,导致主卡显存经常率先被撑爆(OOM),其他卡在干等,显卡利用率极低。
- DDP(现代的标准): 它是多进程的,每张卡一个独立的进程,互不干扰,完全没有 GIL 锁的瓶颈。它没有绝对的主卡,数据分发是去中心化的,显存分配极其均匀。
2. 核心压榨技术:梯度分桶 (Gradient Bucketing)
这是 DDP 能够跑满网络带宽的底层核心。
DDP 在反向传播时,绝对不会傻傻地等整张网络(比如全部 100 层)的梯度全部算完才去发起 NCCL 通信。
- 它在底层把模型的参数倒序编排成一个一个的“桶 (Buckets)”(默认大小一般为 25MB)。
- 当反向传播从输出端往回算,只要后面几层算出的梯度刚好填满了一个 25MB 的桶,DDP 就会立刻、悄悄地调用 NCCL 异步发起这个桶的
AllReduce梯度同步。 - 与此同时,GPU 还在继续往前算前面几层的梯度!这就是我们一直在强调的 Overlap(计算与通信重叠)。这种精妙的机制把网络通信的时间几乎完美地“藏”进了计算时间里。
总结 DDP 的局限性
DDP 固然完美,但它有一个致命的物理前提:单张 GPU 的显存,必须能塞得下“整个模型”加上“一倍的优化器状态”。
- 假如你在跑一个 7B(70亿参数)的模型,FP16 精度下模型本身只有 14GB,单卡 80G 的 A100 可以轻松用 DDP 跑起来。
- 但如果你要跑的是一个 70B 的巨型模型,单卡显存连模型本身都加载不进去,DDP 就彻底瘫痪了。这时候,你就必须把 DDP 升级成我们前面学的 FSDP(分片数据并行) 或者 HSDP。
可以说,DDP 是所有现代分布式并行的“老祖宗”。弄懂了 DDP 的梯度分桶和 NCCL 动作,你再去理解那些更高级的切分策略就会像顺水推舟一样容易。
你目前参与开发或运维的 AI 服务中,业务模型(比如正在调优的 Bot 或者是图像、文本模型)大到需要上 FSDP 切分了,还是说用最经典的 DDP 就能轻松装下?
