不能,但只需补上初始化、封装、同步和启动四步;须调用init_process_group、用DistributedDataParallel封装、DistributedSampler采样、torchrun启动并配对NCCL环境变量。

PyTorch单机代码能直接跑DDP吗?不能,但改得很少
绝大多数单机 nn.Module + torch.optim + DataLoader 代码,只要没硬编码 device='cuda:0' 或绕过 torch.distributed 手动管理 GPU,就能在 DDP 下几乎不改逻辑地运行。关键不是重写模型,而是补上初始化、封装、同步和启动三件事。
常见错误现象:RuntimeError: Default process group is not initialized、NCCL operation failed: unhandled system error、GPU 0 占用远高于其他卡——基本都源于初始化顺序或 NCCL 环境没对齐。
- 必须在每个进程里调用
torch.distributed.init_process_group(),且必须早于任何张量创建或模型实例化 -
model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[local_rank])中的device_ids必须是单元素列表,不能是[0,1]或空 -
DataLoader必须配sampler=torch.utils.data.DistributedSampler(dataset, shuffle=True),且训练循环中要调用sampler.set_epoch(epoch)
torchrun 启动时怎么传参?别碰 --nproc_per_node 和 --nnodes 的坑
torchrun 是目前最稳的启动方式,它自动处理 master 地址发现、进程拉起和信号转发。但参数组合极易出错:
- 单机多卡:用
--nproc_per_node=4,不要设--nnodes(默认为 1),也别手动设--master_addr和--master_port——torchrun会自动生成 - 多机训练:只在主节点运行
torchrun --nnodes=2 --nproc_per_node=4 --node_rank=0 ...,从节点用--node_rank=1,且所有节点必须时间同步、端口互通、SSH 免密打通 - 绝对不要同时指定
--nproc_per_node和CUDA_VISIBLE_DEVICES环境变量——torchrun自动按 rank 绑定可见卡,手动设会导致 rank 0 看到全部卡,rank 1 看不到卡
典型命令:torchrun --nproc_per_node=4 train.py --epochs 10 --batch_size 64。你的 train.py 需通过 int(os.environ["LOCAL_RANK"]) 获取本进程对应 GPU ID。
NCCL 配置为什么总失败?重点盯住这三项环境变量
NCCL 是 DDP 默认后端,但它对网络和驱动极其敏感。90% 的通信失败不是代码问题,而是环境没对齐:
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
-
export NCCL_SOCKET_IFNAME=ib0(InfiniBand)或enp1s0f0(以太网)——必须显式指定物理网卡名,不能依赖默认路由;用ip addr确认真实接口名 -
export NCCL_IB_DISABLE=1(如果没 IB 卡)或export NCCL_IB_DISABLE=0(有 IB 卡但被禁用)——很多云主机默认禁用 IB,导致 fallback 到慢速 TCP -
export NCCL_BLOCKING_WAIT=1——让 NCCL 在通信失败时阻塞报错,而不是静默超时重试,方便定位是哪一步挂了
性能影响明显:NCCL_IB_DISABLE=1 在万兆以太网上可能使 all-reduce 延迟增加 3–5 倍;NCCL_SOCKET_IFNAME 错误则直接卡死在 init 阶段,日志里只显示 timed out。
DDP 封装后 loss.backward() 还需要 .mean() 吗?不需要,但得小心梯度缩放
DDP 内部已对各卡梯度做 all-reduce 求和,所以你看到的 loss 是单卡 loss,不是全局 batch 的平均值。如果你原来单机代码用了 loss.mean(),DDP 下仍要保留——因为 DDP 不改变 loss 计算逻辑,只改梯度同步方式。
真正容易忽略的是梯度缩放(gradient scaling)与 DDP 的交互:
- 用
torch.cuda.amp.GradScaler时,scaler.scale(loss).backward()正常用,DDP 会自动缩放后同步梯度 - 但
scaler.step(optimizer)必须在所有卡上同步执行,否则某卡 optimizer.step() 被跳过会导致权重不一致——torchrun保证进程同步,这点不用额外处理 - 切记:DDP 模型的
forward()输出仍是单卡结果,不要在 forward 里做 world-size 相关除法,那会破坏梯度流
最隐蔽的坑:有些用户在 DDP 外层又套了 torch.nn.DataParallel,或者用 model.to('cuda') 而不是 model.to(local_rank),导致模型参数未绑定到正确设备,后续 all-reduce 时张量 device 不匹配直接报错。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










