Files
Robot_Arm/webapp/r4_probe.html
T

50 lines
4.1 KiB
HTML
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.
<!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;
/* 2026-10-18 第六轮:本地前缀用绝对 URL(ORT 以 import() 加载 .mjs,相对前缀会被当成裸模块名);WebGPU 后端只在 ort.webgpu.min.js 里 */
const BASE = qs.get('ortLocal') === '1' ? new URL('/webapp/vendor/ort/', location.href).href : 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.1/dist/';
res.ortBase = BASE;
if (res.ep === 'webgpu') { // 先看浏览器给不给适配器,区分"没有 GPU"与"ort 包不含 WebGPU"
res.hasGpu = !!navigator.gpu;
try { const ad = navigator.gpu && await navigator.gpu.requestAdapter(); res.adapter = ad ? ((ad.info && (ad.info.vendor + ' ' + ad.info.architecture)) || 'ok') : null; } catch (e) { res.adapter = 'error: ' + e; }
}
await new Promise((ok, no) => { const s = document.createElement('script'); s.src = BASE + (res.ep === 'webgpu' ? 'ort.webgpu.min.js' : '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 z = () => new Float32Array(K * H * 6).map(() => Math.random() * 2 - 1);
/* 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);
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>