fix: R4 探针页 tensor 复用(proxy 下 buffer 被 detach)+ pipeline.sh 7 处 GBK 读取 + r4 分支返回码;r4 端到端 rc=0

- webapp/r4_probe.html:feat 移进 run() 每次新建。proxy=1 时 ORT 把输入 transfer 给 Worker,旧写法第二次 run 报 Tensor size(83) vs data length(0),两种 proxy 配置全挂、r4.json pass=null。
- ml/pipeline.sh:7 处 json.load(open(...)) 补 encoding='utf-8'(:90 :110 :118 :127 :136 :138 :144)。r4.json 以 ensure_ascii=False 写出,Windows GBK 下 :110 实测 UnicodeDecodeError,命令替换为空会让上层分支走错。
- ml/pipeline.sh r4:末行 `[ "$r" = null ] && echo` 在 r=pass 时整体返回 1,set -e + EXIT trap 把通过的 r4 记成 rc=1(all 会因此停)→ 改 if/fi。
- 实测复跑两次:pass=true,部署口径(16 线程+proxy+enc_once)P99 10.22 / 11.25 ms,4 线程 13.58/13.64,无 proxy 9.28/10.92,webgpu 仍 backend not found。
- 文档同步:A 组两处标记已修,仅留 A-3(ortLocal+proxy 离线路径)交实现方。
This commit is contained in:
cbb
2026-10-18 03:21:53 +08:00
parent d70e2b90b8
commit e6388b2f0c
3 changed files with 24 additions and 24 deletions
+13 -14
View File
@@ -3,7 +3,7 @@
六轮历史(`0218d7f` → `a5e779c`):第一轮 13 条、第二轮可行性、第三~五轮处置、第六轮本机实跑。**已解决的条目按用户要求删去,完整原文在 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 部署口径(纯推理)已判通过;阻断本机跑 `r4` / `all` 的三处见 A 组。
当前结论:第 1–6 步已实测可跑;**第 7–13 步零证据**;R4 部署口径(纯推理)已判通过,`bash ml/pipeline.sh r4` 端到端 rc=0(2026-10-18 本机修复后复跑)。A 组原三处中两处已修,只剩离线部署 A-3。
---
@@ -28,24 +28,22 @@ R4(随机初始化模型;延迟只取决于结构,与是否训练无关)
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. 会直接中断本机一键流程的三处(已定位、未修)
### A. 会直接中断本机一键流程(原三处,两处已在本机修复)
**A-1 探针页复用 detached tensor**(`webapp/r4_probe.html:28`、`:30`)
proxy=1 时 ORT 把输入 buffer transfer 给 Worker,而页面把 `feat` 建在循环外、`run()` 每次复用同一个 `Float32Array` → 第二次 `run()` 报 `Tensor's size(83) does not match data length(0)`。两种 proxy 配置全挂 → `r4.json` `pass:null`、`r4_probe.py` 非零退出、阶段 rc=1(`all` 因此会停)。
修法:把 `feat` 的分配移进 `run()`(每次新建,一行)。判据:`bash ml/pipeline.sh r4` 四种配置都出数、`pass` 非 null。
证据:改过一行的副本四种配置全部正常(上表 16 线程行即出自副本);查看器 `Runner._plan` 每次新建、不受影响(`ik-policy.js:257-258`)。
**已修复(本机 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-2 `pipeline.sh` 7 处 `json.load(open(...))` 无 `encoding='utf-8'`**(`:90 :110 :118 :127 :136 :138 :144`)
已复现:`:110` 读 `r4.json`(`r4_probe.py:113` 以 `ensure_ascii=False` 写出、含中文)→ Windows GBK 报 `UnicodeDecodeError: 'gbk' codec can't decode byte 0xa7`。后果不是"读不到就用默认",而是命令替换为空 → 上层分支走错。
其余 6 处同写法;`stats.json` 由 node `JSON.stringify` 写出(不转义非 ASCII),文件里一旦有中文就同样炸。统一补 `encoding='utf-8'` 即可。
**A-3 `&ortLocal=1` + 默认 proxy 必然失败**
**A-3 仍未修:`&ortLocal=1` + 默认 proxy 必然失败。**
`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 无关,大概率同样失败。)
### B. 待实现方判断(数据已摆在上面;分歧按第五轮第〇节规则,以实现方为准)
@@ -73,6 +71,7 @@ proxy=1 时 ORT 把输入 buffer transfer 给 Worker,而页面把 `feat` 建
## 三、第六轮已复核正常(不在问题清单里)
- `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]`。
@@ -80,6 +79,6 @@ proxy=1 时 ORT 把输入 buffer transfer 给 Worker,而页面把 `feat` 建
## 四、建议顺序
1. A 组三条都是分钟级修复;修完立刻重跑 `bash ml/pipeline.sh r4`,验收 `pass` 非 null。
2. 然后 `FROM=gen TO=prep` 跑一次(数小时),拿 `gapDrop` 与 R2 两个口径的数——C-2 / C-3 随之消掉,B-2 的 Node 侧安全层数字可顺手从 m5 前的 bench 拿。
3. B 组等上面的数字齐了再定;C-1 / C-5 / C-6 随第一次真跑逐项核对。
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-5 / C-6 随第一次真跑逐项核对;A-3(离线 ort 加载)交给实现方。
+8 -8
View File
@@ -87,7 +87,7 @@ m5_one() { # $1 = 头标签;返回 0 通过 / 3 不通过
$PY - "$tag" "$ck" <<'PY'
import json, sys
tag, ck = sys.argv[1], sys.argv[2]
p = json.load(open(f'ml/runs/m5_{tag}_policy.json')); s = json.load(open(f'ml/runs/m5_{tag}_safety.json'))
p = json.load(open(f'ml/runs/m5_{tag}_policy.json', encoding='utf-8')); s = json.load(open(f'ml/runs/m5_{tag}_safety.json', encoding='utf-8'))
ok = p['collision_rate'] <= 0.02
out = {'head': tag, 'ckpt': ck, 'pass': ok,
'policy_only': {k: p[k] for k in ('n', 'success', 'collision_rate', 'start_fail')},
@@ -107,15 +107,15 @@ case $stage in
mkdir -p ml/runs/r4
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
r=$($PY -c "import json;p=json.load(open('ml/runs/r4.json')).get('pass');print({True:'pass',False:'fail'}.get(p,'null'))")
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'))")
if [ "$r" = fail ] && [ "${R4_STRICT:-1}" = 1 ]; then
echo "R4 不通过(见 ml/runs/r4.json):部署延迟超标,先别花几十小时训练这个规模的模型。按方案改 CFG=ml/configs/small.json(d=128)重跑;确认要继续设 R4_STRICT=0"; exit 5
fi
[ "$r" = null ] && echo "R4 未判定(没找到浏览器或页面未回报),继续;验收汇总会标红" ;;
if [ "$r" = null ]; then echo "R4 未判定(没找到浏览器或页面未回报),继续;验收汇总会标红"; fi ;;
gate1) mkdir -p ml/data; run node ml/gen/consistency.cjs 300 4242 | tee ml/data/consistency.json.tmp; mv ml/data/consistency.json.tmp ml/data/consistency.json ;; # 中断时不留残缺结果
gen) per=$(( (N_TASKS + NPROC - 1) / NPROC ))
if [ -z "${CONSENSUS:-}" ] && [ -f ml/data/consistency.json ]; then
CONSENSUS=$($PY -c "import json;print(1 if json.load(open('ml/data/consistency.json'))['consistentByGateRule']<0.9 else 0)")
CONSENSUS=$($PY -c "import json;print(1 if json.load(open('ml/data/consistency.json', encoding='utf-8'))['consistentByGateRule']<0.9 else 0)")
echo "一致性门禁结果 → CONSENSUS=$CONSENSUS"
fi
extra=""; [ "${CONSENSUS:-0}" = "1" ] && extra="--consensus 4"
@@ -124,7 +124,7 @@ case $stage in
# 最多 4 次(第五轮由 2 改 4:两种过滤叠加时 2 次可能补不满);已写完的补量 shard 续跑时跳过。
s0=$((1 + NPROC))
for k in 1 2 3 4; do
need=$($PY -c "import json,math;s=json.load(open('ml/data/stats.json'));d=$TARGET_SAMPLES-s['samplesEvery3'];print(0 if d<=0 else math.ceil(d/(s['samplesEvery3']/max(1,s['tasks']))*1.05))")
need=$($PY -c "import json,math;s=json.load(open('ml/data/stats.json', encoding='utf-8'));d=$TARGET_SAMPLES-s['samplesEvery3'];print(0 if d<=0 else math.ceil(d/(s['samplesEvery3']/max(1,s['tasks']))*1.05))")
[ "$need" = "0" ] && break
echo "样本不足目标 $TARGET_SAMPLES,补生成 $need 条任务(第 $k 次)"
per2=$(( (need + NPROC - 1) / NPROC ))
@@ -133,15 +133,15 @@ case $stage in
run bash ml/gen/run_parallel.sh $per2 $NPROC $s0 "$extra"
s0=$((s0 + NPROC))
done
$PY -c "import json;s=json.load(open('ml/data/stats.json'));import sys;sys.exit(0 if s['samplesEvery3']>=0.9*$TARGET_SAMPLES else 7)" || echo "警告:补 4 次后样本仍不足目标的 90%,按现有样本继续(stats.json 见 samplesEvery3)" ;;
$PY -c "import json;s=json.load(open('ml/data/stats.json', encoding='utf-8'));import sys;sys.exit(0 if s['samplesEvery3']>=0.9*$TARGET_SAMPLES else 7)" || echo "警告:补 4 次后样本仍不足目标的 90%,按现有样本继续(stats.json 见 samplesEvery3)" ;;
prep) # 间隙过滤口径必须与生成时一致(审查 N-1)
$PY -c "import json,sys;m=json.load(open('ml/data/stats.json')).get('minGapFilterMm');sys.exit(0 if m is None or float(m)==float($MIN_GAP_MM) else 8)" \
$PY -c "import json,sys;m=json.load(open('ml/data/stats.json', encoding='utf-8')).get('minGapFilterMm');sys.exit(0 if m is None or float(m)==float($MIN_GAP_MM) else 8)" \
|| { echo "MIN_GAP_MM=$MIN_GAP_MM 与生成阶段 stats.json 的 minGapFilterMm 不一致:补量口径与训练集会分叉,请用同一值重跑 gen 或 prep"; exit 8; }
# 已打包完的 shard 跳过(断点续跑,见 run_prep.sh);参数变了由 pstate reset 把旧 samples 移到 ml/archive/
run bash ml/prep/run_prep.sh $NPROC
run $PY -m ml.eval.check_r2 --samples ml/data/samples --out ml/data/r2.json
# 第五轮(审查 N-2):R2 判据与搜索池大小有关、阈值未经实测校准 → 默认只警告继续,R2_STRICT=1 才拦
if ! $PY -c "import json,sys;sys.exit(4 if json.load(open('ml/data/r2.json')).get('pass') is False else 0)"; then
if ! $PY -c "import json,sys;sys.exit(4 if json.load(open('ml/data/r2.json', encoding='utf-8')).get('pass') is False else 0)"; then
if [ "${R2_STRICT:-0}" = 1 ]; then echo "R2 不通过(R2_STRICT=1):停在这里,把 ml/data/r2.json 发我"; exit 4; fi
echo "警告:R2 不通过(近邻标签不自洽,见 ml/data/r2.json)。按默认继续训练,验收汇总会列出"
fi ;;
+3 -2
View File
@@ -25,9 +25,10 @@
if (res.proxy) { ort.env.wasm.proxy = true; try { sess = await ort.InferenceSession.create(MODEL, opts); } catch (e) { res.proxyError = String(e); res.proxy = false; ort.env.wasm.proxy = false; } }
if (!sess) sess = await ort.InferenceSession.create(MODEL, opts);
res.encOnce = !!meta.enc_once;
const feat = new Float32Array((res.encOnce ? 1 : K) * F).map(() => Math.random() - 0.5);
const z = () => new Float32Array(K * H * 6).map(() => Math.random() * 2 - 1);
const run = () => sess.run({ feat: new ort.Tensor('float32', feat, [res.encOnce ? 1 : K, F]), z: new ort.Tensor('float32', z(), [K, H, 6]) });
/* proxy 模式下 ORT 会把输入 buffer 转移给 Worker(detach),feat 必须每次新建 */
const run = () => { const feat = new Float32Array((res.encOnce ? 1 : K) * F).map(() => Math.random() - 0.5);
return sess.run({ feat: new ort.Tensor('float32', feat, [res.encOnce ? 1 : K, F]), z: new ort.Tensor('float32', z(), [K, H, 6]) }); };
for (let i = 0; i < 10; i++) await run();
let last = performance.now(); const gaps = [];
const tk = setInterval(() => { const t = performance.now(); gaps.push(t - last); last = t; }, 20);