docs: redesign gate speedup v2 (continuous concurrent gate, drop preload)

This commit is contained in:
2026-07-16 22:24:28 -04:00
parent 269e0d294c
commit 53a8c4be02
@@ -1,103 +1,118 @@
# gate 验证提速设计:预灌 BaselineCache + 双臂并行 # gate 验证提速设计(v2):连续并发 gate + Redis 复用
> 2026-07-16。背景:Video-MME 900 训练实测,单题型 gate 双臂验证 ~3h(epoch 1 缓存全冷,base 臂每块新鲜跑 8 题;两臂串行),单 step 约 6 题型排队 → step ~18h,3 epochs 不可行。三方流程对比(SkillOpt / TRM4 / TRM5)确认:TRM5 忠实迁移了 TRM4 的 CE-Gate 机制,慢是设计固有成本 + 冷缓存,不是迁移引入的缺陷。本设计在**不碰 e-process 判定逻辑与核心算法语义**(保真 #4/#5/#6)的前提下砍验证成本 > 2026-07-16。背景:Video-MME 900 训练实测,epoch 1 单题型 gate 双臂验证 ~3h(两臂串行 × 块串行 × 题型串行三重串行,~6 题型/step)→ step ~18h,3 epochs 不可行。v1 设计的"预灌 BaselineCache"经 Codex 审查发现统计致命伤已废弃(见 §7)。本版方案:**②″ 连续并发 gate**(题型并行 + 双臂并行 + 逐对连续早停,一次调度重构)+ **③ Redis 复用保障**。统计内核(e-值公式、四出口、配对翻转、信息量阶梯、题尽/试用期通道)一行不动
## 1. 问题与机会 ## 1. 统计合法性(②″ 的前提)
| 事实 | 出处 | | 论点 | 依据 |
|---|---| |---|---|
| BaselineCache 内容寻址:键 `task_type\|sha1(基线skill正文)\|prompts_version\|unit_id` → 单元级对错 | `gate_ladder.py:344` | | 块=8 不是统计需要 | TRM4 注释原文"块大小=推理并发度,块内跑满"——并发 8 时代的工程遗迹。e-process 为 anytime-valid 上鞅(Ville 不等式),**任意时刻停下判定假阳率仍 ≤ 1/e_confirm**;逐对判定是比逐块更细的合法 optional stopping |
| seed 基线 `infer_adhoc` 已用 skills v1 + prompts v1 跑过全部 900 题,predictions 随 seed 入 workspace harness.db | `scripts/train_videomme.sh` Phase 0 | | 到达序计数无偏 | 翻转对需 base/cand 两臂都返回才成立;H0 下两臂可交换,(结果, 延迟) 联合分布对换臂对称 → W 型与 L 型翻转的到达时间分布相同,按完成序计数不偏向任何方向 |
| 内容寻址意义上,seed 基线**就是** epoch 1 的 base 臂,却未被灌入缓存 → base 臂全 miss 全重推 | 实测 b0-b3 base 臂各 8-22 min | | 题型间并行无实质依赖 | 各题型 agent 只加载自己的 skill 文件(12 题型各有专属 .md),A 型 accept 改 A 的文件,对 B 型推理内容零影响。现行字母序滚动版本只是记账先后,非实质依赖。唯一共享文件 default-strategy.md 仅在题型缺专属文件时 fallback——本次 12 型全有专属文件,不触发 |
| gate 块内 base 臂 `await` 完才 `await` cand 臂,两臂串行 | `validate.py:640→661` |
| Redis LLM 缓存盐 = 推理 run_id,派生自 seed 的 `baseline_run_id`(`infer_adhoc_e{e}_s{s}...`),与工程 config 的 run_id 无关 | `inference.py:406,437` |
## 2. 改动一:训练启动时自动预灌 BaselineCache ## 2. 改动 ②″:连续并发 gate 调度器
**位置**:`_init_gate_pools`(`runner.py:976`)末尾、`BaselineCache` 构造后顺手预灌(该函数已持有 900 题基线查询与 gate_pools)。 ### 2.1 新流程(替换 `_gate_batch_skills` 串行 for + `validate.py` 块循环)
```text ```text
预灌(gate_pools, baseline_cache, 基线rows, prompts_version): step 内 gate 阶段:
rows 查询在现有基础上追加 stop_reason 列 Phase A. 并行进化:全部案例包题型(剔除 cooldown)gather 调 evolve_single_skill
for 题型 in gate_pools.task_types: 无真实改动的题型照旧写 skipped step_report 退出
target = resolve_skill_file(skill_store, 题型) # slug.md 缺失 → default-strategy.md, Phase B. 装配:每题型 阶梯出题(排除本 step 案例单元, 截断 n_max=40) + 物化候选目录
# 复用 core/evolution/evolve.py:640 同一解析 Phase C. 连续监控调度:
s_hash = skill_hash(读取 skills_dir/target) # 当前(=v1)基线正文 所有 (题型, 单元, 臂) 任务按「题型轮转 × 阶梯序」交错压入共享并发池(32)
for unit in gate_pools[题型] 的单元: base 臂任务:BaselineCache 命中 → 立即完成;miss → 新鲜跑基线版本,非 INFRA 回写缓存
if unit 任一成员题 stop_reason ∈ {error, parse_error}: skip # INFRA 不入缓存 cand 臂任务:跑该题型候选目录
entries[键] = unit_correctness_view 折叠对错 # AR pair 双向 AND 事件循环:某单元两臂齐 →
baseline_cache.put_many(entries) # 一次原子落盘(见 §5) 任一臂 INFRA → 单元剔除(护栏计数,详 §2.3)
否则配对计翻转 → 该题型 (W,L,n_used) 更新 → gate_decision
判定 ≠ continue → 冻结该题型:排队未启任务撤销(启动前查冻结标志),
in-flight 任务跑完落库但不计入(W,L 已冻结)
某题型全部单元返回仍 continue → n_remaining=0 → 题尽第四出口(provisional/inertia)
Phase D. 汇总:按字母序对已判定题型依次执行现有 _accept_skill / _record_rejected_skill
(文件不相交,顺序仅为确定性;gate_evidence/step_report/quadrant_pairs 照写)
``` ```
| 关键点 | 说明 | ### 2.2 保留与消解
| 项 | 处置 |
|---|---| |---|---|
| 正确性由内容寻址自动保证 | 某题型 skill 被 accept 改写 → hash 变 → 自然 miss 重推;prompts 版本升级同理。无显式失效逻辑 | | e-值公式 / 四出口 / w_net_min / delta_min / lambda_dir / futility | **不动**(判定函数 `gate_decision` 原样,只是调用时机从"每块末"变"每对完成") |
| INFRA 排除 | seed 基线 900 题中 4 题 stop_reason ∈ {error, parse_error},与 `_resolve_baseline_block` 的"INFRA 不写缓存"语义完全一致 | | 信息量阶梯出题 + 案例单元排除 + n_max=40 | **不动**(发射顺序即阶梯序) |
| 折叠口径 | 复用 `unit_correctness_view`(保真 #5:AR pair 双向 AND 折叠一个布尔) | | BaselineCache(miss 新鲜跑、INFRA 不写、内容寻址) | **不动**(无预灌;epoch 1 冷缓存,base 臂新鲜跑但与所有臂共享并发池) |
| 预灌范围 | 仅 gate_pools 内单元(8 题型 ~286 单元),不是全 900 题 | | provisional → probation 试用 / epoch 末结算回滚 / cooldown / 黑名单 | **不动** |
| resume | 每次启动重跑预灌:未进化题型同键同值(无害),已进化题型键不同(互不干扰) | | `gate_block=8` 分块 | **消解删除**(config 键与 validate 块循环一并移除;CLAUDE.md §4.7 #6 需在 commit 标注本次语义修订:块序贯 → 逐对连续序贯,判据不变) |
| 每块两个 run_id(`_b{i}_base/_cand`) | **替换**为每臂一个 run_id(`..._gate_{slug}_base` / `_cand`),predictions 落库与溯源不变 |
| n_remaining 口径 | 等价迁移:= 未配对且未剔除单元数(排队+in-flight);题尽判定与现行一致 |
**新增 `BaselineCache.put_many(entries: dict) -> None`**:批量合并后一次 tmp 写 + os.replace 原子落盘,避免 ~286 次全量 JSON 重写;单条 `put` 语义不变。 ### 2.3 INFRA 护栏(并行下的等价迁移)
## 3. 改动二:gate 块内双臂并行 现行:跨块累计 error 率,分母 ≥10 且 > gate_guard_err(0.10) → 中止训练。迁移为**每题型运行时计数器**:分子=该题型 INFRA 单元数(任一臂),分母=该题型已完成推理的单元次数(两臂各计,缓存命中不计),阈值与中止行为不变。判定在每次单元完成事件时检查,先于配对更新。
`validate.py:_run_local_validation` 块循环内,先后 `await _resolve_baseline_block` / `await _run_candidate_block` 改为 `asyncio.gather(两臂)` ### 2.4 并发与调度事实
| 并发安全项 | 结论 | - 并发池为全 gate 共享的 32(与 rollout 同参),取代"每次 run_inference 各建信号量"——峰值在飞请求恒 ≤32,不再有双臂叠加超限问题(v1 设计此处低估,Codex 已指出)
|---|---| - 浪费上界:每题型判定瞬间 in-flight 的任务 ≤ 并发宽度;排队未启动的全部省下。相比"全发不早停"省 ~50-70% token,相比现行块早停多耗 ≤32 题次/题型
| run_id | 两臂后缀 `_base`/`_cand` 不同,predictions 落库不冲突 | - 小题型单元少、在共享池中最先出结果,天然优先收敛
| HarnessLog | 单连接 + threading.Lock,线程/协程安全 |
| BaselineCache.put | 仅 base 臂协程内发生,cand 臂不触缓存,无竞争 |
| INFRA 护栏 | errors/denom 统计与 `gate_decision` 均在两臂汇合后执行,判定顺序语义不变 |
| 并发上限 | 每臂一块 ≤8 题,双臂 ≤16 并发 < concurrency 32,LLM 端无压力变化 |
与改动一叠加:epoch 1 base 臂全命中时 gather 退化为只等 cand 臂,零额外开销。 ## 3. 改动 ③:Redis 复用保障
## 4. 改动三:Redis 复用保障(重启不白烧已耗调用)
| 措施 | 说明 | | 措施 | 说明 |
|---|---| |---|---|
| 缓存键成分不动 | 消息内容(skills/prompts v1 正文、冻结题池、`build_batches(seed=epoch)` 确定性批次)与盐(run_id 派生自 `infer_adhoc`)在本设计中零变化 | | 缓存键成分不动 | 消息内容(skills/prompts v1 正文、冻结题池、`build_batches(seed=epoch)` 确定性批次)与盐(run_id 派生自 `infer_adhoc`)零变化 → 重启后 rollout / 诊断 / 进化调用命中已写缓存 |
| `.env` `REDIS_CACHE_TTL` 86400→604800 | 仅改一行;只影响未来写入。今日已写键(24h TTL)在当日重启窗口内有效 | | gate 臂 run_id 变化的影响 | ②″ 将 `_b{i}_base` 改为 `_base`,今日旧 gate 调用(仅 Action Reasoning 部分块)的盐失配不复用——量小(~100 题次),可接受,如实记录 |
| 已实证 | 今日运行遥测 6505/15571 次命中(42%),复用机制已在工作;远程 Redis 现存 9.1 万键 | | `.env` `REDIS_CACHE_TTL` 86400→604800 | 仅一行;只影响未来写入,保障多天训练中断重启后早期调用仍在 |
| 一次性续期今日键 | 重启前运行一次:scan `llm_cache:*`,对 `0 < TTL < 604800` 的键 `EXPIRE 604800`(运维动作,见 runbook,不入库代码) |
| 复用边界(诚实声明) | Agent 逐步追加 messages,某步 miss(如原调用 503 未入缓存 / TTL 过期)后该轨迹后续全 miss 并可能分叉——复用是"命中前缀零成本"的尽力而为,非全量保证。今日实测命中率 42% 佐证机制有效 |
## 5. 非功能四维 ## 4. 非功能四维
| 维度 | 保障 | | 维度 | 保障 |
|---|---| |---|---|
| 持久化 | 预灌批量构建后一次原子落盘;单条 put 沿用现有"先盘后存"(tmp + os.replace) | | 持久化 | predictions/traces 逐题落库(HarnessLog 单连接+锁,不变);BaselineCache 逐条原子写(不变);判定即写 gate_evidence/step_report |
| 幂等 | 同键同值重复 put/put_many 结果一致;重复启动安全 | | 幂等 | step 重跑前清同 run_id 旧行(现行机制覆盖新 run_id 命名);BaselineCache 同键同值重写无害 |
| 断点续跑 | 预灌在每次启动执行,fresh/resume 行为一致且正确(§2 resume 行) | | 断点续跑 | checkpoint 粒度仍为 step(gate 中途崩溃 → 重启重跑该 step,与现行一致);Redis/BaselineCache 命中使重跑近零成本 |
| 原子性 | 单文件 tmp 写 + os.replace,无半写窗口 | | 原子性 | 判定冻结为内存事件,落库单条原子;无跨文件半写窗口 |
## 6. 前序行为审计(修改点逐项) ## 5. 前序行为审计
| 现有行为 | 处置 | | 现有行为 | 处置 |
|---|---| |---|---|
| base 臂缓存 miss 才新鲜跑、INFRA 不写缓存 | **保留**(预灌只是提前填充,miss 路径原样) | | 题型串行 for(字母序滚动版本) | **替换**:题型并行,accepts 按字母序统一合并(内容等价,记账顺序确定) |
| 两臂串行执行 | **替换**为 gather 并行(仅调度顺序,数据流/判定不变) | | 进化串行 | **替换**:题型间 gather 并行(rejected_buffer / budget per-type 独立,无共享) |
| `put` 逐条原子落盘 | **保留**;**新增** `put_many` 批量原子落盘 | | 块序贯(8/块)+ 两臂串行 | **替换**:逐对连续序贯 + 全臂共享并发池 |
| e-process 四出口 / 配对翻转 / 阶梯出题 / 防泄漏排除 | **不动** | | base 臂缓存 miss 新鲜跑 / INFRA 不写缓存 | **保留** |
| `_init_gate_pools` 基线查询 | **扩展**:追加 stop_reason 列(仅预灌用) | | INFRA 护栏(分母≥10 且 >10% 中止) | **等价迁移**(§2.3) |
| cooldown / skipped / 无改动跳过 + step_report | **保留** |
| gate_evidence / quadrant_pairs / candidate_correctness 增量合并 | **保留** |
| e-process 判定与四出口、题尽通道、probation | **不动** |
## 7. 测试 ## 6. 测试
- 单测(预灌):预灌后对 gate 单元逐一 `cache.get` 非 None(INFRA 单元除外);AR pair 折叠正确;改写某题型 skill 后同单元 miss;`put_many` 崩溃前后文件完整(读回可解析)。 - 单测(调度器,注入假 run_inference 与可控延迟):
- 单测(并行):注入假 run_inference,断言 gather 版 W/L/E/INFRA 计数与串行版逐块一致;base 臂全命中时 cand 臂正常执行。 - 同一组固定对错序列下,连续监控判定结果(action/W/L/E)与旧块序贯逐块判定一致(早停点可更早,判定方向一致);
- 集成:真实 workspace 启动预灌 → `_resolve_baseline_block` 对首块零推理调用(假 run_inference 断言未被调用)。 - 过线后排队任务不再启动(计数断言),in-flight 结果不改变已冻结 (W,L);
- 回归:现有 validate/gate_ladder 测试全绿。 - 题尽路径:单元耗尽 → 第四出口与现行一致;
- INFRA:单臂 INFRA 单元剔除不入配对,护栏计数阈值触发中止;
- 题型并行:两题型交错完成,互不污染彼此 (W,L) 与判定。
- 单测(进化并行):两题型 gather 进化,rejected_buffer 各自独立生效。
- 集成:小型真实 workspace 跑一个 step,断言 gate_evidence/step_report/quadrant_pairs 落库完整、accepts 正确推进版本。
- 回归:validate/gate_ladder/runner 现有测试全绿(块相关测试改写为连续语义)。
## 7. 已否决备选(含 v1 失败记录)
- **①(v1)预灌 BaselineCache**:❌ 统计致命伤。冷启动阶梯按 seed 基线"错题优先"选题,预灌使 base 臂 = 选题依据的同一份旧样本;对选中的 seed 错题 base 被钉死为错、新鲜 cand 以概率 p 答对 → W 系统性膨胀,候选无改进也会假接受。现行"miss 后新鲜跑"的测量-选题独立性是刻意设计,不可省(Codex Critical,已验证)。
- **B. 小题型合并进化 default-strategy.md**:future work。合并 gate 池(9+14+17+23=63 单元)可达正式接受线且信号更密,但存在子群伤害风险(平均变好掩盖单型受损)且叠加变量;本轮跑 A(小题型走 provisional+probation 通道),其结果作为 B 的对照证据。
- **top-K 题型/step**:用户否决,保留全部有错题型进化。
- **砍 gate_n_max / 放宽 e 阈值**:动统计参数,收益已被 ②″ 覆盖。
- **块轮次锁步(②′)**:被更优的连续监控取代(锁步有轮末空转,连续无)。
## 8. 预期收益 ## 8. 预期收益
| 项 | 现状 | 后 | | 项 | 现状 | ②″ 后 |
|---|---|---| |---|---|---|
| epoch 1 单题型 gate | ~3h(两臂全推,串行) | ~1-1.5h(base 免费,cand 独跑) | | step 内 gate(~6 题型) | 三重串行 ~9-18h | 共享 32 并发连续跑,**~1.5-2.5h**(≈总需题次×单题时长/32) |
| epoch ≥2 / accept 后 miss 块 | 两臂串行 | 双臂并行,≈减半 | | 进化阶段(~6 次 LLM) | 串行 ~30-60min | 并行 ~10min |
| 单 step(~6 题型) | ~18h | **~6-8h**;叠加 batch=40(5 step/epoch),3 epochs 预计 ~2 天 | | 3 epochs(5 step/epoch + epoch 末 val/holdout) | 数天-一周 | **~1.5-2.5 天** |
## 9. 已否决备选 改动面:`app/harness/validate.py`(块循环→连续调度器)、`app/harness/runner.py`(`_gate_batch_skills` 并行装配)、`config`(删 gate_block)、`.env` 一行、运维一次性续期。
- **烘焙进 seed**:缓存键含运行期才确定的 prompts_version,且已有 seed 须重建 — 否决。
- **独立预灌脚本**:多一个易忘的手工步骤,违背零参数复现 — 否决。
- **砍 gate_n_max / 放宽 e 阈值**:动核心算法参数、损统计严谨性,收益可由本设计免推理获得 — 本轮不做。
- **题型间并行 gate**:accept 会推进 skills 版本形成串行依赖(后过门题型的 base 臂定义依赖前一题型结果),改动大风险高 — 记 future work。