updraft / lib /backend-events.js
Nicholas Celestin
Build update — 2026-05-22T18:34:00.912Z
3f22414
Raw
History Blame Contribute Delete
4.97 kB
/**
* Unified channel for inference-backend narrative events. Both the web-mode
* engine (when it tries WebGPU then falls back to WASM) and the native
* desktop bridge (when its EP ladder tries CoreML/CUDA/DML then falls back
* to CPU) emit events on `document` with the same shape:
*
* document.dispatchEvent(new CustomEvent('aitools:backend-event', {
* detail: {
* kind: 'attempt' | 'success' | 'fallback' | 'skipped',
* backend: string, // canonical EP name
* reason?: string, // short failure or skip reason
* }
* }));
*
* `trackBackendEvents()` lets a feature orchestrator subscribe for the
* duration of one run, then ask for a summary on completion.
*/
const CHANNEL = 'aitools:backend-event';
export function dispatchBackendEvent(detail) {
document.dispatchEvent(new CustomEvent(CHANNEL, { detail }));
}
export function trackBackendEvents(onEvent) {
const events = [];
const handler = (e) => {
events.push(e.detail);
onEvent?.(e.detail);
};
document.addEventListener(CHANNEL, handler);
return {
stop() { document.removeEventListener(CHANNEL, handler); },
events: () => events.slice(),
summary() {
let activeBackend = null;
let hadFallback = false;
let hadSkip = false;
const lines = [];
for (const e of events) {
if (e.kind === 'success') {
activeBackend = e.backend || activeBackend;
} else if (e.kind === 'fallback') {
hadFallback = true;
const friendly = friendlyBackend(e.backend);
lines.push(e.reason ? `Failed on ${friendly}: ${e.reason}` : `Failed on ${friendly}`);
} else if (e.kind === 'skipped') {
hadSkip = true;
const friendly = friendlyBackend(e.backend);
lines.push(e.reason ? `Skipped ${friendly} (${e.reason})` : `Skipped ${friendly}`);
}
}
return { activeBackend, hadFallback, hadSkip, lines };
},
};
}
/**
* Trim a runtime error message down to the first useful signal phrase, or
* the first 120 chars if no known phrase matches. ORT/native errors tend
* to be long and noisy; the tooltip wants something compact.
*/
export function shortenReason(e) {
const s = String(e?.message || e || '');
const m = s.match(/Error in building plan|Non-zero status code returned|Failed to load|Could not create|cannot be reshaped|Unexpected input data type|kernel.*not.*found/i);
if (m) return m[0];
return s.length > 120 ? s.slice(0, 117) + '…' : s;
}
/**
* True if the realized backend label denotes a GPU-class execution provider.
*
* 'web-webgpu' counts; everything 'native-*' except 'native-cpu' counts
* (coreml / dml / cuda / rocm all run on the GPU or neural accelerator).
* 'web-wasm' and 'native-cpu' are CPU.
*
* Used by status-bar logic to detect intent/reality mismatches like
* "user asked for GPU but a warm CPU session is what's actually serving".
*/
export function realizedIsGpu(backend) {
if (!backend) return false;
if (backend === 'web-wasm' || backend === 'native-cpu') return false;
return true;
}
/**
* Map a realized backend identifier to a user-facing label.
*
* The canonical shape produced by lib/backend.js is two-part: a mode prefix
* (`web-` or `native-`) plus the concrete EP. We unpack both — the prefix
* tells the user whether they're on the browser stack or the native one,
* which materially affects perf and error shape.
*
* 'web-webgpu' → 'WebGPU'
* 'web-wasm' → 'CPU (WASM)'
* 'native-cpu' → 'CPU (native)'
* 'native-cuda' → 'CUDA'
* 'native-rocm' → 'ROCm'
* 'native-dml' → 'DirectML'
* 'native-coreml/MLProgram' → 'CoreML (MLProgram)'
*
* Legacy values from before the refactor are also accepted so a half-migrated
* caller doesn't render "unknown".
*/
export function friendlyBackend(b) {
if (!b) return 'unknown';
// New-style two-part labels.
if (b === 'web-webgpu') return 'WebGPU';
if (b === 'web-wasm') return 'CPU (WASM)';
if (b.startsWith('native-')) {
const ep = b.slice('native-'.length);
if (ep === 'cpu') return 'CPU (native)';
if (ep === 'cuda') return 'CUDA';
if (ep === 'rocm') return 'ROCm';
if (ep === 'dml') return 'DirectML';
if (ep.startsWith('coreml')) {
const variant = ep.includes('/') ? ep.split('/')[1] : null;
return variant ? `CoreML (${variant})` : 'CoreML';
}
return ep;
}
// Legacy single-token forms (kept for any not-yet-migrated callers).
if (b === 'webgpu') return 'WebGPU';
if (b === 'wasm' || b === 'cpu') return 'CPU';
if (b === 'cuda') return 'CUDA';
if (b === 'dml') return 'DirectML';
if (b.startsWith('coreml')) {
const variant = b.includes('/') ? b.split('/')[1] : null;
return variant ? `CoreML (${variant})` : 'CoreML';
}
return b;
}