From f75ae3fbf7e47a2390f76a0329e221a2e5fb17a2 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Wed, 5 Aug 2026 20:17:27 -0700 Subject: [PATCH] [eric] voice: small.en-q5_1 is the accuracy-first default, bundled base serves while it downloads in background --- electron/voice/whisperModels.js | 13 +++++++------ electron/voice/whisperService.js | 8 ++++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/electron/voice/whisperModels.js b/electron/voice/whisperModels.js index 919a39ab..57bf09e5 100644 --- a/electron/voice/whisperModels.js +++ b/electron/voice/whisperModels.js @@ -19,16 +19,17 @@ const crypto = require('crypto'); const MODELS = [ { id: 'tiny.en-q5_1', file: 'ggml-tiny.en-q5_1.bin', label: 'Tiny', note: 'Fastest, but gets lost past ~15s of speech', bytes: 32166155, sha256: 'c77c5766f1cef09b6b7d47f21b546cbddd4157886b3b5d6d4f709e91e66c7c2b' }, { id: 'base.en-q5_1', file: 'ggml-base.en-q5_1.bin', label: 'Base (compact)', note: 'Base quality and speed, 90MB less to download and hold', bytes: 59721011, sha256: '4baf70dd0d7c4247ba2b81fafd9c01005ac77c2f9ef064e00dcf195d0e2fdd2f' }, - { id: 'base.en', file: 'ggml-base.en.bin', label: 'Base', note: 'Balanced, the default', bytes: 147964211, sha256: 'a03779c86df3323075f5e796cb2ce5029f00ec8869eee3fdfb897afe36c6d002' }, - { id: 'small.en-q5_1', file: 'ggml-small.en-q5_1.bin', label: 'Small', note: 'Steadier on accents and noise, ~2.7x slower', bytes: 190098681, sha256: 'bfdff4894dcb76bbf647d56263ea2a96645423f1669176f4844a1bf8e478ad30' }, + { id: 'base.en', file: 'ggml-base.en.bin', label: 'Base', note: 'Fast and light; the instant fallback while Small downloads', bytes: 147964211, sha256: 'a03779c86df3323075f5e796cb2ce5029f00ec8869eee3fdfb897afe36c6d002' }, + { id: 'small.en-q5_1', file: 'ggml-small.en-q5_1.bin', label: 'Small', note: 'Most accurate, the default; steadier on accents and noise', bytes: 190098681, sha256: 'bfdff4894dcb76bbf647d56263ea2a96645423f1669176f4844a1bf8e478ad30' }, ]; // Measured on an M2 (quiet machine, 3.5s/8.1s/26.4s utterances, median of 5): // tiny 174/154/241ms base-q5_1 212/414/656ms base 208/398/734ms small 865/1108/1869ms -// base.en stays the default: quantizing it saves download and RAM but buys no speed on Metal, and -// small costs 2.7x the latency. large-v3-turbo is deliberately absent, it measured both slowest and -// least accurate here (4.1s on a 3.5s clip, and it fell apart on the long one). +// small.en-q5_1 is the default (Eric's call, 2026-08-05: accuracy first): streaming partials hide +// most of its extra decode cost, and the bundled base.en serves instantly while it downloads. +// large-v3-turbo is deliberately absent, it measured both slowest and least accurate here +// (4.1s on a 3.5s clip, and it fell apart on the long one). -const DEFAULT_MODEL_ID = 'base.en'; +const DEFAULT_MODEL_ID = 'small.en-q5_1'; const BASE_URL = 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/'; function modelById(id) { diff --git a/electron/voice/whisperService.js b/electron/voice/whisperService.js index effed7c4..d9f91b18 100644 --- a/electron/voice/whisperService.js +++ b/electron/voice/whisperService.js @@ -194,6 +194,14 @@ async function p_bootServer(resourceDir, userDataDir) { // settled-rejected promise forever so every later call kept throwing "model-downloading" even after // the model finished. Clearing on rejection here lets the next call retry cleanly. async function ensureServer(resourceDir, userDataDir) { + // The accuracy-first default may not be on disk yet: pull it in the background while the bundled + // fallback serves this dictation; the model-switch check below hot-swaps once it lands. Only runs + // when the user actually dictates, so an idle install never silently downloads 190MB. + if (!whisperModels.isInstalled(userDataDir, selectedModelId) + && !(process.env.OPENSWARM_WHISPER_MODEL && fs.existsSync(process.env.OPENSWARM_WHISPER_MODEL)) + && !whisperModels.downloadStatus().downloading) { + whisperModels.downloadModel(userDataDir, selectedModelId); + } // A warm server is only reusable if it holds the file we would load now: a model switch, or the // user's pick finishing its download while a fallback was serving, has to re-boot. if (proc && port && resolveModel(resourceDir, userDataDir) !== loadedModelFile) stopServer();