Prefer WASM on firefox cause WebGPU is incredibly slow there.
This commit is contained in:
1 parent
74858f63fc
commit
f6019b3a42
7 files changed
+486
-27
No files matched your search
@@ -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.
|
||||
Reference in new issue
Block a user