2026-10-17 18:57:48 +00:00
<!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 z = () => new Float32Array ( K * H * 6 ). map (() => Math . random () * 2 - 1 );
2026-10-18 03:21:53 +08:00
/* 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 ]) }); };
2026-10-17 18:57:48 +00:00
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 >