| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import * as ort from '/ort/ort.all.bundle.min.mjs'; |
|
|
| const params = new URLSearchParams(location.search); |
| const MODEL = params.get('model'); |
| const INPUT = params.get('input') ?? '000000000139.f32'; |
| const THREADS = params.get('threads') ?? 'auto'; |
| const REPEATS = Number(params.get('repeats') ?? 12); |
| const WARMUPS = Number(params.get('warmups') ?? 2); |
| const BUDGET_MS = Number(params.get('budgetMs') ?? 180_000); |
|
|
| const log = (m) => { document.getElementById('log').textContent = m; }; |
|
|
| const percentile = (values, p) => { |
| const sorted = [...values].sort((a, b) => a - b); |
| if (sorted.length === 0) return null; |
| const idx = Math.min(sorted.length - 1, Math.ceil((p / 100) * sorted.length) - 1); |
| return sorted[Math.max(0, idx)]; |
| }; |
| const mean = (v) => v.reduce((a, b) => a + b, 0) / v.length; |
|
|
| const fetchBuffer = async (path) => { |
| const r = await fetch(path); |
| if (!r.ok) throw new Error(`${path}: HTTP ${r.status}`); |
| return r.arrayBuffer(); |
| }; |
|
|
| const measureMemory = async () => { |
| if (typeof performance.measureUserAgentSpecificMemory !== 'function') return null; |
| try { |
| const res = await performance.measureUserAgentSpecificMemory(); |
| return { bytes: res.bytes }; |
| } catch { return null; } |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| const fingerprint = (outputs) => { |
| const labels = outputs.labels; |
| const scores = outputs.scores; |
| const boxes = outputs.boxes; |
| const masks = outputs.masks; |
| const Q = scores.length; |
| const plane = masks.length / Q; |
| const labelsNum = new Array(Q); |
| const maskPix = new Array(Q); |
| for (let q = 0; q < Q; q++) { |
| labelsNum[q] = Number(labels[q]); |
| let c = 0; |
| const base = q * plane; |
| for (let i = 0; i < plane; i++) if (masks[base + i] > 0) c++; |
| maskPix[q] = c; |
| } |
| |
| const inst = []; |
| for (let q = 0; q < Q; q++) { |
| if (!(scores[q] >= 0.4)) continue; |
| const cls = labelsNum[q]; |
| if (cls < 0 || cls >= 80) continue; |
| inst.push({ |
| q, |
| cls, |
| score: Number(scores[q].toFixed(5)), |
| box: [boxes[q * 4], boxes[q * 4 + 1], boxes[q * 4 + 2], boxes[q * 4 + 3]].map((v) => Number(v.toFixed(5))), |
| maskPix: maskPix[q], |
| }); |
| } |
| let nan = false; |
| for (let i = 0; i < scores.length; i++) if (!Number.isFinite(scores[i])) { nan = true; break; } |
| return { |
| numInstances: inst.length, |
| instances: inst, |
| scoresTop5: [...scores].sort((a, b) => b - a).slice(0, 5).map((v) => Number(v.toFixed(5))), |
| anyNaNInf: nan, |
| outputDtypes: { |
| labels: outputs._types.labels, |
| boxes: outputs._types.boxes, |
| scores: outputs._types.scores, |
| masks: outputs._types.masks, |
| }, |
| }; |
| }; |
|
|
| const EM_OVERRIDE = params.get('em'); |
|
|
| const run = async () => { |
| if (!MODEL) throw new Error('missing ?model='); |
| |
| const singleThread = THREADS === '1'; |
| const numThreads = THREADS === 'auto' ? 0 : Number(THREADS); |
|
|
| |
| |
| |
| |
| ort.env.wasm.wasmPaths = '/ort/'; |
| ort.env.wasm.simd = true; |
| ort.env.wasm.numThreads = numThreads; |
| ort.env.logLevel = 'error'; |
|
|
| const environment = { |
| model: MODEL, |
| input: INPUT, |
| threadsRequested: THREADS, |
| crossOriginIsolated: globalThis.crossOriginIsolated === true, |
| sharedArrayBuffer: typeof SharedArrayBuffer !== 'undefined', |
| hardwareConcurrency: navigator.hardwareConcurrency, |
| userAgent: navigator.userAgent, |
| ortVersion: ort.env.versions?.common ?? null, |
| wasmSimd: ort.env.wasm.simd, |
| numThreadsRequested: ort.env.wasm.numThreads, |
| }; |
|
|
| log(`fetching model ${MODEL}`); |
| const modelBuf = await fetchBuffer(`/models/${MODEL}`); |
| const inputBuf = await fetchBuffer(`/input/${INPUT}`); |
| const inputData = new Float32Array(inputBuf); |
|
|
| const baselineMemory = await measureMemory(); |
|
|
| const executionMode = EM_OVERRIDE ?? (singleThread ? 'sequential' : 'parallel'); |
| log(`creating session ${MODEL} (threads=${THREADS}, em=${executionMode})`); |
| const createStart = performance.now(); |
| let session; |
| try { |
| |
| |
| session = await Promise.race([ |
| ort.InferenceSession.create(modelBuf, { |
| executionProviders: ['cpu'], |
| graphOptimizationLevel: 'all', |
| executionMode, |
| logSeverityLevel: 3, |
| }), |
| new Promise((_, rej) => setTimeout(() => rej(new Error('session.create timed out (30s)')), 30_000)), |
| ]); |
| } catch (e) { |
| throw new Error(`session.create failed: ${e?.message ?? e}`); |
| } |
| const sessionCreateMs = performance.now() - createStart; |
| const afterLoadMemory = await measureMemory(); |
|
|
| const makeInput = () => new ort.Tensor('float32', inputData.slice(), [1, 3, 640, 640]); |
| const OUT_NAMES = ['labels', 'boxes', 'scores', 'masks']; |
|
|
| |
| const readOutputs = async (results) => { |
| const out = { _types: {} }; |
| for (const name of OUT_NAMES) { |
| const t = results[name]; |
| out._types[name] = t.type; |
| out[name] = await t.getData(); |
| } |
| return out; |
| }; |
|
|
| |
| log(`warmup ${MODEL}`); |
| let firstOutputs = null; |
| let coldMs = null; |
| for (let i = 0; i < Math.max(1, WARMUPS); i++) { |
| const t = performance.now(); |
| const results = await session.run({ images: makeInput() }); |
| const outs = await readOutputs(results); |
| const dt = performance.now() - t; |
| if (i === 0) { coldMs = dt; firstOutputs = outs; } |
| } |
|
|
| |
| const samples = []; |
| const started = performance.now(); |
| for (let i = 0; i < REPEATS; i++) { |
| if (i > 0 && performance.now() - started > BUDGET_MS) break; |
| const t = performance.now(); |
| const results = await session.run({ images: makeInput() }); |
| await readOutputs(results); |
| samples.push(performance.now() - t); |
| log(`${MODEL} warm ${i + 1}/${REPEATS} — ${Math.round(samples[i])} ms`); |
| } |
|
|
| const afterRunMemory = await measureMemory(); |
|
|
| return { |
| environment, |
| sessionCreateMs, |
| coldInferenceMs: coldMs, |
| warm: { |
| n: samples.length, |
| p50: percentile(samples, 50), |
| p90: percentile(samples, 90), |
| p95: percentile(samples, 95), |
| min: Math.min(...samples), |
| max: Math.max(...samples), |
| mean: mean(samples), |
| samples, |
| }, |
| fingerprint: fingerprint(firstOutputs), |
| memory: { baseline: baselineMemory, afterLoad: afterLoadMemory, afterRun: afterRunMemory }, |
| }; |
| }; |
|
|
| globalThis.benchmarkPromise = run().then( |
| (result) => { log('done'); globalThis.benchmarkResult = { ok: true, result }; return globalThis.benchmarkResult; }, |
| (error) => { |
| log(`failed: ${error?.message ?? error}`); |
| globalThis.benchmarkResult = { ok: false, error: String(error?.stack ?? error?.message ?? error) }; |
| return globalThis.benchmarkResult; |
| } |
| ); |
|
|