准确判断DDP中rank 0主进程需先检查torch.distributed.is_initialized()为True,再调用torch.distributed.get_rank()==0;单卡调试时可默认rank 0,但生产环境必须显式初始化且在模型封装前完成。
如何准确判断 DDP 中的 rank 0 主进程
DDP(DistributedDataParallel)下,torch.distributed.get_rank() 返回当前进程的全局 rank,但仅当 torch.distributed.is_initialized() 为 True 时才有效。未初始化就调用会报 RuntimeError: Default process group is not initialized。常见错误是直接在单卡调试时硬写 if rank == 0,结果一上多卡就因未 init 或 rank 混淆而跳过保存。
正确做法是:先检查分布式是否启用,再获取 rank;若未启用(如单卡 debug),可默认视为 rank 0 —— 但生产环境必须显式初始化。
- 初始化必须在模型 wrap 成
DistributedDataParallel之前完成,且所有进程需同步调用torch.distributed.init_process_group() - 推荐统一用
if torch.distributed.get_rank() == 0:判定主进程,不要依赖os.environ.get("LOCAL_RANK")或args.local_rank,它们只表示本机内 rank,跨节点不唯一 - 注意:即使在单节点多卡场景,
get_rank()仍是全局唯一的(0 ~ world_size-1),所以 rank 0 就是真正的主进程
保存模型时为什么不能只在 rank 0 保存,还要考虑多节点 checkpoint 合并
DDP 默认将模型参数分片到各 GPU,但 model.state_dict() 在每个进程里返回的仍是**完整参数字典**(只是梯度和前向被分片)。因此 rank 0 直接 torch.save(model.state_dict(), ...) 是安全的,无需额外 gather。
真正需要合并的场景是:你用了 FSDP、DeepSpeed 或自定义分片逻辑(比如把 embedding 分到不同节点),或者保存了 optimizer state(它可能含分片状态)。纯 DDP 下 optimizer 的 state_dict 也是完整的,但部分优化器(如 torch.optim.AdamW)的 state 字典中会包含 per-parameter tensor,这些 tensor 在 DDP 中未被自动同步,直接保存可能导致加载时报 shape mismatch。
- 纯 DDP 推荐保存:rank 0 保存
model.state_dict()+optimizer.state_dict()+epoch+lr_scheduler.state_dict() - 若使用
torch.compile()或torch.nn.parallel.DistributedDataParallel包裹后的模型,务必用model.module.state_dict(),否则保存的是 DDP wrapper 的 state_dict,加载时会出错 - 多节点训练时,只要 rank 0 有写权限(比如共享文件系统 NFS / Lustre),就无需“合并” —— 其他 rank 不做任何磁盘 IO 即可
保存路径冲突与文件系统注意事项
常见坑是多个 rank 同时尝试写同一个文件(比如都执行 torch.save(..., "ckpt.pth")),导致文件损坏或覆盖。即使加了 if rank == 0,如果多个节点的 rank 0 同时写同一路径(例如没加 node_id 前缀),仍会冲突。
Python 3.14.3
微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。
另一个容易忽略的点是:NFS 等共享存储对 os.path.exists() 和文件原子写入支持较弱,直接判断文件是否存在再 save 可能引发竞态。
- 始终用唯一路径:例如
f"ckpt_epoch{epoch}_rank{rank}.pth"(调试用)或仅 rank 0 写f"ckpt_epoch{epoch}.pth"(生产用) - 避免在保存前用
os.path.exists()做条件判断;如需覆盖保护,改用try/except OSError捕获写权限异常 - 若用 Slurm,建议通过
SLURM_NODEID或HOSTNAME构建路径前缀,确保跨节点隔离 - 云环境(如 AWS S3)不支持直接
torch.save,需先 save 到本地临时路径,再上传
加载时 rank 0 保存的模型如何在单卡/多卡环境通用加载
rank 0 保存的 state_dict 是完整参数,加载时无论单卡还是多卡,只要模型结构一致,就能直接 load。但要注意 DDP wrapper 层带来的 key 前缀差异:DDP 模型的 state_dict keys 会带 module. 前缀,而单卡模型没有。
最稳妥的方式是在保存和加载两端都统一处理 key 前缀,而不是依赖加载时自动适配。
- 保存时用
model.module.state_dict()(DDP 下)或model.state_dict()(单卡),确保 keys 无module.前缀 - 加载时若遇到
Missing key或Unexpected key,先打印state_dict.keys()和模型model.state_dict().keys()对比,再用{k.replace("module.", ""): v for k, v in sd.items()}清洗 - 别依赖
strict=False隐藏问题——它可能掩盖关键参数未加载的 bug - 验证加载后参数一致性:可在加载后对几个 layer.weight 取 mean,对比保存前的值,确认数值未漂移
DDP 模型保存的核心其实是「谁写、写什么、写哪、怎么读」四个动作的精确控制,而不是「能不能保存」的问题。最容易被绕进去的地方,是把 DDP 当成黑盒,却忘了它底层仍是 PyTorch 的常规 state_dict 机制 —— 所有复杂性都来自分布式调度引入的时机、范围和可见性约束。