第五轮:锁/续跑/超时/自动分支/R4 探针与 encode-once/控制台加固;阶段内断点续跑(gen 按条、prep 按 shard、训练与 APG 定时原子存档+体面停止);分歧以实现方意见为准
This commit is contained in:
@@ -3,6 +3,11 @@
|
||||
* ?policy=1 用 models/traj_policy.onnx
|
||||
* ?policy=models/xxx.onnx 指定模型
|
||||
* &ep=webgpu 改用 WebGPU 后端(默认 wasm-SIMD 多线程)
|
||||
* &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 导出的延迟探针模型(默认拒绝,防把随机策略当真模型跑)
|
||||
* 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 与后台节流影响),
|
||||
* 每 3 步推理一次,安全层认证后才执行;ik-glue 的逐帧碰撞守卫不参与(策略执行不走 animTo),由 Runner 安全层保证。
|
||||
* onnxruntime-web 默认从 jsDelivr 加载;离线部署把 ort.min.js 与 *.wasm 放到 vendor/ort/ 并加 &ortLocal=1。
|
||||
@@ -30,14 +35,24 @@ async function boot() {
|
||||
if (meta.fp !== S.modelFingerprint()) throw new Error('模型碰撞指纹 ' + meta.fp + ' ≠ 当前 ' + S.modelFingerprint() + ':collide.json 已变,须重训');
|
||||
if (meta.feat_ver !== ArmPolicy.FEAT_VER) throw new Error('特征版本不符:模型 ' + meta.feat_ver + ' / 运行时 ' + ArmPolicy.FEAT_VER);
|
||||
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');
|
||||
ort.env.wasm.wasmPaths = ORT_BASE;
|
||||
ort.env.wasm.numThreads = self.crossOriginIsolated ? Math.min(4, navigator.hardwareConcurrency || 1) : 1;
|
||||
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))));
|
||||
const ep = qs.get('ep') === 'webgpu' ? ['webgpu', 'wasm'] : ['wasm'];
|
||||
const sess = await ort.InferenceSession.create(MODEL, { executionProviders: ep, graphOptimizationLevel: 'all' });
|
||||
runner = new ArmPolicy.Runner(S, ArmPolicy.ortInfer(ort, sess), {});
|
||||
let sess = null, proxy = ep[0] === 'wasm' && qs.get('proxy') !== '0'; // proxy 只支持 wasm EP
|
||||
if (proxy) {
|
||||
ort.env.wasm.proxy = true;
|
||||
try { sess = await ort.InferenceSession.create(MODEL, { executionProviders: ep, graphOptimizationLevel: 'all' }); }
|
||||
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 });
|
||||
runner = new ArmPolicy.Runner(S, infer, {});
|
||||
/* 预热:首次推理有编译开销 */
|
||||
await ArmPolicy.ortInfer(ort, sess)(new Float32Array(8 * ArmPolicy.F_DIM), new Float32Array(8 * ArmPolicy.H * 6), 8);
|
||||
await infer(new Float32Array(8 * ArmPolicy.F_DIM), new Float32Array(8 * ArmPolicy.H * 6), 8);
|
||||
const tick = URL.createObjectURL(new Blob(['let h=null;onmessage=e=>{clearInterval(h);if(e.data>0)h=setInterval(()=>postMessage(0),e.data)}'], { type: 'text/javascript' }));
|
||||
worker = new Worker(tick);
|
||||
worker.onmessage = onTick;
|
||||
@@ -50,10 +65,12 @@ 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] }; }
|
||||
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 }; }
|
||||
};
|
||||
ready = true;
|
||||
say('策略模式就绪(' + ep[0] + ',' + ort.env.wasm.numThreads + ' 线程):拖动目标即由策略执行', 'ok');
|
||||
const how = ep[0] + ',' + ort.env.wasm.numThreads + ' 线程' + (proxy ? ',推理在 Worker' : ',推理在主线程');
|
||||
if (probe) say('随机探针模型(仅测延迟,动作无意义):' + how, 'badb');
|
||||
else say('策略模式就绪(' + how + '):拖动目标即由策略执行', 'ok');
|
||||
} catch (e) { say('策略模式不可用:' + e.message, 'badb'); console.error(e); }
|
||||
}
|
||||
function start() { if (running) return; running = true; worker.postMessage(ArmPolicy.DT * 1000); }
|
||||
|
||||
@@ -336,9 +336,13 @@ class Runner {
|
||||
const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now());
|
||||
|
||||
/* ---------------- onnxruntime 适配(web: ort = window.ort;node: require('onnxruntime-node')) ---------------- */
|
||||
function ortInfer(ort, session) {
|
||||
/* 2026-10-18 第五轮:模型 meta.enc_once=true(编码一次、解码 K 次)时只送第 1 行特征 [1,F];
|
||||
Runner 仍按 K 行组织特征(K 行本就相同),旧模型(enc_once 缺省)照旧送 [B,F]。 */
|
||||
function ortInfer(ort, session, opt) {
|
||||
const encOnce = !!(opt && opt.encOnce);
|
||||
return async (feat, z, B) => {
|
||||
const r = await session.run({ feat: new ort.Tensor('float32', feat, [B, F_DIM]), z: new ort.Tensor('float32', z, [B, H, 6]) });
|
||||
const ft = encOnce ? new ort.Tensor('float32', feat.subarray(0, F_DIM), [1, F_DIM]) : new ort.Tensor('float32', feat, [B, F_DIM]);
|
||||
const r = await session.run({ feat: ft, z: new ort.Tensor('float32', z, [B, H, 6]) });
|
||||
return { act: r.act.data, value: r.value.data, sub: r.sub.data };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>R4 推理延迟探针</title>
|
||||
<!-- 2026-10-18 第五轮:由 ml/eval/r4_probe.py 在无头浏览器里打开,测部署形态的单次推理(1 行特征 + K=8 条候选)。
|
||||
参数:model= 模型 URL;ep=wasm|webgpu;threads=auto|N;proxy=0|1;n= 次数;ortLocal=1 用 vendor/ort/。
|
||||
结果 POST 到 /r4/result;同时记录 20 ms 节拍的最大间隔(推理是否卡主线程)。也可手动打开看页面上的结果。 -->
|
||||
<pre id="o">运行中…</pre>
|
||||
<script>
|
||||
(async () => {
|
||||
const qs = new URLSearchParams(location.search), o = document.getElementById('o');
|
||||
const res = { ep: qs.get('ep') || 'wasm', threadsArg: qs.get('threads') || 'auto', hc: navigator.hardwareConcurrency, coi: self.crossOriginIsolated, ua: navigator.userAgent };
|
||||
const post = async () => { o.textContent = JSON.stringify(res, null, 1); try { await fetch('/r4/result', { method: 'POST', body: JSON.stringify(res) }); } catch (e) {} };
|
||||
try {
|
||||
const MODEL = qs.get('model'), N = +qs.get('n') || 150, K = 8, F = 83, H = 16;
|
||||
const BASE = qs.get('ortLocal') === '1' ? '/webapp/vendor/ort/' : 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.1/dist/';
|
||||
await new Promise((ok, no) => { const s = document.createElement('script'); s.src = BASE + 'ort.min.js'; s.onload = ok; s.onerror = () => no(new Error('加载 ort 失败:' + s.src)); document.head.appendChild(s); });
|
||||
const meta = await (await fetch(MODEL.replace('.onnx', '.meta.json'))).json();
|
||||
ort.env.wasm.wasmPaths = BASE;
|
||||
// 与 ik-policy-glue.js 同一套默认值
|
||||
ort.env.wasm.numThreads = !self.crossOriginIsolated ? 1 : (res.threadsArg === 'auto' ? Math.max(1, Math.min(16, Math.floor(res.hc * 0.6))) : +res.threadsArg);
|
||||
res.threads = ort.env.wasm.numThreads;
|
||||
res.proxy = res.ep === 'wasm' && qs.get('proxy') !== '0';
|
||||
const opts = { executionProviders: res.ep === 'webgpu' ? ['webgpu'] : ['wasm'], graphOptimizationLevel: 'all' };
|
||||
let sess = null;
|
||||
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]) });
|
||||
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);
|
||||
const ts = [];
|
||||
for (let i = 0; i < N; i++) { const t = performance.now(); await run(); ts.push(performance.now() - t); await new Promise(r => setTimeout(r, 40)); }
|
||||
clearInterval(tk);
|
||||
const p = (a, x) => { const s = a.slice().sort((u, v) => u - v); return +s[Math.min(s.length - 1, Math.floor(s.length * x))].toFixed(2); };
|
||||
Object.assign(res, { n: N, K, p50: p(ts, .5), p95: p(ts, .95), p99: p(ts, .99), tickGapP99: p(gaps, .99), tickGapMax: +Math.max(...gaps).toFixed(1) });
|
||||
} catch (e) { res.error = String(e && e.message || e); }
|
||||
await post();
|
||||
})();
|
||||
</script>
|
||||
Reference in New Issue
Block a user