已关闭
[Bug]: FSDP2 resume 时 optimizer 状态 save/load 标志不对称导致静默发散 #621
iceflysnow创建于  8月14日关闭于  9月18日
iceflysnow
8月14日 创建

问题概述

FSDP2 训练在 resume 时,optimizer 状态的 save/load 由 no_save_optim 与 no_load_optim 两个独立标志控制,二者之间没有任何一致性校验。当存盘时 no_save_optim=true(不保存 optimizer)、而 resume 时 no_load_optim=false(请求加载 optimizer)时,由于 DCP load 采用 allow_partial_load(非 strict),源 checkpoint 中不存在 optimizer 分片会被静默跳过,optimizer 保持全新初始化状态(AdamW 的 exp_avg / exp_avg_sq 为零),resume 后训练从冷启动 Adam 开始,可能造成发散且无任何告警。

对照 RNG 状态路径:save 端(train_engine.py:423)写 torch_rng_state,load 端(:387-391)有显式守卫——if "torch_rng_state" not in state["extra_state"]: print_rank(..., "No RNG state found in checkpoint, skipping RNG loading")。optimizer 路径没有对等的守卫,形成 save/load 不对称。

复现

环境:Ascend 910B2 ×8,CANN 8.5.2,master HEAD f9b6ad7d,Qwen3.5-0.8B,FSDP2。

  1. Phase A(存盘,不保存 optimizer):no_save_optim: true、no_load_optim: true(从 HF 权重冷启动),训练 2 iter 存盘。检查 checkpoint 元数据:optimizer 张量数 = 0(no_save_optim=true 确实未写入 optimizer 分片)。
  2. Phase B(resume,请求加载 optimizer):no_load_optim: false,从 Phase A 的 checkpoint resume。日志显示 Loaded checkpoint from .../iter_0000002,且 RNG 路径触发 No RNG state found in checkpoint, skipping RNG loading(因 Phase A 同时 no_save_rng=true);但 optimizer 路径无任何告警,optimizer 静默停留在全新初始化状态。

期望 / 实际

  • 期望:resume 时若请求加载 optimizer 而源 checkpoint 未保存 optimizer,应像 RNG 路径一样打印告警,提示 optimizer 将从冷启动开始、可能导致发散。
  • 实际:静默跳过,无告警,optimizer 状态为零,训练在不知情下从冷启动 Adam 继续。

根因

mindspeed_mm/fsdp/train/train_engine.py:

  • save(:421-422):if not args.training.no_save_optim: state["optimizer"] = self.optimizer —— no_save_optim=true 时不写入 optimizer。
  • load(:367-368):if not args.training.no_load_optim: state["optimizer"] = self.optimizer —— 把 optimizer 占位放入 state 后交由 DCP load 填充;DCP allow_partial_load(非 strict)在源分片缺失时静默跳过,无日志无异常。
  • RNG 路径(:387-391)有 not in 守卫 + 告警;optimizer 路径无对等守卫。

两个标志独立、无一致性校验 + DCP 静默跳过,共同导致 resume 静默发散。

修复建议

在 save 端记录 saved_optim 标志到 extra_state,在 load 端仿照 RNG 守卫检测 save/load 不对称并告警:

  • save(:422 之后):state["extra_state"]["saved_optim"] = not args.training.no_save_optim
  • load(RNG 守卫之后):if not args.training.no_load_optim: if not state["extra_state"].get("saved_optim", True): print_rank(logger.warning, "Optimizer state was not saved ...")

get(..., True) 默认 True,对修复前已存的 checkpoint 向后兼容(不会误报)。

likedislike
ascend-robotascend-robot成员
8月14日 添加了label:bug
Iiceflysnow
8月14日 关联了pull request:fix(train): warn on optimizer save/load asymmetry to prevent silent resume divergence
iceflysnow
9月3日 评论:

已提交针对该问题的修复PR,请审核:https://gitcode.com/Ascend/MindSpeed-MM/merge_requests/2983 (在 save/load 两侧增加 optimizer 存盘/加载不对称的告警,兼容旧 checkpoint 行为)

likedislike
yeqm成员
9月15日 评论:

感谢您同步 PR 进展。该 PR 当前仍开放,可继续关注评审意见与合入进展。

likedislike
LKONELKONE成员
9月17日 将 wanglikai1019 设为负责人
LKONELKONE成员
9月18日 添加了label:resolved
LKONE
LKONE成员
9月18日 评论:

您好,此issue已经超过一周没有更新,且关联PR已合入,现先将issue关闭,有需要可以重新打开,谢谢!

likedislike
LKONELKONE成员
9月18日 issue状态由 TODO 改变为 DONE
LKONELKONE成员
9月18日 关闭了 issue