ddp_setup() 必须在模型加载前调用,否则因通信组未初始化而报错;正确顺序为:读取local_rank→set_device→init_process_group→model.to(device)→ddp实例化。

ddp_setup() 必须在模型加载前调用
不初始化通信组就创建 DDP 模型,会直接报错 RuntimeError: Default process group is not initialized。PyTorch 2.x 不再容忍“先建模后组网”的写法,init_process_group() 必须早于任何 DDP 实例化和模型参数移动。
常见错误是把 model.to(device) 放在 ddp_setup() 前——这会让模型参数意外加载到 GPU:0(即使 LOCAL_RANK=3),后续 DDP(model, device_ids=[local_rank]) 会因设备不匹配崩溃。
- 正确顺序:读取
LOCAL_RANK→torch.cuda.set_device(local_rank)→init_process_group(...)→model.to(device)→DDP(model, device_ids=[local_rank]) - PyTorch 2.x 默认启用
torch.compile(),若开启,请确保编译发生在DDP包装之后,否则会触发UnsupportedAliasError - 单机多卡时,
MASTER_ADDR和MASTER_PORT可省略(torchrun自动注入),但手动启动时必须显式设置,否则卡在连接等待
DistributedSampler 的 shuffle 参数要分训练/验证设
DistributedSampler 默认开启 shuffle=True,这对训练数据是必要的——它保证每张卡看到不同子集;但验证阶段若也开启,会导致各卡评估样本不一致,最终 dist.all_reduce() 汇总指标时出现不可复现的波动。
验证集应显式传入 shuffle=False,否则即使你手动打乱了 val_dataset,DistributedSampler 仍会在每个 epoch 重排索引,破坏验证逻辑。
- 训练:
train_sampler = DistributedSampler(train_dataset, shuffle=True) - 验证:
val_sampler = DistributedSampler(val_dataset, shuffle=False) - 注意
DataLoader的drop_last=True在训练中建议开启,避免最后一轮 batch size 不均导致all_reduce同步失败
loss 和 metric 必须用 reduce_tensor() 手动同步
每个 GPU 上计算出的 loss 或准确率只是局部值。直接打印或记录会导致日志里出现 8 个重复但数值不同的 loss 曲线——这不是 bug,是 DDP 的正常行为。你不做同步,rank=0 进程看到的仍是自己那块卡的 loss。
PyTorch 不自动聚合标量,必须显式调用 dist.all_reduce()。官方推荐封装为 reduce_tensor() 函数,且注意除法要在 all_reduce 之后做,否则精度丢失(尤其是 float16 训练)。
- 示例:
loss = reduce_tensor(loss, world_size)内部执行dist.all_reduce(loss, op=dist.ReduceOp.SUM)后再除以world_size - 验证阶段的 accuracy 若基于
torch.eq(pred, label).sum().item(),需先转成tensor再同步,不能对 Python scalar 调用all_reduce - 使用
torch.no_grad()下的验证循环里,记得在all_reduce前加torch.cuda.synchronize(),防止异步执行导致值未就绪
torchrun 启动比 python -m torch.distributed.launch 更可靠
torch.distributed.launch 已在 PyTorch 2.0+ 中被标记为 deprecated,运行时可能抛出 FutureWarning,且在多机场景下对 NCCL_ASYNC_ERROR_HANDLING 支持不完整,容易静默失败。
torchrun 是官方推荐替代方案,它自动管理环境变量(RANK、WORLD_SIZE、MASTER_ADDR 等),支持故障恢复(--max-restarts),并内置健康检查。尤其在 A100/V100 集群上,torchrun 能更好适配 NCCL_IB_DISABLE=0 等底层配置。
- 单机 4 卡启动命令:
torchrun --nproc_per_node=4 --master_port=29500 train.py - 务必确认
train.py入口函数不依赖全局变量初始化模型(如if __name__ == "__main__": main()),否则多进程下会重复加载数据集 - 调试时加
--rdzv_backend=c10d --rdzv_endpoint=localhost:29400可绕过 DNS 解析问题,适合离线环境
torch.cuda.empty_cache() 的位置——它不能放在 ddp_setup() 前,也不该在每个 epoch 结尾无差别调用。真正关键的释放点是在 cleanup_ddp() 之前,且仅对 rank=0 执行,否则多进程竞争会引发 CUDA context 错误。











