diff --git a/research-wiki/designs/2026-07-16-preflight-fixes-design.md b/research-wiki/designs/2026-07-16-preflight-fixes-design.md index e23000c..ad1fcd5 100644 --- a/research-wiki/designs/2026-07-16-preflight-fixes-design.md +++ b/research-wiki/designs/2026-07-16-preflight-fixes-design.md @@ -1,6 +1,6 @@ # 训练前缺陷修复设计(video-split → Video-MME 900 自进化训练) -> 2026-07-16。两轮审查(接线预检 + 8 维度多代理审查)共确认 19 bug + 接线缺口 + 切分层优化,本设计一次性覆盖。 +> 2026-07-16。两轮审查(第一轮接线预检 + 第二轮 8 维度多代理审查:**19 条确认 / 4 条存疑 / 12 条误报**)。本设计覆盖:19 条确认缺陷 + 4 条存疑中采纳 2 条 + 接线缺口 2 项 + 切分层优化 3 项,逐条映射见 §2.5 覆盖矩阵。 > **目标**:让 video-split 冻结切分驱动的 Video-MME 900 题自进化训练**一次跑出有效结果**(不崩、不静默失效、信号不被污染)。 ## 1. 背景与问题总览 @@ -17,10 +17,48 @@ ## 2. 设计目标与非目标 -**目标**:修复全部 23 项确认缺陷 + 接线;切分层做三处优化(可训练性门槛、val_ratio、tier 感知分配)提升信号质量;产出零参可复现的训练入口。 +**目标**:修复 §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 | §6(step 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 | §6(gate_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 @@ -49,14 +87,25 @@ WP1 零风险纯搬运先解锁引擎;WP2/WP4 与 WP3 无代码交集可并行 trainval 内部视频组分配从"correctness 分层随机"升级为 **tier 感知确定性贪心**(纯函数、固定 seed): ``` -输入: 视频组(t2_count/t1_count/wrong_count/question_count,tier 来自 baseline_diagnosis 按 fingerprint 读) -目标: 最大化 diag 池 T2 捕获 -约束: ①视频原子 ②val 题数 ≈ val_ratio ③val 错题 ≥ val_wrong_min 否则 fail loud -算法: 按 (t2_count desc, wrong_count desc, video_id) 排序装入 diag 至题数预算满 → 余下进 val - → 校验③,违反则从 diag 尾部换出低 T2 组直至满足;有限步无解 → InfeasibleSplitError +输入: trainval 视频组集合 G(每组: t2_count/t1_count/wrong_count/question_count, + tier 来自 baseline_diagnosis 按 fingerprint 读);val_ratio;val_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 ``` -配置:`config/video_split.yaml` `val_ratio: 0.3 → 0.4`(甜点:val 错题 29→~38 让整包终审更抗噪,diag 错题 64→~56 进化信号仍过剩;0.5 过冲、迭代次数受损)。tier 感知不加开关(无扫动需求,YAGNI)。重切零 LLM 成本(诊断已按指纹落库)。 +与既有 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~56(tier 感知使 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) @@ -81,7 +130,7 @@ seed 最终形态:`seed.json / baseline.db(900) / pools.json / split_manifest. | 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 真评 600;best_hard 版本备忘录(入 checkpoint)未评过且≠final 才评;best_mixed 引用赢家成绩标指针(0 推理)。**去重做在 harness 逻辑层**(配合 epoch 盐,同版本不重采样)| +| holdout | 四向重复评估 | baseline 从 seed 基线预测推导(0 推理);final 真评 600;best_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`) 消除双计窗口 | @@ -101,7 +150,8 @@ seed 最终形态:`seed.json / baseline.db(900) / pools.json / split_manifest. | # | 缺陷 | 修法 | |---|------|------| -| P1-1 | 缓存跨 epoch 重放 | `chat()` 加 `cache_salt`;训练 rollout/val/test 链路注入 `f"{run_id}:e{epoch}"`(跨 epoch 真实重采样、同 epoch 续跑仍命中);judge/evolve/离线切分无盐。TTL 修:`REDIS_CACHE_TTL≤0` 启动即 ValueError(消灭"0=永不过期"隐式语义)。**同步改 `.env`/`.env.example` 给正整数**(当前值 0,不改则修复后启动即崩),训练场景建议 ≥ 单次训练时长| +| 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 锁),成功才闭合 |