docs: apply Codex plan review fixes (tail-INFRA rejudge, slot deadlock guard)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# gate 验证提速设计(v2):连续并发 gate + Redis 复用
|
||||
# gate 验证提速设计(v3):连续并发 gate + Redis 复用
|
||||
|
||||
> 2026-07-16。背景:Video-MME 900 训练实测,epoch 1 单题型 gate 双臂验证 ~3h(两臂串行 × 块串行 × 题型串行三重串行,~6 题型/step)→ step ~18h,3 epochs 不可行。v1 设计的"预灌 BaselineCache"经 Codex 审查发现统计致命伤已废弃(见 §7)。本版方案:**②″ 连续并发 gate**(题型并行 + 双臂并行 + 逐对连续早停,一次调度重构)+ **③ Redis 复用保障**。统计内核(e-值公式、四出口、配对翻转、信息量阶梯、题尽/试用期通道)一行不动。
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
| 块=8 不是统计需要 | TRM4 注释原文"块大小=推理并发度,块内跑满"——并发 8 时代的工程遗迹。e-process 为 anytime-valid 上鞅(Ville 不等式),在**预先声明的样本顺序**下任意时刻停下判定假阳率仍 ≤ 1/e_confirm;逐对判定是比逐块更细的合法 optional stopping |
|
||||
| **统计消费必须按固定阶梯序前缀,不得按完成到达序**(Codex v2 复审 C1) | 到达序消费不合法:base 臂可缓存命中瞬间返回、cand 臂必新鲜跑,两臂延迟不对称,配对完成时间由 cand 延迟主导;若 cand 延迟与对错相关(走满 40 步的慢轨迹更易错),早到翻转对系统性偏向 W 型 → e-值虚高 → 假接受。**修法**:推理全并发乱序执行,但 (W,L) 更新与 gate_decision 只在"阶梯序最长已配齐前缀"延伸时推进——判定顺序回到预先声明的阶梯序,无条件合法;INFRA 单元视为"已解决(剔除)"不阻塞前缀推进 |
|
||||
| 冻结后 in-flight 结果丢弃合法 | 在固定前缀消费下,停止时刻 τ 之后的样本不进入统计是标准 optional stopping;丢弃不引入偏差(到达序消费下则不成立,故必须配合上一条) |
|
||||
| 题型间并行无实质依赖 | 各题型 agent 只加载自己的 skill 文件(12 题型各有专属 .md),A 型 accept 改 A 的文件,对 B 型推理内容零影响。现行字母序滚动版本只是记账先后,非实质依赖。唯一共享文件 default-strategy.md 仅在题型缺专属文件时 fallback——本次 12 型全有专属文件,不触发;**启动时 fail-fast 断言:同 step 内多个题型不得映射同一 target_file**,违反即退回串行(防未来配置漂移) |
|
||||
| 题型间并行无实质依赖 | 各题型 agent 只加载自己的 skill 文件(12 题型各有专属 .md),A 型 accept 改 A 的文件,对 B 型推理内容零影响。现行字母序滚动版本只是记账先后,非实质依赖。唯一共享文件 default-strategy.md 仅在题型缺专属文件时 fallback——本次 12 型全有专属文件,不触发;**启动时 fail-fast 断言:同 step 内多个题型不得映射同一 target_file**,违反即中止(RuntimeError,防未来配置漂移) |
|
||||
|
||||
## 2. 改动 ②″:连续并发 gate 调度器
|
||||
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
- 所有命令在 `Video-Tree-TRM` conda 环境:`conda run -n Video-Tree-TRM pytest ...`
|
||||
- 禁止 `except Exception: pass`;中文 docstring;loguru 日志
|
||||
- 现有纯函数复用,不重写:`_ladder_units` / `_load_run_rows` / `_infra_question_ids_from_db` / `_candidate_correctness_from_db` / `_count_infra_units` / `_check_infra_guard` / `_finalize_outcome` / `materialize_candidate_skill` / `unit_correctness_view` / `pair_block` / `gate_decision`
|
||||
- 现有函数复用,不重写。路径钉死(Codex I4:勿在 core 层找它们):
|
||||
- `app/harness/validate.py`:`_ladder_units` / `_load_run_rows` / `_infra_question_ids_from_db` / `_candidate_correctness_from_db` / `_count_infra_units` / `_check_infra_guard` / `_finalize_outcome` / `materialize_candidate_skill`
|
||||
- `app/harness/question_units.py`:`unit_correctness_view` / `build_units` / `flatten_units`
|
||||
- `core/evolution`(纯函数,零改动):`pair_block` / `gate_decision` / `classify_quadrants`
|
||||
- 每个 Task 结束跑 `conda run -n Video-Tree-TRM pytest tests/unit -x -q` 保持全绿后才 commit
|
||||
|
||||
---
|
||||
@@ -90,7 +93,11 @@ def test_prefix_consumes_in_ladder_order_after_head_arrives() -> None:
|
||||
|
||||
|
||||
def test_freeze_on_terminal_verdict_stops_consumption() -> None:
|
||||
"""过线(此处 8 连胜 E≥20 confirmed)即冻结,后续已配齐单元不再消费。"""
|
||||
"""过线即冻结,后续已配齐单元不再消费。
|
||||
|
||||
数值:W 连胜 L=0 时 E=(2^(W+1)-1)/(W+1),W=6→18.14<20,W=7→31.875≥20,
|
||||
故 7 连胜恰好 confirmed 过线(Codex 复核)。
|
||||
"""
|
||||
run = _mk_run(12)
|
||||
for i in range(12):
|
||||
run.slots[i].base = False
|
||||
@@ -98,7 +105,23 @@ def test_freeze_on_terminal_verdict_stops_consumption() -> None:
|
||||
_advance_prefix(run, _PARAMS)
|
||||
assert run.frozen and run.verdict is not None
|
||||
assert run.verdict.decision == "accept_confirmed"
|
||||
assert run.n_used < 12 # 早停,没吃满
|
||||
assert run.n_used == 7 # 第 7 个净胜恰好过线,早停不吃满
|
||||
|
||||
|
||||
def test_tail_infra_reaches_terminal_not_continue() -> None:
|
||||
"""尾部全 INFRA:剔除后须重判(n_remaining 归 0 → 题尽第四出口),
|
||||
verdict 不得停留在 continue(Codex plan 审 C1 回归锁)。"""
|
||||
run = _mk_run(4)
|
||||
run.slots[0].base = False
|
||||
run.slots[0].cand_per_q = {"q0": True}
|
||||
run.slots[1].base = True
|
||||
run.slots[1].cand_per_q = {"q1": True}
|
||||
for i in (2, 3):
|
||||
run.slots[i].base_infra = True
|
||||
run.slots[i].cand_per_q = {f"q{i}": True}
|
||||
_advance_prefix(run, _PARAMS)
|
||||
assert run.verdict is not None and run.verdict.decision != "continue"
|
||||
assert run.frozen
|
||||
|
||||
|
||||
def test_infra_unit_skipped_not_counted() -> None:
|
||||
@@ -127,8 +150,10 @@ def test_ties_hit_futility_early_and_freeze() -> None:
|
||||
run.slots[i].cand_per_q = {f"q{i}": True}
|
||||
_advance_prefix(run, _PARAMS)
|
||||
assert run.frozen
|
||||
assert run.verdict is not None and run.verdict.decision != "continue"
|
||||
assert run.w == 0 and run.l == 0
|
||||
# 精确锁定 futility 出口:首个消费后 W=L=0,n_remaining=2,
|
||||
# 乐观 E=E(2,0)=(2^3-1)/3=2.33<3 → 立即 reject_futility(Codex 复核)
|
||||
assert run.verdict is not None and run.verdict.decision == "reject_futility"
|
||||
assert run.n_used == 1 and run.w == 0 and run.l == 0
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 跑测试确认失败**
|
||||
@@ -238,6 +263,16 @@ def _advance_prefix(run: _GateRun, params: GateParams) -> None:
|
||||
run.prefix_ptr += 1
|
||||
if slot.excluded():
|
||||
run.n_excluded += 1
|
||||
# 剔除使 n_remaining 缩小,必须重判(Codex plan 审 C1):否则尾部全 INFRA
|
||||
# 时 verdict 停留在 "continue",绕过题尽第四出口且 _finalize_outcome
|
||||
# 查 stop_reason 映射 KeyError。n_used==0(纯前导 INFRA)时无证据可判,跳过。
|
||||
if run.n_used > 0:
|
||||
n_remaining = (len(run.slots) - run.n_excluded) - run.n_used
|
||||
run.verdict = gate_decision(
|
||||
run.w, run.l, run.n_used, n_remaining, params=params
|
||||
)
|
||||
if run.verdict.decision != "continue":
|
||||
run.frozen = True
|
||||
continue
|
||||
uid = slot.unit.unit_id
|
||||
assert slot.base is not None and slot.cand_per_q is not None
|
||||
@@ -270,7 +305,7 @@ def _advance_prefix(run: _GateRun, params: GateParams) -> None:
|
||||
- [ ] **Step 4: 跑测试确认通过**
|
||||
|
||||
Run: `conda run -n Video-Tree-TRM pytest tests/unit/test_gate_prefix.py -x -q`
|
||||
Expected: 5 passed
|
||||
Expected: 6 passed
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
@@ -429,6 +464,14 @@ async def test_question_slots_caps_inflight() -> None:
|
||||
|
||||
await asyncio.gather(*[_job(1) for _ in range(6)], *[_job(2) for _ in range(3)])
|
||||
assert peak["max"] <= 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_question_slots_rejects_oversized_request() -> None:
|
||||
"""申请槽数超宽度:fail-fast ValueError 而非自死锁(Codex C2 回归锁)。"""
|
||||
slots = _QuestionSlots(1)
|
||||
with pytest.raises(ValueError, match="自死锁"):
|
||||
await slots.acquire(2)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 跑测试确认失败**
|
||||
@@ -449,11 +492,18 @@ class _QuestionSlots:
|
||||
|
||||
def __init__(self, width: int) -> None:
|
||||
assert width > 0, f"并发宽度必须为正: {width}"
|
||||
self._width = width
|
||||
self._sem = asyncio.Semaphore(width)
|
||||
self._acquire_lock = asyncio.Lock()
|
||||
|
||||
async def acquire(self, n: int) -> None:
|
||||
"""原子获取 n 个题槽。"""
|
||||
"""原子获取 n 个题槽。
|
||||
|
||||
fail-fast:n > 宽度时任务持锁等待永不满足的槽位 → 自死锁
|
||||
(AR pair 单元 2 题 + width=1 的病态配置,Codex plan 审 C2),直接报错。
|
||||
"""
|
||||
if n > self._width:
|
||||
raise ValueError(f"单次申请题槽 {n} 超过并发宽度 {self._width},将自死锁")
|
||||
async with self._acquire_lock:
|
||||
for _ in range(n):
|
||||
await self._sem.acquire()
|
||||
@@ -511,7 +561,11 @@ async def _run_unit_arm(
|
||||
infra_qids = _infra_question_ids_from_db(log, r.run_id, questions)
|
||||
run.infra_denom += r.total
|
||||
if infra_qids:
|
||||
run.errors += 1
|
||||
# 单元级去重(Codex plan 审 I3):同一单元双臂都 INFRA 只计 1 个 error,
|
||||
# 与设计 §2.3"分子=INFRA 单元数(任一臂)"及旧块实现口径一致
|
||||
# (旧实现 cand 不跑 base-INFRA 单元,天然无双计)。
|
||||
if not slot.excluded():
|
||||
run.errors += 1
|
||||
if arm == "base":
|
||||
slot.base_infra = True
|
||||
else:
|
||||
@@ -536,7 +590,7 @@ async def _run_unit_arm(
|
||||
- [ ] **Step 4: 跑测试确认通过**
|
||||
|
||||
Run: `conda run -n Video-Tree-TRM pytest tests/unit/test_gate_unit_arm.py tests/unit/test_gate_prefix.py -x -q`
|
||||
Expected: 10 passed
|
||||
Expected: 12 passed
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
@@ -802,7 +856,7 @@ async def validate_skills_concurrent(
|
||||
- [ ] **Step 5: 跑测试确认通过**
|
||||
|
||||
Run: `conda run -n Video-Tree-TRM pytest tests/unit/test_gate_concurrent.py tests/unit/test_gate_prefix.py tests/unit/test_gate_unit_arm.py -x -q`
|
||||
Expected: 13 passed
|
||||
Expected: 15 passed
|
||||
注意:`test_out_of_order_arrival_still_ladder_order` 里 cand 臂识别依赖 `run_id.endswith("_cand")`,与实现的 run_id 命名 `{prefix}_{arm}` 一致;若失败先核对 run_id 拼接。
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
@@ -978,6 +1032,22 @@ def test_shared_target_file_fails_fast() -> None:
|
||||
_assert_disjoint_target_files(
|
||||
{"OCR Problems": "default-strategy.md", "Spatial Reasoning": "default-strategy.md"}
|
||||
)
|
||||
|
||||
|
||||
# ---- runner 级红绿(Codex plan 审 I6):进化并行 + Phase D 字母序 + 共享 gate_log ----
|
||||
# 实现说明:_gate_batch_skills 是 Runner 方法,构造完整 Runner 成本高。用
|
||||
# `Runner.__new__(Runner)` 裸实例 + 手工挂属性(_config/_paths/_evolve_llm/
|
||||
# _gate_questions_by_id/_gate_units_by_id/_current_version/_record_run/
|
||||
# _class_baseline_acc/_accept_skill/_record_rejected_skill 等按 AttributeError
|
||||
# 逐个补假实现),monkeypatch 以下三点后调用 _gate_batch_skills:
|
||||
# 1. core.evolution.evolve_single_skill → 假实现:记录 (task_type, 开始/结束时间),
|
||||
# 各 sleep 0.05s;断言两题型的时间窗重叠(证明 gather 并行而非串行)。
|
||||
# 2. app.harness.runner.validate_skills_concurrent → 假实现:记录收到的 specs 与
|
||||
# log 参数,返回两题型的假 ValidationOutcome(一 accept 一 reject)。
|
||||
# 3. runner._accept_skill / _record_rejected_skill → 假实现:append 到调用序列表;
|
||||
# 断言调用顺序 == sorted(题型)(Phase D 字母序),且 accept/reject 分派正确。
|
||||
# 若挂属性成本失控(超过 ~60 行 fixture),降级为拆出可测纯函数并在 PR 说明,
|
||||
# 但字母序与并行两条断言不得省略。
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 跑测试确认失败**
|
||||
@@ -1261,6 +1331,7 @@ git commit -m "feat: parallel evolve + continuous gate wiring in runner (algo #6
|
||||
- Modify: `app/harness/checkpoint.py:61`(从字段清单删 `"gate_block"`)
|
||||
- Modify: `config/*.yaml` 全部含 `gate_block:` 的文件(train_videomme / default / question_gen_180_补 / question_gen_360 / train_action_recognition / train_ar30)删该行
|
||||
- Modify/Delete: `tests/unit/test_gate_block_unit.py`、`tests/unit/test_harness_validate.py` 等引用被删符号的测试
|
||||
- Modify: `tests/integration/test_checkpoint_pair.py:128`(含 `gate_block`,Codex I7;grep 范围必须覆盖 tests/integration/ 与 tests/e2e/)
|
||||
|
||||
- [ ] **Step 1: 全局定位被删符号的引用**
|
||||
|
||||
|
||||
Reference in New Issue
Block a user