Files
Video-Tree-TRM5/research-wiki/designs/2026-07-16-preflight-fixes-design.md
T

203 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 训练前缺陷修复设计(video-split → Video-MME 900 自进化训练)
> 2026-07-16。两轮审查(第一轮接线预检 + 第二轮 8 维度多代理审查:**19 条确认 / 4 条存疑 / 12 条误报**)。本设计覆盖:19 条确认缺陷 + 4 条存疑中采纳 2 条 + 接线缺口 2 项 + 切分层优化 3 项,逐条映射见 §2.5 覆盖矩阵。
> **目标**:让 video-split 冻结切分驱动的 Video-MME 900 题自进化训练**一次跑出有效结果**(不崩、不静默失效、信号不被污染)。
## 1. 背景与问题总览
自进化训练对标 PyTorch 训练循环(出题=DataLoader / 推理=forward / 诊断=backward / 进化=optimizer.step)。审查发现三类根问题:
| 类别 | 代表 | 后果 |
|------|------|------|
| **进化引擎哑火** | 5 个 evolve/momentum 模板 TRM4→TRM5 迁移遗漏,`else ""` 静默兜底 | 3 epochs 跑完但 skills 零进化,best 恒 v1 |
| **诊断链路坍缩** | traces 表从不写入(TracePlugin 未迁移) | 诊断瀑布拿空轨迹,算法保真 #7 失效 |
| **崩溃与信号污染** | 微型题型断言崩、prediction 非标量击穿、缓存跨 epoch 重放、SSE 截断毒化等 | 训练中途崩溃或统计信号失真 |
叠加**接线缺口**:冻结 `pools.json` 无路径进入训练 workspace`--fresh` 从 seed 重建不带切分),直接训练会静默丢弃标定成果、按 unit 重切。
## 2. 设计目标与非目标
**目标**:修复 §2.5 矩阵所列全部缺陷(19 确认 + 2 存疑采纳)+ 接线;切分层做三处优化(可训练性门槛、val_ratio、tier 感知分配)提升信号质量;产出零参可复现的训练入口。
**非目标**:不改 gate/EMA 核心数学(算法保真 #4/#5);不实现 epoch 层 per-skill best 混搭(见 §9 future work);不引入向后兼容(P4 显式优于隐式,直接改)。
## 2.5 缺陷覆盖矩阵(可追溯性)
原始编号取自第二轮工作流报告(`research-wiki/reviews/2026-07-16-preflight-train-review.md`)与第一轮接线预检。状态:C=确认 / P=存疑采纳 / W=接线(已知缺口)。
| 设计编号 | 原始位置 | 状态 | 修法节 |
|---------|---------|------|--------|
| P0-1 | runner.py:2272 evolve 模板缺失 | C | §4 |
| P0-6 | momentum.py:151 slow_momentum.md 缺失 | C | §4 |
| P0-2 | inference.py:462 traces 未写 | C | §6 |
| P0-3 | validate.py:682 + gate.py:99 + runner.py:2044 微型题型三连雷 | C | §6 |
| P0-4 | inference.py:423 prediction 非标量击穿 | C | §6 |
| P0-5 | runner.py:316 early_stop step 单位 | C | §6 |
| P1-1 | main.py:93 缓存跨 epoch 重放 + TTL | C | §8 |
| P1-2 | llm.py:116 SSE 截断毒化 | C | §8 |
| P1-3 | llm.py:182 断连不重试 | C | §8 |
| P1-4 | validate.py:304 基线臂 INFRA 污染 | C | §8 |
| P1-5 | diagnose.py:2194 + runner.py:1019 降级误入 defect | C | §6 |
| P1-6 | patch.py:343 冻结区跨度 | C | §7 |
| P1-7 | workspace.py:282 manifest 非原子写 | C | §8 |
| P2-1 | video_split_cli.py:557 冻结产物无覆盖保护 | C | §5.2 |
| P2-2 | log.py:77 基线元数据被改写 | C | §8 |
| P2-3 | diagnose.py:2081 + inference.py:461 重复行双计 | C+P | §6step DELETE |
| P2-4 | diagnose.py:2194(离线)uncertain 永久降级 | C | §8--retry-uncertain |
| P2-5 | runner.py:1444 dual_metric 口径歧义 | P | §8 |
| P2-6 | breaker.py:36 熔断半开烧穿 | P | §8 |
| 慢更新非幂等 | 第一轮已知 #5 | W | §6gate_epoch_observed 立即落盘) |
| 接线-1 | 冻结 pools 无路径进 workspace(已知 #1 | W | §5.3 |
| 接线-2 | global 加载零一致性校验(已知 #2 | W | §5.3 |
**有意合并/不单列的项**(说明去向,避免"静默漏修"):
| 第一轮原始项 | 处置 |
|-------------|------|
| gate_ladder.py:93 strict=False 缺题静默当错题 | 由 §6 可训练性预检消除主场景(缺题类被剔除);残余以 fail-loud 补强(预检后 gate 建立时断言 baseline 覆盖全部 ladder 单元) |
| batching.py:200 缺 correctness 静默丢弃 | 同上,预检保证 diag 池题全在 correctness 内;保留 fail-loud(缺 correctness 即报错而非丢弃) |
| resume 硬依赖 checkpoint(已知 #8 | 本轮为 fresh 训练,seed 携带 pools 后无需 resume 建池;不改 resume 语义 |
| baseline_diagnosis 断点续跑粒度(存疑,非采纳) | 仅修 docstring 表述(§8 末),行为不改(Redis 缓存已缓解重烧) |
## 3. 架构:四工作包按依赖序
```mermaid
graph LR
WP1[WP1 资产迁移<br/>模板+fail-loud+死字段] --> WP3[WP3 训练循环与进化引擎]
WP2[WP2 切分与接线<br/>产出训练输入] --> RUN[训练启动]
WP3 --> RUN
WP4[WP4 韧性与持久化] --> RUN
WP1 -.集成测试依赖.-> WP3
```
WP1 零风险纯搬运先解锁引擎;WP2/WP4 与 WP3 无代码交集可并行;WP3 主体修复依赖 WP1 模板做集成测试。
## 4. WP1 —— 资产迁移(修 P0-1 / P0-6
`/home/iomgaa/Projects/Video-Tree-TRM4/prompts/` 拷 5 文件到 TRM5 根 `prompts/``evolve_skill/system/tool/rank.md``slow_momentum.md`(进化引导 prompt = 引擎一部分,不参与版本化进化)。
- **迁移验收(强制)**:逐个核对模板输出契约与 TRM5 解析代码期望——`evolve_*``{suggestions, edits}` 结构 vs `_parse_llm_json`/`_apply_one``slow_momentum.md` vs `run_slow_momentum`。TRM4→TRM5 代码有漂移,裸拷贝不核对 = 新 bug。
- **加载器 fail-loud**`_load_evolve_prompts`/`_load_diagnose_prompts``else ""``FileNotFoundError`(列缺失文件名)。
- **死字段清理**:删 `EvolvePrompts.consolidate_system`consolidate_appendix 用内联 prompt)、`DiagnosePrompts.span_eval_user`(无消费点)及对应 runner 加载行。
## 5. WP2 —— 切分与接线
### 5.1 tier 感知切分(`split_selection.py` / `build_split.py`
trainval 内部视频组分配从"correctness 分层随机"升级为 **tier 感知确定性贪心**(纯函数、固定 seed):
```
输入: trainval 视频组集合 G(每组: t2_count/t1_count/wrong_count/question_count
tier 来自 baseline_diagnosis 按 fingerprint 读);val_ratioval_wrong_min
目标: 最大化 diag 池 T2 捕获,同时满足 val 功效
约束: ①视频原子(组内题同进同出) ②val 题数 ≈ val_ratio×trainval题数 ③val 错题 ≥ val_wrong_min
阶段1 初分(单趟,O(n log n):
按 (t2_count desc, wrong_count desc, video_id) 排序 G
依次装入 diag,累计题数达 (1-val_ratio)×总题数 即停 → 余下全部进 val
阶段2 功效修复(单调收敛,每组至多移动一次):
while val 错题 < val_wrong_min:
从 diag 中挑「wrong_count>0 且 t2_count 最小」的组移入 val # 严格减少 diag 错题冗余
该组标记 moved,后续不再参与挑选 # 单调变元:候选集严格缩小
if 无 moved=false 且 wrong_count>0 的组可挑 → raise InfeasibleSplitError
# 终止性:每轮候选集 |{未 moved 且 wrong>0}| 至少减 1,有限步内收敛或 fail loud
```
与既有 video-split 设计(`2026-07-15-results-driven-video-split-design.md §8`)的口径变更:**保留** correctness 分层的本质(阶段2 只在 wrong_count>0 的组间挪动,保证两池都有对/错题)、`eval_min_per_class`(由 §6 预检独立强制)、McNemar 功效(`val_wrong_min≈20` 硬约束不变);**替代**"随机分层"为"tier 优先确定性贪心"(新增 T2 捕获目标 + 固定 seed 可复现)。
配置:`val_ratio` 作用域 = **trainval 池(当前 300 题)内部**的 val 题数占比,`0.3 → 0.4`。数字推算基于当前冻结切分统计(trainval 错题 93 = val 29 + diag 64):val 错题 ≈ ⌈93×0.4⌉ = 38、diag 错题 ≈ ⌊93×0.6⌋ = 55~56tier 感知使 T2 尽量留 diag,实际错题落点略有偏移,以重切后统计为准)。甜点判断:val 38 让整包终审更抗噪、diag 56 进化信号仍过剩;0.5 过冲(每类 3 epochs 仅 ~4 次进化机会,迭代受损)。`val_ratio`、`val_wrong_min` 作为**数值参数进科研 YAML + run 快照**(会被扫动/对比);tier 感知**策略**固定不加 on/off 开关(无扫动需求,YAGNI)。重切零 LLM 成本(诊断已按指纹落库)。
### 5.2 冻结产物覆盖保护(修 P2-1)
`build_split` 冻结前检查产物存在性:存在且内容指纹不同 → 报错并提示 `--force`CLI 补上此缺失参数);`--force` 时旧产物重命名 `pools.json.bak.<旧指纹前8>` 再写。
### 5.3 seed 携带切分(接线核心)
| 项 | 设计 |
|----|------|
| `init_seed` | 新增可选 `pools_json`/`split_manifest`,提供则拷入 seed 目录 |
| `init_workspace_from_seed` | seed 目录有 `pools.json` 则连 manifest 拷入 workspace(拷 baseline.db 之后) |
| `build_or_load_pools`global 补校验,修已知 #2 | 加载冻结 pools 强校验 `baseline_run_id` 匹配 seed;有 manifest 时校验 `sha256(pools.json)==manifest.pools_sha256` |
| 新 seed `adhoc-baseline` | `extract_run_db``dedupe_per_question`(每 qid 按 rowid 取首行,902→900 对齐 canonical+ `init_seed(baseline_run_id='infer_adhoc', pools_json=…)` |
seed 最终形态:`seed.json / baseline.db(900) / pools.json / split_manifest.json / skills/v1 / prompts/v1`
## 6. WP3 —— 训练循环与进化引擎
| # | 缺陷 | 修法 |
|---|------|------|
| P0-3 | 微型题型三连雷 | `train()` 加**可训练性预检**(纯函数):题型 `n_val<eval_min_per_class(2)``n_units<trainable_min_units(8)` → 从 diag/val + gate task_types 剔除并打印清单;断言/空阶梯保留作纵深防御;test 池不过滤 |
| P0-4 | prediction 非标量击穿 | 落库前 `_to_text_field` 归一化;`log.insert` 移入单题 try 块,绑定异常记 error 不击穿 gather |
| P0-5 | early_stop 单位错误 | step→**epoch** 计数(`epochs_since_best_improved`),config 注释对齐,checkpoint 字段同步改名 |
| P0-2 | traces 空 | 复用离线管线 `StepsJsonRunLog` 适配器包装训练 RunLog`get_traces` 空时从 steps_json 转换,算法 #7 恢复 |
| holdout | 四向重复评估 | baseline 从 seed 基线预测推导(0 推理)final 真评 600best_hard 版本备忘录(未评过且≠final 才评);best_mixed 引用赢家成绩标指针(0 推理)。**去重做在 harness 逻辑层**(配合 epoch 盐,同版本不重采样)。**resume 语义**:备忘录不单独持久化,而在 resume 时从 `holdout_eval` 表 hydrate——查已落库的 `(skills_v,prompts_v)→test_acc` 重建已评集合(该表本就是每次评估的落库处),checkpoint 无需新增字段、无旧 checkpoint 迁移问题;hydrate 后同版本不再重评 |
| P1-5 | 诊断降级误入 defect | `cause_category=None`(judge 基础设施异常)与 `degraded=True` 错题按 **lapse 方向分流**step 降级占比>50% 报错中止 |
| P2-3/#5 | step 重跑双计 + 慢更新非幂等 | `_run_step` 开始先 `DELETE ... WHERE run_id=<本step>``_refresh_gate_ladder` 保存 gate_pools 后**立即**原子落盘 checkpoint(`gate_epoch_observed=True`) 消除双计窗口 |
`run_holdout_eval` 保持开启(去重版),逐 epoch test 曲线四线齐全,成本从 2400 降至 600~1200 次/epoch。
## 7. WP3 —— 进化引擎 patch 加固(修 P1-6,算法 #8 防御)
| 层 | 改动 |
|----|------|
| 跨度判定 | `_in_ranges` 从"只查起点"改为"整个 target 跨度 [pos,pos+len) 与保护区相交即拒"`_do_insert_after` 同 |
| 注入检查 | edit payload/target 含 `APPENDIX_START/END``MOMENTUM_START/END` 字面量 → 拒绝该 edit |
| 最后防线 | `validate_skill` 增 marker 完整性校验(成对、有序、各至多一对),违反整体 reject |
不改"保护跨度"语义方向,只把已声明的保护做完整。
## 8. WP4 —— 韧性与持久化
| # | 缺陷 | 修法 |
|---|------|------|
| P1-1 | 缓存跨 epoch 重放 | `chat()``cache_salt`;训练 rollout/val/test/**gate 候选臂**推理注入含 epoch 的盐(跨 epoch 真实重采样、同 epoch 续跑仍命中);judge/evolve/离线切分无盐。TTL 修:`REDIS_CACHE_TTL≤0` 启动即 ValueError(消灭"0=永不过期"隐式语义)。**同步改 `.env`/`.env.example` 给正整数**(当前值 0,不改则修复后启动即崩),训练场景建议 ≥ 单次训练时长 |
| P1-1a | epoch 盐 vs gate 配对保真(Codex Critical | **gate 双臂配对语义由架构保证、不受 LLM 缓存盐影响**:基线臂走 app 层 `BaselineCache`unit 键 `task_type+s_hash+prompts_version+unit_id`,固定快照,`validate.py:_resolve_baseline_block`),候选臂每块新采样(`_run_candidate_block`)。配对翻转比较的是「固定基线快照 vs 新候选采样」(算法保真 #6 现有语义),epoch 盐只作用于**候选臂/rollout 的 LLM 层缓存键**,不触碰基线臂、不改变配对语义。gate 序贯 e-process 消费阶梯上**不同 block(不同 unit**累积 e-value,非对同 unit 反复测;候选臂盐用 block 级 `run_id`(已含 epoch+block+candidate 区分)保证每次真实重测独立采样。**实现约束**:基线臂 miss 时的新鲜推理**不注入 epoch 盐**(保持基线快照跨 epoch 稳定,与 BaselineCache 语义一致)|
| P1-2 | SSE 截断毒化 | `_consume_stream` 校验 `[DONE]`/finish_reason 才算成功;流耗尽未完成抛 `_SseAnomaly` 进重试梯,**绝不写缓存** |
| P1-3 | 断连不重试 | `_is_transient_error` 扩为 `httpx.TimeoutException`+`httpx.TransportError` 两族 |
| P2-6 | 熔断半开烧穿 | cooldown 到期只放行 1 探针(half-open 锁),成功才闭合 |
| P1-4 | 基线臂 INFRA 污染 | INFRA 单元不写 BaselineCache、不计 W/L;护栏检查移到写缓存**之前** |
| P1-7 | manifest 非原子写 | `update_manifest/record_run/update_best/_scaffold` 统一 tmp+`os.replace`(复用 checkpoint 模式)|
| P2-2 | 基线元数据被改写 | 只读查询基线的两处改走只读连接,不经 `HarnessLog.__init__` upsert |
| P2-5 | dual_metric 口径歧义 | 慢更新 R2 行 `version_kind``slow_candidate``final` 恢复唯一语义 |
| P2-4 | 离线 uncertain 不可重试 | `video_split_cli``--retry-uncertain`done 集排除 uncertain 行);docstring "逐行落库"表述修正为"run 末批量落库" |
## 9. Rejected alternatives / Future work
| 选项 | 为何不做 |
|------|---------|
| epoch 层 per-skill best 混搭 | per-class val 样本太小(中类 5-10 题)终审频繁选错;prompts 随慢更新漂移致跨 epoch 成绩不可拼接。合理形态是训练**结束后**一次性 final assembly(每类取 gate 末次接受版本 + 最终 prompts,全 val 评一次比 best_hard),成本一次推理、不动循环 → **记 future work** |
| val_ratio 0.5 | 每类 3 epochs 仅 ~4 次进化机会,迭代受损;val 错题 38→46 边际收益趋零 |
| 确定性评估(temp=0) | 统计效率最高但基线用默认温度采,换口径要重烧全部基线,本轮不可行 |
| 会计层去重替代 epoch 盐 | 需动 gate/EMA 保真区且造不出新信息(每题永远只见一次抽样)|
## 10. 非功能四维(全设计汇总)
| 维度 | 保障 |
|------|------|
| **持久化** | 冻结产物/manifest/checkpoint 全 tmp+replace 原子写;holdout 备忘录入 checkpoint |
| **幂等** | step 重跑先 DELETE 后写;seed/冻结产物存在即拒覆盖(--force 显式);诊断按指纹 upsert 不变 |
| **断点续跑** | epoch 盐保同 epoch 命中;gate_epoch_observed 在 gate_pools 保存后立即落盘,消除慢更新双计窗口 |
| **原子性** | manifest 补齐原子写(对齐 checkpoint 先例);pools 冻结沿用现有原子写 |
## 11. 测试策略
| 层 | 覆盖 |
|----|------|
| 单元(新增) | tier 选择器(真实 baseline_diagnosis 二次构造:T2 捕获/val 错题≥20/视频原子);可训练性预检(真实 pools 驱动,5 微型类被剔);patch 三层校验;SSE 未完成流拒收;瞬时错误清单;缓存盐入键;extract_run_db 去重;seed 携带拷贝;holdout 备忘录 |
| 单元(改语义) | early_stop 按 epoch;加载器 fail-loudprediction 归一化 |
| 集成 | fake-LLM 小池 train 冒烟(预检→模板→traces 适配→holdout 去重计数→step DELETE)5 模板契约测试 |
| 回归 | 全量 1473 保持绿;agent/LLM 测试产 MD 到 `tests/outputs/` |
## 12. 训练启动 runbook(修复完成后)
```
1. 四工作包完成 + make test 绿
2. 备份 workspaces/video-split/ → 改 val_ratio:0.4 → 重跑 build_video_split.sh(诊断命中缓存秒级)
→ 核对: val 错题≥20 / T2 入 diag 数 / 预检模拟的可训类清单
3. 建 seed: extract_run_db(infer_adhoc,dedupe) + init_seed('adhoc-baseline', pools_json=…)
4. 新增 config/train_videomme.yaml + scripts/train_videomme.sh(零参可复现):
epochs=3, early_stop_patience=2(epoch), run_holdout_eval=true(去重版),
trainable_min_units=8, 其余 gate/batch 沿用 default.yaml
5. tmux: CUDA_VISIBLE_DEVICES=0 bash scripts/train_videomme.sh
```