第六、七轮:离线 ort/WebGPU、R9 查看器整周期、安全层截止下沉、enc_once 前瞻 bug(feat 动态轴)、UTF-8、断点 RNG 续跑与 --stop-at、R2 池无关性自检、R8 出题缓存、本机自检 selftest、冒烟 smoke;遗留清单并入审查文档第六节
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
* &threads=N wasm 线程数(默认 min(16, ⌊0.6×逻辑核⌋);2026-10-18 R4 实测 28 核机 16 最优、24 反而变慢、4 线程 P99 37 ms)
|
||||
* &proxy=0 关闭推理代理 Worker(默认开:wasm 推理不占主线程,见下)
|
||||
* &probe=1 允许加载 --random 导出的延迟探针模型(默认拒绝,防把随机策略当真模型跑)
|
||||
* &autotest=N 自动跑 N 道贴立柱题(与 tests/policy_bench.cjs 同种子同题),结果 POST 到 &report=(默认 /r4/result);
|
||||
* 由 python -m ml.eval.r4_probe --viewer 驱动,测部署形态的整次规划耗时与节拍(方案 R9)
|
||||
* 2026-10-18 第五轮:ort.env.wasm.proxy=true 把 wasm 推理挪到 ort 自带的 Worker。此前推理在主线程同步算,
|
||||
* R4 实测时 CDP 15 s 拿不到执行槽,50 Hz tick 也被它拖住——丢步不只来自推理时长本身。
|
||||
* 目标变化(松手求解 / 实时追踪)都交给 ArmPolicy.Runner:50 Hz 执行时钟放在 Web Worker(不受 rAF 60 Hz 与后台节流影响),
|
||||
@@ -18,7 +20,10 @@
|
||||
const qs = new URLSearchParams(location.search);
|
||||
if (!qs.has('policy')) return;
|
||||
const MODEL = qs.get('policy') && qs.get('policy') !== '1' ? qs.get('policy') : 'models/traj_policy.onnx';
|
||||
const ORT_BASE = qs.get('ortLocal') === '1' ? 'vendor/ort/' : 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.1/dist/';
|
||||
/* 2026-10-18 第六轮(审查 A-3):本地路径必须是绝对 URL——ORT 用 import() 加载 .mjs,相对前缀 'vendor/ort/' 会被当成裸模块名而失败 */
|
||||
const ORT_BASE = qs.get('ortLocal') === '1' ? new URL('vendor/ort/', location.href).href : 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.1/dist/';
|
||||
/* WebGPU 后端只在 ort.webgpu.min.js 里(ort.min.js 只有 wasm,第六轮实测 "[webgpu] backend not found" 即此因) */
|
||||
const ORT_JS = qs.get('ep') === 'webgpu' ? 'ort.webgpu.min.js' : 'ort.min.js';
|
||||
const badge = () => document.getElementById('ikBadge');
|
||||
const say = (t, cls) => { const b = badge(); if (b) { b.textContent = t; b.className = 'badge ' + (cls || 'wait'); } console.log('[policy]', t); };
|
||||
let ready = false, runner = null, running = false, worker = null;
|
||||
@@ -37,7 +42,7 @@ async function boot() {
|
||||
if (meta.tcp0 && meta.tcp0.some((v, i) => Math.abs(v - S.TCP0[i]) > 0.01)) throw new Error('TCP 与训练时不同:' + S.TCP0 + ' vs ' + meta.tcp0);
|
||||
const probe = !!meta.random_init;
|
||||
if (probe && qs.get('probe') !== '1') throw new Error('这是随机初始化的延迟探针模型(meta.random_init),不是训练产物;仅测延迟时加 &probe=1');
|
||||
if (!window.ort) await loadScript(ORT_BASE + 'ort.min.js');
|
||||
if (!window.ort) await loadScript(ORT_BASE + ORT_JS);
|
||||
ort.env.wasm.wasmPaths = ORT_BASE;
|
||||
const hc = navigator.hardwareConcurrency || 1;
|
||||
ort.env.wasm.numThreads = !self.crossOriginIsolated ? 1 : (+qs.get('threads') || Math.max(1, Math.min(16, Math.floor(hc * 0.6))));
|
||||
@@ -49,7 +54,7 @@ async function boot() {
|
||||
catch (e) { console.warn('[policy] 代理 Worker 不可用,退回主线程推理:', e); proxy = false; ort.env.wasm.proxy = false; sess = null; }
|
||||
}
|
||||
if (!sess) sess = await ort.InferenceSession.create(MODEL, { executionProviders: ep, graphOptimizationLevel: 'all' });
|
||||
const infer = ArmPolicy.ortInfer(ort, sess, { encOnce: !!meta.enc_once });
|
||||
const infer = ArmPolicy.ortInfer(ort, sess, { encOnce: !!meta.enc_once, featDyn: !!meta.feat_dyn });
|
||||
runner = new ArmPolicy.Runner(S, infer, {});
|
||||
/* 预热:首次推理有编译开销 */
|
||||
await infer(new Float32Array(8 * ArmPolicy.F_DIM), new Float32Array(8 * ArmPolicy.H * 6), 8);
|
||||
@@ -65,18 +70,55 @@ async function boot() {
|
||||
},
|
||||
stop, stats: () => runner.stats, runner: () => runner,
|
||||
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; };
|
||||
return { inferP50: p(runner.stats.inferMs, .5), inferP99: p(runner.stats.inferMs, .99), safetyP50: p(runner.stats.safetyMs, .5), safetyP99: p(runner.stats.safetyMs, .99), threads: ort.env.wasm.numThreads, ep: ep[0], proxy, encOnce: !!meta.enc_once, probe }; }
|
||||
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),
|
||||
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 }; }
|
||||
};
|
||||
ready = true;
|
||||
const how = ep[0] + ',' + ort.env.wasm.numThreads + ' 线程' + (proxy ? ',推理在 Worker' : ',推理在主线程');
|
||||
if (probe) say('随机探针模型(仅测延迟,动作无意义):' + how, 'badb');
|
||||
else say('策略模式就绪(' + how + '):拖动目标即由策略执行', 'ok');
|
||||
if (qs.get('autotest')) autotest(+qs.get('autotest') || 30, meta);
|
||||
} catch (e) { say('策略模式不可用:' + e.message, 'badb'); console.error(e); }
|
||||
}
|
||||
/* 节拍统计(R9):tick 间隔、饥饿次数(执行时钟到了但队列为空、又没到达 = 实际丢步) */
|
||||
const TICKS = { gaps: [], last: 0, starve: 0, n: 0 };
|
||||
/* autotest:同一批贴立柱题(PolicyCases.ncCases,与 Node bench 同种子 20261017)逐题跑,每题限时 10 s,结果 POST 回驱动方 */
|
||||
async function autotest(N, meta) {
|
||||
const S = window.IK.solver;
|
||||
if (!window.PolicyCases) await loadScript(new URL('js/policy-cases.js', location.href).href);
|
||||
const cases = PolicyCases.ncCases(S, N, 20261017), per = [];
|
||||
const t0 = performance.now();
|
||||
for (let i = 0; i < cases.length; i++) {
|
||||
const c = cases[i]; stop(); await new Promise(r => setTimeout(r, 50));
|
||||
window.IK.setJoints(c.q0, false); runner.reset(c.q0); runner.setGoal(c.T, null);
|
||||
const s0 = { plans: runner.stats.plans, brakes: runner.stats.brakes }, ts = performance.now(); let col = 0;
|
||||
TICKS.last = 0; start();
|
||||
while (running && performance.now() - ts < 10000) {
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
if (runner.q && !S.collideFree(runner.q)) col++;
|
||||
}
|
||||
stop();
|
||||
const { e } = runner.err(runner.q);
|
||||
per.push({ i, reached: runner.done && e < 1, err: +e.toFixed(1), colSamples: col, plans: runner.stats.plans - s0.plans, brakes: runner.stats.brakes - s0.brakes, s: +((performance.now() - ts) / 1000).toFixed(2) });
|
||||
}
|
||||
const lat = window.__POLICY.latency();
|
||||
const res = { kind: 'viewer_autotest', model: MODEL, probe: !!meta.random_init, n: cases.length, reached: per.filter(x => x.reached).length,
|
||||
collisionTasks: per.filter(x => x.colSamples).length, brakes: runner.stats.brakes, plans: runner.stats.plans, certMemoHits: runner.stats.certHit,
|
||||
certFailWhy: runner.stats.certWhy, wallS: +((performance.now() - t0) / 1000).toFixed(1), coi: self.crossOriginIsolated, ua: navigator.userAgent, ...lat, per };
|
||||
say('autotest 完成:到达 ' + res.reached + '/' + res.n + ',整次规划 P99 ' + res.planP99 + ' ms', 'ok');
|
||||
try { await fetch(qs.get('report') || '/r4/result', { method: 'POST', body: JSON.stringify(res) }); } catch (e) { console.warn(e); }
|
||||
window.__POLICY.autotest = res;
|
||||
}
|
||||
function start() { if (running) return; running = true; worker.postMessage(ArmPolicy.DT * 1000); }
|
||||
function stop() { running = false; if (worker) worker.postMessage(0); }
|
||||
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.needPlan()) runner.plan().catch(e => { console.error(e); stop(); say('策略推理出错:' + e.message, 'badb'); });
|
||||
const q = runner.tick();
|
||||
if (q) window.IK.setJoints(q, false);
|
||||
|
||||
Reference in New Issue
Block a user