尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MMCV 1.6.0 之 Runner/Hook/EMAHook:模型 EMA 权重更新链路拆解与可复现配置

MMCV 1.6.0 之 Runner/Hook/EMAHook:模型 EMA 权重更新链路拆解与可复现配置 1. MMCV 1.6.0 里 EMAHook 到底在什么时候动权重如果你正在用 MMDetection 或 MMSegmentation 训练检测/分割模型大概率在 config 里见过custom_hooks[dict(typeEMAHook)]这一行。很多人加完就完事了但心里其实没底EMA 到底在哪一步更新更新的是哪份权重验证时用的是原始权重还是 EMA 权重为什么我加了 EMA 之后 mAP 反而掉了这篇就把 MMCV 1.6.0 里 Runner 驱动 Hook 的调用时序拆开重点讲 EMAHook 的生效路径并给出一份可以直接复制进 config 的配置最后用日志和权重对比来验证 EMA 是否真的按 interval 在更新。核心检索词就是 MMCV EMAHook 权重更新链路适合正在调训练超参、想让模型更稳的同学。先说结论性的机制MMCV 1.6.0 的 EMAHook 在before_run阶段给模型每个参数注册一个同形状的 buffer命名规则是ema_加参数名点号换成下划线。训练过程中after_train_iter按interval决定这次要不要更新 buffer每个 epoch 结束时after_train_epoch会把模型参数和 EMA buffer 做一次交换让 EvalHook 用 EMA 权重去验证验证完before_train_epoch再换回来。所以训练本身用的始终是原始权重EMA 只影响验证和测试。理解这个「交换」是理解 EMAHook 的关键。很多人以为 EMA 是训练时直接改权重其实不是。它维护的是一份影子副本只在评估窗口临时顶替。这也解释了为什么 EMA 不会拖慢训练收敛但会让验证曲线更平滑。下面按「问题场景 → 前置准备 → 可复制配置 → 验证请求 → 报错排查 → 工具入口」的顺序展开每一步都给到能直接跑的命令或代码。2. 训练侧调参场景与 TaoToken 前置准备在讲配置之前先明确一个现实问题训练侧调参往往需要反复试 momentum、interval、warm_up 这几个值而本地 GPU 资源有限很多时候要借助远程模型服务来辅助分析日志、生成对比脚本或者用 coding agent 帮你改 config。这时候一个稳定的模型 API 入口就很实用。TaoToken 在这里的角色是提供统一的模型调用入口方便你在调参过程中用对话模型解释报错、用编码模型生成权重对比脚本。它本身不参与训练也不碰你的数据只是把模型调用这件事变得省心。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 。如果你只是想在本地跑通 EMAHook其实不需要任何外部服务PyTorch MMCV 就够了。但如果你要边调参边让模型帮你读日志、写对比脚本可以先把 key 准备好。获取 key 的路径是控制台的 API Keys 页面https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。拿到 key 之后接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有 Base URL 和鉴权方式的说明。这里要强调一点TaoToken 是合规的模型调用入口不是任何形式的网络中转工具。你用它来辅助分析训练日志、生成配置片段是合适的但训练本身的算力还是在你自己的机器或集群上。前置准备清单Python 3.8PyTorch 1.8MMCV 1.6.0 对 torch 版本有要求建议 1.9~1.11mmcv-full 1.6.0安装命令pip install mmcv-full1.6.0 -f https://download.openmmlab.com/mmcv/dist/cu113/torch1.10/index.html按你的 CUDA 和 torch 版本替换一个能跑起来的 MMDetection 或 MMSegmentation 工程可选TaoToken key用于辅助分析装好之后先用python -c import mmcv; print(mmcv.__version__)确认版本是 1.6.0。版本不对的话EMAHook 的 buffer 命名和交换逻辑会有差异后面验证会对不上。3. 可复制配置EMAHook 在 config 中的完整片段MMCV 1.6.0 的 EMAHook 定义在mmcv/mmcv/runner/hooks/ema.py注册名就是EMAHook。它的构造参数有四个momentum默认 0.0002、interval默认 1、warm_up默认 100、resume_from默认 None。注意源码里有一行self.momentum momentum**interval也就是说你传的 momentum 会先做 interval 次幂再用于实际更新。这个细节很多人忽略导致 interval 调大后 EMA 变化比预期慢很多。下面是一份可以直接放进 MMDetection config 的片段。假设你的 config 里已经有custom_hooks字段没有的话新增即可# configs/_base_/schedules/ema_schedule.py custom_hooks [ dict( typeEMAHook, momentum0.0002, # 基础动量实际使用时会做 momentum**interval interval1, # 每 1 次迭代更新一次 EMA buffer warm_up100, # 前 100 步用更小的动量避免早期抖动 resume_fromNone, # 断点续训时填 checkpoint 路径 ) ]如果你用的是 MMSegmentation写法完全一样因为都走 MMCV 的 Hook 注册机制。把这段放进你的主 config或者用_base_继承进来_base_ [ ../_base_/schedules/ema_schedule.py, # ... 其他 base ]对于想用 MMEngine 风格新版 OpenMMLab的同学配置长这样注意参数名和 MMCV 1.6.0 不同custom_hooks [ dict( typeEMAHook, ema_typeExponentialMovingAverage, momentum0.0002, interval1, update_buffersFalse, begin_iter0, ) ]这里要提醒MMCV 1.6.0 的 EMAHook 和 MMEngine 的 EMAHook 是两套实现。前者在mmcv.runner.hooks后者在mmengine.hooks。如果你混用会出现KeyError: EMAHook或者参数不识别的问题。判断方法很简单看你的 Runner 是mmcv.runner.Runner还是mmengine.runner.Runner。MMDetection 2.x 用前者MMDetection 3.x 用后者。参数对照表方便你调参时心里有数参数MMCV 1.6.0 默认作用调参建议momentum0.0002基础动量实际为 momentum**interval检测任务常用 0.0001~0.001interval1每多少次迭代更新 EMA显存紧张可调大但别超过 8warm_up100前多少步用更小动量小数据集可调小到 50resume_fromNone续训时加载 EMA 权重断点续训必填配置写好后启动训练python tools/train.py configs/your_config.py --work-dir work_dirs/ema_test启动日志里如果看到Register ema_xxx as buffer之类的信息取决于日志级别说明 EMAHook 的before_run已经执行buffer 注册成功。4. 验证 EMA 是否按预期更新日志与权重对比配置加完不代表 EMA 真的生效了。我见过不少人 config 写对了但因为 Hook 优先级或者 Runner 版本问题EMA 根本没跑。下面给两个验证动作一个看日志一个直接对比权重。4.1 用日志确认调用时序MMCV 1.6.0 的 Hook 调用顺序由priority决定。EMAHook 的优先级是NORMAL而 EvalHook 和 CheckpointSaverHook 的优先级也是NORMAL但 EMAHook 在注册顺序上更靠前所以after_train_epoch里它会先执行交换EvalHook 再评估。你可以在after_train_iter里临时加一行打印来确认# 临时调试用验证完删掉 def after_train_iter(self, runner): curr_step runner.iter if curr_step % 50 0: print(f[EMA-DEBUG] iter{curr_step}, interval{self.interval}, fmomentum{self.momentum}) # ... 原有逻辑更省事的办法是直接看训练日志里的验证指标。如果 EMA 生效验证 mAP 通常会比不加 EMA 时更平滑波动更小。你可以跑两组对比一组带 EMAHook一组不带看验证曲线的方差。4.2 用权重对比脚本确认数值更新日志只能证明 Hook 被调用了不能证明 buffer 数值真的在变。写个小脚本在训练中途 dump 一次模型参数和 EMA buffer对比差异import torch from mmcv import Config from mmcv.runner import build_runner cfg Config.fromfile(configs/your_config.py) runner build_runner( cfg, default_argsdict(work_dirwork_dirs/ema_check) ) # 手动跑几步观察 EMA buffer 变化 model runner.model ema_buffers_before { name: buf.clone() for name, buf in model.named_buffers() if name.startswith(ema_) } # 这里假设你已经跑了几步训练或者手动调用 hook # 对比前后差异 for name, buf in model.named_buffers(): if name.startswith(ema_): diff (buf - ema_buffers_before[name]).abs().sum().item() print(f{name}: diff{diff:.6f})如果 diff 全是 0说明 EMA 没更新检查interval和runner.iter的关系。MMCV 1.6.0 的判断是if curr_step % self.interval ! 0: return注意curr_step从 0 开始所以 interval1 时每步都更新interval2 时在第 0、2、4... 步更新。还有一个更直接的验证训练结束后checkpoint 里会同时保存原始权重和 EMA 权重。加载 checkpoint 对比import torch ckpt torch.load(work_dirs/ema_test/latest.pth, map_locationcpu) state ckpt[state_dict] ema_keys [k for k in state if k.startswith(ema_)] print(fEMA buffer 数量: {len(ema_keys)}) # 对比某个参数和它的 EMA 副本 for k in state: if k.startswith(ema_backbone): origin_key k.replace(ema_, ).replace(_, ., 1) if origin_key in state: diff (state[k] - state[origin_key]).abs().mean().item() print(f{k} vs {origin_key}: mean_diff{diff:.8f})正常情况下训练足够多步后EMA 权重和原始权重的差异会稳定在一个小范围内不会完全相等也不会差到离谱。如果完全相等说明 EMA 没生效如果差异巨大可能是 momentum 设得太大或者 interval 设得太小导致更新过猛。5. 常见报错排查从 401 到 buffer 命名冲突调 EMAHook 的过程中报错主要集中在几类。下面按真实遇到的频率排一下。第一类Hook 注册失败KeyError: EMAHook。这通常是因为你用的 Runner 是 MMEngine 的但 config 里写的是 MMCV 风格的参数。解决办法是确认 Runner 类型MMDetection 2.x 用mmcv.runner.Runnerconfig 里custom_hooks[dict(typeEMAHook)]即可MMDetection 3.x 用mmengine.runner.Runner需要写ema_type等参数。两者不能混。第二类RuntimeError: Error(s) in loading state_dict for ... Missing key(s) in state_dict: ema_xxx。这是断点续训时常见的问题。原因是 checkpoint 里没有 EMA buffer但模型结构里已经注册了。MMCV 1.6.0 的before_run里如果resume_from不为 None会调用runner.resume(self.checkpoint)此时如果 checkpoint 是旧版本没存 EMA buffer就会报这个错。解决办法是先用不带 EMAHook 的 config 跑一次生成一个干净的 checkpoint再加载。第三类AssertionError: momentum must be in range (0.0, 1.0)。这个在 MMCV 1.6.0 的 EMAHook 里其实没有显式断言但如果你传了momentum1.0或者负数momentum**interval会算出奇怪的值导致 buffer 更新异常。检查 config 里的 momentum 是否在 (0, 1) 区间。第四类验证指标不升反降。这不是报错但比报错更让人头疼。常见原因有三个一是warm_up设得太小早期 EMA 权重还没稳定就被拿去验证二是interval设得太大EMA 更新太慢跟不上模型变化三是momentum设得太大EMA 权重几乎等于原始权重失去了平滑意义。建议先用默认值momentum0.0002, interval1, warm_up100跑通再逐步调。第五类local proxy failed或401。如果你在调参过程中用外部模型服务辅助分析日志可能会遇到鉴权失败。401 通常是 key 没填对或者过期检查请求头里的 Authorization 字段。local proxy failed一般是本地网络配置问题确认你的请求地址是https://taotoken.net/api而不是其他。这类问题不影响训练本身只是辅助工具用不了。第六类reading choices相关报错。这个通常出现在你用模型服务解析日志时返回格式不是预期的 JSON。检查请求参数里的response_format或者 prompt 是否明确要求了结构化输出。排查顺序建议先确认 Hook 注册成功看启动日志再确认 buffer 注册成功看named_buffers最后确认数值在更新用上面的对比脚本。三步都过了EMA 就是真的在跑。6. 长期编码与 Agent 场景下的工具入口如果你不只是调一次 EMA而是要长期做训练侧调参、写对比脚本、维护多套 config那用 coding agent 来辅助会省很多事。比如让 agent 帮你批量改 config 里的 momentum、自动生成权重对比脚本、分析训练日志里的异常波动。这类长期编码场景适合用 Coding Plan入口是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。它面向的是持续性的编码任务不是单次问答。如果你只是想快速验证某个模型对 EMA 配置的理解或者让模型解释一段报错用模型对话就够了https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。把报错贴进去让它帮你定位是 Hook 优先级问题还是 buffer 命名问题。接入相关的文档都在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite API Keys 在 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。API 基址统一用 https://taotoken.net/api 不要加多余路径。最后回到 EMAHook 本身。我自己的经验是默认参数在大多数检测任务上够用真正需要调的是interval和warm_up。小数据集几千张图可以把warm_up降到 50让 EMA 早点稳定大数据集保持 100 或更高。interval在显存紧张时可以设成 2 或 4但记得momentum会做 interval 次幂实际更新会变慢需要相应调大 momentum 来补偿。验证 EMA 是否生效最可靠的办法还是 dump 权重对比别只看验证指标——指标受太多因素影响权重差异才是硬证据。
返回列表