Files
Robot_Arm/docs/ML运行手册审查.md
T

559 lines
60 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.
# ML 运行手册审查(ml/README.md)
审查对象:`ml/README.md`(12 阶段运行手册)+ `ml/pipeline.sh` + `ml/gen|prep|model|sim|apg|mining|eval` 全部代码。
判据来源:`docs/Transformer轨迹拟合训练方案.md`(方案)。
硬件:本机 CBB-003,RTX 3090 24 GB(driver 617.42)、28 核 CPU、Python 3.12.10 + torch 2.10.0+cu130 + onnxruntime 1.22.0、Node v24.15.0。
方法:代码逐行读 + 在本机真跑第 2/4/5/6 步(两种头)与闭环仿真。**APG 及之后(8–11 步)未跑**,被中断,不声称可用。
结论:流程骨架是通的(对拍 PASS、数据生成、打包、预训练、闭环仿真都实测跑过)。问题集中在**判据自相矛盾**和**指标口径不一致**两类,不是代码写错。
---
## 一、实测记录(可复现)
冒烟产物落在 gitignore 目录 `ml/data/_smoke/`、`ml/runs/_smoke/`,工作树干净,`ml/assets/kin.json` 未重新生成。
| 步 | 命令 | 实测结果 | 手册声称 | 判定 |
|---|---|---|---|---|
| 2 对拍 | `node ml/eval/dump_parity.cjs 300` + `python -m ml.eval.check_parity` | `PASS: true`,fk 3.4e-13 mm、orient 3.3e-16、间隙 2.5e-13 mm、83 维特征 6.0e-8、间隙梯度相对误差 1.4e-6,7.6 s | 2 分钟,须 PASS | 符合 |
| 4 生成 | `node ml/gen/gen_dataset.cjs --n 80 --seed 7` | **1.56 s/条**,80 条 122 s;老师 plan p50 291 ms / p90 1490 ms,认证 p50 247 ms / p90 944 ms | 约 2 秒/核 | 符合(偏乐观余量) |
| 4 统计 | `node ml/gen/stats.cjs`(88 条) | keepRate **0.977**、samplesPerTask 36.5、projectedSamplesFor50k **178.4 万**、exec_s p50 2.3 / p90 3.0 / max 3.7 s、travel p50 350°、**cert.minGap p50 4.4 / p90 5.9 mm**、尾部补零样本占比 14.7% | keepRate≈0.95、projected≈180 万 | 符合,但 minGap 没进检查项(见 P0-2、口径-4) |
| 5 打包 | `node ml/prep/build_dataset.cjs` + `python -m ml.data --index` | 86 任务 → train 3004 / val 136 样本,`index.json` 键名与手册一致;增广 847 提交 / 5 拒绝 | train 170 万 / val 9 万(5 万条口径) | 比例自洽(5% split) |
| 6 预训练 MeanFlow | `python -m ml.train --steps 250 --no-eval --config ml/configs/base.json` | 参数量 **4.92 M**,吞吐 **6 208 样本/s**(step 200,batch=1024) | 1.5–6 小时;方案 R10 要 ≥2 万样本/s | 与 1.5–6 h 自洽(60 000 步 ≈ **2.8 h**),**与 R10 判据冲突** |
| 6 预训练 WTA | `--config ml/configs/wta.json --steps 40` | 跑通,参数量 4.93 M(同骨干换头成立) | 回退约 1 天 | 通路成立 |
| 闭环仿真 | `python -m ml.sim.closed_loop --split val --n 3` | 跑通 9.7 s,按 cat/mode 出 success/collision(未训模型 0/0,符合预期) | — | 通路成立 |
吞吐换算:60 000 步 × 1024 = 6 144 万样本 ÷ 6 200 = **9 910 s ≈ 2.75 h**(不含 eval)。2 万样本/s 意味着 51 分钟跑完 60 000 步——当前配置在 3090 上不可能达到。
---
## 二、P0:会让人在终端前停住或误判
### P0-1 训练吞吐判据自相矛盾(方案 R10 vs 手册)
- 方案 §10.1 R10:`2000 步吞吐 ≥ 2 万样本/s`,缓解写的是"降 epoch 或增大 batch"。
- 实测 6 208 样本/s → 按 R10 判"不达标",但 60 000 步 2.75 h 完全落在手册 1.5–6 h 的预算内。
- 训练是算力受限(MeanFlow 的 `torch.func.jvp` 双前向 + FK 损失),增大 batch 只拉长每步墙钟,samples/s 不上去。R10 给的缓解措施对它自己定的阈值无效。
- 手册 §6 只写"测吞吐",**没写通过线**:`ml/train.py:98` 打出 `samples_per_s`,读者看到 6k 无从判断该继续还是改配置。
建议:R10 阈值改为终态判据("60 000 步 ≤ 3 h")或把 samples/s 线降到 ~5 千;手册 §6 补一行"实测参考:本机 3090 batch=1024 → 6.2 k 样本/s ≈ 2.8 h/60 k 步"。
### P0-2 共识过滤分支的期望数字整段缺失
- `ml/pipeline.sh:27-31`:`consistentByGateRule < 0.9` 时自动加 `--consensus 4`。
- 方案 §10.1 R1 记录首测一致率 **78%**(100 任务 / 373 对邻居,门禁 90%)→ **默认走的就是 consensus 分支**。
- `ml/gen/gen_dataset.cjs:51`:邻居多数不同意就 `keep=false`(why=consensus)→ keepRate 与样本量双双下降。
- 手册 §4 的 `keepRate≈0.95`、`projectedSamplesFor50k≈180 万`(实测无共识 0.977 / 178 万)是**关共识**的口径;开共识后没有对应期望值,也没写"样本不够要不要加 N_TASKS"。
- §13 回传表要求按 `stats.json` 判下一步 → 判据缺一条分支。
建议:§4 增加共识分支的两行期望(keepRate≈? / projected≈?),并明确"低于 X 就把 N_TASKS 提到 8 万"这类动作。
### P0-3 WTA 回退会覆盖 MeanFlow 的训练产物
- `ml/pipeline.sh:34-35`:`train` 阶段输出目录写死 `ml/runs/bench` 与 `ml/runs/base`,`CFG` 只换头不换目录。
- `ml/pipeline.sh:37`(m5)与 `:44`(apg)写死读 `ml/runs/base/best.pt`。
- `ml/train.py:57`:`log.jsonl` 以 append 打开 → 两次不同头的日志混在同一文件。
- 后果:手册 §7 的 `CFG=ml/configs/wta.json bash ml/pipeline.sh train` 会盖掉已训好的 MeanFlow `best.pt/last.pt`,对比物消失;而方案 §1.1 选头的依据恰恰是两者对比(沙箱里两者 100%/0%,"分不出高下")。
建议:`OUT=${OUT:-ml/runs/base_$HEADTAG}`,m5/apg/bench 用 `CKPT=` 变量指;两头的 run 分目录共存。
### P0-4 手册要求 Linux/WSL2,但本机 Windows 原生可跑,只差一处
- 实测第 2/4/5/6 步与闭环仿真在 Windows + Git Bash 下全部跑通(Python 用 `python`)。
- `ml/prep/run_prep.sh:8` 硬用 `python3`,而 Windows 的 `python3` 是 `WindowsApps` 假壳(`python3 -c` 直接 exit 1)→ 第 5 步在**最后一步**失败,`set -e` 会让 `pipeline.sh all` 在此中止(前面的 npy 已写好,重跑代价大)。
- 同类:`ml/pipeline.sh` 内联的 `python -c`(gate1 的 CONSENSUS 判定、m5 的打印)用的是 `python`,在 Windows 没问题 → 两个脚本各用一种,本身就是不一致。
建议:统一 `${PYTHON:-python}`(或 `${PYTHON:-python3}`);手册 §1 补一句"Windows 原生实测可跑,需 Git Bash + `python` 在 PATH"。
---
## 三、口径不一致("这一步通过"不等于"部署能过")
### 口径-1 闭环仿真的安全层 ≠ 部署运行时的安全层
- 仿真只有"逐步候选干涉则换下一条"(`ml/sim/closed_loop.py:118-130`)。
- 运行时多出:1 步胶囊梯度投影(`webapp/js/ik-policy.js:107`)、刹停段 + 每条候选 40 次距离查询预算(`:177`)、安全层单次推理 30 ms 预算(`:322`)、卡死检测与换向锁 1 s(`:296-304`)。
- §6 EVAL、§7 M5 门禁、§9 挖错/门禁**全部**用仿真那套(默认 `safety=False`)。
- 于是 §7 的"穿柱率 ≤2%"和 §11 的"collisionFrames 必须为 0"是两把尺,互不预示;手册没有任何一处说明这点。
建议:§7 明说"M5 是纯策略指标,§11 是策略+安全层指标";m5 对同一门禁集加跑一遍 `--safety`,两个口径都写进 `m5_gate.json`。
### 口径-2 门禁集/挖错题的时长预算是假的
- `ml/mining/tasks.py:47` 每题写死 `exec_s: 3.0, travel: 0.0`。
- `ml/sim/closed_loop.py:67` 超时 = `clamp(2*exec_s, min=3)` = 6 s;`ml/mining/mine_round.py:38` 又用 `max_sec=6.0` 覆盖。
- 后果:§7 M5 的 1000 题、§9 的门禁集里,"到达"一律 6 s 硬顶、`time_ok_1p3` 一律 3.9 s;而 §6 训练期 EVAL 用的是老师真实时长 ×1.3。两个"到达率"不可比却共用同名指标。`travel=0` 还使 `travel_ratio_median`(`closed_loop.py:154`)在门禁集上直接返回 None。
- 方案 §10.1 R8"3 s 内恢复 ≥95%、活锁=0"在 12 步里没有执行点。
建议:`tasks.py` 出题时带上老师时长(或明确写"门禁集按定长 6 s 口径");R8 补对抗用例集与命令。
### 口径-3 APG 的"到达"是定长 3.6 s 窗口内的距离塑形,且碰撞只查端点
- `configs/apg.json`:`plans:60`,每次推理提交 3 步 × `DT=0.02` → 展开 **3.6 s**(`ml/apg/apg_finetune.py:67`),无"到达即停";到达项只在每个 10 块段末用 `((T-pe)/100)²`(`:104-109`)。
- 实测这批老师轨迹 exec_s p50 2.3 / p90 3.0 / max 3.7 s → 样本刚好压线;真数据里老师时长 >3.6 s 的绕远长任务(正是 C/B 难题)APG **永远看不到绕出段**。
- 碰撞 hinge 只打每步末端点(`:95-97`),仿真与运行时都查端点+中点(`closed_loop.py:142-144`、`ik-policy.js:222`)。
- 手册 §8 写"直接优化到达、碰撞余量",读者会以为是全 horizon 到终点。
建议:§8 补一句"定长 3.6 s 滚动 + 段末距离替代到达项";把 `plans` 提到覆盖 exec_s p90(≥55 s 不现实,折中:对 exec_s>3.6 s 的题按比例加 plans,或在 README 说明这是已知截断)。
### 口径-4 训练余量 5 mm 压在老师轨迹自身的间隙分布上
- 实测 88 条 keep 轨迹 `cert.minGap` **p50 4.4 mm / p90 5.9 mm**(`ml/gen/stats.cjs:18` 已算出 `vertexMinGapMm`)。
- `docs/collide.json` 的 `margin_mm = 0.3`,`kin.gaps()` 的 gap 已减掉它 → 这 4.4 mm 是实打实的几何间隙。
- `configs/apg.json` 的 `margin_train_mm = 5.0` + 运行时投影目标 `margin:5`(`ik-policy.js:174`)→ **约一半 BC 标签落在 5 mm 以下**,APG 碰撞项与 BC 项方向相反。
- 手册 §4 让检查的三项(keepRate / byCatMode / projectedSamples)里没有 `vertexMinGapMm`;方案 §7.1 只写"margin=认证+5",没和老师的间隙分布对齐。
建议:§4 检查项加 `vertexMinGapMm` 并给期望区间;要么 `margin_train_mm` 降到老师分布 p50 附近(~4 mm),要么承认 BC/APG 有对抗并把它当成设计取舍写下来。
---
## 四、缺项与细节
1. **验收表落不了地**:方案 §9 要求 held-out 可达 ≥98%、D 类停靠 ≤老师+5 mm、刹停发生率 <2%、R7 左右比例与行程门禁、R8 对抗用例——12 步里都没有对应命令。`ml/mining/gate.py:41` 只查 3 项(到达增益 / A 类降幅 / 上轮仍对);`tests/policy_bench.cjs:81` 已经算出 `brakes/locks/uncertifiedSegs` 但没进任何门禁。
2. **挖错池累积**:`ml/mining/loop.sh:20` `ls ml/runs/mine/r*/pool.jsonl` 把**所有历史轮**的错题池喂给每轮 APG,池子逐轮变大,且 `mine_round.py:57` 对 E1 复制 ×2、每题再带 10 个邻居。手册 §9 只说"错题与邻居进采样池",没说会跨年累积 → 后期轮次的采样分布严重偏向老错题(A 类降幅门禁是唯一的兜底)。
3. **fp16 导出方向可疑**:手册 §10 把 `--fp16` 报错当偶发("去掉 --fp16 手动导出 fp32")。但部署端是 wasm(`webapp/js/ik-policy-glue.js:36` 默认 wasm)和 Node CPU(`tests/policy_bench.cjs:55` `executionProviders:['cpu']`),fp16 在 CPU EP 上要么缺 kernel 要么靠大量 Cast 兜底,不保证比 fp32 快。参数量实测 4.92 M → fp32 ≈ 20 MB(与手册一致),fp16 省的 10 MB 对浏览器收益有限。**本机 `onnxconverter_common` 未装,此条未实测。**
4. **`--n` 同时控制两个集合**:`tests/policy_bench.cjs:85-86` 的 `+opt('--n')` 对 nc 和 route 一起生效,手册说的"30 例 / 40 例"只在默认值成立。
5. **val 为空时崩**:小样本摸底(几百条、5% split 可能 0 个 val 任务)下 `ml/sim/closed_loop.py:161` 的 `evaluate` 返回空 dict,`ml/train.py:106` 取 `res['success']` KeyError。真实数据不会撞,但手册鼓励先小跑摸底。
6. **文件地图缺 4 个真实文件**:`ml/gen/cert.cjs`(认证实现在 ik-policy.js,这里是跳转壳)、`ml/gen/pathdev.cjs`(同伦类判定,共识过滤与门禁都依赖它)、`ml/prep/npy.cjs`、`ml/toy/toy_bimodal.py`(方案 §沙箱 的依据)。
7. **R2 无执行点**:方案要求"近邻样本标签方差 <10% 步长、起步失败率=0",手册 §3–§5 的检查项里没有。
---
## 五、按优先级的改动清单
| 优先级 | 改什么 | 落点 |
|---|---|---|
| 1 | 吞吐判据对齐:R10 改成终态判据或降线,§6 写实测参考值 | `docs/Transformer轨迹拟合训练方案.md` §10.1、`ml/README.md` §6 |
| 2 | train 阶段输出目录随头分开,m5/apg/bench 用 CKPT 变量 | `ml/pipeline.sh:34-37,44,48` |
| 3 | `python3` → `${PYTHON:-python}`,§1 说明 Windows 原生可跑 | `ml/prep/run_prep.sh:8` |
| 4 | §4 补共识分支的 keepRate/样本量期望与"加 N_TASKS"动作;检查项加 `vertexMinGapMm` | `ml/README.md` §3–§4 |
| 5 | §7 说明 M5 与 §11 两个口径,m5 加 `--safety` 对照 | `ml/pipeline.sh:36-42`、`ml/README.md` §7 |
| 6 | 出题带真实老师时长(或明写定长 6 s),补 R7/R8 执行点 | `ml/mining/tasks.py:47`、`ml/mining/gate.py:41`、`ml/README.md` §9 |
| 7 | §8 明写 APG 的 3.6 s 截断与只查端点;`margin_train_mm` 与老师间隙分布对齐 | `ml/README.md` §8、`ml/configs/apg.json` |
| 8 | §10 默认 fp32 + `__POLICY.latency()` 实测后才考虑 fp16(仅 WebGPU);loop.sh 池子只取本轮;文件地图补全;evaluate 空任务保护 | `ml/README.md` §10/文件地图、`ml/mining/loop.sh:20`、`ml/sim/closed_loop.py:161` |
未验证范围:第 7(m5)、8(apg)、9(mine)、10(export)、11(bench)步与浏览器 `?policy=1` 第 12 步都没跑过(`ml/node_modules` 未安装、`webapp/models/` 无模型、`onnxconverter_common` 未装)。这些通路只做过静态代码核对。
---
---
# 第二轮:对修订版(提交 `d3b7c7b`)的可行性评估
评估对象:另一台机器按上述审查做的修订(18 文件、+493/−156)。方式:**只读代码 + 只读 diff,本轮没有跑任何东西**,第一轮的实测数字(6208 样本/s、keepRate 0.977、minGap p50 4.4 mm、1.56 s/条)继续沿用。上文第一轮的正文按 0218d7f 时的代码写,路径与章节号(`ml/runs/base`、12 步)在修订后已变,不逐条订正,以本节为准。
## 一、第一轮 13 条的处置
| 第一轮条目 | 状态 | 落点 |
|---|---|---|
| P0-1 R10 吞吐判据矛盾 | 已修 | 方案 §10.1 R10 改为"60000 步 ≤ 3.5 h,即 ≥5000 样本/s",缓解改成 `fk_caps` 96→48 / `steps`→40000(增大 batch 明确标注无效);`ml/train.py:101` 加 `eta_h` |
| P0-2 共识分支缺期望数字 | 已修 | README §4 加两张表(不共识实测 / 共识估算 + 异常动作);`ml/gen/stats.cjs:20` 加 `consensusDrop`、分位数加 p10;`ml/pipeline.sh` gen 阶段按实测"样本/任务"自动补量到 `TARGET_SAMPLES=1700000`(最多 2 次) |
| P0-3 WTA 回退覆盖 MeanFlow 产物 | 已修 | `pipeline.sh`:输出 `ml/runs/$TAG`、`ml/runs/bench_$TAG`;m5 通过后写 `ml/runs/selected_ckpt`,apg/export/accept 经 `ckpt()` 取;`ml/train.py:58` 与 `apg_finetune.py:51` 日志改为新训覆盖、续训追加 |
| P0-4 `python3` 在 Windows 断第 5 步 | 已修 | `run_prep.sh:8` 与各脚本统一 `${PYTHON:-python}`;README §1 加"Windows 原生 + Git Bash 实测可跑",并写明 Windows 不要用 `python3` |
| 口径-1 仿真安全层 ≠ 运行时 | 已修(说明层) | m5 同跑 `--safety` 一遍,产出 `m5_gate_<tag>.json` 的 `policy_only` / `with_sim_safety` 两块;README §7 明写"三口径互不替代,部署只看第 11 步" |
| 口径-2 门禁题假时长 | 已修 | `ml/mining/tasks.py:54` `exec_s/travel` 置 None;`ml/sim/closed_loop.py:24` `NOREF_SEC=6.0`,`time_ok_1p3`/`travel_ratio` 只在有老师参考的题上统计,新增 `n_ref`、`time_budget` 字段 |
| 口径-3 APG 定长 3.6 s + 只查端点 | 已修(但引入新问题,见下 B-2) | `apg_finetune.py:70` 展开长度随批取 `exec_s p90 × 1.2`,夹在 60–150 次推理;`:102` 铰链改端点 + 中点 |
| 口径-4 5 mm 余量压老师间隙分布 | 已改,方向不认同 | `configs/apg.json` 与 `webapp/js/ik-policy.js:174` 一起降到 3 mm;方案决策表"训练余量 5 mm"改为 3 mm,R3 回退改"提高到 4–6 mm" |
| 缺项-1 §9 验收表无执行点 | 已补 | 新增 `ml/eval/acceptance.py`(第 12 步 `accept`):held-out 到达率 ≥98%、D 类停靠 ≤老师+5 mm、起步失败=0,并汇总 R2/R7/R8/R10/M5/部署形态 |
| 缺项-2 挖错池累积 | 已修 | `ml/mining/loop.sh:21` 改为 `--pool $D/pool.jsonl` 只用本轮 |
| 缺项-3 fp16 部署 | 已修 | `pipeline.sh` export 去掉 `--fp16`,README §10 改默认 fp32,只在 `&ep=webgpu` 实测受益才另出 fp16 对比 |
| 缺项-4 `--n` 混控两集合 | 已修 | 拆成 `--nc-n` / `--route-n` / `--r8-n`,positional 过滤改用白名单 `VALOPT` |
| 缺项-5 val 为空崩 | 已修 | `closed_loop.evaluate` 空任务返回 NaN 字典;`train.py:59` 无 val 任务时提示并自动 `--no-eval` |
| 缺项-6 文件地图缺 4 文件 | 已修 | README 文件地图补 cert.cjs / pathdev.cjs / npy.cjs / toy_bimodal.py |
| 缺项-7 R2 无执行点 | 已补 | 新增 `ml/eval/check_r2.py`,prep 末尾自动跑 |
| 附带 | 新增 | R7(左右比例 + 行程)进 `ml/mining/gate.py:47-53`;R8 对抗集 + `brakeRate` 进 `tests/policy_bench.cjs`;出题与间隙计算分块防 OOM(`tasks.py:9` `min_gap_chunked`) |
## 二、修订新引入的问题(按严重度)
### B-1(最高)3 mm 是拿真机护栏迁就数据分布
依据是本轮实测"老师最小间隙 p50 4.4 mm",但正解是在认证环节把 `cert.minGap < 3 mm` 的老师样本过滤掉(`stats.cjs` 已经算这个数,逐条记录里就有),而不是把**运行时投影余量**一起降下来。认证只保证扫掠间隙 > 0,`collide.json` 的 `margin_mm` 只有 0.3 mm,`project()` 是唯一主动把臂往外推的力量(`ik-policy.js:279`)。方案 R3 原文写的回退是"训练余量**提高到** 3–6 mm 以缓解认证过保守",修订把这个值定成 3 = 把回退方向当成了默认。贴柱真实距离从 ≥5 mm 变 ≥3 mm 是机械/安全决定,需要单独确认,不该由 ML 口径推出。
### B-2 APG 两处改动耦合且没重标定
- `apg_finetune.py:102-104`:端点与中点两个 hinge 直接 `view(2, B).sum(0)` 相加 → 碰撞项数值约为旧版 **2 倍**,而 `w_col=2.0` 未动。
- `:70` + `plans_max=150`:展开最坏 60→150 次推理(2.5 倍),每步 `kin.gaps` 的位形数又 ×2(端点+中点)→ 单个 APG step 代价上界约 **4 倍**。
- 同时 margin 5→3 让铰链激活的样本数下降。一增一减方向相反,幅度都没实测。
- README §8 的"2–4 小时"沿用旧口径,没随改动更新。
后验表现无法预判:APG 之后若行程变长/贴柱反而更近,第一嫌疑是这个组合而不是学习率。
### B-3 新代码路径一次没跑,check_r2 自身可能成为闸门
- `check_r2.py:39`:`m.sum()==0` 时对空张量取 `torch.quantile(std, 0.9)` 直接报错(小样本摸底必撞;`label_std_mean` 也是 NaN)。
- 默认 `--queries 20000 --pool 200000`,分块距离矩阵 1024×200000 ≈ 0.8 GB/块,`torch.where` 再复制一份 → 峰值 2–3 GB 显存;`Samples` 本身又把 X/Y/SG 全量上卡(约 1.5 GB)。CPU 兜底时 20000×200000 fp32 = 16 GB 内存,docstring 说"降到 50000"仍然不轻。
- 它挂在第 5 步末尾,README 写"不通过先发我,不要直接训练"→ 一个自身可能 OOM/崩的硬门禁。
### B-4 R7 的左右比例被成功率污染,会漏报它想抓的东西
`gate.py:47` 的 `left(...)` 以"到达"为条件掩码。APG 把某一侧绕行全做失败时,剩余成功集里的左右比例可以纹丝不动 → 而 R7 的目标(方案:APG 压缩多模态)恰恰是这种情况。建议按全部题的首次 J1 方向统计,不筛到达。
### B-5 r8 集合没实现"两侧都要绕远"
`policy_bench.cjs:47-64` 只约束终点贴立柱 + |ΔJ1|≥140° + 直线被挡,**起点没做 `nearColumn` 检查**。方案 §10.1 R8 的口径是"两侧都要绕远",现在起点对侧无约束 → 恢复率会比设计意图偏松(判据偏松是危险的偏)。
### B-6 估时里"实测"两字一处用错、两处仍是拍的
- §2 写"1 分钟(实测 7.6 s 对拍)"——7.6 s 是 `dump_parity 300` 的数,pipeline 跑 2000 条,不同口径。
- §6 的"测速 6 分钟 / 正式 3 小时"由 6.2k 样本/s 换算,可信。
- §7 的 10–20 分钟(1000 题 ×2 遍 × 定长 6 s)、§11 的"约 1 小时"(题数已从 1070 增到 1120,且 r8 采样每条最多 200 次 `collideDetail` 重试)、§8 的 2–4 小时(见 B-2)都无依据。
### B-7 方案自标的最大不确定项 R4 仍然是空的
策略推理在 wasm 多线程、batch=8 下的 P99 从未量过:`webapp/models/` 只有一个 README.md(至今没导出过任何模型),方案 §1.1 表里的 0.19 ms 是 2D 玩具沙箱(`ml/toy/toy_bimodal.py`)的数,不是这个 4.92 M 模型。而 60 ms 一次推理的部署节奏、`safetyMs:30` 预算、`certBudget:40` 全靠它。R4 不过就要改模型规模(d=128)或提交步数(3→2),是伤筋动骨的返工,却排在第 13 步才第一次被测。
### B-8 杂项
- `build_dataset.cjs:46` 噪声增广的接受地板仍是 `S.clearance(qn, 5) > 1`,与新的 3 mm 投影不同步(一松一紧,不是矛盾但没对齐)。
- `acceptance.py:44` 用 `start_fail == 0` 硬判:held-out 约 2500 题里任何一题 0.5 s 内行程 <1° 就整项未过,判据脆。
- `pipeline.sh` m5 不通过 `sys.exit(3)` → `all` 会中止(有意,但应写进"常见问题")。
- `requirements.txt` 仍列 `onnxconverter-common`(fp16 已非默认,留着无害)。
- `tasks.py:36` 的 `column_gap(kin, b)` 仍在 2048 行整批上算(`free_q`/`straight_blocked` 已分块),单卡可过但和"已分块防 OOM"的说法不完全一致。
## 三、可行性判断
1. **前段(第 1–6 步)可执行**:数据生成、打包、两种头、闭环仿真、对拍都在本机实测过,数字与手册自洽。修订把 R10、共识补量、目录覆盖、`python3` 这几个会真卡住人的点解决了,这部分我认可。
2. **后段(第 7–13 步)逻辑闭合但无证据**:判据齐全、口径终于分开了,可"APG+挖错能不能把纯策略穿柱率压到 ≤2%"、"机时是不是 2–4 h"、"held-out 能不能 98%"一个都没跑过。别把 §8–§12 的估时当预算。
3. **不建议按现值直接跑第 7 步之前**:B-1(3 mm)是需要你拍板的安全决定,B-2(w_col 没跟着重标)会让后段任何异常都无法归因。
## 四、第 7 步之前建议先做的两件事(都不需要训练)
1. **结掉 R4**:用一个随机初始化的 4.92 M 模型导出 fp32 ONNX,走 `ml/serve.py` + `?policy=1`,在目标浏览器实测 `__POLICY.latency()` 的 batch=8 P99 与安全层 P99(方案要求 <15 ms)。不过就要改 `d`/`commit`,此时改配置的成本远低于训完 3 小时再改。
2. **把 margin 决定单独拍板**:在"过滤老师数据保 5 mm"与"放宽护栏到 3 mm"之间选一个,再把 `w_col` 与 §8 估时按选择重算。这两条是本轮唯一拦住往下跑的理由。
---
---
# 第三轮:对第二轮 B-1~B-8 的处置 + 新增训练控制台
修订方:沙箱(Hark)。基于 `655e1fb`。沙箱只做了 CPU 小数据冒烟(见第四节),所有耗时、keepRate、gapDrop 比例仍以你机器上的实测为准。
本轮改动以补丁形式交付(`git apply review2_and_monitor.patch`),确认后再推送。
## 一、B-1~B-8 处置
| 条目 | 处置 | 改动位置 |
|---|---|---|
| B-1 3 mm 拿护栏迁就数据 | **采纳你的方向**:运行时投影护栏恢复 **5 mm**;改为在数据侧剔除认证最小间隙 `cert.minGap < 3 mm` 的老师轨迹;APG 碰撞项余量 3 mm 与过滤线一致(碰撞项不与 BC 标签对抗)。护栏比数据更保守,二者不再绑定 | `webapp/js/ik-policy.js:174` margin 3→5;`ml/gen/stats.cjs` 新增 `gapDrop`、`minGapFilterMm`,样本数与补量只按过滤后计;`ml/prep/build_dataset.cjs` 同一过滤(`MIN_GAP_MM=3`,可用环境变量改),打包输出也报 `gapDrop` |
| B-2 端点+中点 hinge ×2、w_col 未重标 | 两处 hinge 由求和改为**取均值**,与只查端点同量纲,`w_col=2.0` 不用动。展开 60–150 次的单步代价不再拍估时:手册 §8/§9 改为"未实测,以监控页前 50 步换算的 ETA 为准" | `ml/apg/apg_finetune.py:104`;README 阶段表 |
| B-3 check_r2 空匹配崩、显存峰值 | 近邻对 < 20 时输出 `pass: null` 不报错;默认 5000 查询 × 50000 池、分块 256、`masked_fill_` 原地,GPU 峰值 < 0.5 GB。`pass: false` 时 prep 以返回码 4 结束、一键流程停下;`null` 不拦 | `ml/eval/check_r2.py`;`ml/pipeline.sh` prep |
| B-4 R7 以到达为条件会漏报 | 左右比例改为按**全部** B/C 题的首次 J1 方向统计,不筛到达 | `ml/mining/gate.py:47` |
| B-5 r8 起点没贴柱 | 起点也要求 `nearColumn`,两侧都要绕远 | `tests/policy_bench.cjs` r8Cases |
| B-6 估时口径 | 对拍改为"约 1 分钟(估算:300 条实测 7.6 s,流程跑 2000 条)";M5、部署评测标"估算";APG、挖错标"未实测" | README 阶段表 |
| B-7 R4 排在最后 | **提到训练前**:新增 `bash ml/pipeline.sh r4`,用随机初始化模型导出 fp32 ONNX(延迟只与结构有关),`python ml/serve.py` + `?policy=1` 测 `__POLICY.latency()`;不过就把 `model.d` 192→128、`ff` 768→512 后重测 | `ml/export_onnx.py` 新增 `--random <配置>`;`pipeline.sh` r4;README §2;方案 R4 行"M1 前" |
| B-8 杂项 | 噪声增广接受地板 1 mm → 3 mm(与过滤线一致);验收起步失败 `= 0` → `≤ 0.2%`;`column_gap` 分块 256(沙箱实测约 20 MB/位形,整批 2048 会爆);m5 返回码 3 中止 `all` 写进常见问题;`set -o pipefail` + 门禁结果先写 `.tmp` 再改名(中断不留残缺 `consistency.json`) | `build_dataset.cjs`、`acceptance.py`、`tasks.py`、`pipeline.sh`、README 常见问题 |
| `onnxconverter-common` | 保留(`--fp16` 仍可用于 WebGPU 对比) | — |
方案文档同步:决策表"训练余量"改为"数据过滤与 APG 碰撞项 3 mm、运行时护栏 5 mm";APG 碰撞项写明端点与中点取均值;R4 检测时点改为训练前。HTML 已重新生成。
**需要你确认的一处**:3 mm 过滤会剔除多少老师轨迹没实测过。沙箱 30 条小样本里剔除了 9/29(p10 = 1 mm),量太小不具代表性;你那边第一轮实测 p50 4.4 mm。生成完看 `stats.json` 的 `gapDrop`:> 25% 说明老师整体太贴柱,把 `stats.json` 发我再定。自动补量会按过滤后的样本数补,所以样本总量不受影响,只是生成时间相应变长。
## 二、新增:训练控制台(一键运行 + 全程可视化)
### 怎么用
1. Windows 双击 `ml/console.bat`(Linux/WSL:`bash ml/console.sh`),浏览器自动打开 `http://localhost:8100/`。
2. 点右上角「▶ 开始训练」,在对话框里选起始阶段、结束阶段和参数,点「开始」。
3. 之后整个流程(对拍 → 老师门禁 → 生成 → 打包 → 预训练 → M5 → APG → 挖错 → 导出 → 部署评测 → 验收)都在这一页看;「■ 停止」随时停,再开始默认从停下的那一步继续。
网页可以关,训练不受影响;关闭控制台窗口会结束训练。R2、M5 不通过时流程会停在那一步。
### 组成与接口
| 文件 | 作用 |
|---|---|
| `ml/console.bat` / `ml/console.sh` | 一键启动:`python -m ml.monitor --open` |
| `ml/monitor.py` | Python 标准库 HTTP 服务,默认只监听 127.0.0.1:8100 |
| `ml/monitor.html` | 网页,零依赖(原生 canvas 画图),不连外网;`?demo=1` 或直接双击打开时用内置示例数据 |
| `ml/pstate.py` | 写 `ml/runs/pipeline.json`:`pipeline.sh` 每个阶段开始/结束时自动调用(终端里跑也会写) |
| 接口 | 方法 | 说明 |
|---|---|---|
| `/` | GET | 网页 |
| `/api/runs` | GET | `ml/runs/*/log.jsonl` 列表(名称、类型 train/apg/bench、更新时间、是否发散) |
| `/api/log?run=&offset=` | GET | 增量读日志:只返回 offset 之后的完整行,日志被新训练覆盖时自动从头读 |
| `/api/status` | GET | GPU、`pipeline.json` + 当前阶段进度、数据统计、R2、M5、挖错各轮、部署评测、验收 |
| `/api/job` | GET | 控制台启动的流水线进程:是否在跑、PID、起止阶段、返回码、是否手动停止 |
| `/api/console?offset=` | GET | `ml/runs/console.log` 增量(首次只给最后 64 KB) |
| `/api/start` | POST | `{"from":"kin","to":"accept","env":{...}}` → 后台 `bash ml/pipeline.sh all`;env 只接受白名单:`N_TASKS NPROC TARGET_SAMPLES CFG CONSENSUS ROUNDS APG_STEPS MINE_N`,逐个正则校验 |
| `/api/stop` | POST | 结束整个进程树(Windows `taskkill /T /F`,其他系统进程组 SIGTERM,10 秒后 SIGKILL),当前阶段记为返回码 -15 |
安全:开始/停止只接受本机请求(`--host 0.0.0.0` 开放给局域网时其他机器只能看,`--remote-control` 才放开);已经有流水线在跑(控制台自己起的,或终端里起的、日志 2 分钟内有更新)时拒绝再开始。
Windows 找 bash 的顺序:环境变量 `BASH` → `C:\Program Files\Git\bin\bash.exe` → `C:\Program Files (x86)\Git\...` → `%LOCALAPPDATA%\Programs\Git\...` → `git` 所在目录推断;不用 `System32\bash.exe`(那是 WSL)。控制台把自己的 `sys.executable` 作为 `PYTHON` 传给流水线(路径含空格时不传,回落到 `python`)。
### 流水线脚本配套改动
- `bash ml/pipeline.sh all` 支持 `FROM` / `TO` 只跑一段(如 `FROM=gen TO=train`);在终端里用 `all` 时也会在后台自动起控制台(`MONITOR=0` 关闭)。
- 每个阶段开始写 `begin`(生成阶段带 `target_tasks`,补量时更新;预训练带 `tag`;挖错带 `rounds`),退出时由 `trap EXIT` 写返回码。
- `pipeline.json` 在从中间续跑时保留计划之外各步的记录,所以流水线卡片能显示"之前已完成"。
- 训练与 APG 日志每条记录新增 `total`(总步数)与 `t`(时间戳),评估记录也带 `t`,用于进度条、已用时间与 ETA。
### 网页内容
流水线阶段条 → 全流程卡(总百分比按各步粗估耗时加权、当前步骤及其进度、总用时、本步用时、剩余粗估、开始/停止)+ GPU 仪表 → 当前运行(自动跟随正在跑的训练,可手动切换)→ 指标卡(带趋势小图)→ 4 张曲线(预训练:总损失含原始值淡线、各分量、闭环评估带"最好"标记、吞吐带 R10 5000 线;APG:到达误差、损失分量、闭环评估、展开长度)→ 数据 / M5 / 挖错 / 部署评测 / 验收面板 → 终端输出(报错标红)。顶栏有平滑系数、对数纵轴、自动刷新开关。
当前步骤进度的口径:生成 = 已写任务条数 / 目标条数;打包 = 已打包分片 / 分片数;预训练 = (测速 2000 步 + 正式步数) / (2000 + 总步数);APG = 步数;挖错 = 已完成轮数 + 本轮 APG 步数;M5 = 两遍评估;对拍、老师门禁、导出、部署评测、验收无法计量,显示用时与粗估。
## 三、本轮改动文件清单
```
新增 ml/monitor.py ml/monitor.html ml/pstate.py ml/console.bat ml/console.sh
修改 ml/pipeline.sh r4 / monitor 阶段、all 支持 FROM/TO 与自动起控制台、阶段状态记录、pipefail、R2 不通过停下、补量更新目标
ml/train.py 日志加 total、t
ml/apg/apg_finetune.py 日志加 total、t;端点+中点 hinge 取均值;docstring 余量说明
ml/gen/stats.cjs gapDrop / minGapFilterMm,样本数按过滤后计
ml/prep/build_dataset.cjs 剔除 minGap < 3 mm 的轨迹;增广地板 3 mm;输出 gapDrop
ml/eval/check_r2.py 空匹配保护、默认规模、分块 256
ml/eval/acceptance.py 起步失败 ≤ 0.2%
ml/mining/gate.py R7 左右比例不以到达为条件
ml/mining/tasks.py column_gap 分块
ml/export_onnx.py --random
tests/policy_bench.cjs r8 起点贴柱
webapp/js/ik-policy.js 投影护栏 5 mm
ml/README.md 一键运行、R4、训练控制台、过滤口径、估时标注、回传文件、常见问题、文件地图
docs/Transformer轨迹拟合训练方案.md / .html 余量决策、APG 碰撞项、R4 时点
```
## 四、沙箱验证(CPU,2 核,小数据)
| 项 | 结果 |
|---|---|
| `stats.cjs` 过滤 | 30 条任务:keep 20、gapDrop 9;`MIN_GAP_MM=4.4` 时 keep 16、gapDrop 13,口径一致 |
| `build_dataset.cjs` | 20 条任务 761 样本,gapDrop 9,增广拒绝 5/226 |
| `check_r2.py` | 小样本可跑,不再报错 |
| APG 2 步 + 门禁(含新 R7) | 通过 |
| `column_gap` 分块 | 300 位形 / 块 100 正常;块 512 在 7 GB 内存下被杀 → 默认 256 |
| `export_onnx --random` | 走到导出一步,沙箱缺 `onnx` 包未完成,需在你机器上跑 `r4` |
| 控制台接口 | 网页开始 `gen→prep`(6 条任务):进度 2/6 → 4/6 → 打包 0/2 → 完成,返回码 0;停止 `gate1`:进程树结束、该步记为已停止、不留残缺 `consistency.json`、"已停止"提示不被覆盖 |
| 网页 | jsdom 跑 4 个示例场景 + 开始/停止流程,无脚本错误;沙箱没有浏览器,没有截图 |
| 未验证 | Windows 下 `console.bat` 与 Git Bash 查找、`taskkill` 停止;`policy_bench.cjs` 完整运行(沙箱无 `onnxruntime-node`) |
## 五、你机器上的建议顺序
1. `git apply review2_and_monitor.patch`(或等我推送后 `git pull`)。
2. `bash ml/pipeline.sh setup`(如依赖已装可跳过)→ `bash ml/pipeline.sh r4` + 浏览器测延迟,把 `__POLICY.latency()` 发我。
3. 双击 `ml/console.bat` → 「开始训练」,起始「对拍」、结束「打包样本」,跑完看 `gapDrop` 与 R2。
4. 没问题再「开始训练」从「预训练」跑到「验收汇总」。
---
---
# 第四轮:对第三轮(`14d72a9`)与训练控制台的审查
审查对象:B-1~B-8 的处置代码 + 新增控制台(`ml/monitor.py` 315 行、`ml/monitor.html` 795 行、`ml/pstate.py`、`ml/console.bat|sh`)+ `ml/pipeline.sh` 改动。
方式:逐行读代码;只跑了三个秒级 shell/python 验证(不碰 GPU、不跑流水线)。**控制台与第 7 步之后仍然零实测。**
## 一、B-1~B-8 处置核对
| 条目 | 代码事实 | 判定 |
|---|---|---|
| B-1 护栏 | `ik-policy.js:174` margin 回 5;`stats.cjs:7` 与 `build_dataset.cjs:30` 同一 `MIN_GAP_MM=3` 过滤,`gapDrop` 两边都报;增广地板同步到 3(`:49`) | 方向对,已落地。尾巴见 N-1 |
| B-2 hinge 量纲 | `apg_finetune.py:104` 由 `sum(0)` 改 `mean(0)` → 与只查端点同量纲,`w_col` 无需重标 | 已解决 |
| B-3 check_r2 | `fin.sum()<20` → `pass:null` 提前返回;默认 5000×50000、分块 256(一块 256×50000 fp32 ≈ 51 MB)、`masked_fill_` 原地;`pipeline.sh:72` 在 `pass is False` 时 `exit 4` | 崩溃与显存问题解决了;判据本身新增误拒风险,见 N-2 |
| B-4 R7 条件 | `gate.py:47` 分母改 `det.sum()`,不再以到达为条件 | 解决了,但口径名不副实,见 N-3 |
| B-5 r8 起点 | `policy_bench.cjs:57` 起点也要求 `nearColumn` | 已落地;出题成本未知,见 N-4 |
| B-6 估时 | 对拍改「估算:300 条实测 7.6 s,流程跑 2000 条」;M5/bench 标「估算」、APG/mine 标「未实测」 | 已解决 |
| B-7 R4 前置 | `export_onnx.py:24` 新增 `--random <配置>`(`meta.random_init`),`pipeline.sh:100` 的 `r4` 阶段,README 表新增「2′ R4 延迟」 | 思路对(延迟只取决于结构)。但导出路径会覆盖真模型,见 N-5 |
| B-8 杂项 | 起步失败 `≤0.2%`(`acceptance.py:44` 带理由)、`column_gap` 分块 256、`set -e -o pipefail`、`consistency.json` 先 `.tmp` 再 `mv` | 已解决 |
| 附带 | `pstate` 原子写(`os.replace`)、训练/APG 日志加 `total`/`t`、`all` 支持 `FROM/TO` | 结构合理 |
我这条也纠正自己一处:静态读 `pipeline.sh:117` 时我认为「端口已被占用就不重复启动」的判断写反了,实测**我错了**——端口空闲时 guard 退 0(启动)、被占用退 1(跳过),逻辑正确。
## 二、控制台的问题(按严重度,N-x 供下一轮出方案)
### N-7(最高:会真丢数据)并发无锁,且重启后"已在跑"守卫在最危险的阶段失效
- `monitor.py:212` 判断「终端里是不是另有一条在跑」的依据是 `ml/runs/*/log.jsonl` 120 秒内有更新。但 **gate1 / gen / prep / m5 / bench / accept 这些阶段根本不写 `log.jsonl`**(只有 train/apg 写)→ 在 4–5 小时的 `gen` 期间,网页「开始训练」会被放行。
- 一旦二次启动:`run_parallel.sh:8` 用同一批种子(`pipeline.sh:57` 固定 `S0=1`)生成同名 `ml/data/shard_%04d.jsonl`,而 `gen_dataset.cjs:23` 是 `fs.openSync(OUT,'w')` → **两边互相截断,谁都没法要**。
- 反方向同样裸奔:`pipeline.sh` 内部没有任何锁,网页在跑时终端敲 `bash ml/pipeline.sh all` 照样起跑。
- 触发概率不低:README 与审查文档都明确鼓励「网页可以关、重新打开接着看」,而 monitor 重启后 `self.p=None`,它自己的「已经在运行」判据(`:207`)也一起失效。
### N-8(安全)`/api/start`、`/api/stop` 没有 Origin / CSRF 校验
`monitor.py:288` 的门槛是 `client_address ∈ {127.0.0.1,::1}`,`--remote-control` 只管远端 IP。浏览器里任意第三方网页发往 `http://localhost:8100/api/stop` 的 POST,源地址就是 127.0.0.1 → **陌生网页可以停掉你跑了几小时的训练,或塞进一条新流水线**(响应被同源策略挡住,读不走数据,但破坏成立)。文档里「开始/停止只接受本机请求」给出的保证比代码实际提供的强。
### N-9(性能:整轮都在白付 IO)每次刷新把全部 shard 全文读两遍数行数
`monitor.py:157-161` 无条件对每个 shard 做 `sum(1 for _ in fh)`,`stage_progress` 的 gen 分支(`:100-101`)再读一遍;网页 5 秒轮询一次(`monitor.html:664`)。按 ~5 KB/条 × 5 万条 ≈ 250 MB 估算(**未实测**,口径是记录里带 `traj` 顶点数组),即每 5 秒约 500 MB 顺序读,而且训练/APG 阶段那段统计并没有关掉 → 与 28 个 node 写盘、训练数据加载抢同一块盘。
### N-10(环境)`console.bat` 找 Python 的方式会"看起来正常、三小时后炸"
- `.bat:6` 用 `where python` 判存在。Windows 的 WindowsApps 假壳也能通过 `where`(本机 `python3 -c` 实测 exit 1 就是它的行为),存在性检查形同虚设;`pipeline.sh:25` 用的是更强的 `command -v && python -c "import sys"`,两个启动器强弱不一致。
- 双击 `.bat` 不会激活 conda/venv → monitor 只用标准库,照样起得来;`:218` 把自己的 `sys.executable` 当 `PYTHON` 传给流水线 → `gen`/`prep` 全是 node 不受影响,**到 `train` 才 ImportError**,最坏的失败姿势。
- `console.sh` 认 `$PYTHON`,`.bat` 不认。
### N-11(生命周期)几小时的训练挂在"别关那个黑窗口"上,且中间步骤没有续跑能力
`CREATE_NEW_PROCESS_GROUP` 不脱离控制台,关窗口即杀整棵树。`train` 有 `--resume`,但 **apg 没有 resume 参数、`mine` 轮内无 resume、`gen` 补量会从头重扫全部 shard**。Linux 侧文档给了 `nohup`,Windows 侧没有等价做法。
### N-16(可观测性 + 潜在死循环)生成阶段网页看不到任何节拍
`run_parallel.sh:8` 把每个进程的进度 `2> ml/data/log_$s.txt` 重定向到文件,不流向 `console.log` → 4–5 小时的 gen 期间,网页「终端输出」面板是空的,只能看行数进度。叠上 `sampler.cjs:19-20` 的 C 类采样是**无上限 `for(;;)`**(`nearColumn` 命中率一旦因 `collide.json` 重拟合塌掉就永远出不来),故障表现就是"网页上一个字都没有、任务数不再增长",最难判断是慢还是死。
### N-5(数据踩踏)`r4` 的随机模型和真模型同名同路径
`pipeline.sh:100` 是 `--random $CFG --out webapp/models/traj_policy.onnx`,与第 10 步导出的是**同一个文件**。`meta.random_init` 没有任何消费方检查(`ik-policy-glue.js:30` 只查 fp/feat_ver/tcp0,`policy_bench.cjs:22` 同)→ 训完之后补做 R4 会把真模型盖掉,随后 `bench`/`accept`/查看器 `?policy=1` 会把随机策略当真模型跑,徽标照样显示「策略模式就绪」。
### N-2(新闸门可能误拒)R2 判据与搜索池耦合,现在是硬停
池从 20 万降到 5 万(1.7 M 样本的约 3%)→ 最近邻系统性更远 → `label_std` 系统性偏大;而阈值 0.10 是按哪个池定的没有任何记录。`pass:false` 现在以 `exit 4` 停掉一键流程,等于在最可能出假阴性的配置上加了一道会拦掉 3 小时训练的闸。代码已经输出 `nn_dist_threshold` 但没有任何地方看它。
### N-3(口径名不副实)R7 的"左右"不是首次方向
`closed_loop.py:172` 的 `side = j1_max >= -j1_min`,是**整条展开的 J1 累计行程正负极值比较**(净方向),而 `gate.py:46` 注释与第三轮文档写的是「首次 J1 方向」。来回摆动的题会被记成净方向;R7 想抓的「一侧被压缩」在"两侧都走但先左后右收回来"这种情况下会被误判。另外 `left(sd, ok)` 的 `ok` 形参现在没人用。
### N-4(成本未知)r8 三重拒绝采样的出题耗时没测
起点贴柱 + 终点贴柱 + `|ΔJ1|≥140°` + 直线被挡,四个条件串联,`tries` 上限 4e6、内层再 200 次找起点。README §11 的「R8 出题约 2 秒/题」是估的;更要紧的是 `while (cases.length<N && tries<4e6)` 到上限就**静默返回不足 50 题**(不报错),R8 判据 `≥95%` 的分母随之缩水。
### N-13(显示不可信)补量时的 `target_tasks` 可能算错但不报错
`pipeline.sh:65` 用内层 `python -c` 读旧 target 再相加。内层失败(例如 `pstate begin` 因 `|| true` 没写成、`pipeline.json` 缺 gen 记录)→ 空替换 → 实测 `$(( + per2*NPROC ))` 得 56 这类值(**我验证过:不中断、不报错**)→ 旧目标丢失,进度条 >100% 或倒退。
### N-14(总百分比偏乐观)网页权重与修订后的耗时表不一致
`monitor.html:341` 的 `STAGE_META`:`gen=1.0 / train=3.0 / mine=9.0`。共识分支的 gen 实际 4–5 小时(README 自己写的),权重仍按 1 个单位 → 「全流程总百分比」「剩余粗估」在最可能走的那条分支上系统性偏乐观。
### N-12(状态竞态)`pipeline.json` 有两个写者,其中一个非原子
`pstate.save()` 走 `.tmp` + `os.replace`(`:20-24`);`monitor.stop()` 却在 `:247` 手工 `open(PJ,'w')` 直写,且与 `:244` 的读之间没有锁 → 与子进程 `begin/end` 撞车会丢阶段记录。另:Ctrl+C / SIGKILL 时 bash 的 `trap EXIT` 不执行 → `current` 悬空 → 网页显示幻影「进行中」,同时 `:210` 的 7 天窗口把它当"终端可能还在跑"的候选。
### N-1(配置面)`MIN_GAP_MM` 没有贯通
默认两边都是 3 ✓。但它是两个 node 脚本各自读的环境变量:只给 `gen`(影响 `stats`/补量口径)不给 `prep`(影响实际进训练集的条数),或反过来,就会让「补量算的样本数」和「真实样本数」分叉,而且没有任何一致性检查。`monitor.py:175` 的 env 白名单里也没有它,网页上根本设不了。
### N-6(次要)分块尺寸自相矛盾
`column_gap` 默认 256、`min_gap_chunked` 默认 512,注释给的理由分别是「约 20 MB/位形」和「约 1.5 MB/位形」——同一台机器上同一个 `kin.gaps` 的峰值差 13 倍,说明其中至少一个是沙箱小内存机器上看来的数,24 GB 显存下 256 会把出题拖慢。未实测。
### N-15(误导风险,低)`file://` 双击就是"看起来在训练"
`monitor.html:216` 在 `file:` 协议下自动进 demo 模式并按刷新推进假数据;有 `示例数据` 标签(`:470`)但 README 没强调"别双击 html"。对不熟悉的人有误读空间。
(说明:`/api/log` 的 `run` 参数用 `re.fullmatch(r'[\w.\-]+')` 卡住了 `/` 与 `\`,路径逃不出去——这条我核过,不是问题。)
## 三、第四轮的可行性判断
1. **B-1~B-8 全部处置到位**,方向都对(尤其 B-1 改成"数据侧过滤 + 护栏回 5 mm"是我更认可的那条路)。剩下的 N-1/N-3/N-5/N-6 属于口径与副作用的尾巴,不影响能不能跑。
2. **控制台:读侧可用,写侧现在不能用。** 进度/日志/统计的接口设计干净(纯标准库、无危险输入、增量读日志、覆盖检测都对),拿来在终端跑阶段、网页只看进度是合适的。但 N-7(同名 shard 互相截断 + 守卫在最坏阶段失灵)与 N-8(浏览器可驱动的无校验控制口)这两个洞,恰好都长在「一键开始/停止」这条路径上;不补掉就别用它起训练,只把它当仪表盘。
3. **后 6 步依然零实测**,而且现在多了两道新闸:R2 的 `exit 4`(N-2 有误拒概率)和 M5 的 `exit 3`。前 6 步的可行性结论不变。
4. **不改代码就能规避的用法**(供你这两天先往下走):终端按阶段跑;网页只看进度不点开始;`r4` 手工加 `--out ml/runs/r4_probe.onnx`;`MIN_GAP_MM` 别设(保持两边默认 3);`console.bat` 换成在激活过的环境里 `python -m ml.monitor --open`。
## 四、给下一轮的待处置清单(按优先级)
| # | 要解决的问题 | 判据(怎样算解决) |
|---|---|---|
| 1 | N-7 流水线互斥 | 一条真锁(`ml/runs/pipeline.lock` + pid + 心跳,或改 shard 文件名为 `shard_<seed>_<runid>`),**任何**入口(网页/终端/`all`)都走同一把锁;并发起第二条时立刻拒绝而不是等日志判活 |
| 2 | N-8 控制口鉴权 | `start/stop` 校验 `Origin`/`Sec-Fetch-Site` 或首次访问生成的本机 token;文档口径与代码一致 |
| 3 | N-9 刷新代价 | 行数统计只在 gen 阶段且带缓存(增量 tail 或按文件大小比例),其他阶段不扫 shard;给出一次 /api/status 的实测耗时 |
| 4 | N-5 随机模型踩踏 | `r4` 输出独立路径(或不写 `webapp/models/`),并在 glue/bench/acceptance 里对 `random_init` 显式拒绝或红字警示 |
| 5 | N-10 启动器环境 | `.bat` 改用 `python -c "import torch"` 作可用性判据并尊重 `%PYTHON%`;monitor 启动时自检并把"当前 Python / 有无 torch / GPU"显示在网页上 |
| 6 | N-11 生命周期 | 给出 Windows 等价的脱离控制台起法(或训练进程自托管 + 端口锁),apg/mine 补 `--resume` 语义或至少文档化"断了从哪重跑" |
| 7 | N-16 gen 阶段可观测 | gen 的每进程节拍汇入 `console.log`(或网页读 `ml/data/log_*.txt`),并给 C 类采样加 tries 上限与告警 |
| 8 | N-2 R2 阈值 | 判据与池大小解耦(报告 `nn_dist_threshold` 并按配对距离归一,或池改成全量的固定比例并写明);`pass:false` 先降为"警告 + 需人工确认" |
| 9 | N-3 / N-4 / N-14 / N-13 / N-12 / N-1 / N-6 | 口径对齐(首次方向 vs 净方向)、r8 不足额时报 `n<50`、网页权重与耗时表同源、`target_tasks` 读失败可见、状态文件单一写者、`MIN_GAP_MM` 贯通并校验、分块尺寸按本机实测统一 |
## 五、修法分层(阻塞 / 必要 / 可选)
判据口径:**阻塞 = 不修就会毁掉已有产出或让几小时白跑;必要 = 不修会让你误判或断一次损失一整轮;可选 = 只是显示不准,不损失工作。**
### A. 阻塞:修完这 4 条才可以用网页「开始训练」,或用任何方式挂 4 小时以上的流程
| # | 要修的 | 落点 | 算修好了的判据 |
|---|---|---|---|
| A-1 | N-7 流水线互斥 | `monitor.py:207-213` 的判活依据、`run_parallel.sh:8` 的 `shard_%04d.jsonl` 命名、`pipeline.sh`(终端入口也要一把锁) | ① 已经有一条在跑时,第二个入口(网页或终端)立刻被拒并指名锁文件;② 两轮生成的输出文件名不重叠(带 run-id),故意并发一次,两边的 shard 都不被截断 |
| A-2 | N-5 随机模型踩真模型 | `pipeline.sh:100` 的 `--out`、`ik-policy-glue.js:29-32`、`policy_bench.cjs:22`、`acceptance.py` | 跑一次 `r4` 后:`webapp/models/traj_policy.onnx` 仍是训练产物(或 r4 走独立文件名),且 `random_init:true` 的模型在查看器徽标 / bench 输出 / acceptance 任一处都被显式标成"随机探针"而不是"策略模式就绪" |
| A-3 | N-10 启动器拿不到带 torch 的 Python | `console.bat:6-7`、`monitor.py:218`、monitor 启动自检 | 用一个没有 torch 的解释器起 monitor 时,网页顶栏直接标红"当前 Python 无 torch,不能开始训练"并拒绝 `/api/start`;`.bat` 尊重 `%PYTHON%`,与 `console.sh` 行为一致;用 WindowsApps 假壳路径能测出"拒绝"而不是"跑起来到 train 才炸" |
| A-4 | N-8 控制口鉴权 | `monitor.py:286-289` | 带外站 `Origin`(或无 `Sec-Fetch-Site: same-origin`)的 POST `/api/stop` 返回 403;本机网页正常;`--remote-control` 语义不变 |
### B. 必要:在对应那一步之前完成(不挡 A 组之前的操作)
| # | 要修的 | 什么时候必须完成 | 落点 / 判据 |
|---|---|---|---|
| B-1 | N-16 gen 阶段没有节拍 + C 类采样无上限 | **第一次挂 gen(4–5 小时)之前** | `run_parallel.sh:8` 的 `2> ml/data/log_$s.txt` 汇入 console 或网页可读;`sampler.cjs:19-20` 的 `for(;;)` 加 tries 上限并打印原因。判据:故意造一个命中率塌掉的 `collide.json`,gen 要么报"采样不出"要么报错退出,不会静默空转 |
| B-2 | N-2 R2 阈值与搜索池耦合,且已改成 `exit 4` 硬停 | **第一次跑 prep→train 之前** | `check_r2.py` 把 `nn_dist_threshold` 与池大小一起判,或阈值按配对距离归一;`pipeline.sh:72` 先降为"警告 + 需确认"。判据:同一份数据用 `--pool 50000` 与 `--pool 200000` 两跑,结论一致,否则判据不成立 |
| B-3 | N-11 apg/mine 无 resume,训练挂在"别关黑窗口"上 | **第一次跑第 8 步之前** | apg 支持 `--resume`、`loop.sh` 支持从 `r<k>` 续跑,或文档逐阶段写清"断了从哪重跑、损失多少";Windows 给出等价 `nohup` 的脱离控制台起法。判据:中途强杀一次,续跑不重复已完成的轮次 |
### C. 可选(先记账,等实测数出来再排)
N-9 刷新的全量行数统计、N-14 网页权重与耗时表同源、N-13 补量 `target_tasks` 丢失、N-12 `pipeline.json` 双写者与悬空 `current`、N-3 R7 净方向 vs 首次方向、N-4 r8 不足额静默、N-1 `MIN_GAP_MM` 贯通、N-6 分块尺寸不统一、N-15 `file://` demo 误导。
### 建议的并行方式
- 你(或另一台机器)现在做 **A-1…A-4**;A 组四条都不需要 GPU、不碰训练,改动面小。
- 与此同时本机按终端逐阶段跑 `r4`(用 `--out ml/runs/r4_probe.onnx`)→ `kin` → `gate1` → `gen` → `prep` → `train` → `m5`,把四个未知数拿回来:**gapDrop 比例、R2 在 1.7 M 样本上的实际判定、samples_per_s 与 eta_h、M5 双口径数字**。
- 拿到这四个数,B-1/B-2/B-3 的优先级和 C 组要不要做才有依据;现在给 C 组排序只是猜。
## 六、目标改成「一条命令无人值守跑完」,需要怎么改
上一节按"能不能马上开始跑"分层,那是把流程切成两段的做法;如果目标是 `bash ml/pipeline.sh all`(或网页一个按钮)从 `kin` 一路到 `accept` 无人值守跑完、中间不需要人在终端前守着,那么 A/B/C 的排法不适用——需要的是下面四个结构性缺口一起补,缺任何一个都谈不上"完整流程"。约束我不放松:5 mm 护栏、M5 ≤2%、`collisionFrames=0` 三道门槛照旧。
### 缺口 1:状态机不闭环——"续跑"要变成一个动作,而不是让人挑起始阶段
现状:`all` 每次 `pstate reset` 把计划内阶段的记录清空重跑;`FROM/TO` 要人判断该从哪续;`gen` 重跑会把所有 seed 从头再生;`loop.sh` 重跑会把已完成的轮次再挖一遍;只有 `train` 有 `--resume`。所以一次中断(断电、关窗口、OOM、门禁停)= 人工重建上下文。
改成:
- `pstate reset` 加 `--keep-done`:`all` 启动时读 `ml/runs/pipeline.json`,`rc==0` 的阶段跳过并保留用时,从第一个未完成阶段继续;网页「开始训练」不再问起始阶段(只问参数)。
- 阶段内幂等:`run_parallel.sh` 跳过已存在且行数达标的 `shard_%04d.jsonl`;`loop.sh` 跳过已存在 `gate.json` 的轮次并复用 `current.pt`;`gen` 补量的目标条数落进 `pipeline.json`(重入时不重复补)。
- `train` 的 `--resume ml/runs/$TAG/last.pt` 由 pipeline.sh 自动判断(存在就带),apg 加同样的 `--resume`(`last.pt` 已经在存)。
- 判据:任意时刻强杀整棵进程树,再点一次「开始训练」,日志与网页显示"跳过 k 个已完成阶段,从第 j 步继续",且各产物文件不被重复生成。
### 缺口 2:入口不互斥、进程不脱离控制台
现状即 N-7 + N-11:靠"120 s 内有日志更新"猜有没有别的实例(gen/prep/m5/bench 阶段猜不到),shard 同名互相 `openSync(OUT,'w')` 截断;`CREATE_NEW_PROCESS_GROUP` 不脱离控制台,关黑窗口就杀整条链。
改成:
- 一把真锁 `ml/runs/pipeline.lock`(pid + 心跳时间戳),网页/终端/`all` 三个入口都拿,超时未刷新即视为陈旧可抢占并打印提示。
- monitor 起子进程时加 `DETACHED_PROCESS`(POSIX 侧 `start_new_session` 已有),训练与控制台进程解耦;控制台窗口关掉只关面板。
- 判据:故意并发两个入口,第二个立刻被拒并指名持锁 pid;关闭控制台窗口 10 分钟后训练进程仍在跑且网页重开能接上。
### 缺口 3:三道"要人拍板"的门槛,要变成流程内的自动分支
现在这三处会把无人值守的长跑停死在人不在场的时刻:
| 门槛 | 现状 | 改成 |
|---|---|---|
| R2(`check_r2` `exit 4`) | 判据与池大小耦合(5 万池 = 1.7 M 的 3%,最近邻系统性偏远 → `label_std` 系统性偏大),误拒就停 3 小时训练 | 阈值与池解耦(同时看已有的 `nn_dist_threshold`,或按配对距离分位归一),并在 `pipeline.json` 记 `pool`;不通过时默认**警告并继续**,`R2_STRICT=1` 才拦 |
| M5(`exit 3`) | 不过就停下等人手敲两条 `CFG=wta` 命令 | 流程内自动分支:base 不过 → 自动 `train CFG=wta` + `m5 CFG=wta`;两头都不过才停(并把两个 `m5_gate_*.json` 一并落盘)。目录已分开了,这条纯接线 |
| gate1 → CONSENSUS | 已经自动 ✓ | 保留,但把 `consensusDrop`/`gapDrop` 双阈值写进 `pipeline.json`,让 gen 的补量次数按实际保留率放开(现在硬编码最多 2 次,共识 + 间隙过滤叠加时 2 次可能补不满 170 万 → 让它按 `TARGET_SAMPLES` 收敛到 4 次) |
| R4(浏览器延迟) | 需要人开浏览器看 `__POLICY.latency()`,于是被排除在 `all` 之外 → 流程天生不完整 | 加自动探针:`ml/eval/r4_probe.py` 起 `ml/serve.py`(已有 COOP/COEP)→ headless Edge 打开 `?policy=1` → 用页面暴露的 `__POLICY.setGoal(T,R,tool)` 注入 30 个贴柱目标 → 回收 `__POLICY.latency()`/`stats()` 写 `ml/runs/r4.json` 并判 P99 < 15 ms;探针用 `--random` 导出的独立文件名模型,不碰真模型(顺手解掉 N-5) |
判据:`all` 的计划里含 `r4`,且全程不需要人做任何一次浏览器/终端判断;三道门槛的"停"都只在两种头/两种配置都失败时才发生。
### 缺口 4:无人值守 = 失败要能被发现,长阶段要有心跳
现状:gen 的每进程节拍被 `2> ml/data/log_$s.txt` 吞掉;`sampler.cjs:19` 的 C 类采样是无上限 `for(;;)`(`collide.json` 重拟合后命中率塌掉就是静默死循环);补量的 `target_tasks` 读失败会算成 56 这类值而不报错(我验过)。
改成:
- 每个阶段在 `pipeline.json` 带 `timeout_h`(按表里估时 ×3)与心跳(`info.lastBeat`);超时 → 非零退出 → 阶段标失败,网页顶栏红字,锁释放。
- gen 的 `log_$s.txt` 尾行汇入 console(或网页直接读这些文件),并给 C 类采样加 tries 上限 + 失败原因落盘。
- monitor 启动自检并把"当前 Python 路径 / 有无 torch / GPU 名 / 锁持有者"显示在顶栏(解 N-10:不能等到 `train` 才 ImportError)。
### 这样改完之后的机时与剩余风险(明说,不当已解决)
- 一次成功的完整流程下界约 **15–28 小时**:gate1 0.5 + gen 1–5 + prep 0.5 + train 3 + m5 0.3(选中 wta 再加 3.3)+ apg 2–4 + mine 6×1–2 + export/bench/accept 1.5。其中 **apg 与 mine 每轮的机时仍是零实测**,补量/过滤叠加时 gen 可能更长。
- 补了这四组缺口,"跑不完"的风险从"中途停了没人知道、只能人肉重建上下文"降到"某一步真失败且已落盘可查";但 **跑通 ≠ 达标**:M5 的 2%、held-out 98%、`collisionFrames=0` 能不能过,仍然要这一次真跑才知道。这是我不会用改造掩盖掉的唯一一件事。
- 改动量估计(都不动模型结构与数据口径):`pipeline.sh` ~60 行、`monitor.py` ~70 行、`pstate.py` ~15 行、`run_parallel.sh`/`loop.sh` 各 ~10 行、`check_r2.py` ~15 行、新增 `r4_probe.py` ~80 行、`sampler.cjs` ~5 行。
- 顺序建议(一次提交内做完,不分批放出去):缺口 1 + 2 是"敢挂长跑"的前提,先做;缺口 4 的心跳与超时和它们同一个改动面;缺口 3 的 r4 探针最大但独立,可以并行走。
## 七、R4 实测(本机 2026-10-18,随机初始化模型,不需要训练)
复现:`python -m ml.export_onnx --random ml/configs/base.json --out webapp/models/traj_policy_r4.onnx`(3.6 s,fp32 19.77 MB,ONNX↔torch 最大误差 4.8e-7)→ `python ml/serve.py 8000` → 打开 `?policy=models/traj_policy_r4.onnx`。
环境:RTX 3090 机器、28 核、Edge(Qoder Browser Connector)、onnxruntime-web 1.20.1、`crossOriginIsolated=true`、wasm EP、`graphOptimizationLevel:'all'`。模型 = `configs/base.json`(d192/heads8/ff768/enc4/dec4,4.92 M 参数),batch=8 即部署的 8 个候选。
| 配置 | batch=8 P50 | P95 | P99 | 说明 |
|---|---|---|---|---|
| **部署现状(4 线程,在真查看器里)** | **33.2 ms** | 36.1 | **37.0** | 150 次;徽标"wasm,4 线程" |
| 4 线程(空白页,无渲染) | 32.2 | — | 35.9 | 与上一行差 ~1 ms → 查看器渲染不抢这块 |
| 8 线程 | 22.2 | 24.5 | 24.9 | 140 次 |
| **16 线程** | **13.7** | — | **19.5** | 160 次,最好的线程数 |
| 24 线程 | 19.2 | 23.8 | 25.8 | 比 16 差 → 超线程反而挤 |
batch 缩放(4 线程,同一会话):B=1 → 6.2/7.2;B=2 → 10.3;B=4 → 18.2/20.1;B=8 → 33.2/37.0;B=16 → 60.1;B=64 → 80.1(P50/P99)。
结论:
1. **R4 判据不过,差 2.4 倍**(P99 37.0 vs 要求 < 15)。而且这不是"训练完再优化"的项——延迟只取决于结构与线程,随机权重与真权重同速。
2. 一行改 `ik-policy-glue.js:35` 的 `Math.min(4, hardwareConcurrency)`(本机 16 最优)能把 P99 从 37 拉到 19.5,**仍然不达标**。所以达标必须动结构,两个方向都不是"降精度换速度"的取舍:① 部署的 8 行输入是同一观测复制 8 份,encoder 被重复算 8 次(`Runner._plan` 把 `f` 复制 K 遍、`Policy.sample_k` 里 `mem.repeat_interleave(K,0)`)→ 改成"编码一次、解码 8 次"是纯冗余消除;② K=8 降到 4(B=4 在 16 线程下大概率 <10 ms),这是候选数与选优质量的权衡,要用 M5/闭环数字判损失,不能拍。
3. **周期账**:部署每 3 步(60 ms)一次推理,安全层单次预算 `safetyMs:30`。4 线程下推理 33 ms 已吃掉一半以上——安全层只要超过 27 ms 就必然丢步;16 线程下 13.7 + ≤30 才谈得上有余量。也就是说 R4 与 R9(贴柱处安全层全程 P99 < 5 ms)是同一个 60 ms 周期的两半,必须一起测。
4. **没拿到的数(如实)**:闭环实际倍率/丢步率没采到——我挂的探针一运行,CDP 连 15 s 都取不到执行槽(连 `list_console_messages` 都超时),标签页要到切走才恢复。这本身就是主线程(投影有限差分 + 认证递归都在主线程)在 4 线程 wasm 下已经很挤的证据。R9 因此仍无数据。
5. 残留:`webapp/models/traj_policy_r4.onnx`(+`.meta.json`,共 19.8 MB) 是未跟踪文件,`meta.random_init=true`;serve.py 已停,浏览器标签已释放。它同时验证了 N-5 的隐患是真的——这个文件与第 10 步 `export` 的默认输出同名同目录,只差文件名后缀。