Python怎么在PyTorch DDP中正确保存模型_判定rank0主进程与多节点文件合并

2026-05-24日常编程201407

准确判断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。

真正需要合并的场景是:你用了 FSDPDeepSpeed 或自定义分片逻辑(比如把 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_NODEIDHOSTNAME 构建路径前缀,确保跨节点隔离
  • 云环境(如 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 keyUnexpected 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 机制 —— 所有复杂性都来自分布式调度引入的时机、范围和可见性约束。

本文地址:https://www.ufcn.cn/tutorials/3427869.html

如非特殊说明,本站内容均来自于网友自主分享,概不代表本站观点,如有任何问题我们都将在收到反馈后的第一时间进行处理!