Files
mcm-26-b/docs/training-ui.md
2026-09-12 01:56:34 +08:00

11 KiB
Raw Permalink Blame History

模型训练前端(training-ui)

本地回环 Web UI,用于实时观察一次 HRL 高层 PPO 训练:子进程状态、步进进度、 参数收敛曲线、逐回合记录与完整 stdout 日志。设计与评测前端(referee/ WebUI) 同构——同一套暗色面板语言、同一个「只监听 127.0.0.1 + 静态白名单 + JSON 轮询 API」模式——差别在于本页托管的是一个训练子进程而不是评测会话。

启动

uv run python -m mcm26b.training_ui --ui-port 8036   # 打开 http://127.0.0.1:8036
uv run python scripts/train_hrl_ui.py --ui-port 8036 # 等价薄入口(支撑材料命令)
uv run python -m mcm26b.training_ui --print-schema   # 只看配置契约,不起服务
uv run python -m mcm26b.training_ui --print-preview  # 只看离线示例快照

--no-browser 阻止自动打开浏览器;Ctrl+C 退出时会先停止训练子进程。

无训练进程时,界面顶栏「离线预览」可渲染三组合成快照(上升 / 平台期 / KL 过猛), 用于检查曲线、指标卡、时间线与日志着色——预览不启动任何进程、不写任何文件。

界面结构

区块 内容
顶栏 任务摘要、进程/进度/墙钟/设备/连接五个状态 chip、刷新·预览·暂停/继续·停止
执行进度与进程 步进进度条、剩余时间与预计结束、PID/吞吐/退出码、PPO 阶段时间线、完整命令行
训练配置 由 /api/train/schema 生成的五组表单 + 四个预设,运行中锁定
参数收敛状态 判读 chip + 判据说明,每个指标一张卡片(最新值、增量、迷你趋势线、异常高亮)
曲线 主指标(平均回合奖励)+ 可切换诊断量,悬停十字线读数、KL=0.05 等参考线
实时运行日志 关键字过滤、错误/进度 JSON 开关、自动跟随、最近 4000 行
逐回合记录 Monitor CSV 的每回合奖励/长度/时刻

数据来源与契约

三个端点都在 src/mcm26b/training_ui/server.py:

端点 说明
GET /api/train/schema 字段/分组/预设/指标字典,与 TrainConfig 同源(schema.py)
GET /api/train/state 快照;支持 log_tail、metric_limit、after_metric 游标与 preview=1
POST /api/train/start 允许只提交部分字段(其余取默认值),校验失败返回 409
POST /api/train/pause 写控制文件请求在 step 边界挂起(跨平台)
POST /api/train/resume 解除挂起,从暂停处继续
POST /api/train/stop 请求优雅停止;宽限期(STOP_GRACE_S,默认 15s)后强杀进程树

暂停/停止走控制文件而不是信号,原因与平台差异见 training-ui-windows-audit.md:Windows 没有 SIGSTOP,且 os.kill(pid, SIGTERM) 会退化成 TerminateProcess 硬杀,训练脚本 "保存模型再退出"的收尾分支不会执行。控制文件在 POSIX 与 Windows 上都只是普通 文件 IO,语义完全一致;信号(POSIX SIGTERM/SIGINT、Windows SIGBREAK)只作兜底。

进度数据的两个来源,服务端都已解析:

  1. progress JSON(主来源):train_hrl_high.py --progress-json 每若干环境步打印 一行 {"event": "progress", "timesteps": ..., "ep_rew_mean": ...},字段最全; 热身阶段间隔会自动缩小,短程 smoke 也能看到完整曲线。
  2. SB3 verbose 表格(补充来源):解析 | key | value | 表格,用于补 clip_fraction/learning_rate 等诊断量。表格按段分组缓存、等本表的 total_timesteps 到位后统一打横轴刻度;与 JSON 同一步数的表格版本会被跳过, 避免重复点。

timesteps 是整个前端的横轴:服务端不伪造刻度(缺刻度的表宁可丢弃,截断的表 按末尾已知刻度兜底并在收尾提交),前端不再自行编号。

收敛判读口径

summarize_convergence() 只做可解释的启发式判读,不替代正式学习曲线分析:

状态 触发条件
等待首个 rollout 尚无任何指标
预热阶段 进度点 < 3,无法判断趋势
KL 偏大,策略更新可能过猛 最近 approx_kl > 0.05(判定优先级最高)
价值函数尚未拟合 最近 explained_variance < 0
奖励未形成上升趋势 最近窗口奖励斜率为负
已趋于平稳(疑似收敛) 窗口对半位移 ≤ 逐点噪声、变异系数 ≤ 0.15、极差 ≤ 15% 量级
收敛趋势良好 其余(斜率正向)

平台判据特意用「对半窗口位移 / 逐点噪声」而不是单看斜率绝对值:慢速但持续的 提升(斜率很小)不会被误判成收敛。所有判据数字都会写进 notes,便于人工复核。

安全边界

  • 只监听 127.0.0.1;静态资源是固定白名单,不提供目录遍历。
  • 命令由服务端以参数列表构造,不经过 shell;字符串参数只拒绝控制字符。
  • 同一时刻只允许一个训练任务;暂停中的任务仍算「在跑」,必须先停止才能开新一局。
  • 暂停/停止走控制文件(--control-file,默认 output/hrl/ui/control/<job_id>.control.json), 进程组隔离按平台选择(POSIX setsid / Windows CREATE_NEW_PROCESS_GROUP), 超时强杀按平台选择(POSIX killpg(SIGKILL) / Windows taskkill /F /T)。
  • 产物路径限定在仓库根下解析,模型输出沿用训练脚本自身的 --force/--resume 语义(两者互斥,由后端校验)。

