
1. PyTorch分布式训练基础概念当你面对海量数据和复杂模型时单卡训练就像用吸管喝珍珠奶茶——效率低还容易堵住。PyTorch的分布式数据并行DDP就是给你的超级吸管让多张GPU协同工作。先理解几个核心概念rank相当于进程身份证号rank 0是主进程其他进程依次编号。比如4卡训练时rank就是0到3local_rank每台机器内部的GPU编号比如两台8卡机器上第二台机器的local_rank也是0到7world_size全局GPU总数8卡单机就是8两台8卡机器就是16我刚开始用DDP时经常混淆rank和GPU编号。其实它们没有强制绑定关系但约定俗成会让一个rank对应一块GPU。通信后端backend的选择也有讲究# GPU训练选NCCLCPU训练选Gloo dist.init_process_group(nccl) # NVIDIA的优化通信库2. 环境初始化实战初始化分布式环境就像组织多人会议需要确定会议室地址master_addr和门牌号master_port。推荐两种设置方式2.1 TCP初始化方式import os os.environ[MASTER_ADDR] 192.168.1.1 # 主节点IP os.environ[MASTER_PORT] 29500 # 任意空闲端口 dist.init_process_group(nccl, rankrank, world_sizeworld_size)2.2 ENV初始化方式更简单的做法是直接读取环境变量dist.init_process_group(nccl, init_methodenv://)实测发现在多机训练时TCP方式更稳定。曾经踩过一个坑防火墙阻塞了默认端口导致节点间无法通信换成高端口号如29500-29599后解决。3. 数据分布式处理技巧DDP的核心思想是数据并行就像把披萨切成多块分给不同人。DistributedSampler是关键from torch.utils.data.distributed import DistributedSampler sampler DistributedSampler( dataset, num_replicasworld_size, rankrank, shuffleTrue, seed42 # 保证各进程初始打乱顺序一致 ) dataloader DataLoader(dataset, batch_size64, samplersampler)注意每个epoch前要调用sampler.set_epoch(epoch)否则每次分到的数据顺序相同我在早期项目中漏掉这行导致模型收敛异常。4. 模型包装与梯度同步用DistributedDataParallel包装模型后魔法就开始了model MyModel().cuda() ddp_model DistributedDataParallel( model, device_ids[local_rank], output_devicelocal_rank )它的工作原理是前向传播时各卡独立计算反向传播时自动聚合梯度All-Reduce各卡用平均梯度更新参数实用技巧通过model.module访问原始模型。有次我需要提取中间层特征直接调ddp_model.features报错改用ddp_model.module.features才成功。5. 训练启动的三种姿势5.1 手动启动适合调试# 终端1 python train.py --rank 0 --world_size 2 # 终端2 python train.py --rank 1 --world_size 25.2 mp.spawn方式推荐单机import torch.multiprocessing as mp def train(rank, world_size): # 训练代码 if __name__ __main__: world_size torch.cuda.device_count() mp.spawn(train, args(world_size,), nprocsworld_size)5.3 torchrun方式适合生产# 单机多卡 torchrun --nproc_per_node4 train.py # 多机多卡主机 torchrun --nnodes2 --nproc_per_node4 --node_rank0 --master_addrhost1 train.py # 从机 torchrun --nnodes2 --nproc_per_node4 --node_rank1 --master_addrhost1 train.py最近项目中使用torchrun时遇到个典型问题没设置--master_port导致端口冲突。建议在脚本中添加自动检测def find_free_port(): import socket with socket.socket() as s: s.bind((, 0)) return s.getsockname()[1]6. 模型保存与加载策略分布式训练下保存模型要注意只需rank 0保存即可避免重复写入加载时要映射到当前GPUif rank 0: torch.save(ddp_model.module.state_dict(), model.pth) # 所有进程都需执行的加载代码 map_location fcuda:{local_rank} state_dict torch.load(model.pth, map_locationmap_location) ddp_model.module.load_state_dict(state_dict)7. 分布式评估实现评估时需要聚合各进程结果常用all_gatherdef gather_tensors(tensor): output [torch.zeros_like(tensor) for _ in range(world_size)] dist.all_gather(output, tensor) return torch.cat(output, dim0) # 使用示例 all_preds gather_tensors(preds) all_labels gather_tensors(labels) if rank 0: accuracy (all_preds all_labels).float().mean()8. 性能优化锦囊梯度累积小批量数据时累积梯度再更新with ddp_model.no_sync(): # 前N-1次不同步 loss.backward() # 最后一次同步 loss.backward() optimizer.step()通信优化调整bucket_cap_mb参数默认25MBddp_model DDP(model, bucket_cap_mb50)混合精度搭配AMP使用from torch.cuda.amp import autocast with autocast(): outputs ddp_model(inputs)9. 常见报错与解决CUDA out of memory尝试减小batch_size使用gradient_checkpointingNCCL错误NCCL_WARN Failed to open libibverbs.so添加环境变量解决os.environ[NCCL_IB_DISABLE] 1死锁问题 确保所有进程执行相同操作避免条件分支10. 完整代码模板import torch import torch.distributed as dist from torch.nn.parallel import DistributedDataParallel as DDP def setup(rank, world_size): dist.init_process_group(nccl, rankrank, world_sizeworld_size) torch.cuda.set_device(rank) def cleanup(): dist.destroy_process_group() class Trainer: def __init__(self, rank, world_size): self.rank rank self.model Model().to(rank) self.ddp_model DDP(self.model, device_ids[rank]) self.optimizer torch.optim.Adam(self.ddp_model.parameters()) def train(self, dataloader): for epoch in range(epochs): dataloader.sampler.set_epoch(epoch) for batch in dataloader: inputs, labels batch outputs self.ddp_model(inputs.to(self.rank)) loss F.cross_entropy(outputs, labels.to(self.rank)) loss.backward() self.optimizer.step() self.optimizer.zero_grad() def main(rank, world_size): setup(rank, world_size) trainer Trainer(rank, world_size) dataloader get_dataloader(rank, world_size) trainer.train(dataloader) cleanup() if __name__ __main__: world_size torch.cuda.device_count() torch.multiprocessing.spawn(main, args(world_size,), nprocsworld_size)