diff --git a/docs/Decisions.md b/docs/Decisions.md index d347f84..6d61c24 100644 --- a/docs/Decisions.md +++ b/docs/Decisions.md @@ -325,3 +325,34 @@ on both engines. Three independent causes, all length-dependent: **Revisit:** when Transformers.js changes the seek-loop condition (currently `return_timestamps && !max_new_tokens`, `src/models/whisper/modeling_whisper.js`), retest whether the explicit `max_new_tokens` is still needed. + +## D-022 — Medium models never default to fp16 in the browser +**Status:** Accepted (2026-07-15) +Reported symptom: transcribing `en_aiproblem` in-browser with Whisper Medium +printed just "the". The medium catalog entries defaulted to fp16 on WebNN +(whole model) and WebGPU (encoder) — and D-017's "auto" order now tries WebNN +first, so browsers exposing `navigator.ml` silently moved medium onto fp16. +Two problems with those fp16 conversions +(`onnx-community/whisper-medium(.en_timestamped|-ONNX)`): +1. The fp16 ONNX files crash onnxruntime's graph optimizer at session + creation (`SimplifiedLayerNormFusion` fails on their + `InsertedPrecisionFreeCast_*` nodes) — reproduced on both onnxruntime-node + and onnxruntime-web 1.26.0-dev (wasm EP). EPs whose optimizer pipeline + skips that fusion can load the model and hit problem 2 instead. +2. Whisper medium/large encoder activations are the classic fp16 overflow + case — degenerate output with no error raised. +Meanwhile q4 medium was verified correct end-to-end on the real browser +runtime (onnxruntime-web wasm EP driven under Bun via the +`Symbol.for("onnxruntime")` override, `graphOptimizationLevel: "basic"` as in +D-014, `max_new_tokens: 440` as in D-021; both the `.en` and multilingual +conversions, auto language detect included). **Fix:** medium's `webgpu` and +`webnn` defaults are q4 (`ACCEL_Q4` in `src/lib/models.ts`). A backend that +cannot execute q4 (`MatMulNBits`) throws at pipeline creation and falls down +the D-017 device chain — strictly better than silently emitting garbage. +**Scope:** medium only — that's where load-failure evidence exists. +tiny/base/small keep fp16 on WebNN and small keeps fp16+q4 on WebGPU; +large-v3-turbo keeps fp16+q4 on WebGPU (the widely-used Transformers.js demo +configuration). If a one-word/garbage transcript ever shows up on those, suspect +their fp16 encoders and extend this decision. +**Revisit:** if the onnx-community medium conversions are re-exported with +working fp16 files, or once WebNN/WebGPU runs can be exercised in CI. diff --git a/src/lib/models.ts b/src/lib/models.ts index b4133a7..5848458 100644 --- a/src/lib/models.ts +++ b/src/lib/models.ts @@ -128,6 +128,23 @@ const WEBNN_FP16: DtypeDefault = { note: "fp16 is commonly used with WebNN.", }; +/** + * The medium fp16 conversions are unusable (D-022, verified 2026-07-15): the + * ONNX files crash onnxruntime's graph optimizer (SimplifiedLayerNormFusion + * chokes on their InsertedPrecisionFreeCast nodes — reproduced on both + * ort-node and ort-web 1.26.0-dev), and Whisper medium/large encoder + * activations are the classic fp16 overflow case besides. In the browser the + * symptom was a degenerate one-word transcript with no error. q4 is verified + * correct for medium on the WASM runtime; a backend that cannot run it + * throws at session creation and falls down the D-017 device chain instead + * of silently producing garbage. + */ +const ACCEL_Q4: DtypeDefault = { + dtype: "q4", + verified: false, + note: "q4: the medium fp16 conversions fail to load / overflow (D-022).", +}; + // --------------------------------------------------------------------------- // The catalog (D-006: curated, not exhaustive) // --------------------------------------------------------------------------- @@ -203,7 +220,7 @@ export const MODELS: readonly WhisperModel[] = [ multilingual: false, encoderParamsM: 306, decoderParamsM: 463, - defaults: { wasm: WASM_Q4, webgpu: WEBGPU_FP16_Q4, webnn: WEBNN_FP16 }, + defaults: { wasm: WASM_Q4, webgpu: ACCEL_Q4, webnn: ACCEL_Q4 }, }, { id: "onnx-community/whisper-medium-ONNX", @@ -212,7 +229,7 @@ export const MODELS: readonly WhisperModel[] = [ multilingual: true, encoderParamsM: 306, decoderParamsM: 463, - defaults: { wasm: WASM_Q4, webgpu: WEBGPU_FP16_Q4, webnn: WEBNN_FP16 }, + defaults: { wasm: WASM_Q4, webgpu: ACCEL_Q4, webnn: ACCEL_Q4 }, }, { id: "onnx-community/whisper-large-v3-turbo", diff --git a/tests/models.test.ts b/tests/models.test.ts index 45de3f2..9be0f19 100644 --- a/tests/models.test.ts +++ b/tests/models.test.ts @@ -107,6 +107,17 @@ describe("resolveDefaultDtype", () => { ).toEqual({ encoder_model: "fp16", decoder_model_merged: "q4" }); }); + test("medium never defaults to fp16 on any backend (D-022: broken conversions)", () => { + for (const id of [ + "onnx-community/whisper-medium.en_timestamped", + "onnx-community/whisper-medium-ONNX", + ]) { + for (const device of ["wasm", "webgpu", "webnn"] as const) { + expect(resolveDefaultDtype(id, device)).toBe("q4"); + } + } + }); + test("unknown model ids fall back to q8", () => { expect(resolveDefaultDtype("someone/some-model", "wasm")).toBe("q8"); });