暂停、停止与落盘保证

「暂停」是协作式的:前端原子写控制文件,train_hrl_high.py 的 _ControlPoll 每 32 个环境步抽样读一次;暂停时训练阻塞在 _on_step 的轮询里, 进程与显存保留、不消耗算力,点「继续」从同一个 step 边界恢复。暂停时长不计入 --max-wall-s(否则"挂起一小时回来直接判超时")。

「停止」同样是控制文件请求(跨平台)。train_hrl_high.py 有多层兜底, 点一下「停止」不再等于这一局白跑;而急停(强杀,例如 SIGKILL / taskkill /F,进程没有任何收尾机会)也保证可恢复:

机制 行为
滚动恢复点 --recovery-every(默认 2000 环境步)把 <checkpoints>/<stem>_latest.zip 原子覆盖写一次;训练开始前先写一份起步基线。因此急停后最多只丢 --recovery-every 个环境步,且磁盘上始终有一份完整、可加载、可 --resume 的产物
原子落盘 模型 zip、旁挂 json、resume.json 都是「同目录临时文件 + os.replace」:保存途中被强杀不会留下半截文件,也不会破坏上一份完整快照
恢复指针 <run_dir>/resume.json 记录当前最佳恢复目标 + 步数(增量语义)+ 生效过的 PPO 超参;前端顶部的「用最近检查点续训」按钮直接读它
定期 checkpoint --checkpoint-every(默认 20000 环境步)另留 <stem>_<步数>.zip,--checkpoint-keep 只保留最近 K 个(默认 3),供返祖对比与多档恢复;滚动恢复点与起步基线不受清理影响
优雅停止 控制文件 stop=true(或 POSIX 信号 / Windows SIGBREAK)后,主进程在 step 边界收尾,保存最终模型与元数据(stopped_by_signal=true、stop_signal、checkpoint_files、recovery_model)并正常关闭环境
超时强杀 宽限期(STOP_GRACE_S,默认 15s)内没退出则强杀整棵进程树,快照 forced_kill=true,并在 <run_dir>/forced_stop.json 写明 recovery.model(可直接续训的目标)
异常兜底 learn() 抛出任何异常时先落 *_emergency.zip + 元数据 + resume.json,再写 training_failure.json 并抛出

急停后恢复的操作方式:界面点「用最近检查点续训」;命令行则 --resume --model <resume.json 里的 model>,--timesteps 按追加步数理解。

checkpoint 与最终模型共用同一份 json 契约,因此 --resume <checkpoint.zip> 直接 续训(--timesteps 为增量步数)。注意 --resume/--init 会沿用 checkpoint 内的 PPO 超参(n_steps/batch_size/n_epochs):命令行这三项被忽略,脚本会打一条 hyperparams_from_checkpoint 事件说明差异,元数据 ppo_hyperparams 记录实际生效值。 回归测试见 tests/test_hrl_checkpoint.py,其中端到端用例真起 subproc 子进程,覆盖 控制文件停止、POSIX 信号停止、强杀后仍可续训与暂停→继续(信号路径在 Windows 上自动跳过)。

测试

.venv/bin/python -m pytest tests/test_training_ui.py tests/test_training_ui_js.py tests/test_hrl_checkpoint.py -q
  • tests/test_training_ui.py:配置契约同源、预设可直接提交、参数校验、SB3 表格 刻度打点、游标分页、describe_process 进度/ETA、示例快照与真实快照同键; 用桩训练脚本真起子进程覆盖启动/轮询/暂停/继续/停止/退出码全链路;并覆盖 跨平台进程控制——控制文件往返与损坏容错、spawn_kwargs 平台分支、Windows 走 taskkill、无 killpg 环境下强杀不抛异常(旧 bug 的直接回归)、控制文件被 忽略时强杀并写 forced_stop.json 恢复指引、discover_recovery 的 resume.json/扫描/悬空指针三分支、HTTP pause/resume。
  • tests/js/training_ui_harness.cjs(经 tests/test_training_ui_js.py 调起): DOM 桩加载发布的 charts.js/app.js 原文,用服务端生成的真实快照驱动渲染, 断言表单/指标卡/曲线/日志/逐回合表都拿到数据,覆盖增量游标三条分支与 classList、HTML 转义、极端数值等边界。
  • tests/test_hrl_checkpoint.py:落盘与恢复的端到端回归——原子保存失败不破坏 上一份快照、JSON 原子替换、历史清理不误删滚动恢复点、起步基线可加载、 强杀急停后 resume.json 与滚动恢复点仍可继续训练、控制文件停止落盘、 暂停→继续、POSIX 信号停止。

已知取舍

  • 进度点保留上限 5000(服务端 deque 与前端本地缓存同值),超长训练只保留最近一段。
  • 表格解析依赖 SB3 的段名(rollout//time//train//system/);SB3 若改表格 格式,退化路径是「只显示 progress JSON」,不会导致界面崩溃。
  • 收敛判读是启发式;正式结论仍需用 scripts/benchmark_hrl.py 的配对评测与 学习曲线人工复核。
  • 暂停粒度是 poll_every 个环境步(默认 32),不是即时挂起;停止宽限期默认 15s。
  • Windows 强杀依赖系统自带的 taskkill;若被策略禁用会退回 Popen.kill() (只杀主进程)。更严格的 Job Object 方案需要额外依赖,未引入。

Windows 兼容性问题的完整根因与修复记录见 training-ui-windows-audit.md。