diff --git a/src-tauri/src/commands/models.rs b/src-tauri/src/commands/models.rs index 0ee3b1a..2b644e4 100644 --- a/src-tauri/src/commands/models.rs +++ b/src-tauri/src/commands/models.rs @@ -4,9 +4,10 @@ use serde::Serialize; use tauri::Emitter; use crate::AppState; +use kon_core::constants::WHISPER_SAMPLE_RATE; use kon_core::hardware::{self, CpuFeatures}; use kon_core::model_registry::{self, Engine, LanguageSupport, ModelEntry}; -use kon_core::types::ModelId; +use kon_core::types::{AudioSamples, ModelId, TranscriptionOptions}; use kon_transcription::model_manager; use kon_transcription::{load_parakeet, load_whisper, LocalEngine}; @@ -179,6 +180,20 @@ pub fn prewarm_default_model(whisper_engine: Arc) { let result = tauri::async_runtime::spawn_blocking(move || { load_model_from_disk(&model_id).map(|model| { whisper_engine.load(model, model_id); + // Silent warm-up pass: feed one second of silence through + // the freshly-loaded engine. Pre-allocates the Whisper + // context window + warms GPU shader caches so the user's + // first real transcription completes in ≤1.5× steady-state + // latency instead of the ~4–5s cold-start documented in + // ufal/whisper_streaming #96 and #135. Silence returns + // empty segments — the *work* is the context allocation. + let silence = + AudioSamples::mono_16khz(vec![0.0_f32; WHISPER_SAMPLE_RATE as usize]); + let options = TranscriptionOptions::default(); + match whisper_engine.transcribe_sync(&silence, &options) { + Ok(_) => eprintln!("[startup] Whisper warm-up inference complete"), + Err(e) => eprintln!("[startup] Whisper warm-up inference failed: {e}"), + } }) }) .await;