Merge feat/parakeet-engine: Parakeet TDT 0.6B v3 English engine (spec 001 workstream 7)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> # Conflicts: # AGENTS.md # bun.lock # docs/Decisions.md # src/lib/models.ts # src/worker/inference.worker.ts # src/worker/protocol.ts # tests/models.test.ts
This commit is contained in:
commit
30969e86b7
29 files changed
+1741
-54
No files matched your search
+89
-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. */
|
||||
@@ -63,8 +86,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];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -90,9 +128,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),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -116,8 +154,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));
|
||||
}
|
||||
|
||||
@@ -209,6 +247,10 @@ function danishRepos(name: string): Pick<WhisperModel, "id" | "fasterWhisperMode
|
||||
// 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",
|
||||
@@ -325,6 +367,47 @@ export const MODELS: readonly WhisperModel[] = [
|
||||
decoderParamsM: 156,
|
||||
defaults: { wasm: DANISH_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,
|
||||
canTranslate: 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 {
|
||||
|
||||
Reference in new issue
Block a user