Prefer WASM on firefox cause WebGPU is incredibly slow there.

This commit is contained in:
Lucas Winther
2026-07-17 16:35:49 +02:00
parent 74858f63fc
commit f6019b3a42
7 changed files with 486 additions and 27 deletions
+7 -1
View File
@@ -118,7 +118,13 @@ are the remaining M5 work; see `WorkBreakdown.md`.)
`navigator.ml` (WebNN) first, then `navigator.gpu?.requestAdapter()` (WebGPU),
and picks in priority order WebNN → WebGPU → WASM; if pipeline creation on the
picked device throws, it retries down the same chain (WASM always last). An
explicit `"webnn"`/`"webgpu"` preference falls back straight to WASM. It then
explicit `"webnn"`/`"webgpu"` preference falls back straight to WASM.
Two D-025 refinements: WebGPU is skipped up front when the dtype it would
use needs fp16 but the adapter lacks `shader-f16` (Firefox), and a WASM
fallback *after* a failed accelerator attempt does not run in-place — the
failed attempt pinned ort's thread pool at 1 — but posts `load-restart`,
and the UI respawns the worker with an explicit `wasm` load (transcribe
posts wait on a ready gate in `App.tsx`, so none are lost). It then
resolves the catalog's dtype default for the device actually used and creates
the Transformers.js `automatic-speech-recognition` pipeline.
Download progress events are forwarded to the UI; the terminal
+102
View File
@@ -437,3 +437,105 @@ start's region. Covered by `tests/live.test.ts` (batching + cap) and the WER
gates (`tests/live-accuracy.test.ts`, `tests/live-real-model.test.ts` — the
real-model test's fast feed makes real inference lag, so merging is exercised
there with real weights).
## D-025 — A degraded-thread WASM fallback restarts the worker; WebGPU is skipped when fp16 needs an absent shader-f16
**Status:** Accepted (2026-07-17)
Reported symptom: Firefox performance far below Chrome, live transcription
worst. Chrome resolves D-017's auto chain to an accelerator; Firefox has no
WebNN and its WebGPU (where exposed at all) fails onnxruntime-web's
Chromium-built JSEP EP — notably it lacks the `shader-f16` feature, which the
catalog's fp16-encoder WebGPU defaults (small, large-v3-turbo) need. So
Firefox lands on WASM — but *how* it lands there was the real bug: the failed
WebGPU attempt initializes ort's WASM runtime at the accelerator-safe
`numThreads: 1` (D-023 amendment), the thread pool is fixed at first init,
and the fallback session then runs **single-threaded for the worker's whole
life** (measured 5.5×: 52 s vs 9.5 s on a 14 s clip). Live never respawns the
worker, and every live pass pads to a full 30 s window (D-024), so passes
took ~40–60 s: tentative text froze and the stop drain ballooned — D-023
amendment's "correct over fast" edge case is Firefox's *default* path.
**Fix (three parts):**
1. **Restart instead of a degraded fallback.** When `createPipeline` reaches
the `wasm` attempt after any accelerator attempt ran and multithreading is
actually available (`desiredWasmThreads() > 1`), the worker posts
`{type:"load-restart", modelId, preference, device:"wasm"}` and aborts the
load (sentinel error, never surfaced). The UI terminates the worker,
respawns it, re-issues the load with the explicit device, and remembers
`modelId::preference → device` for the session so later loads skip the
doomed attempt (session-only: a browser update may fix the backend; an
explicit user backend choice is a different key, so it retries once).
To make the respawn safe, transcribe posts now wait on a **load gate** in
`App.tsx` that resolves on `ready` (rejects on load error/cancel/crash):
no pass is ever queued inside a worker a restart is about to terminate —
its transferred audio buffer would die with it. The worker serialized
passes on the load anyway, so nothing starts later than before.
2. **shader-f16 probe.** The worker's WebGPU probe also records
`adapter.features.has("shader-f16")`; when the dtype that a WebGPU attempt
would use requires fp16 (`dtypeRequiresF16`, pure + unit-tested) and the
feature is absent, WebGPU is skipped outright — Firefox then loads
straight into multithreaded WASM with no wasted attempt.
3. **Live-on-WASM hint.** With live active on `wasm` and a heavy model
(encoder ≥ 300 M params: medium, large-v3-turbo), the UI says the model
runs slower than real time and suggests a smaller one — WASM lag there is
inherent (D-024's fixed window cost), not a bug, so tell the user instead
of looking frozen.
**Alternatives rejected:** re-posting in-flight passes after respawn (their
buffers are transferred — gone with the worker); a scout worker pre-flighting
the accelerator (doubles accelerated-path load time to save the failure
path); ort-web's proxy-worker mode to escape the pinned pool (undocumented
interplay with JSEP).
**Consequence:** the accelerator-failure path costs one worker respawn + a
pipeline reload from cache (a few seconds, once per model+preference per
session). A genuine both-attempts-fail load still reports its error
normally. `deviceAttempts` is unchanged; the new policy pieces
(`dtypeRequiresF16`, the `load-restart` message guard) are unit-tested in
`tests/protocol.test.ts`. Real-Firefox verification (badge shows WASM with
full threads, live keeps up on small models) still needs a manual run — same
no-browser dev-env caveat as M2/M3.
**Amendment (2026-07-17, same day — field report: tiny fixed, medium still
slow in Firefox):** three observability/robustness additions to tell "slow is
the CPU-vs-GPU physics" apart from "something is still degraded":
1. The probe **skips software (fallback) WebGPU adapters**
(`isFallbackAdapter`, both spec locations) — SwiftShader/llvmpipe/WARP
would *succeed* at session creation and then run slower than multithreaded
WASM, and the fallback chain only reacts to failures, not slowness.
onnxruntime-web itself uses whatever adapter it is handed (verified in
1.26.0-dev: no fallback-adapter or Firefox guard).
2. The `ready` message now carries **`threads`** — read back from
`env.backends.onnx.wasm.numThreads` *after* session creation, so ort's own
clamp-to-1 (missing isolation) is visible. The footer shows it:
"WASM (on-device, 11 threads)".
3. The worker **logs the device chain** (`[whisper] …` console lines): each
skipped backend (software adapter, missing shader-f16), each failed
attempt with its error, and the final device+threads — previously fallback
errors were swallowed silently, making field reports unactionable.
Expected healthy Firefox numbers for medium q4 on WASM (from the D-023/D-024
measurements): ~4 s per 30 s window at 12 threads, ~9–12 s at 4 — i.e. a
14 s clip in ~10 s. Chromium's GPU path is a further ~3–5× faster; that
remaining gap is inherent until Firefox's WebGPU works with ort-web's JSEP
EP. If the badge shows 1 thread or the console shows a surviving software
adapter, it is a bug in this decision's scope instead.
## D-026 — "Auto" never picks WebGPU on Firefox
**Status:** Accepted (2026-07-17)
Field-verified on Firefox (D-025's observability build): explicit
Backend = WASM runs medium with a full thread pool and healthy speed, while
explicit Backend = WebGPU (`device=webgpu wasmThreads=1`, correct per D-023)
**completes but runs far slower** than the same machine's multithreaded WASM.
That is the worst case for D-017's failure-driven fallback chain: ort-web's
WebGPU EP (Chromium-developed; no Gecko handling in 1.26.0-dev, verified)
creates the session *successfully* on Firefox, so nothing ever falls back —
"auto" happily selects the slowest available backend.
**Fix:** the worker's capability probe skips the WebGPU probe entirely when
the preference is `"auto"` and the UA is Firefox (`/Firefox\//` on
`navigator.userAgent`), with a `[whisper]` console line saying why. Firefox
auto therefore resolves straight to multithreaded WASM (no accelerator
attempt → no D-025 restart either). An **explicit** WebGPU selection is still
honored — that is the deliberate retest hatch: when a future
Transformers.js/ort-web bump gains real Gecko WebGPU support, verify via the
explicit path, then delete the UA check.
**Why UA sniffing:** there is no feature to detect — the sessions create and
run; only throughput distinguishes them, and a benchmark probe would cost
more than it saves. The check is one line, logged, scoped to `"auto"`, and
targets the one engine with field evidence.
**Revisit:** on any onnxruntime-web bump — retest explicit WebGPU on Firefox
(file `tests/live.md` note) and drop the check when it reaches parity.
+8
View File
@@ -247,6 +247,14 @@ Notes (M5):
transcription start (file drop / live start) or via an explicit
per-model Download button in `ModelPicker` — and never when the engine
toggle is set to "server".
- [x] Firefox performance: never run the WASM fallback single-threaded after
a failed accelerator attempt (worker posts `load-restart`, UI respawns
with explicit wasm — D-025); skip WebGPU when fp16 weights need the
absent `shader-f16` feature; hint that medium/large lag in live mode on
WASM. Field-verified: WASM now gets the full thread pool; ort-web's
WebGPU EP on Firefox completes but runs far slower than WASM, so
"auto" skips WebGPU on Firefox (D-026, explicit selection still
honored — retest on ort bumps).
- [ ] Basic a11y pass (keyboard, focus, aria on progress/live transcript)
- [ ] Production build + `bun start` deployment check; README for deployers