Add a model family field and the Parakeet v3 catalog entry with exact file sizes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c3a490548a
commit
d554162a2c
2 files changed
+136
-9
No files matched your search
+88
-6
@@ -35,9 +35,32 @@ export interface DtypeDefault {
|
||||
note: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Which inference path a catalog entry runs on (D-WS7a). Whisper models use
|
||||
* the Transformers.js ASR pipeline; Parakeet uses the worker's own TDT path
|
||||
* (src/worker/parakeetEngine.ts). Absent ⇒ "whisper", so the Whisper entries
|
||||
* carry no field at all.
|
||||
*/
|
||||
export type ModelFamily = "whisper" | "parakeet";
|
||||
|
||||
/** Exact weight-file sizes per dtype, for entries whose files we know (D-WS7a). */
|
||||
export interface ExactModelBytes {
|
||||
encoder: Partial<Record<WhisperDtype, number>>;
|
||||
decoder: Partial<Record<WhisperDtype, number>>;
|
||||
/** Tokenizer/vocab/config files. */
|
||||
aux: number;
|
||||
}
|
||||
|
||||
export interface WhisperModel {
|
||||
/** Hugging Face Hub id, e.g. "onnx-community/whisper-tiny.en". */
|
||||
id: string;
|
||||
/** Inference path (D-WS7a); absent ⇒ "whisper". */
|
||||
family?: ModelFamily;
|
||||
/**
|
||||
* Real file sizes; when present they replace the parameter-count estimate
|
||||
* in `estimateDownloadBytes` / `estimatePeakLoadBytes`.
|
||||
*/
|
||||
exactBytes?: ExactModelBytes;
|
||||
/** Human-readable name for the picker. */
|
||||
displayName: string;
|
||||
/** False for `.en` (English-only) variants. */
|
||||
@@ -54,8 +77,23 @@ export interface WhisperModel {
|
||||
* CTranslate2 conversions on the Hub (`Systran/faster-whisper-*`;
|
||||
* large-v3-turbo → `mobiuslabsgmbh/faster-whisper-large-v3-turbo`), so the
|
||||
* pinned library version stays the single authority on the exact repo.
|
||||
* Absent for browser-only families (Parakeet, D-WS7a): the server engine
|
||||
* answers 400 for them and the picker hides them while it is selected.
|
||||
*/
|
||||
fasterWhisperModel: string;
|
||||
fasterWhisperModel?: string;
|
||||
}
|
||||
|
||||
/** The entry's inference path; absent family (or unknown model) ⇒ "whisper". */
|
||||
export function modelFamily(model: WhisperModel | undefined): ModelFamily {
|
||||
return model?.family ?? "whisper";
|
||||
}
|
||||
|
||||
/** Bytes of one part at one dtype: the exact file size when known, else the estimate. */
|
||||
function partBytes(model: WhisperModel, part: "encoder" | "decoder", dtype: WhisperDtype): number {
|
||||
const exact = model.exactBytes?.[part][dtype];
|
||||
if (exact !== undefined) return exact;
|
||||
const params = part === "encoder" ? model.encoderParamsM : model.decoderParamsM;
|
||||
return params * 1e6 * BYTES_PER_PARAM[dtype];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -81,9 +119,9 @@ export function estimateDownloadBytes(
|
||||
const encDtype = typeof dtype === "string" ? dtype : dtype.encoder_model;
|
||||
const decDtype = typeof dtype === "string" ? dtype : dtype.decoder_model_merged;
|
||||
return Math.round(
|
||||
model.encoderParamsM * 1e6 * BYTES_PER_PARAM[encDtype] +
|
||||
model.decoderParamsM * 1e6 * BYTES_PER_PARAM[decDtype] +
|
||||
AUX_FILES_BYTES,
|
||||
partBytes(model, "encoder", encDtype) +
|
||||
partBytes(model, "decoder", decDtype) +
|
||||
(model.exactBytes?.aux ?? AUX_FILES_BYTES),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,8 +145,8 @@ export function formatModelSize(bytes: number): string {
|
||||
export function estimatePeakLoadBytes(model: WhisperModel, dtype: DtypeSpec): number {
|
||||
const encDtype = typeof dtype === "string" ? dtype : dtype.encoder_model;
|
||||
const decDtype = typeof dtype === "string" ? dtype : dtype.decoder_model_merged;
|
||||
const encoderBytes = model.encoderParamsM * 1e6 * BYTES_PER_PARAM[encDtype];
|
||||
const decoderBytes = model.decoderParamsM * 1e6 * BYTES_PER_PARAM[decDtype];
|
||||
const encoderBytes = partBytes(model, "encoder", encDtype);
|
||||
const decoderBytes = partBytes(model, "decoder", decDtype);
|
||||
return Math.round(2 * Math.max(encoderBytes, decoderBytes));
|
||||
}
|
||||
|
||||
@@ -168,6 +206,10 @@ const ACCEL_Q4: DtypeDefault = {
|
||||
// The catalog (D-006: curated, not exhaustive)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Parakeet TDT 0.6B v3 ONNX repo and the revision its file sizes were taken from (D-WS7a). */
|
||||
export const PARAKEET_MODEL_ID = "ysdede/parakeet-tdt-0.6b-v3-onnx";
|
||||
export const PARAKEET_REVISION = "e5356bf6e24bdb7a269536b0e9e7869bc034b893";
|
||||
|
||||
export const MODELS: readonly WhisperModel[] = [
|
||||
{
|
||||
id: "onnx-community/whisper-tiny.en",
|
||||
@@ -259,6 +301,46 @@ export const MODELS: readonly WhisperModel[] = [
|
||||
decoderParamsM: 174,
|
||||
defaults: { wasm: WASM_Q4, webgpu: WEBGPU_FP16_Q4, webnn: WEBNN_FP16 },
|
||||
},
|
||||
{
|
||||
// NVIDIA Parakeet TDT 0.6B v3 (CC-BY-4.0), NeMo→ONNX conversion by
|
||||
// ysdede, run through parakeet.js on our onnxruntime-web (D-WS7a).
|
||||
// English-only in this app (the model itself knows 25 European
|
||||
// languages, but only English is measured here). Desktop-only: its
|
||||
// ~1.3 GB load peak is far past every phone budget (D-027–D-029), so
|
||||
// the picker hides it on mobile instead of just warning.
|
||||
id: PARAKEET_MODEL_ID,
|
||||
family: "parakeet",
|
||||
displayName: "Parakeet v3 (English)",
|
||||
multilingual: false,
|
||||
// Parameter counts only feed fallbacks; the sizes below are exact.
|
||||
encoderParamsM: 609,
|
||||
decoderParamsM: 17,
|
||||
exactBytes: {
|
||||
encoder: { q8: 652_183_999, fp16: 1_238_960_452 },
|
||||
decoder: { q8: 18_202_004, fp16: 36_266_140 },
|
||||
aux: 102_132, // vocab.txt
|
||||
},
|
||||
defaults: {
|
||||
wasm: {
|
||||
dtype: "q8",
|
||||
verified: false,
|
||||
note: "int8 encoder + decoder: the measured WASM path (D-WS7a).",
|
||||
},
|
||||
// Only used when WebGPU is chosen explicitly — "auto" never picks it
|
||||
// for Parakeet (D-WS7a): int8 on WebGPU is reported broken, and fp16
|
||||
// is a separate 1.24 GB download. The decoder stays int8 on WASM.
|
||||
webgpu: {
|
||||
dtype: { encoder_model: "fp16", decoder_model_merged: "q8" },
|
||||
verified: false,
|
||||
note: "fp16 encoder on WebGPU (needs shader-f16) + int8 decoder on WASM.",
|
||||
},
|
||||
webnn: {
|
||||
dtype: "q8",
|
||||
verified: false,
|
||||
note: "No WebNN path: runs the WASM int8 setup.",
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function getModel(id: string): WhisperModel | undefined {
|
||||
|
||||
+48
-3
@@ -9,6 +9,8 @@ import {
|
||||
formatModelSize,
|
||||
getFasterWhisperModel,
|
||||
getModel,
|
||||
modelFamily,
|
||||
PARAKEET_MODEL_ID,
|
||||
resolveDefaultDtype,
|
||||
} from "../src/lib/models";
|
||||
|
||||
@@ -19,6 +21,8 @@ function stripRepoDecorations(id: string): string {
|
||||
return id.replace(/(_timestamped|-ONNX)$/, "");
|
||||
}
|
||||
|
||||
const WHISPER_MODELS = MODELS.filter((model) => modelFamily(model) === "whisper");
|
||||
|
||||
describe("MODELS catalog", () => {
|
||||
test("contains the curated D-006 lineup from the onnx-community org", () => {
|
||||
const ids = MODELS.map((model) => model.id);
|
||||
@@ -32,12 +36,15 @@ describe("MODELS catalog", () => {
|
||||
"onnx-community/whisper-medium.en_timestamped",
|
||||
"onnx-community/whisper-medium-ONNX",
|
||||
"onnx-community/whisper-large-v3-turbo",
|
||||
// Not Whisper: the D-WS7a Parakeet engine path, last so it never
|
||||
// becomes anyone's accidental default.
|
||||
"ysdede/parakeet-tdt-0.6b-v3-onnx",
|
||||
]);
|
||||
expect(new Set(ids).size).toBe(ids.length);
|
||||
});
|
||||
|
||||
test(".en variants are English-only; the rest are multilingual", () => {
|
||||
for (const model of MODELS) {
|
||||
for (const model of WHISPER_MODELS) {
|
||||
// Hub repo names may carry decorations (e.g. "_timestamped", "-ONNX")
|
||||
// after the size suffix — judge English-ness on the undecorated name.
|
||||
expect(model.multilingual).toBe(!stripRepoDecorations(model.id).endsWith(".en"));
|
||||
@@ -69,8 +76,8 @@ describe("MODELS catalog", () => {
|
||||
expect(getModel("onnx-community/whisper-nonexistent")).toBeUndefined();
|
||||
});
|
||||
|
||||
test("every model maps to a faster-whisper name for the server engine (D-013)", () => {
|
||||
for (const model of MODELS) {
|
||||
test("every Whisper model maps to a faster-whisper name for the server engine (D-013)", () => {
|
||||
for (const model of WHISPER_MODELS) {
|
||||
// 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
|
||||
@@ -209,3 +216,41 @@ describe("WHISPER_LANGUAGES", () => {
|
||||
expect(WHISPER_LANGUAGES.some((entry) => entry.code === "auto")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Parakeet catalog entry (D-WS7a)", () => {
|
||||
const parakeet = getModel(PARAKEET_MODEL_ID)!;
|
||||
|
||||
test("family defaults to whisper when absent", () => {
|
||||
expect(modelFamily(getModel(DEFAULT_MODEL_ID))).toBe("whisper");
|
||||
expect(modelFamily(undefined)).toBe("whisper");
|
||||
expect(modelFamily(parakeet)).toBe("parakeet");
|
||||
});
|
||||
|
||||
test("is English-only and browser-only (no faster-whisper name)", () => {
|
||||
expect(parakeet.multilingual).toBe(false);
|
||||
expect(parakeet.fasterWhisperModel).toBeUndefined();
|
||||
expect(getFasterWhisperModel(PARAKEET_MODEL_ID)).toBeUndefined();
|
||||
});
|
||||
|
||||
test("WASM runs int8 for both parts; WebGPU is fp16 encoder + int8 decoder", () => {
|
||||
expect(resolveDefaultDtype(PARAKEET_MODEL_ID, "wasm")).toBe("q8");
|
||||
expect(resolveDefaultDtype(PARAKEET_MODEL_ID, "webgpu")).toEqual({
|
||||
encoder_model: "fp16",
|
||||
decoder_model_merged: "q8",
|
||||
});
|
||||
});
|
||||
|
||||
test("download size is the real file sizes, not a parameter estimate", () => {
|
||||
expect(estimateDefaultDownloadBytes(parakeet, "wasm")).toBe(
|
||||
652_183_999 + 18_202_004 + 102_132,
|
||||
);
|
||||
expect(estimateDefaultDownloadBytes(parakeet, "webgpu")).toBe(
|
||||
1_238_960_452 + 18_202_004 + 102_132,
|
||||
);
|
||||
expect(formatModelSize(estimateDefaultDownloadBytes(parakeet, "wasm"))).toBe("670 MB");
|
||||
});
|
||||
|
||||
test("peak load memory is twice the larger weight file", () => {
|
||||
expect(estimatePeakLoadBytes(parakeet, "q8")).toBe(2 * 652_183_999);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user