From d03f34822ca7705ad1da7c33f9edad8b7540e4be Mon Sep 17 00:00:00 2001 From: Hark Date: Sat, 17 Oct 2026 22:12:53 +0000 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E5=85=AB=E8=BD=AE=EF=BC=9Amonitor=20?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E4=B8=89=E9=A1=B9=E8=A2=AB=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E5=90=9E=E6=8E=89=E7=9A=84=20bug=EF=BC=9BR9=20=E6=94=B9?= =?UTF-8?q?=E5=88=A4=E7=A8=B3=E6=80=81=20planWarmP99=20+=20=E8=BF=90?= =?UTF-8?q?=E5=8A=A8=E4=B8=AD=E9=A5=A5=E9=A5=BF=20+=20=E8=AE=A4=E8=AF=81?= =?UTF-8?q?=E8=B6=85=E6=97=B6=E5=8D=A0=E6=AF=94=EF=BC=9BAPG=20steps=208000?= =?UTF-8?q?=EF=BC=88batch=2048=20=E4=BF=9D=E6=8C=81=E6=80=BB=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E9=87=8F=EF=BC=89=EF=BC=9Br4=20=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E9=9D=99=E9=BB=98=E9=80=80=E5=9B=9E=20CDN=EF=BC=9B=E8=87=AA?= =?UTF-8?q?=E6=A3=80=E5=89=AF=E6=9C=AC=E5=90=AB=E6=9C=AA=E8=B7=9F=E8=B8=AA?= =?UTF-8?q?=E6=96=87=E4=BB=B6=EF=BC=9B=E6=98=BE=E5=AD=98=E6=96=87=E6=A1=88?= =?UTF-8?q?=E6=8C=89=2019=E2=80=9320=20GB=EF=BC=9B=E5=AE=A1=E6=9F=A5?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E7=B2=BE=E7=AE=80=E4=B8=BA=E5=AE=9E=E6=B5=8B?= =?UTF-8?q?/=E5=88=A4=E6=8D=AE/=E5=BE=85=E6=95=B0=E6=8D=AE/=E4=B8=89?= =?UTF-8?q?=E6=AE=B5=E8=AE=AD=E7=BB=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ML运行手册审查.md | 290 +++----------------------- docs/Transformer轨迹拟合训练方案.html | 15 +- docs/Transformer轨迹拟合训练方案.md | 6 +- ml/README.md | 12 +- ml/configs/apg.json | 3 +- ml/eval/acceptance.py | 4 +- ml/eval/check_r2.py | 2 +- ml/eval/r4_probe.py | 22 +- ml/monitor.py | 3 +- ml/pipeline.sh | 2 +- ml/selftest.sh | 3 +- tests/policy_bench.cjs | 6 +- webapp/js/ik-policy-glue.js | 5 +- webapp/js/ik-policy.js | 6 +- 14 files changed, 81 insertions(+), 298 deletions(-) diff --git a/docs/ML运行手册审查.md b/docs/ML运行手册审查.md index 2fdcf3d..12ccbd5 100644 --- a/docs/ML运行手册审查.md +++ b/docs/ML运行手册审查.md @@ -1,177 +1,46 @@ # ML 运行手册审查(ml/README.md)· 精简版 -六轮历史(`0218d7f` → `a5e779c`):第一轮 13 条、第二轮可行性、第三~五轮处置、第六轮本机实跑。**已解决的条目按用户要求删去,完整原文在 git 历史里**。本文件只剩三部分:仍有效的实测数字 / 仍未解决的问题 / 已复核正常的项,供实现方下一轮处置。 +八轮审查(`0218d7f` → 本次)的已解决条目全部删去,原文在 git 历史里。本文件只留:实测数字 / 已定判据 / 仍待真数据回答的问题 / 正式训练步骤。 -环境:本机 CBB-003(RTX 3090、28 核、Python 3.12 + torch 2.10 + onnxruntime 1.22、Edge + onnxruntime-web 1.20.1)。 -当前结论:第 1–6 步已实测可跑;**第 7–13 步零证据**;R4 部署口径(纯推理)已判通过,`bash ml/pipeline.sh r4` 端到端 rc=0(2026-10-18 本机修复后复跑)。A 组原三处中两处已修,A-3 由实现方第七轮改掉、待本机复跑验证。**第七轮本机复核与显存预算(19–20 GB)见第七节。** +环境:本机 CBB-003(RTX 3090,训练可用 19–20 GB;28 核;Python 3.12 + torch 2.10;Edge + onnxruntime-web 1.20.1,已入库 `webapp/vendor/ort/`)。 +当前结论:第 1–6 步本机实测可跑;R4 通过;自检 7/8 通过(唯一失败项是 Windows 限制,已接受);**审查条目已全部处置,下一步直接正式训练。** --- -## 一、仍有效的实测数字(后续对照用) +## 一、实测数字(对照用) | 项 | 数字 | 口径 | |---|---|---| -| 对拍 300 条 | PASS(fk 3.4e-13 mm、特征 6e-8、间隙梯度 1.4e-6),7.6 s | 第 2 步 | -| 生成 | 1.56 s/条(80 条 122 s) | 第 4 步 | -| 统计 | keepRate 0.977、samplesPerTask 36.5、projected 178.4 万、exec_s p50 2.3 / p90 3.0 / max 3.7 s、cert.minGap p50 4.4 / p90 5.9 mm | 88 条,**未加 3 mm 过滤** | -| 预训练 | 4.92 M 参数、6208 样本/s(batch=1024)→ 60k 步 ≈ 2.8 h | 250 步冒烟 | +| 对拍 300 条 | PASS(fk 3.4e-13 mm、特征 6e-8、间隙梯度 1.4e-6) | 第 2 步 | +| 生成 | 1.56 s/条 | 第 4 步,80 条 | +| 统计 | keepRate 0.977、samplesPerTask 36.5、cert.minGap p50 4.4 / p90 5.9 mm | 88 条,未加 3 mm 过滤 | +| 预训练 | 4.92 M 参数、6208 样本/s、60k 步 ≈ 2.8 h、显存峰值 2.76 GiB | batch 1024 | +| APG 显存 | batch 48 / plans 150 → 13.08 GiB;batch 128 必 OOM | d=192,1 步 | +| R4 部署口径 | wasm·16 线程·proxy P99 **12.67 ms**(< 15 通过);WebGPU P99 27.09,不用 | `feat_dyn` 新模型 | +| 自检 | 7/8,3.5 分钟;`train_term=false`(Windows 下 TERM 送不到 python,超时靠 STOP 文件 + 每 10 分钟存档) | `selftest.json` | -R4(随机初始化模型;延迟只取决于结构,与是否训练无关): - -| 接口 | 线程 | proxy | P50 | P95 | P99 | -|---|---|---|---|---|---| -| **enc_once(部署口径)** | 16 | 开 | 7.36 | 9.48 | **11.07** | -| enc_once | 4 | 开 | 10.22 | 11.69 | 12.92 | -| enc_once | 16 | 关 | 7.43 | 9.21 | 10.79 | -| 旧接口(对照) | 16 | 开 | 9.53 | 11.29 | 16.47 | -| 旧接口 | 4 | 开 | 13.88 | 16.35 | 17.19 | - -tickGapMax 全部 ≈21.5 ms(20 ms 节拍没被卡住)。背景:第五轮前默认(4 线程、无 proxy、旧接口、真查看器)P99 37.0;线程扫描 8 线程 24.9 / 16 线程 19.5 / 24 线程 25.8 → 16 最优,超线程反而挤。**部署口径纯推理 P99 11.07 < 15,R4 判通过**;`enc_once` 相对旧接口省约 30–33%。 - -修复后由 `pipeline.sh r4` 自身复跑(两次):部署口径 P99 **10.22 / 11.25**(P50 7.34 / 7.10),4 线程 13.58 / 13.64,无 proxy 9.28 / 10.92;webgpu 仍报 `[webgpu] backend not found`。 - -真查看器同一会话(随机权重、`?policy=1&probe=1`):inferMs P50/P99/max = **30.36 / 61.64 / 68**,safetyMs = **63.52 / 248.76 / 320.6**;622 次 plan、brakes 4、locks 48、projected 283、lookahead 219。单变量对照:`__POLICY.stop()` 后纯 infer 7.5 / 9.7 ms → 差额是主线程排队,不是模型慢。 - ---- - -## 二、仍未解决的问题 - -### A. 会直接中断本机一键流程(原三处,两处已在本机修复) - -**已修复(本机 2026-10-18,`bash ml/pipeline.sh r4` 连跑两次均 rc=0、`pass=true`)** -- 探针页复用 detached tensor:`feat` 的分配移进 `run()`(`webapp/r4_probe.html`)→ proxy=1 的两种配置均正常出数。 -- `pipeline.sh` 7 处 `json.load(open(...))` 全部补 `encoding='utf-8'`(`:90 :110 :118 :127 :136 :138 :144`)。 -- 附带发现并修掉:r4 分支末行 `[ "$r" = null ] && echo` 在 `r=pass` 时整体返回 1,`set -e` + EXIT trap 把**通过的 r4 记成 rc=1**(`all` 会因此停)。已改为 `if ... then ... fi`。 - -**A-3 离线 ort 加载:实现方第七轮已改(绝对 URL + `vendor_ort.sh`),本机待复跑验证。** -`ik-policy-glue.js` 把 `ort.env.wasm.wasmPaths` 设为相对前缀 `vendor/ort/`,ORT 对 `.mjs` 走 `import()` 需要绝对说明符 → `no available backend found. ERR: [wasm] TypeError: Failed to resolve module specifier 'vendor/ort/ort-wasm-simd-threaded.mjs'`。README 明确支持的离线部署路径在默认配置下不可用。(`ortLocal=1` + `proxy=0` 未测;`wasmPaths` 与 proxy 无关,大概率同样失败。)**复跑判据**:断网/CDN 不可达时 `bash ml/pipeline.sh r4` 仍能出数(探针默认走本地 `webapp/vendor/ort/`),且 `&ortLocal=1` 的查看器徽标显示「策略模式就绪」。 - -### B. 待实现方判断(数据已摆在上面;分歧按第五轮第〇节规则,以实现方为准) - -**B-1 R4 判据用哪个口径。** 纯推理 P99 11.07 判通过;但同一台机、同一会话,整周期 plan = infer 61.6 + safety 248.8(P99),随机权重下 1/3 的规划 >100 ms。如果 R4 的意义是"浏览器能不能按 60 ms 周期推一次",纯推理口径会放过实际丢步的配置。供下一轮的建议(不要求现在改):`r4.json` 记两个数 `infer_p99` 与 `plan_p99 = infer + safety`,后者另设一条线(如 <45 ms,给 60 ms 周期留余量)。 - -**B-2 `safetyMs` 两把尺。** bench 在 Node 里跑同一份 `ik-policy.js`(CPU EP),浏览器里同一段代码 p99 249 ms——互不可替代,与"仿真安全层 ≠ 部署运行时"是同一类问题换了个位置。判据:同一批贴柱题两边各测一次,差 >3× 就要解释。建议核对 `certBudget=40` 在贴柱位形下的实际查询次数与 `makeCert` 的 memo 命中率——249 ms 的量级不是"随机策略质量"能解释的;真模型是否不同未知。**R9 至今无有效数据**(探针只测纯推理;闭环丢步率因探针一运行 CDP 就拿不到执行槽而没采到——这本身是主线程拥挤的证据)。 - -**B-3 WebGPU 是判定顺序里的备用通过路径,但至今零数据。** 第五轮判定顺序写了"wasm 不过而 WebGPU 过,也判通过",本机 headless 报 `[webgpu] backend not found`,且 CDN 间歇不可达(jsdelivr 多次 000、unpkg 200)→ 需要一条真能测到 WebGPU 的路(非 headless,或 A-3 修好后本地放 ort)。 - -### C. 尚未实测(跑之前必须知道这些是空的) - -1. **第 6–13 步全部没跑过**:train 只有 250 步吞吐冒烟;m5 / apg / mine / export / bench / accept 零实测;`all` 端到端从未完整跑通。一次完整流程机时下界约 15–28 h,其中 **apg 2–4 h/轮、mine 6 轮都是估的**。 -2. **3 mm 间隙过滤的真实剔除比例(`gapDrop`)没测过**:只测过沙箱 30 条(剔 9/29,不具代表性);上表 p50 4.4 mm 是未过滤口径。生成后看 `stats.json` 的 `gapDrop`。 -3. **R2 新判据(`label_std_norm`)的池无关性**:需在 1.7 M 样本上 `--pool 50000` 与 `--pool 200000` 两跑、差 <0.02 才成立;没做。默认 `R2_STRICT=0` 只警告,开着就是一道未校准的闸。 -4. **R8 出题耗时**:沙箱 2 题 9 分钟没出完;本机未测(`policy_bench.json` 会记 `requested` / `genS`,不足额时看警告)。 -5. ~~**Windows 侧生命周期未实测**~~ → **已实测**(见 7.2):整树杀净 ✅、关黑窗口训练继续 ✅、`timeout` 的 TERM 送不到 python ❌(超时只能硬杀,靠 STOP 文件与定时存档)。 -6. ~~**断点续跑未在本机复核**~~ → **已实测**(见 7.2):gen 按条续写逐条一致、train STOP 2 s 存档续训、apg `--resume`、prep 二次运行「待打包 0 个」全部通过。 -7. **APG 的机时仍然零实测**(显存已量:`batch=48/plans=150` → 13.08 GiB)。`batch` 从 128 降到 48 后每步代价与收敛都要重算一遍才知道。 - -### D. 小遗留 - -~~`webapp/models/README.md` 过时~~ → 实现方第七轮已改,本机复核过:现在写的是 `bash ml/pipeline.sh export`(默认 fp32)、第 10 步、新输入签名 `feat[1 或 K,83]` 与 `feat_dyn`,并说明离线要先跑 `ml/prep/vendor_ort.sh`。 - ---- - -## 三、第六轮已复核正常(不在问题清单里) - -- `bash ml/pipeline.sh r4` 端到端:修完 A 组两处后连跑两次 rc=0、`pass=true`。 -- 锁:`r4` 阶段拿到 `ml/runs/pipeline.lock`,结束已删。 -- N-5 三处防护在位:查看器拒绝 `random_init`(提示 `&probe=1`)、`policy_bench.cjs` 拒绝、`acceptance` 有"部署模型是训练产物"项。 -- `enc_once` 运行时对接一致:只送 1 行特征(`ik-policy-glue.js:52` / `ik-policy.js:344`),旧模型回退 `[B,F]`。 -- `r4` 导出:19.76 MB、`enc_once=true`、与 torch 对拍 2.2e-7。 - -## 四、建议顺序 - -1. ~~A 组修复并重跑 `r4`~~:已完成(本机 2026-10-18,rc=0、P99 11.25)。 -2. 接着 `FROM=gen TO=prep` 跑一次(数小时),拿 `gapDrop` 与 R2 两个口径的数——C-2 / C-3 随之消掉,B-2 的 Node 侧安全层数字可顺手从 m5 前的 bench 拿。 -3. B 组等上面的数字齐了再定;C-1/C-7 随第一次真跑逐项核对;A-3 由实现方已改,本机复跑 `r4` 时顺带验(会弹有界面窗口)。 - ---- - -## 五、实现方处置(第六轮,2026-10-18) - -逐条对应第二部分。分歧按第五轮第〇节规则以实现方为准,下一轮用实测数据定。 - -**A-3 离线 ort 加载**:已修。`ik-policy-glue.js` 与 `r4_probe.html` 的本地前缀改为绝对 URL(`new URL('vendor/ort/', location.href)`),ORT 的 `import()` 不再把它当裸模块名。新增 `ml/prep/vendor_ort.sh`(`setup` 与 `r4` 自动调用)用 `npm pack onnxruntime-web@1.20.1` 把 `ort.min.js`、`ort.webgpu.min.js` 与两套 wasm/mjs 放进 `webapp/vendor/ort/`(33 MB,`.gitignore`);探针检测到本地副本就默认用它,不再依赖间歇不可达的 jsdelivr(`ORT_LOCAL=0` 强制 CDN)。 - -**GBK 兜底**:除了本机已补的 7 处,`pipeline.sh`、`console.bat`、`console.sh`、监控起的子进程统一 `PYTHONUTF8=1`,所有 Python 读写默认 UTF-8,不再逐处补 `encoding`。 - -**B-1 R4 口径**:R4 保持纯推理判据(随机权重只代表推理开销,这点不变)。整周期另立 **R9**,用**训练好的模型**在**真查看器**里测:`python -m ml.eval.r4_probe --viewer`(`bench` 阶段自动跑,写 `ml/runs/r9_browser.json`),查看器 `&autotest=30` 跑与 Node bench **同种子同题**的 30 道贴柱题,判据 **整次规划 `planP99` < 45 ms 且饥饿节拍 < 1%**(饥饿 = 执行时钟到点时队列为空 = 实际丢步)。`r4.json` 另带 `viewer_ref`(随机模型的同一测试),只作参考不判定——随机策略几乎每次都要认证多条候选,安全层路径不代表真模型。验收汇总加 R9 一项。方案 R9 行同步改写(原"安全层 P99 < 5 ms"不可测也不对口径)。 - -**B-2 安全层两把尺**:找到了原因,而且 Node 侧也一样慢,不是浏览器独有。沙箱 Node 单次调用:`gapMatrix` cap20 **3.6 ms** / cap40 **5.5 ms**、`collideFree` 1.5 ms、`criticalPairs` 3.1 ms,而推理只有 **1.5 ms**——**耗时在几何查询,不在模型**。具体三处: -1. 截止只在候选之间检查,单条候选内部最多 40 次查询(≈150 ms)不受限 → 墙钟截止下沉到认证递归内部:第 1 条候选截止 45 ms,其余共享到 30 ms,到点判不过(`certFailWhy.time`),沿已认证刹停段停,安全性不变。 -2. `safetyMs` 从推理结束开始计,**把前瞻(第二次推理 + 8 份特征 = 8 次 gapMatrix cap40)算进去了** → 拆成 `lookaheadMs` / `safetyMs` / `planMs` 三项分别记。 -3. 前瞻在"卡死"时也触发,随机或差的策略几乎一直卡死 → 改为只在冷启动(新目标 / 目标跳变)前瞻,与仿真 `closed_loop.py`(只在首块前瞻)对齐;卡死由换向锁处理。 -沙箱 Node、同 2 道贴柱题、小模型实测(同一机器前后对比):安全层 P99 **200.9 → 51.9 ms**,前瞻 P99 126 → 83 ms(只剩冷启动那几次),整次规划 P99 **236.7 → 63.3 ms**(P50 57.5)。代价是沙箱上 `time` 截止判不过占多数、刹停多(2 vCPU 上每次 gapMatrix 约 4 ms,45 ms 只够约 10 次查询;未训练小模型本身也差)——你机器单核快 2× 左右,真模型候选第 1 条通过率高,R9 以本机实测为准;若本机 R9 仍不过,下一轮把几何查询(特征、投影、认证)整体搬进 Worker,主线程只执行。 -另外 bench 与查看器都记 `certQueriesP99`、`certMemoHitRate`(沙箱 0.69–0.72)、`certFailWhy`;验收加"同题安全层 浏览器 / Node ≤ 3×"一项。 - -**顺带发现并修掉一个真 bug**:第五轮 `enc_once` 后,前瞻的 8 行特征**各不相同**,但 `ortInfer` 只送了第 1 行 → 8 个候选的前瞻价值全是同一个观测算的,冷启动时的候选排序等于随机。现在 ONNX 的 `feat` 轴改为动态(1 行 = 主推理编码一次;K 行 = 前瞻逐行编码,`meta.feat_dyn=true`),导出时对拍 8 行不同特征与 `sample()` 一致(2e-7 量级);第五轮导出的旧模型运行时自动退化为逐行推(正确但慢)。**你本机的 r4 模型需重新导出**(`bash ml/pipeline.sh r4` 会重导)。 - -**B-3 WebGPU**:`backend not found` 的直接原因是 `ort.min.js` 里**没有 WebGPU 后端**(只在 `ort.webgpu.min.js`)。查看器与探针在 `ep=webgpu` 时改载 `ort.webgpu.min.js`;探针先记 `navigator.gpu` 与适配器信息(区分"没有 GPU"与"包不对");WebGPU 配置改用有界面窗口打开(无头模式拿不到真适配器),跑完自动关。 - -**D**:`webapp/models/README.md` 已改为 fp32、第 10 步、新输入签名。 - -**C 组**:第七轮已处置,下一步见第六节。 - -## 六、实现方处置与待本机验证(第七轮,2026-10-18) - -本节是遗留问题的**唯一清单**:不需要计算的已在代码里修完(「本轮已修完」);需要计算的只有一条技术验证命令(「本机技术验证」),由你的 PC 跑,沙箱不跑;做法已全部定下(「已定的做法」);需要你确认的写在「还需要你确认的」。 - ---- - -### 6.1 本轮已修完(不需要计算) - -| 遗留 | 处置 | 位置 | -|---|---|---| -| A-3 离线 ort 加载失败 | 本地前缀改绝对 URL;`ml/prep/vendor_ort.sh` 放好 ort 本地副本,探针默认用本地、不依赖 CDN | `ik-policy-glue.js`、`r4_probe.html`、`vendor_ort.sh` | -| GBK 读中文 json | 全流程 `PYTHONUTF8=1`(pipeline / console.bat / console.sh / 监控子进程) | `pipeline.sh` 等 | -| B-1 R4 口径 | R4 仍判纯推理;整周期另立 R9(真查看器、训练好的模型、与 bench 同题:`planP99` < 45 ms、饥饿 < 1%),验收加 R9 | `r4_probe.py --viewer`、`acceptance.py` | -| B-2 安全层两把尺 | 原因定位:几何查询慢(gapMatrix 3.6–5.5 ms/次),不是模型。截止下沉到认证内部;耗时拆 4 项记录;前瞻只在冷启动;验收加"同题浏览器 / Node ≤ 3×" | `ik-policy.js`、`policy_bench.cjs` | -| 前瞻 bug(新发现) | enc_once 后前瞻只送 1 行特征 → feat 轴改动态(`meta.feat_dyn`),导出对拍 K 行 | `policy.py`、`export_onnx.py` | -| B-3 WebGPU 零数据 | 根因:`ort.min.js` 不含 WebGPU → 改载 `ort.webgpu.min.js`;有界面窗口测;记录适配器信息 | 同上 | -| C-3 R2 池无关性 | 每次 R2 自动以 4 倍池再测一次,记 `pool_check.independent`(差 < 0.02) | `check_r2.py` | -| C-4 R8 出题耗时 | 出过的题按碰撞指纹缓存,`policy_bench.json` 记 `genS`、`casesCached` | `policy_bench.cjs` | -| C-5 / C-6 本机生命周期与续跑 | 新增一条命令自检(见「本机技术验证」 V1) | `ml/selftest.sh`、`ml/selftest_proc.py` | -| C-1 第 7–13 步零实测 | 靠断点续跑兜底(V1 验证),正式训练分三段跑、出错从该阶段续;另备 `ml/smoke.sh` 全流程冒烟,不必跑 | `ml/smoke.sh`、`configs/smoke.json` | -| 续跑后数值不一致 | `last.pt` 存 torch / CUDA / random 随机数状态,续跑 batch 顺序与一口气跑完一致 | `ml/common/ckpt.py` | -| D 模型目录说明过时 | 已改(fp32、第 10 步、新签名) | `webapp/models/README.md` | - -沙箱只做了语法检查与几秒级的对拍,**以上全部以你本机结果为准**。 - ---- - -### 6.2 本机技术验证(只验可行性与意外,不做正式训练) - -一条命令,5–10 分钟,在仓库旁的副本目录里跑,不碰本仓库的数据与产物,跑完副本可删。 - -| # | 命令(Git Bash,仓库根目录) | 耗时 | 回传 | 验证什么 | -|---|---|---|---|---| -| V1 | `bash ml/pipeline.sh selftest` | 5–10 分钟 | `ml/runs/selftest.json` | 意外中断能否恢复:gen 截断续写、prep 二次运行、训练 STOP / 续训、APG 续跑;Windows 进程:控制台停止能否杀净整棵树、关掉黑窗口后流程是否继续、TERM 能否送到 python | - -判读:`all_pass = true`。没过就把文件发我,不用排查。 - -不单独跑全流程冒烟:V1 通过即证明断点续跑可靠,正式训练中途某一步出错时,已完成的阶段与训练进度都保留,我修完后从出错那一步接着跑(`FROM=<阶段>`),损失只是出错到发现之间的空闲时间。为缩短这段时间,正式训练按"数据 → 训练 → 后训练与评测"三段跑,每段结束看一眼(见「正式训练」)。`ml/smoke.sh` 保留在仓库里,不作为必跑项。 - ---- - -### 6.3 已定的做法 +## 二、已定的判据与做法 | 事项 | 定为 | |---|---| -| R9 通过线 | 整次规划 P99 < 45 ms、饥饿节拍 < 1%(60 ms 周期留 15 ms 给渲染) | -| 浏览器测试 | 有界面窗口(无头拿不到真 GPU) | -| R9 本机不过 | 几何查询(特征、投影、认证)整体搬进 Worker,主线程只执行 | -| 训练余量 3 mm、`R2_STRICT`、共识过滤 | 按正式数据段的 `stats.json` / `r2.json` 定:间隙 p10 ≥ 3 mm 保持 3 mm,否则降到 p10;R2 `pool_check.independent = true` 且截距 < 0.10 时打开 `R2_STRICT`;门 1 < 0.90 自动开共识 | +| R9(真查看器、训练好的模型、30 道贴柱题) | 稳态 `planWarmP99` < 45 ms、运动中饥饿 < 1%、认证超时占比 < 5%;冷启动 P99、刹停率、`tickGapMax` 只报 | +| R9 不过 | 几何查询(特征、投影、认证)搬进 Worker,主线程只执行 | +| APG | batch 48、steps 8000(总任务量同原 128×3000),进程显存硬帽 `VRAM_FRAC=0.78` | +| 离线 ort | 只用入库本地副本,缺失即报错,不退回 CDN | +| 训练余量 3 mm | `stats.json` 间隙 p10 ≥ 3 mm 保持,否则降到 p10 | +| `R2_STRICT` | `pool_check.independent=true` 且截距 < 0.10 时打开 | +| 共识过滤 | 门 1 < 0.90 自动开 | +| 分歧 | 实现方为准,下一轮数据定 | ---- +## 三、仍待真数据回答(正式训练会自动产出,不单独验证) -### 6.4 还需要你确认的 +1. `gapDrop`:3 mm 过滤的真实剔除比例 → `stats.json`。 +2. R2 池无关性 → `r2.json` 的 `pool_check`。 +3. R8 出题耗时 → `policy_bench.json` 的 `genS`。 +4. APG 收敛与机时(batch 48 下首测)→ APG 日志。 +5. 真模型 R9 → `r9_browser.json`。 -无。V1 结果回来后,我只按结果改代码;若结果暴露出需要你决定的事,再写进本节。 - ---- - -### 6.5 正式训练(以后再跑,不在本轮) - -V1 通过后再跑,分三段,每段跑完把回传文件发我: +## 四、正式训练(三段,每段跑完回传) | 段 | 命令 | 耗时 | 回传 | |---|---|---|---| @@ -179,107 +48,4 @@ V1 通过后再跑,分三段,每段跑完把回传文件发我: | 训练 | `FROM=train TO=m5 bash ml/pipeline.sh all` | 约 3–6 小时 | `ml/runs/m5_gate_*.json` | | 后训练与评测 | `FROM=apg TO=accept bash ml/pipeline.sh all` | 约 10–20 小时 | `ml/runs/acceptance.json`、`policy_bench.json`、`r9_browser.json` | -任何一段中途出错:把出错信息发我,我修完后用 `FROM=<出错阶段>` 接着跑,前面的不重做。 - ---- - -## 七、本机复核第六/七轮处置(2026-10-18,含显存预算) - -**先说约束(用户定的,之前所有估算都按整卡 24 GB 写了)**:本机 Windows + 4K 桌面**空闲就占 4.4 GB**(`nvidia-smi` 空载 23604→4394 MiB 实测),所以 **ML 只能用 19–20 GB**。 - -### 7.1 APG 会直接把第 8 步打崩(实测,非推算) - -用 d=192(与正式训练同结构)、`--steps 1`(峰值在第一次反向就到位)、每进程硬帽 19.2 GB,逐档实测 `max_memory_allocated`(GiB): - -| batch | plans=60 | 90 | 120 | 150 | -|---|---|---|---|---| -| 32 | 6.14 | | | 9.53 | -| **48(已定为新值)** | 7.67 | | | **13.08** | -| 56 | | | 13.61 | | -| 64 | 10.81 | 15.41 | | 17.66 | -| **128(原值)** | **OOM** | | | **OOM**(崩前已分配 17.85 GiB) | - -- **原 `batch=128` 无论 `plans` 取 60 还是 150 都 OOM**(d=32 的小模型也一样,崩前 17.54 GiB)→ 显存是被展开图吃掉的,与模型大小、数据量无关。 -- **已把 `apg.json` 的 batch 定为 48,`plans 60→150` 不动**:最坏 13.08 GiB,加桌面 4.4 GB ≈ 17.5 GB,在 19–20 GB 预算内。没选"降 plans"是因为降展开长度会把长任务的绕出段截掉(就是口径-3 那个问题)。`batch=64 + plans=150` 要 17.66 GiB,加桌面就到 22 GB 了,不取。 -- **加了每进程显存硬帽**:`ml/common/vram.py`,`VRAM_FRAC` 默认 0.78 ≈ 18.7 GiB,训练与 APG 启动时打印上限。没帽时它会把整卡顶到 23.6 GB、连桌面一起挤掉(就是这次报显存的原因)。 -- **显存用量现在看得见**:`train.py` / `apg_finetune.py` 每条日志带 `vram`(峰值 GiB);监控页指标卡加「显存峰值」(>14 黄、>17 红),GPU 卡的显存条改成按 **20 GB 预算**算而不是整卡 24。实测:预训练 `batch=1024` 记到 `vram: 2.71`,APG 自检档 `vram: 0.33`。 -- 对照:预训练 `batch=1024` 峰值 **2.76 GiB**(够);`check_r2` 每样本常驻 **776 B** → 1.7 M 样本 **1.32 GB** + 分块 0.05(4× 池自检 0.2)≈ **1.6 GB**,脚本里"GPU 峰值 <0.5 GB"错了约 3 倍,但不会崩。 -- 顺带:N-6 的"按空闲显存 1/4 自动定块"、出题分块等估算都要按 19 GB 重算。 -- **`ml/configs/smoke.json` 原来没有 `apg` 段**,而 `selftest.sh` 调 APG 时不带 `--config` → 自检第 6 项实际按正式 `batch=128` 跑,在本机必然 OOM、自检永远跑不完。已补 `apg` 段(`batch=8`、实测 `vram` 0.33 GiB)并让自检带 `--config`。 - -### 7.2 生命周期与断点续跑:本机整轮自检 **7/8 通过**,用时 3.5 分钟 - -| 项 | 结果 | -|---|---| -| `gen_resume` | ✅ true(判据修正后)——「续写:已有 2/6 条,从第 3 条继续」,与一口气生成**逐条一致** | -| `prep_rerun` | ✅ true——二次运行「待打包 0 个」 | -| `train_stop` | ✅ true——`--stop-at 20` → rc 143、`last.pt` 第 20 步、续训到 40 步 rc 0 | -| `train_stopfile` | ✅ true——写 `ml/runs/STOP` 后 **2 s** 退出并存档(第 351 步)。控制台「停止」走的就是这条路 | -| `train_term` | ❌ **false(Windows 的真实限制)**——`timeout` 的 TERM 送不到原生 python,rc=124 是硬杀。结论:阶段超时不能指望体面存档,靠 STOP 文件 + 每 10 分钟定时存档 | -| `apg_resume` | ✅ true(自检档 `batch=8`,`vram` 0.33 GiB) | -| `tree_killed` | ✅ true(实测)——`taskkill /T /F` 把 bash→python+node 整树杀净,没留孤儿 | -| `survive_parent_exit` | ✅ true(实测)——启动器退出后子树仍活着 = **关掉控制台黑窗口不影响训练** | - -用时 3.5 分钟(APG 用自检档;上一轮 APG 走正式 `batch=128` 时是 13.2 分钟,而且 OOM)。 -之前测不到的原因:① `monitor.find_bash()` 在本机返回 `None`(Qoder 自带 git 在 `mingw64\bin\git.exe`,"上上级 + bin/bash.exe"推出一个不存在的路径);② `selftest_proc.alive()` 用 `text=True` 读 `tasklist`,中文 Windows 的表头是 GBK → 读线程 `UnicodeDecodeError`、`stdout` 变 `None` → `TypeError`,异常又被 `wait_pids` 的 `except Exception` 吞掉。 - -### 7.3 两条判据自身的问题(会让"通过/失败"失真) - -1. **`selftest.sh:27` 用 `cmp -s` 逐字节比生成结果**,但记录里含 `plan_ms` 与 `cert.ms` 两个**墙钟耗时字段** → 两次运行不可能相同,**这项永远是 false**。我在副本里重跑基线、只把这两个字段剔掉后逐行比:**6 条完全一致** → 续写逻辑正确,是判据错。 -2. **`rec()` 用 `json.loads` 解析传入值**,而 `str(None).lower()`=`none` 不是合法 JSON → 抛异常、整项**静默不进结果文件**,`all_pass` 照样算出来。所以"第七轮说自检已修完"的两项进程测试,实际上是没跑过。 -3. **自检副本只复制 `git ls-files` 里的文件**(`selftest.sh:16`)→ 任何**未跟踪的新文件**在副本里不存在,调用它的项会以 `ModuleNotFoundError` 变成 rc=1,看起来像功能坏了。本轮新加 `ml/common/vram.py` 就踩了:`train_stop`/`train_stopfile`/`apg_resume` 三项同时 rc=1,`git add` 之后才恢复。判据建议:自检开头先 `git ls-files --others --exclude-standard` 非空就报出来(或改用 `git ls-files -o --exclude-standard -t` 一起复制)。 - -### 7.4 本机已改的(都按实测落地,不改判据含义) - -| 位置 | 改动 | 验证 | -|---|---|---| -| `ml/monitor.py` `find_bash()` | 便携版 git 的 `mingw64` 布局要再上一层;再兜 `which('bash')`(排除 System32 的 WSL bash) | 返回 `...\.qoder\bin\git\bin\bash.exe`(bash 5.2.37);网页「开始训练」在本机的拦路石解除 | -| `ml/selftest_proc.py` `alive()` / `wait_pids()` | `tasklist` 用 `text=True, errors='replace'` 并防 `None`;起不来时把最后一次异常带进结果 | `tree_killed`、`survive_parent_exit` 两项实测 true | -| `ml/selftest.sh` | `rec` 解析失败记 false 不再丢项;`gen_resume` 判据剔除 `plan_ms`/`cert.ms`;APG 两项带 `--config` | 重跑第 1 项已变 **true**(「续写:已有 2/6 条 → 逐条一致」) | -| `ml/configs/apg.json` | `batch 128 → 48`,加 `_vram` 记录实测表 | 最坏 13.08 GiB(实测),在 19–20 GB 预算内 | -| `ml/configs/smoke.json` | 补 `apg` 段(`batch=8`) | 自检 APG 档 `vram: 0.33` | -| `ml/common/vram.py`(新)+ `train.py` + `apg/apg_finetune.py` | 每进程硬帽 `VRAM_FRAC=0.78`(启动打印上限);日志每条记录加 `vram` 峰值 | `显存上限:本进程 18.7 GiB(整卡 24.0 × 0.78)`;train `vram: 2.71` | -| `ml/monitor.py` + `ml/monitor.html` | 状态带 `vram_budget_gb`(默认 20,`VRAM_BUDGET_GB` 可改);GPU 显存条按预算算并超线变色;指标卡加「显存峰值」 | headless 加载渲染出的 DOM 里有「显存峰值 / 预算 19–20 GB(整卡 24,桌面占 4–5)」 | - -### 7.5 仍需实现方处理的(我没动) - -- **APG 的 `steps` 与机时**:`batch` 从 128 降到 48,每步看到的任务少 2.7 倍。`steps=3000` 要不要加、加多少是收敛判断,我只有显存实测、没有这个依据。`R2/R4/N-6` 各处按整卡写的显存估算文案也要按 19–20 GB 重写。 -- `R4 复跑`:`feat_dyn` 要重导模型,探针现在会**弹有界面窗口**跑 `viewer_ref`(30 题×≤10 s)与 WebGPU——本机跑之前请知悉会有窗口,别动它。 -- B-2 的几何查询搬 Worker(若本机 R9 仍不过);R9 用真模型才有意义。 - ---- - -## 八、R4 复跑(2026-10-18,`feat_dyn` 新模型 + 本地 ort + WebGPU) - -`bash ml/pipeline.sh r4` 端到端 **rc=0**。这次先把 `webapp/vendor/ort/`(onnxruntime-web 1.20.1,33 MB)**入库**(`.gitignore` 同步移除),不再依赖 npm/CDN——上一轮 `vendor_ort.sh` 的 `npm pack` 失败后探针静默退回 CDN,而 jsDelivr 本机完全不通,四种配置全变成「加载 ort 失败」,一条数字都没有。**这是个真问题**:下载失败时流程不该"看起来跑完了"。 - -### 8.1 R4 判定口径:通过 - -| 配置 | P50 | P95 | P99 | 备注 | -|---|---|---|---|---| -| **wasm·16 线程·proxy(部署口径)** | 7.06 | 8.88 | **12.67** | 本地 ort,`enc_once` + `feat_dyn` 新模型 | -| wasm·4 线程·proxy | 10.25 | 12.48 | 13.22 | | -| wasm·16 线程·无 proxy | 6.83 | 8.28 | 9.53 | | -| **WebGPU** | 18.26 | 22.76 | **27.09** | 首次拿到真数:`hasGpu:true`、adapter `nvidia ampere`、有界面窗口;比 wasm 慢,不用 | - -结论:纯推理 P99 12.67 < 15,**R4 通过**;与修复前(10.22–11.25)同量级,`feat_dyn` 没让主推理变慢(主推理仍只送 1 行)。 - -### 8.2 `viewer_ref`:随机模型的整周期数字(只作参考,但暴露了三件事) - -30 道贴柱题、随机权重、真查看器(有界面窗口)、16 线程 + proxy: - -| 指标 | 值 | 说明 | -|---|---|---| -| `planP50` / `planP99` | 68.5 / **131.2 ms** | R9 线是 45;随机策略几乎每次都认证多条候选 | -| `starve_rate` | **32.7%** | 执行时钟到点队列为空 = 实际丢步 | -| `inferP99` | 58.3 ms | 纯推理探针只有 12.7 → 差额是主线程排队 | -| `lookaheadP99` | **170 ms** | 前瞻(8 行特征逐行编码 + 8 次 gapMatrix) | -| `certFailWhy` | **time 948** / box 346 / cert 19 | 3902 次规划里 70% 的失败是"墙钟到点判不过" | -| `tickGapMax` | **736 ms** | 主线程仍会被一次性占住(安全层在主线程) | - -**三条判读(给实现方,不是结论)**: -1. 前瞻是现在最贵的一环(170 ms P99)。虽然已改成只在冷启动做,但 30 题就是 30 次冷启动,每次都落在 P99 上——R9 的 `planP99` 会被冷启动那几次主导。建议 `r4_probe --viewer` 同时报 `planP99`(含冷启动)与 `planP99Warm`(剔除每题第一次规划),否则这条线测的是"题数"而不是稳态性能。 -2. **`time` 截止占失败原因 70%**:45 ms 预算在随机模型上基本等于"到点判不过 → 刹停"(1019 次刹停 / 3902 次规划)。R9 若"通过",必须同时报 `certFailWhy.time` 占比与 `brakeRate`,否则可能是靠拒绝候选换来的。 -3. `tickGapMax 736 ms` 说明安全层(几何查询)仍在主线程;B-2 的"搬进 Worker"没有做,这条是真模型跑之前要盯的。 - -这些全是随机权重的数,R9 判据本来就规定用训练好的模型测,所以**不据此判 R9 过不过**;`r4.json` 里 `viewer_ref` 已标注"仅参考"。 +中途出错:把出错信息发我,修完用 `FROM=<出错阶段>` 接着跑,前面的不重做。 diff --git a/docs/Transformer轨迹拟合训练方案.html b/docs/Transformer轨迹拟合训练方案.html index 26616be..c691003 100644 --- a/docs/Transformer轨迹拟合训练方案.html +++ b/docs/Transformer轨迹拟合训练方案.html @@ -361,7 +361,8 @@ id="toc-101-只能靠实测消除的风险按里程碑顺序">10.1

