diff --git a/docs/Decisions.md b/docs/Decisions.md index 5dc8f08..ce27729 100644 --- a/docs/Decisions.md +++ b/docs/Decisions.md @@ -1224,3 +1224,19 @@ whole-file output is equally bad, which is exactly what the absolute bar exists to catch. **Consequence:** full `bun test` ≈ 19 min on CPU (en_alien ≈ 20 min of it on a cold run); the fast suite excludes this file. + +## D-WS6a — Danish fine-tuned Whisper models: our own ONNX + CTranslate2 conversions, unpublished until the owner says go +**Status:** Proposed (2026-10-29, spec 001 Goal 6) +**Candidates checked (licence + architecture, from each repo's model card, config and notices):** +- `thorhojhus/whisper-small-danish` — MIT; plain `WhisperForConditionalGeneration`, whisper-small + shapes and tokenizer unchanged (80 mel bins, 12/12 layers, vocab 51865). **Kept.** +- `thorhojhus/whisper-large-v3-turbo-danish` — MIT; plain large-v3-turbo (128 mel, 32/4 layers, + vocab 51866). **Kept.** +- `danish-foundation-models/edda-v0.2` — **Apache-2.0 confirmed** (licensor: the Alexandra + Institute; base model MIT); plain large-v3-turbo architecture, so Transformers.js loads it like the + stock turbo. Its `generation_config.json` pins `language: "da"`, `task: "transcribe"` and + `num_beams: 5`. **Kept.** +**Catalog:** three entries with `verified: false` everywhere, ids built from one exported constant +`DANISH_MODELS_HF_OWNER` (placeholder `"PENDING-OWNER"`): `/-ONNX` for the browser +engine and `/-ct2-int8` as `fasterWhisperModel` (a Hub repo id — faster-whisper accepts +those as well as its aliases). Publishing under the owner's account is a one-line change. diff --git a/src/lib/models.ts b/src/lib/models.ts index ad82c0c..06e900b 100644 --- a/src/lib/models.ts +++ b/src/lib/models.ts @@ -164,6 +164,27 @@ const ACCEL_Q4: DtypeDefault = { note: "q4: the medium fp16 conversions fail to load / overflow (D-022).", }; +// --------------------------------------------------------------------------- +// Danish fine-tunes (D-WS6a) +// --------------------------------------------------------------------------- + +/** + * Hugging Face account that hosts our conversions of the Danish Whisper + * fine-tunes (D-WS6a). Both the ONNX repos (browser engine) and the + * CTranslate2 int8 repos (server engine) hang off this one value, so + * publishing under the owner's account is a one-line change here. Until the + * owner confirms the account and the repos exist, it stays a placeholder. + */ +export const DANISH_MODELS_HF_OWNER = "PENDING-OWNER"; + +/** Catalog id + matching faster-whisper repo for one published conversion. */ +function danishRepos(name: string): Pick { + return { + id: `${DANISH_MODELS_HF_OWNER}/${name}-ONNX`, + fasterWhisperModel: `${DANISH_MODELS_HF_OWNER}/${name}-ct2-int8`, + }; +} + // --------------------------------------------------------------------------- // The catalog (D-006: curated, not exhaustive) // --------------------------------------------------------------------------- @@ -259,6 +280,37 @@ export const MODELS: readonly WhisperModel[] = [ decoderParamsM: 174, defaults: { wasm: WASM_Q4, webgpu: WEBGPU_FP16_Q4, webnn: WEBNN_FP16 }, }, + // Danish fine-tunes (D-WS6a): unchanged Whisper architectures, so the + // parameter counts are their base models'. Conversions by us, weights by + // the original authors (credited in each published repo's model card). + { + // thorhojhus/whisper-small-danish (MIT), fine-tune of whisper-small. + ...danishRepos("whisper-small-danish"), + displayName: "Whisper Small (Danish)", + multilingual: true, + encoderParamsM: 88, + decoderParamsM: 156, + defaults: { wasm: WASM_Q8, webgpu: WEBGPU_FP16_Q4, webnn: WEBNN_FP16 }, + }, + { + // thorhojhus/whisper-large-v3-turbo-danish (MIT). + ...danishRepos("whisper-large-v3-turbo-danish"), + displayName: "Whisper Large v3 Turbo (Danish)", + multilingual: true, + encoderParamsM: 635, + decoderParamsM: 174, + defaults: { wasm: WASM_Q4, webgpu: WEBGPU_FP16_Q4, webnn: WEBNN_FP16 }, + }, + { + // danish-foundation-models/edda-v0.2 (Apache-2.0, Alexandra Institute), + // fine-tune of whisper-large-v3-turbo. + ...danishRepos("edda-v0.2"), + displayName: "Edda v0.2 (Danish)", + multilingual: true, + encoderParamsM: 635, + decoderParamsM: 174, + defaults: { wasm: WASM_Q4, webgpu: WEBGPU_FP16_Q4, webnn: WEBNN_FP16 }, + }, ]; export function getModel(id: string): WhisperModel | undefined { diff --git a/tests/models.test.ts b/tests/models.test.ts index 1ef20c3..79d89ea 100644 --- a/tests/models.test.ts +++ b/tests/models.test.ts @@ -1,5 +1,6 @@ import { describe, test, expect } from "bun:test"; import { + DANISH_MODELS_HF_OWNER, DEFAULT_MODEL_ID, MODELS, WHISPER_LANGUAGES, @@ -32,6 +33,9 @@ describe("MODELS catalog", () => { "onnx-community/whisper-medium.en_timestamped", "onnx-community/whisper-medium-ONNX", "onnx-community/whisper-large-v3-turbo", + `${DANISH_MODELS_HF_OWNER}/whisper-small-danish-ONNX`, + `${DANISH_MODELS_HF_OWNER}/whisper-large-v3-turbo-danish-ONNX`, + `${DANISH_MODELS_HF_OWNER}/edda-v0.2-ONNX`, ]); expect(new Set(ids).size).toBe(ids.length); }); @@ -70,7 +74,9 @@ describe("MODELS catalog", () => { }); test("every model maps to a faster-whisper name for the server engine (D-013)", () => { - for (const model of MODELS) { + // The Danish conversions (D-WS6a) have no faster-whisper alias; their + // server-engine name is a Hub repo id — covered by the Danish suite below. + for (const model of MODELS.filter((m) => !m.id.startsWith(`${DANISH_MODELS_HF_OWNER}/`))) { // The onnx-community ids and faster-whisper's aliases share the same // size suffix ("tiny.en", …, "large-v3-turbo") once repo-name // decorations ("_timestamped", "-ONNX") are stripped; keeping them @@ -86,6 +92,51 @@ describe("MODELS catalog", () => { }); }); +describe("Danish fine-tune conversions (D-WS6a)", () => { + const owner = `${DANISH_MODELS_HF_OWNER}/`; + const danish = MODELS.filter((model) => model.id.startsWith(owner)); + + test("are published under one owner constant, still a placeholder until the owner confirms", () => { + expect(DANISH_MODELS_HF_OWNER).toBe("PENDING-OWNER"); + expect(danish.map((model) => model.id)).toEqual([ + `${owner}whisper-small-danish-ONNX`, + `${owner}whisper-large-v3-turbo-danish-ONNX`, + `${owner}edda-v0.2-ONNX`, + ]); + }); + + test("point the server engine at the matching CTranslate2 int8 conversion under the same owner", () => { + for (const model of danish) { + const ct2 = model.id.replace(/-ONNX$/, "-ct2-int8"); + expect(model.fasterWhisperModel).toBe(ct2); + expect(getFasterWhisperModel(model.id)).toBe(ct2); + } + }); + + test("are multilingual (language/task are sent) and unverified on every device", () => { + for (const model of danish) { + expect(model.multilingual).toBe(true); + for (const device of ["wasm", "webgpu", "webnn"] as const) { + expect(model.defaults[device].verified).toBe(false); + } + } + }); + + test("share their base model's parameter counts (unchanged architecture)", () => { + const small = getModel("onnx-community/whisper-small")!; + const turbo = getModel("onnx-community/whisper-large-v3-turbo")!; + const [smallDa, turboDa, edda] = danish; + for (const [model, base] of [ + [smallDa!, small], + [turboDa!, turbo], + [edda!, turbo], + ] as const) { + expect(model.encoderParamsM).toBe(base.encoderParamsM); + expect(model.decoderParamsM).toBe(base.decoderParamsM); + } + }); +}); + describe("resolveDefaultDtype", () => { test("WASM defaults: q8 for small models, q4 for large ones", () => { expect(resolveDefaultDtype("onnx-community/whisper-tiny.en", "wasm")).toBe("q8");