Skip to content

Model Loading & Caching

Vigilo WASM loads four neural models into onnxruntime-web sessions. This guide explains how models are fetched, cached, and scheduled across execution providers.


The Four Models

SlotModel NameInput ShapeSizeWarm Latency (WASM SIMD)
faceYuNet 2023mar[1, 3, 640, 640]232 KB51 ms
poseMobileNetV3-Small[1, 3, 224, 224]5.8 MB13 ms
gazeMobileOne-S0 (L2CS)[1, 3, 448, 448]4.7 MB104 ms
objectsYOLOX-Nano[1, 3, 416, 416]3.5 MB75 ms

Total download footprint: ~14.2 MB.


Two Measurements Worth Knowing

1. Float YuNet vs Quantized int8 YuNet

You might expect a quantized int8 YuNet model to run faster than a float32 model. On the web, the exact opposite is true:

  • Float32 YuNet: 232 KB, 51 ms
  • int8 Quantized YuNet: 110 KB, 209 ms (4x slower!)

onnxruntime-web's WebAssembly execution provider lacks optimized vector kernels for quantized convolutions on x86/ARM. It falls back to unvectorized dequantize-and-multiply routines. Always use the float32 model.

2. Gaze is the Budget

The MobileOne-S0 gaze model processes a 448×448 input tensor and accounts for over 60% of the entire pipeline compute budget. That is why gazeEvery: 2 is the default cadence divisor.


Loading Models with the Cache API

Model weights should not be redownloaded on every page load or exam refresh. loadModels automatically utilizes the browser's Cache API under the cache bucket vigilo-models-v1:

ts
import { loadModels } from 'vigilo-wasm';

const models = await loadModels({
  face:    '/models/face_detection_yunet_2023mar.onnx',
  pose:    '/models/headpose_mobilenetv3_small.onnx',
  gaze:    '/models/mobileone_s0_gaze.onnx',
  objects: '/models/yolox_nano.onnx',
}, {
  cache: true, // Default: true
  onProgress: (slot, loaded, total) => {
    const pct = total > 0 ? ((loaded / total) * 100).toFixed(0) : '?';
    console.log(`Loading ${slot}: ${pct}%`);
  },
});

Subsequent loads fetch instantly from local disk storage without any network requests.


WebGPU Acceleration

Where available, WebGPU drastically reduces latency, especially for the heavy 448×448 gaze model:

ts
const models = await loadModels(urls, {
  executionProviders: ['webgpu', 'wasm'],
});

When configured with ['webgpu', 'wasm'], onnxruntime-web attempts to initialize each model on the GPU via WebGPU. If a device lacks WebGPU support or encounters a shader compile error, it automatically falls back to WASM SIMD.


Multi-Threaded WASM & Cross-Origin Isolation

By default, onnxruntime-web operates in single-threaded mode. Enabling multi-threaded WASM requires SharedArrayBuffer, which browsers only expose in cross-origin isolated contexts.

Check isolation status:

ts
import { isCrossOriginIsolated } from 'vigilo-wasm';

if (isCrossOriginIsolated()) {
  console.log('Page is cross-origin isolated. Multi-threading enabled!');
} else {
  console.warn('Single-threaded mode: serve COOP and COEP headers to unlock multi-threading.');
}

Required HTTP Headers

Configure your reverse proxy (Nginx, Caddy, Cloudflare) or dev server to send:

http
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Handling Failed Models Gracefully

If an optional model fails to download (e.g. 404 or network timeout), loadModels will not throw. Instead, it populates models.failed:

ts
const models = await loadModels({
  face: '/models/face.onnx',
  pose: '/models/broken-link.onnx',
});

if (models.failed.length > 0) {
  for (const { slot, error } of models.failed) {
    console.warn(`Model ${slot} failed to load: ${error}`);
  }
}

When VigiloBrowser starts, it automatically marks failed models as not_configured in the temporal fusion engine and dispatches a degraded event. The exam can proceed with the remaining models (e.g. face detection only).

Released under the AGPL-3.0 License.