基于 main@3c7c814(贴立柱修复后的规划器)。本文是方案,代码尚未编写。 -训练机:RTX 3090 24 GB。部署:浏览器(后续真机)。

+训练机:RTX 3090 24 GB,训练可用 19–20 GB(桌面常驻 4–5 GB,进程硬帽 +0.78)。部署:浏览器(后续真机)。


0. 定位与边界

@@ -879,8 +880,9 @@ WTA 谁更好;因此 WTA 只作为 M5 预训练 + 后训练 -用户 RTX 3090 24 GB -batch 1024–2048 +用户 RTX 3090(可用 19–20 GB) +预训练 batch 1024(峰值 2.76 GiB);APG batch 48 × 8000 步(峰值 +13.08 GiB) 部署推理 @@ -1566,9 +1568,10 @@ d=128(configs/small.json)。K=8 不降 整周期超标(推理 + 前瞻 + 安全层 > 60 ms 周期,执行时钟饥饿丢步) 训练好的模型在真查看器里跑与 bench 同一批 30 -道贴柱题:整次规划 P99 < 45 ms、饥饿节拍 < -1%(r4_probe --viewer → -r9_browser.json);同题安全层浏览器 / Node 差 ≤ 3× +道贴柱题:稳态规划 P99 < 45 ms、运动中饥饿节拍 < 1%、认证超时占比 +< 5%(r4_probe --viewer → +r9_browser.json;冷启动 P99、刹停率、tickGapMax +只报);同题安全层浏览器 / Node 差 ≤ 3× 几何查询(features、投影、认证)移到 Worker,主线程只执行;前瞻只在冷启动时做 M7 diff --git a/docs/Transformer轨迹拟合训练方案.md b/docs/Transformer轨迹拟合训练方案.md index ac3d2be..35a74f1 100644 --- a/docs/Transformer轨迹拟合训练方案.md +++ b/docs/Transformer轨迹拟合训练方案.md @@ -1,7 +1,7 @@ # Transformer 避障运动策略:最终方案 > 基于 `main@3c7c814`(贴立柱修复后的规划器)。本文是方案,代码尚未编写。 -> 训练机:RTX 3090 24 GB。部署:浏览器(后续真机)。 +> 训练机:RTX 3090 24 GB,训练可用 19–20 GB(桌面常驻 4–5 GB,进程硬帽 0.78)。部署:浏览器(后续真机)。 --- @@ -129,7 +129,7 @@ | 阶段 | 在哪里跑 | 说明 | |---|---|---| | 数据生成 | Hark 沙箱(2 vCPU / 7 GB,Node)或用户机器 | 纯 JS,无需 GPU | -| 预训练 + 后训练 | 用户 RTX 3090 24 GB | batch 1024–2048 | +| 预训练 + 后训练 | 用户 RTX 3090(可用 19–20 GB) | 预训练 batch 1024(峰值 2.76 GiB);APG batch 48 × 8000 步(峰值 13.08 GiB) | | 部署推理 | 浏览器 onnxruntime-web wasm-SIMD 多线程(代理 Worker);ONNX 接口 feat[1,83]、z[K,16,6]:观测编码一次、K=8 条候选共享编码再解码;后续真机控制器 | 与现有查看器同页 | ```bash @@ -449,7 +449,7 @@ for 每个 batch(随机采样 q0 和目标,不需要老师): | R6 | 位姿目标的多 IK 分支带来额外多峰 | 不同肘/腕分支子集到达率 ≥ 主集 − 2 个百分点 | 加目标分支标志 | M5 | | R7 | APG 压缩多模态或拉长路径(ELMP 中路径 +23%) | 每轮门禁:B/C 类左右绕行比例变化 ≤ 5 个百分点、行程中位数比 ≤ 1.05(`ml/mining/gate.py`) | 加大 BC 锚定权重;行程项 | M6 | | R8 | 卡死换向活锁 | 50 个"两侧都要绕远"对抗用例(J1 起终差 ≥ 140°、终点贴立柱,部署运行时)首次换向锁后 3 s 内到达 ≥ 95%,活锁 = 0(`tests/policy_bench.cjs` r8) | 子目标改为离散绕行锚点分类 | M6 / M7 | -| R9 | 整周期超标(推理 + 前瞻 + 安全层 > 60 ms 周期,执行时钟饥饿丢步) | 训练好的模型在**真查看器**里跑与 bench 同一批 30 道贴柱题:整次规划 P99 < 45 ms、饥饿节拍 < 1%(`r4_probe --viewer` → `r9_browser.json`);同题安全层浏览器 / Node 差 ≤ 3× | 几何查询(features、投影、认证)移到 Worker,主线程只执行;前瞻只在冷启动时做 | M7 | +| R9 | 整周期超标(推理 + 前瞻 + 安全层 > 60 ms 周期,执行时钟饥饿丢步) | 训练好的模型在**真查看器**里跑与 bench 同一批 30 道贴柱题:稳态规划 P99 < 45 ms、运动中饥饿节拍 < 1%、认证超时占比 < 5%(`r4_probe --viewer` → `r9_browser.json`;冷启动 P99、刹停率、tickGapMax 只报);同题安全层浏览器 / Node 差 ≤ 3× | 几何查询(features、投影、认证)移到 Worker,主线程只执行;前瞻只在冷启动时做 | M7 | | R10 | 训练耗时估计偏差 | 60000 步 ≤ 3.5 h,即 2000 步测速 ≥ 5000 样本/s(3090 实测 6.2 k/s ≈ 2.8 h) | FK 损失胶囊数 96 → 48;仍不够则步数降到 40000(MeanFlow JVP 受算力限制,增大 batch 无效) | M5 | ### 10.2 已知缺陷 diff --git a/ml/README.md b/ml/README.md index 09f4ea8..d80ffa0 100644 --- a/ml/README.md +++ b/ml/README.md @@ -38,7 +38,7 @@ --- -> 分歧规则:审查方与实现方意见讨论后仍不一致时,以实现方(Hark)的意见为准先落地;下一轮用实测数据说话(详见 docs/ML运行手册审查.md 第五轮第〇节)。 +> 分歧规则:审查方与实现方意见讨论后仍不一致时,以实现方(Hark)的意见为准先落地;下一轮用实测数据说话(见 docs/ML运行手册审查.md 第二节)。 ## 1. 环境 @@ -61,7 +61,7 @@ bash ml/pipeline.sh kin 检查:最后打印的 JSON 里 `"PASS": true`(fk、姿态、组对间隙、83 维特征、间隙梯度与 JS 一致)。 `docs/collide.json` 以后只要重新拟合或重生成 `pair2d`/`roadmap`,这一步、数据、模型都要重来(指纹不符时各脚本会拒绝运行)。 -**真跑之前(第七轮新增,5–10 分钟)**:`bash ml/pipeline.sh selftest`(进程生命周期与断点续跑自检 → `ml/runs/selftest.json`,不碰本仓库数据)。通过后正式流程按「数据 → 训练 → 后训练与评测」三段跑,中途出错从该阶段 `FROM=` 续跑。`bash ml/pipeline.sh smoke`(副本里极小模型全流程冒烟,40–70 分钟)备用,不必跑。待办与已定做法见 `docs/ML运行手册审查.md` 第六节。 +**真跑之前(第七轮新增,5–10 分钟)**:`bash ml/pipeline.sh selftest`(进程生命周期与断点续跑自检 → `ml/runs/selftest.json`,不碰本仓库数据)。通过后正式流程按「数据 → 训练 → 后训练与评测」三段跑,中途出错从该阶段 `FROM=` 续跑。`bash ml/pipeline.sh smoke`(副本里极小模型全流程冒烟,40–70 分钟)备用,不必跑。已定判据与待回答问题见 `docs/ML运行手册审查.md`。 **R4 延迟(训练前做,自动)**:推理延迟只取决于模型结构,与权重无关,所以用随机初始化模型先测,不过就在训练前改结构,不必等训完。 @@ -131,7 +131,7 @@ bash ml/pipeline.sh train # MeanFlow 头 → ml/runs/b - **通过线(R10):`samples_per_s` ≥ 5000,即 60000 步 ≤ 3.5 小时**。参考:3090、batch 1024 实测 6.2 k 样本/s ≈ 2.8 小时。MeanFlow 的 JVP 双前向使训练受算力限制,加大 batch 不会提高样本/s;低于通过线时把 `configs/base.json` 的 `fk_caps` 从 96 降到 48,仍不够再把 `steps` 降到 40000 - 训练中每 2000 步打印一行 `EVAL {...}`:val 任务闭环(部署节奏、不开安全层)`success`、`collision_rate`、`time_ok_1p3`、`start_fail`。时限 = 老师时长 × 2(至少 3 s) -- 显存不够把 `batch` 改成 512。中断后再跑 `bash ml/pipeline.sh train`(或 `all`)会自动从 `ml/runs/<头>/last.pt` 续训(每 10 分钟存一次),测速那 2000 步做过就跳过。训练发散时不会用发散的权重覆盖 `last.pt` / `best.pt`。 +- 显存预算按 **19–20 GB** 算(Windows + 4K 桌面常驻占 4–5 GB),训练与 APG 进程启动时自设硬帽 `VRAM_FRAC=0.78`(≈18.7 GiB),日志 `vram` 字段记峰值;预训练 `batch=1024` 实测峰值 2.76 GiB。显存不够把 `batch` 改成 512。中断后再跑 `bash ml/pipeline.sh train`(或 `all`)会自动从 `ml/runs/<头>/last.pt` 续训(每 10 分钟存一次),测速那 2000 步做过就跳过。训练发散时不会用发散的权重覆盖 `last.pt` / `best.pt`。 ## 7. M5 门禁:选定动作头 @@ -162,7 +162,7 @@ bash ml/pipeline.sh apg - **展开长度**:每批覆盖该批老师时长 p90 × 1.2(无老师题按 6 s),在 60–150 次推理(3.6–9 s)之间;日志 `plans` 字段是本批实际次数 - **到达项**:每 10 次推理段末的 TCP 距离与姿态误差,没有「到达即停」。这是有意的:段末项在整条展开上持续给出接近目标的梯度 - **碰撞项**:每步端点与中点的逐胶囊对铰链 `ReLU(3 mm − 间隙)`,两处取均值(与只查端点同量纲,`w_col` 不变)。3 mm 与训练数据的过滤线一致,碰撞项不与 BC 标签对抗;运行时投影护栏是独立的 5 mm -- 显存不够把 `configs/apg.json` 的 `batch` 降到 64,或把 `seg_plans` 从 10 降到 5 +- `configs/apg.json` 定为 `batch=48`、`steps=8000`(本机实测 `batch=48/plans=150` 峰值 13.08 GiB;`batch=128` 必 OOM;总任务量与原 128×3000 相同)。仍超预算时把 `seg_plans` 从 10 降到 5,不降 `plans_max`(会截掉长任务的绕出段) ## 9. 挖错循环 @@ -197,7 +197,7 @@ bash ml/pipeline.sh bench 看 `ml/runs/policy_bench.json`:各集合 `collisionFrames` 为 0;`near_column_*` 与 `route40` 全部 `ok`;`near_column_1000` 0 失败;`brakeRate` < 0.02;`r8_adversarial` 的 `recoveryRate` ≥ 0.95、`livelock` = 0。 同一步还会在查看器里跑**同一批** 30 道贴柱题(与 Node 同种子),写 `ml/runs/r9_browser.json`(会弹出浏览器窗口几分钟): -- **R9 通过线:整次规划 `planP99` < 45 ms、饥饿节拍 `starve_rate` < 1%**(整次规划 = 推理 + 前瞻 + 安全层,60 ms 周期留 15 ms 给渲染与执行时钟;饥饿 = 执行时钟到点时队列空了,即实际丢步)。 +- **R9 通过线:稳态规划 `planWarmP99` < 45 ms、运动中饥饿节拍 `starve_rate` < 1%、认证超时占比 `cert_time_rate` < 5%**(整次规划 = 推理 + 前瞻 + 安全层,60 ms 周期留 15 ms 给渲染与执行时钟;稳态 = 剔除每题静止起步那次冷启动规划,冷启动只是起步延迟,`planColdP99` 只报;饥饿 = 运动中执行时钟到点队列空了,即实际丢步;认证超时一闸防止靠拒绝候选、刹停换来耗时达标)。`brake_rate`、`tickGapMax` 只报不判。 - 两边对照:`policy_bench.json` 的 `near_column_pos.safetyMsP99` 与浏览器 `safetyP99` 差 > 3× 时验收标红,用 `certQueriesP99`(每次规划新增距离查询)、`certMemoHitRate`、`certFailWhy`(`cert` / `budget` / `time` / `box`)定位。 ## 12. 验收汇总 @@ -276,7 +276,7 @@ Windows:双击 ml/console.bat Linux / WSL:bash ml/console.sh - **指纹不符**:`collide.json` 变了。依次重跑第 2、4、5 步并重训。 - **训练发散**(loss 出现 nan/inf,自动停在 `diverged.pt`):先把 `lr` 降到 5e-4 重试一次,仍发散就走 WTA 回退(第 7 步)。 -- **显存/内存不足**:降 `batch`;APG 还可把 `seg_plans` 降到 5。出题与间隙计算按空闲显存自动分块(用 1/4)。 +- **显存/内存不足**:降 `batch`;APG 还可把 `seg_plans` 降到 5。出题与间隙计算按空闲显存自动分块(用 1/4;空闲量本就扣除了桌面占用)。`check_r2` 常驻约 1.6 GB(1.7 M 样本)。 - **小样本摸底**:几百条任务时 val 可能为空,训练会提示并跳过闭环验证;R2 检查近邻对不足时 `pass` 为 null,不拦后续步骤。 - **返回码**:3 = M5 两种头都没过;4 = R2 不过且 `R2_STRICT=1`;5 = R4 不过;6 = 生成进程崩溃(看 `ml/data/log_*.txt`,采样器连续失败会写明原因);8 = `MIN_GAP_MM` 前后不一致;9 = 已有一条在跑;124 = 阶段超时。 - **生成阶段报「采样失败:…连续 N 次未命中」**:多半是 `collide.json` 或关节限位变了,某类位形几乎不存在;把该日志发我。 diff --git a/ml/configs/apg.json b/ml/configs/apg.json index 474f3e3..e908144 100644 --- a/ml/configs/apg.json +++ b/ml/configs/apg.json @@ -1,8 +1,9 @@ { "apg": { - "batch": 48, "lr": 1e-4, "steps": 3000, "grad_clip": 1.0, "amp": "bf16", + "batch": 48, "lr": 1e-4, "steps": 8000, "grad_clip": 1.0, "amp": "bf16", "plans": 60, "plans_max": 150, "seg_plans": 10, "_vram": "batch 由 128 降到 48:2026-10-18 本机实测(d=192 同结构、每进程 19.2 GB 硬帽)batch=128 无论 plans 取 60 还是 150 都 OOM(崩前已分配 17.85 GiB);batch=48/plans=150 峰值 13.08 GiB、plans=60 为 7.67 GiB,加 Windows+4K 桌面常驻 4.4 GB 仍在 19–20 GB 预算内。batch=64/plans=150 要 17.66 GiB(不取)。展开长度 plans 没动——降 plans 会把长任务的绕出段截掉。", + "_steps": "2026-10-18 第八轮:batch 128→48 后 steps 3000→8000,保持总任务量(3000×128 ≈ 8000×48)不变;每步展开代价按 batch 线性降,总机时与原估同量级(约 4 h),学习率不动。", "margin_train_mm": 3.0, "w_tgt": 1.0, "w_rot": 0.3, "w_col": 2.0, "w_smooth": 0.02, "w_len": 0.001, "w_bc": 0.5, "cat_weights": {"A": 0.4, "B": 1.0, "C": 1.6, "D": 0.6, "Dr": 0.6}, diff --git a/ml/eval/acceptance.py b/ml/eval/acceptance.py index 45a7806..b5d1429 100644 --- a/ml/eval/acceptance.py +++ b/ml/eval/acceptance.py @@ -62,8 +62,8 @@ def main(): r4 = jload('ml/runs/r4.json') add('R4 浏览器推理 batch=8 P99(ms)', (r4 or {}).get('deploy', {}).get('p99') if r4 else 'missing', bool(r4 and r4.get('pass')), '< 15(随机初始化同结构模型实测)') r9 = jload('ml/runs/r9_browser.json'); rr = (r9 or {}).get('result') or {} - add('R9 查看器整次规划 P99(ms)/ 饥饿节拍占比', [rr.get('planP99'), rr.get('starve_rate')] if r9 else 'missing', bool(r9 and r9.get('pass')), - '< 45 ms 且 < 1%(训练好的模型、30 道贴柱题,ml.eval.r4_probe --viewer)') + add('R9 查看器稳态规划 P99(ms)/ 运动中饥饿占比 / 认证超时占比', [rr.get('planWarmP99'), rr.get('starve_rate'), rr.get('cert_time_rate')] if r9 else 'missing', bool(r9 and r9.get('pass')), + '< 45 ms、< 1%、< 5%(训练好的模型、30 道贴柱题,ml.eval.r4_probe --viewer;冷启动 P99 / 刹停率 / tickGapMax 见 r9_browser.json)') pb = jload('ml/runs/policy_bench.json') if pb is None: add('部署形态评测', 'missing', False, '先跑 bash ml/pipeline.sh bench') diff --git a/ml/eval/check_r2.py b/ml/eval/check_r2.py index ea8cc50..8fb5ef3 100644 --- a/ml/eval/check_r2.py +++ b/ml/eval/check_r2.py @@ -7,7 +7,7 @@ 改为按"近邻距离 → 标签差"做线性回归,取距离 → 0 的截距 label_std_norm 作判据(与池大小无关:池大只是近邻更近、点更多, 截距估计的是"同一输入下的固有标签分歧");旧口径 label_std_mean 保留作参考。pipeline 默认只警告,R2_STRICT=1 才拦。 自检(第七轮起每次自动做):--pool 50000 与 4 倍池 200000 各测一次,label_std_norm 差 < 0.02 记 pool_check.independent=true。 -2026-10-18 新增(审查:R2 无执行点)。默认 5000 查询 × 50000 池,分块 256:GPU 峰值 < 0.5 GB,连同 4 倍池自检 CPU 约 5–10 分钟、GPU 约 1 分钟;近邻对为空时输出 pass=null 而不报错。""" +2026-10-18 新增(审查:R2 无执行点)。默认 5000 查询 × 50000 池,分块 256:GPU 常驻约 1.6 GB(每样本 776 B × 1.7 M + 分块,第八轮按本机实测改),连同 4 倍池自检 CPU 约 5–10 分钟、GPU 约 1 分钟;近邻对为空时输出 pass=null 而不报错。""" import argparse, json, math import torch diff --git a/ml/eval/r4_probe.py b/ml/eval/r4_probe.py index e00742a..0eddea4 100644 --- a/ml/eval/r4_probe.py +++ b/ml/eval/r4_probe.py @@ -21,12 +21,19 @@ import argparse, http.server, json, os, shutil, socket, subprocess, sys, tempfil ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) LIMIT_MS = 15.0 PLAN_LIMIT_MS = 45.0 # R9:整次规划 P99(60 ms 周期留 15 ms 给渲染与 tick) -STARVE_LIMIT = 0.01 # R9:饥饿节拍占比上限 +STARVE_LIMIT = 0.01 # R9:饥饿节拍占比上限(只计运动中,冷启动不计) +TIME_LIMIT = 0.05 # R9:认证因墙钟截止判不过的占比上限——防止靠拒绝候选、刹停换来 planP99 达标(第八轮,viewer_ref 随机模型 time 占失败 70%) +# 2026-10-18 第八轮:R9 判稳态 planWarmP99(剔除每题首次冷启动规划)。冷启动发生在静止起步,只是起步延迟,单独报 planColdP99 不设闸; +# 否则 30 题 = 30 次冷启动,planP99 测的是题数而不是稳态。 def ort_local(): - if os.environ.get('ORT_LOCAL') in ('0', '1'): return os.environ['ORT_LOCAL'] == '1' - return os.path.exists(os.path.join(ROOT, 'webapp', 'vendor', 'ort', 'ort.min.js')) + # 2026-10-18 第八轮:ort 已入库(webapp/vendor/ort),默认只用本地;本地缺失直接报错,不再静默退回 CDN + # (第七轮本机:npm pack 失败 → 退回 CDN → jsDelivr 不通 → 四种配置全是「加载 ort 失败」却显示跑完)。确要用 CDN 设 ORT_LOCAL=0。 + if os.environ.get('ORT_LOCAL') == '0': return False + if not os.path.exists(os.path.join(ROOT, 'webapp', 'vendor', 'ort', 'ort.min.js')): + sys.exit('webapp/vendor/ort/ort.min.js 不存在:git pull 拿入库的 ort,或 bash ml/prep/vendor_ort.sh;确要用 CDN 设 ORT_LOCAL=0') + return True def find_browser(): @@ -105,7 +112,10 @@ def viewer(srv, browser, model_url, n, probe): if r.get('planP99') is not None: sr = (r.get('starve') or 0) / max(1, r.get('ticks') or 1) r['starve_rate'] = round(sr, 4) - r['r9_pass'] = r['planP99'] < PLAN_LIMIT_MS and sr < STARVE_LIMIT + pl = max(1, r.get('plans') or 1); why = r.get('certFailWhy') or {} + r['cert_time_rate'] = round((why.get('time') or 0) / pl, 4); r['brake_rate'] = round((r.get('brakes') or 0) / pl, 4) + w = r.get('planWarmP99') if r.get('planWarmP99') is not None else r['planP99'] + r['r9_pass'] = w < PLAN_LIMIT_MS and sr < STARVE_LIMIT and r['cert_time_rate'] < TIME_LIMIT print('viewer', json.dumps({k: v for k, v in r.items() if k != 'per'}, ensure_ascii=False), flush=True) return r @@ -152,7 +162,7 @@ def main(): def main_viewer(a): meta = json.load(open(a.model.replace('.onnx', '.meta.json'), encoding='utf-8')) - out = {'rule': f'R9:查看器整次规划 planMs P99 < {PLAN_LIMIT_MS:g} ms 且饥饿节拍 < {STARVE_LIMIT:.0%}', 'model': a.model, 'pass': None} + out = {'rule': f'R9:查看器稳态规划 planWarmP99 < {PLAN_LIMIT_MS:g} ms、运动中饥饿节拍 < {STARVE_LIMIT:.0%}、认证超时占比 < {TIME_LIMIT:.0%}(planColdP99 / brake_rate / tickGapMax 只报不判)', 'model': a.model, 'pass': None} br = find_browser() if not br: out['note'] = '没找到 Edge / Chrome(可设 R4_BROWSER=浏览器路径)' else: @@ -164,7 +174,7 @@ def main_viewer(a): os.makedirs(os.path.dirname(a.out) or '.', exist_ok=True) json.dump(out, open(a.out, 'w', encoding='utf-8'), indent=1, ensure_ascii=False) r = out.get('result') or {} - print(f"R9:{ {True: '通过', False: '未通过', None: '未判定'}[out['pass']] } planMs P99 {r.get('planP99')} ms(推理 {r.get('inferP99')} / 安全层 {r.get('safetyP99')}),饥饿 {r.get('starve_rate')}") + print(f"R9:{ {True: '通过', False: '未通过', None: '未判定'}[out['pass']] } 稳态 planMs P99 {r.get('planWarmP99')} ms(冷启动 {r.get('planColdP99')};推理 {r.get('inferP99')} / 安全层 {r.get('safetyP99')}),饥饿 {r.get('starve_rate')},认证超时 {r.get('cert_time_rate')},刹停 {r.get('brake_rate')},tickGapMax {r.get('tickGapMax')}") if __name__ == '__main__': diff --git a/ml/monitor.py b/ml/monitor.py index 40f2e80..f035bcf 100644 --- a/ml/monitor.py +++ b/ml/monitor.py @@ -193,7 +193,8 @@ def status(): if pb: pb = {k: {kk: v.get(kk) for kk in ('n', 'ok', 'reached', 'collisionFrames', 'brakeRate', 'recoveryRate', 'livelock', 'inferMsP99', 'safetyMsP99')} for k, v in pb.items() if isinstance(v, dict) and 'n' in v} return {'now': time.time(), 'gpu': gpu(), - 'vram_budget_gb': float(os.environ.get('VRAM_BUDGET_GB', 20)), # 本机桌面常驻占 4–5 GB,给训练的预算 19–20 GB 'pipeline': pl, 'check': CHECK.get(), 'lock': lock_info(), + 'vram_budget_gb': float(os.environ.get('VRAM_BUDGET_GB', 20)), # 本机桌面常驻占 4–5 GB,给训练的预算 19–20 GB + 'pipeline': pl, 'check': CHECK.get(), 'lock': lock_info(), # 2026-10-18 第八轮修:上一版这三项被行尾注释吞掉,网页步骤条/锁状态拿不到 'stage_meta': [{'id': k, 'name': n, 'est_h': h, 'timeout_h': pstate.TIMEOUT_H[k]} for k, n, h in pstate.STAGES], 'r4': jload(P(RUNS, 'r4.json')), 'data': {'consistency': (jload(P(DATA, 'consistency.json')) or {}).get('consistentByGateRule'), diff --git a/ml/pipeline.sh b/ml/pipeline.sh index 301caab..951a079 100755 --- a/ml/pipeline.sh +++ b/ml/pipeline.sh @@ -110,7 +110,7 @@ case $stage in kin) run node ml/prep/export_kin.cjs; run node ml/eval/dump_parity.cjs 2000; run $PY -m ml.eval.check_parity ;; r4) # 浏览器推理延迟(方案 R4):随机初始化模型导出到独立文件(不碰 webapp/models,审查 N-5),无头浏览器自动测 mkdir -p ml/runs/r4 - bash ml/prep/vendor_ort.sh || echo "本地 ort 没放好(npm 不通?),探针改用 CDN" + [ -f webapp/vendor/ort/ort.min.js ] || [ "${ORT_LOCAL:-1}" = 0 ] || { echo "webapp/vendor/ort 缺失(ort 已入库,先 git pull);确要用 CDN 设 ORT_LOCAL=0"; exit 3; } # 第八轮:不再静默退回 CDN run $PY -m ml.export_onnx --random $CFG --out ml/runs/r4/traj_policy_r4.onnx run $PY -m ml.eval.r4_probe --model ml/runs/r4/traj_policy_r4.onnx --out ml/runs/r4.json ${R4_ARGS:-} r=$($PY -c "import json;p=json.load(open('ml/runs/r4.json', encoding='utf-8')).get('pass');print({True:'pass',False:'fail'}.get(p,'null'))") diff --git a/ml/selftest.sh b/ml/selftest.sh index 432438a..78882cc 100755 --- a/ml/selftest.sh +++ b/ml/selftest.sh @@ -13,7 +13,8 @@ cd "$(dirname "$0")/.." SRC=$(pwd); PY=${PYTHON:-python}; export PYTHONUTF8=1 D=${SELFTEST_DIR:-$(dirname "$SRC")/$(basename "$SRC")_selftest} rm -rf "$D"; mkdir -p "$D" -git ls-files -z | tar --null -T - -cf - | (cd "$D" && tar xf -) +# 第八轮:连同未跟踪的新文件一起复制(本机实测:新加 vram.py 未 add 时三项假失败) +git ls-files -z -co --exclude-standard | tar --null -T - -cf - | (cd "$D" && tar xf -) [ -d ml/node_modules ] && cp -r ml/node_modules "$D/ml/node_modules" cd "$D"; mkdir -p ml/runs ml/data R=ml/runs/selftest_items.jsonl; : > $R diff --git a/tests/policy_bench.cjs b/tests/policy_bench.cjs index 3340e82..8105135 100644 --- a/tests/policy_bench.cjs +++ b/tests/policy_bench.cjs @@ -67,7 +67,7 @@ const pct = (a, p) => { if (!a.length) return null; const s = a.slice().sort((x, const out = { model: MODEL, meta: { fp: meta.fp, eval: meta.eval } }; const runSet = async (name, cases, mode) => { let ok = 0, reached = 0, colTasks = 0, colFrames = 0, uncert = 0, brakes = 0, locks = 0, plans = 0, recovered = 0, lockTasks = 0, livelock = 0; - const inf = [], saf = [], look = [], planT = [], cq = [], times = [], fails = []; let hits = 0; const why = {}; + const inf = [], saf = [], look = [], planT = [], planW = [], cq = [], times = [], fails = []; let hits = 0; const why = {}; for (let ci = 0; ci < cases.length; ci++) { const c = cases[ci]; const R = mode === 'pose' ? c.R : null; @@ -86,7 +86,7 @@ const pct = (a, p) => { if (!a.length) return null; const s = a.slice().sort((x, const { e, oe } = r.err(r.q); const rc = r.done && e < 1 && oe < 0.5; if (rc) reached++; if (cf) { colTasks++; colFrames += cf; } if (rc && !cf) { ok++; times.push(r.t); } else fails.push({ ci, err: +e.toFixed(1), oerr: +oe.toFixed(1), colFrames: cf, brakes: r.stats.brakes }); - inf.push(...r.stats.inferMs); saf.push(...r.stats.safetyMs); look.push(...r.stats.lookaheadMs); planT.push(...r.stats.planMs); cq.push(...r.stats.certQ); hits += r.stats.certHit; + inf.push(...r.stats.inferMs); saf.push(...r.stats.safetyMs); look.push(...r.stats.lookaheadMs); planT.push(...r.stats.planMs); planW.push(...r.stats.planWarmMs); cq.push(...r.stats.certQ); hits += r.stats.certHit; for (const k in r.stats.certWhy) why[k] = (why[k] || 0) + r.stats.certWhy[k]; brakes += r.stats.brakes; locks += r.stats.locks; plans += r.stats.plans; if (tLock != null) { lockTasks++; if (rc && !cf && r.t - tLock <= 3.0) recovered++; } else if (rc && !cf) recovered++; if (!rc && r.stats.locks >= 2) livelock++; @@ -96,7 +96,7 @@ const pct = (a, p) => { if (!a.length) return null; const s = a.slice().sort((x, plans, brakeRate: plans ? +(brakes / plans).toFixed(4) : null, lockTasks, recovered3s: recovered, recoveryRate: cases.length ? +(recovered / cases.length).toFixed(4) : null, livelock, timeP50: pct(times, 0.5), inferMsP50: pct(inf, 0.5), inferMsP99: pct(inf, 0.99), safetyMsP50: pct(saf, 0.5), safetyMsP99: pct(saf, 0.99), // 2026-10-18 第六轮(审查 B-1/B-2):前瞻、整次规划耗时,每次规划的距离查询数与记忆命中率,认证失败原因——与查看器 autotest 同口径对照 - lookaheadMsP99: pct(look, 0.99), planMsP50: pct(planT, 0.5), planMsP99: pct(planT, 0.99), certQueriesP50: pct(cq, 0.5), certQueriesP99: pct(cq, 0.99), + lookaheadMsP99: pct(look, 0.99), planMsP50: pct(planT, 0.5), planMsP99: pct(planT, 0.99), planWarmMsP99: pct(planW, 0.99), certQueriesP50: pct(cq, 0.5), certQueriesP99: pct(cq, 0.99), certMemoHitRate: (() => { const q = cq.reduce((a, b) => a + b, 0); return q + hits ? +(hits / (q + hits)).toFixed(3) : null; })(), certFailWhy: why, fails: fails.slice(0, 15) }; console.error(name, JSON.stringify({ ok, n: cases.length, collisionFrames: colFrames })); }; diff --git a/webapp/js/ik-policy-glue.js b/webapp/js/ik-policy-glue.js index 482a9d2..6064709 100644 --- a/webapp/js/ik-policy-glue.js +++ b/webapp/js/ik-policy-glue.js @@ -72,7 +72,7 @@ async function boot() { latency: () => { const p = (a, x) => { const s = a.slice().sort((u, v) => u - v); return s.length ? s[Math.min(s.length - 1, Math.floor(s.length * x))] : null; }; const st = runner.stats; return { inferP50: p(st.inferMs, .5), inferP99: p(st.inferMs, .99), safetyP50: p(st.safetyMs, .5), safetyP99: p(st.safetyMs, .99), - lookaheadP99: p(st.lookaheadMs, .99), planP50: p(st.planMs, .5), planP99: p(st.planMs, .99), certQP99: p(st.certQ, .99), + lookaheadP99: p(st.lookaheadMs, .99), planP50: p(st.planMs, .5), planP99: p(st.planMs, .99), planWarmP99: p(st.planWarmMs, .99), planColdP99: p(st.planColdMs, .99), coldPlans: st.planColdMs.length, certQP99: p(st.certQ, .99), tickGapP99: p(TICKS.gaps, .99), tickGapMax: TICKS.gaps.length ? Math.max(...TICKS.gaps) : null, starve: TICKS.starve, ticks: TICKS.n, threads: ort.env.wasm.numThreads, ep: ep[0], proxy, encOnce: !!meta.enc_once, featDyn: !!meta.feat_dyn, probe }; } }; @@ -118,7 +118,8 @@ function onTick() { if (!running) return; const tn = performance.now(); if (TICKS.last) TICKS.gaps.push(tn - TICKS.last); TICKS.last = tn; TICKS.n++; if (TICKS.gaps.length > 20000) TICKS.gaps.splice(0, 10000); - if (!runner.queue.length && !runner.done && runner.goal) TICKS.starve++; + /* 第八轮:冷启动(静止起步、首次规划含前瞻)时队列本就为空、机械臂没在动,不算丢步;只统计运动中断粮 */ + if (!runner.queue.length && !runner.done && runner.goal && !runner.cold) TICKS.starve++; if (runner.needPlan()) runner.plan().catch(e => { console.error(e); stop(); say('策略推理出错:' + e.message, 'badb'); }); const q = runner.tick(); if (q) window.IK.setJoints(q, false); diff --git a/webapp/js/ik-policy.js b/webapp/js/ik-policy.js index 785f782..141d555 100644 --- a/webapp/js/ik-policy.js +++ b/webapp/js/ik-policy.js @@ -184,7 +184,7 @@ class Runner { this.rng = makeRng(this.o.seed); /* 2026-10-18 第六轮(审查 B-1/B-2):耗时拆开记——inferMs 主推理、lookaheadMs 前瞻(第二次推理 + 8 份特征)、safetyMs 只含认证、 planMs 整次规划(R9 口径:必须 < 60 ms 周期);certQ 每次规划新增的距离查询数,certHit 记忆命中数,certWhy 认证失败原因计数 */ - this.stats = { plans: 0, inferMs: [], lookaheadMs: [], safetyMs: [], planMs: [], certQ: [], certHit: 0, certWhy: {}, brakes: 0, refine: 0, lookahead: 0, locks: 0, projected: 0 }; + this.stats = { plans: 0, inferMs: [], lookaheadMs: [], safetyMs: [], planMs: [], planWarmMs: [], planColdMs: [], certQ: [], certHit: 0, certWhy: {}, brakes: 0, refine: 0, lookahead: 0, locks: 0, projected: 0 }; this.reset(null); } reset(q0) { @@ -238,11 +238,11 @@ class Runner { async plan() { if (!this.needPlan()) return; this.planning = true; - const t = now(), c0 = this.cert.counters(); + const t = now(), c0 = this.cert.counters(), wasCold = this.cold; // 第八轮:冷启动(含前瞻)与稳态分开记,R9 判稳态 try { await this._plan(); } finally { this.planning = false; this._until = undefined; const c1 = this.cert.counters(); - this.stats.planMs.push(now() - t); this.stats.certQ.push(c1.queries - c0.queries); this.stats.certHit += c1.hits - c0.hits; + const dt = now() - t; this.stats.planMs.push(dt); (wasCold ? this.stats.planColdMs : this.stats.planWarmMs).push(dt); this.stats.certQ.push(c1.queries - c0.queries); this.stats.certHit += c1.hits - c0.hits; } } async _plan() {