[Bug]: resume_from_checkpoint silently skips the root-level (trained) adapter when the checkpoint contains adapter subdirectories — DPO loss

使用 resume_from_checkpoint 从包含 PEFT adapter 子目录的 checkpoint 恢复训练时, Trainer._load_from_checkpoint 只加载子目录中的非默认 adapter(如 ref ),会静默跳过存放在 checkpoint 根目录的 d

快速结论:使用 resume_from_checkpoint 从包含 PEFT adapter 子目录的 checkpoint 恢复训练时,Trainer._load_from_checkpoint 只加载子目录中的非默认 adapter(如 ref),会静默跳过存放在 checkpoint 根目录的 default adapter,表现为不报错但 DPO loss 卡在 ln(2) ≈ 0.6931。优先检查 checkpoint 目录结构与 PEFT adapter 加载逻辑。

适用环境:transformers 5.19.0、peft 0.21.2、trl 1.15.0、datasets 5.1.0、Python 3.13.15、torch 2.14.1+cu130(CUDA 13.0 可用)、Windows 11(AMD64)、NVIDIA GeForce RTX 5060 Laptop GPU。修复分支的验证在 Windows 11 + CPU 环境完成。

最快修复方案:在 Trainer._load_from_checkpoint 的子目录循环之前,先把根目录下的 adapter 加载为 "default":model.load_adapter(resume_from_checkpoint, "default", is_trainable=(active_adapter == "default"))(仅在 adapter_weights_file 或 adapter_safe_weights_file 存在时执行)。Issue 作者在本地应用该 4 行修复后确认恢复训练正常,loss 不再卡在 0.6931。

注意事项:该修复目前以 PR #50368 形式提交,Issue 内可见的结论来自维护者与 PEFT 作者的口头认可及提交者本地验证,尚未在本文写作时确认已合并进正式版本;直接修改本地 transformers 源码后,升级或重装会覆盖改动。若你的 checkpoint 根目录根本没有 adapter 权重文件,此方案不适用。

问题场景

用户用 TRL 的 DPOTrainer 对通过 get_peft_model 包装的 PeftModel 做 DPO 训练,配置了 save_strategy='steps'、save_steps=5、precompute_ref_log_probs=True。训练中途中断后,用 resume_from_checkpoint 恢复训练,训练不报错也不警告,但 loss 始终停在 0.6931。由于 DPOTrainer 会同时保存 default 与 ref 两个 adapter,checkpoint 目录中出现了 adapter 子目录,从而触发了该加载缺陷。问题同样影响 TRL 的 GRPO、KTO、RLOO 训练器(当模型以 PeftModel 传入时)。

报错原文

[Bug]: resume_from_checkpoint silently skips the root-level (trained) adapter when the checkpoint contains adapter subdirectories — DPO loss stuck at ln(2) ≈ 0.6931, no error or warning

{'loss': '0.6931', 'grad_norm': '1.432', 'learning_rate': '0', 'entropy': '2.667', 'num_tokens': '916', 'logits/chosen': '-2.034', 'logits/rejected': '-2.021', 'mean_token_accuracy': '0.3457', 'rewards/chosen': '0', 'rewards/rejected': '0', 'rewards/accuracies': '0', 'rewards/margins': '0', 'logps/chosen': '-96.28', 'logps/rejected': '-61.6', 'epoch': '0.07143'}

原因分析

PEFT 的保存约定是:default adapter 存放在 checkpoint 根目录,其他 adapter(如 DPOTrainer 自动生成的 ref)存放在子目录中。而 Trainer._load_from_checkpoint 只要检测到 adapter 子目录存在,就会走子目录循环,只恢复子目录里的 adapter,跳过根目录的 default。

结果就是恢复训练时,实际参与训练的 adapter 没有被正确恢复,训练相当于从初始状态或错误状态继续,DPO loss 稳定在 ln(2) ≈ 0.6931。现有测试 test_multiple_peft_adapters 使用的是两个非默认 adapter 名称,两者都落在子目录里,因此没有覆盖“根目录 default + 子目录 ref”这一场景。

环境排查

  • 确认 transformers 版本(报告为 5.19.0),检查是否已包含 PR #50368 的修复;未合并前需自行打补丁。
  • 确认 peft 版本(报告为 0.21.2)及 adapter 保存布局:default 是否在根目录,ref 等是否在子目录。
  • 确认 trl 版本(报告为 1.15.0),以及训练器是 DPO、GRPO、KTO 还是 RLOO。
  • 确认 PyTorch/CUDA 环境(报告为 torch 2.14.1+cu130、CUDA 13.0、RTX 5060 Laptop GPU)与 bf16 设置;这些与根因无关,但会影响复现环境一致性。
  • 检查 checkpoint 目录内容:根目录下是否存在 adapter 权重文件及其配置,子目录中有哪些 adapter。

解决步骤

  1. 打开本地 transformers 中 Trainer._load_from_checkpoint 的实现,定位处理 adapter 子目录的循环。
  2. 在进入子目录循环之前,加入根目录 adapter 的加载逻辑:判断 adapter_weights_file 或 adapter_safe_weights_file 是否存在,若存在则调用 model.load_adapter(resume_from_checkpoint, "default", is_trainable=(active_adapter == "default"))。
  3. 保留原有子目录循环,使其继续加载 ref 等非默认 adapter,不要删除或跳过这一步。
  4. 可优先尝试参考提交者分支 fix-resume-root-adapter 中的 4 行修改与回归测试,核对你的改动是否一致。
  5. 关注 PR #50368 的合并进度,正式版本发布后升级 transformers 并移除本地补丁。

验证方法

用同一 checkpoint 再次以 resume_from_checkpoint 启动 DPO 训练,观察恢复后第一步日志:loss 不再停在 0.6931,而是接近中断前的水平,且 rewards/chosen、rewards/rejected、rewards/margins 不以全 0 形式出现(Issue 中恢复训练后确认“包括 adapter 在内的所有内容都被完整恢复”)。也可运行提交者提供的回归测试,确认打补丁前失败、打补丁后通过。

参考来源

huggingface/transformers #50204

ziywang50/transformers: fix-resume-root-adapter 分支

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 34867

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注