根本原因是各进程未加载同一份初始权重,ddp只同步梯度不负责参数初值对齐;必须在init_process_group后、构建ddp前,由rank==0加载权重并广播,或所有进程同步加载并用barrier保证时序。

为什么torch.nn.parallel.DistributedDataParallel启动后模型参数不一致?
根本原因不是DDP本身失效,而是初始化和数据分发没对齐。DDP默认不做模型参数广播(broadcast),它依赖torch.distributed.init_process_group完成后的首次model = DDP(model)触发同步——但前提是所有进程加载的是**同一份初始权重**。
常见错误是:主进程加载预训练权重后直接传给DDP,而其他进程用随机初始化的模型进DDP,导致各卡参数从第一步就不同。
- 必须在
init_process_group之后、构建DDP之前,由rank == 0加载权重,再用torch.distributed.broadcast或model.load_state_dict(..., strict=False)配合torch.distributed.barrier()确保同步 - 更稳妥的做法是:所有进程都从同一文件加载,但加
if rank == 0: download_checkpoint(); dist.barrier(); else: dist.barrier(); load_checkpoint() - 别依赖
DDP自动同步初始化——它只同步forward/backward时的梯度,不负责参数初值对齐
torch.utils.data.distributed.DistributedSampler怎么避免数据重复或漏采?
关键不在sampler本身,而在DataLoader的shuffle和drop_last配合方式。DistributedSampler内部按world_size切分数据集,每个进程拿到一个子集;但如果shuffle=True且没重置sampler的epoch,多轮训练会复用同一打乱顺序。
- 必须在每个epoch开始前调用
sampler.set_epoch(epoch),否则各卡看到的数据顺序固定,等效于没shuffle -
drop_last=True建议始终开启:避免最后一batch样本数不足world_size倍数,导致某些卡无数据可算、all_reduce卡死 - 验证阶段如果用
drop_last=False,务必确保各卡batch数一致——可通过len(dataset) % world_size == 0校验,否则需手动pad或调整batch_size
梯度同步失效的三个典型信号和定位方法
现象包括:loss震荡剧烈、各卡grad.norm()差异超1e-3、训练精度远低于单卡。这不是DDP配置错,而是底层通信或计算图被意外破坏。
- 检查是否在forward中用了
torch.no_grad()或detach()截断了部分分支——DDP只同步参与反向传播的叶子节点梯度 - 确认优化器step前没调用
optimizer.zero_grad(set_to_none=True)以外的清零方式,set_to_none=False(默认)会导致历史梯度残留干扰all_reduce - 打印
model.parameters().__next__().grad在各rank的值,若某卡为None,说明该参数未参与当前batch计算(如条件分支未覆盖)
混合精度训练下torch.cuda.amp.GradScaler和DDP的协作要点
GradScaler本身不感知分布式,但它缩放后的梯度必须在unscale前完成跨卡同步,否则各卡用不同scale更新,模型迅速发散。
- 必须把
scaler.scale(loss).backward()放在DDP模型的forward之后,且scaler.unscale_(optimizer)要在optimizer.step()之前——DDP的梯度同步发生在backward()末尾,此时梯度已是scaled状态 - 不要在
backward()后手动torch.distributed.all_reduce梯度——DDP已接管,重复操作会损坏梯度 - 启用
enabled=False调试时,记得同步关闭scaler,否则scaler.step()仍会尝试unscale空梯度,报ValueError: No gradients were found
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











