PRIVYSE runs a quantized MobileViT-Small inside the browser — not on a server, not in a worker talking to a GPU daemon, but through ONNX Runtime Web in the extension itself. The model classifies screenshot tiles so the privacy pipeline knows which regions need Tier C protection. This article is meant to collect the debugging lessons from making that reliable. The setup is verified; the war stories are still being checked against notes, so they are stubs for now.
The setup (verified)
What is documented in the PRIVYSE repo and can be stated as fact:
- Model: MobileViT-Small, quantized to q8 ONNX, 6.3 MB, bundled in the extension (
extension/public/models/) — no CDN downloads, ever. - Runtime: ONNX Runtime Web with the WebGPU execution provider first. When
navigator.gpuis present, tiles classify on the GPU; any op the WebGPU EP lacks falls through to the bundled WASM EP. The same.jsepruntime serves both paths, so nothing is re-downloaded on fallback. - Host context: Chrome MV3 service workers have no DOM, so the vision stack (MobileViT + BlazeFace + Tesseract.js) runs in a hidden offscreen document. Firefox MV2 has neither the offscreen API nor the side panel API — the background page is the DOM host, sharing the same
core/vision-host.ts. - Visibility: the active provider is surfaced live in the side panel resource meter (
perception · ⚡ WebGPU/wasm), and reported in perception stats alongside tile tags. - Caching: a pixel-hash LRU reuses the tile map when the page has not visually changed — cached perception ≈ 0 ms vs ≈ 1.4 s first run. Faces, OCR, and the privacy gate still run fresh every step.
- Measured accuracy: ViT page classification 7/7 on the labeled benchmark, photo-vs-blank 100%.
// WebGPU first, WASM fallback — same bundled .jsep runtime
const hasWebGPU = typeof navigator !== "undefined" && !!navigator.gpu;
const providers = hasWebGPU ? ["webgpu", "wasm"] : ["wasm"];
// active provider is reported to the side-panel resource meter
This code block illustrates the documented behavior (WebGPU-first with WASM fallback, provider reported to the UI). It is not copied from the source tree. The verified implementation lives in extension/core/perception.ts in the PRIVYSE repo.
Failure modes worth writing up (TODO)
The following debugging sessions happened while building the perception stack. Each one needs its notes re-checked before it can be written as fact:
Notes exist on at least one environment where navigator.gpu was present but WebGPU device requests failed inside the MV3 offscreen document, silently dropping to WASM with a latency jump. To verify: exact browser version, the error surfaced, whether requestAdapter() returned null, and the fallback log line. Do not publish until the console capture is recovered.
Some MobileViT ops were expected to miss the WebGPU EP and fall through. To verify: which ops, whether ORT logged partition boundaries, and the measured GPU-vs-WASM tile-classification latency delta on the same page.
Long-running sessions appeared to accumulate memory in the WASM heap. To verify: whether session reuse or per-step session creation was the cause, the observed growth curve, and what the current code does (release vs reuse).
The same-page cache keys on a pixel hash; dynamic pages (clocks, spinners) were expected to thrash it, and near-identical frames to false-hit it. To verify: whether any false hit was actually observed in benchmarks, the hash scheme, and the eviction policy in the LRU.
Debugging playbook (TODO — outline only)
The shape of the playbook this article will end with, pending verification:
- Always log the active execution provider per step — silent fallback is the root cause of most "it got slow" reports.
- Keep a WASM-only baseline measurement per machine; compare, don't assume.
- Test the offscreen-document path and the Firefox background-page path separately; they fail differently.
- Treat cache hits as untrusted until the hash scheme is re-read — measure first-run vs cached latencies in CI-style runs.
Takeaways (provisional)
Two things are already safe to claim because they are documented and benchmarked, not anecdotal:
- Surface the execution provider in the UI. Making WebGPU/WASM visible in the resource meter turned "why is it slow?" from a mystery into a screenshot.
- Bundle both runtimes. Shipping the
.jsepbuild with WASM fallback inside the extension means first-run latency never depends on a CDN, and fallback never depends on a network fetch.
References
- PRIVYSE repository — github.com/The-Infernix/PRIVYSE
extension/core/perception.ts— WebGPU-first ORT session setupextension/core/vision-host.ts— shared Chrome-offscreen / Firefox-background hostdocs/perception-failure-path-audit.md— the perception failure-path audit- Companion article: Building PRIVYSE: Designing a Privacy-First Browser Agent