From a90235862e73f9a54975140fb944d74a83474bd7 Mon Sep 17 00:00:00 2001 From: burak Date: Sat, 19 Sep 2026 15:09:08 +0300 Subject: [PATCH] fix(transcription): compile the GGUF session owner on Linux 2324ee4 moved `OfflineGgufSession` into `src/transcription.rs`, which is `#[cfg(target_os = "macos")]`, while the Linux-only `src/linux_transcriber.rs` imports it, so `main` does not compile for the Linux target: error[E0432]: unresolved import `crate::transcription` --> src/linux_transcriber.rs:8:12 `transcription.rs` cannot be ungated because it depends on the macOS-only `apple_speech` and `parakeet` modules, so the session owner moves to a platform-neutral module that both adapters import. macOS behavior is unchanged. Linux keeps the shared session owner from 2324ee4 instead of the raw `transcribe_cpp::Session` it held before that commit. Verified on x86_64 Linux: `cargo test --locked --bin voice-control` (145 passed), `cargo clippy --locked --bin voice-control --tests -- -D warnings`, `cargo fmt --all --check`. --- src/linux_transcriber.rs | 2 +- src/main.rs | 2 ++ src/offline_gguf.rs | 47 ++++++++++++++++++++++++++++++++++++++++ src/parakeet.rs | 3 ++- src/transcription.rs | 42 ----------------------------------- 5 files changed, 52 insertions(+), 44 deletions(-) create mode 100644 src/offline_gguf.rs diff --git a/src/linux_transcriber.rs b/src/linux_transcriber.rs index ee53c6b..7d165a0 100644 --- a/src/linux_transcriber.rs +++ b/src/linux_transcriber.rs @@ -5,7 +5,7 @@ use std::time::Instant; use color_eyre::eyre::{Result, WrapErr, eyre}; use transcribe_cpp::{Backend, Model, ModelOptions, RunOptions, TimestampKind}; -use crate::transcription::OfflineGgufSession; +use crate::offline_gguf::OfflineGgufSession; use crate::transcription_models::{ ModelDefinition, TranscriptionModelId, TranscriptionSelection, definition, download_with_progress, model_path, validate, diff --git a/src/main.rs b/src/main.rs index db2153b..7f70037 100644 --- a/src/main.rs +++ b/src/main.rs @@ -93,6 +93,8 @@ mod microphone_activity; mod moonshine; #[cfg(all(target_os = "macos", debug_assertions))] mod moonshine_lab; +#[cfg(any(target_os = "macos", target_os = "linux"))] +mod offline_gguf; #[cfg(target_os = "macos")] mod onboarding; #[cfg(target_os = "macos")] diff --git a/src/offline_gguf.rs b/src/offline_gguf.rs new file mode 100644 index 0000000..ee4089b --- /dev/null +++ b/src/offline_gguf.rs @@ -0,0 +1,47 @@ +//! The GGUF inference session owner shared by the macOS and Linux adapters. +//! +//! Both platforms load the same `transcribe-cpp` models, so the session +//! lifecycle rules that keep inference scratch memory bounded belong in one +//! place rather than per platform. + +pub(crate) struct OfflineGgufSession { + model: transcribe_cpp::Model, + session: Option, +} + +impl OfflineGgufSession { + pub(crate) fn new(model: transcribe_cpp::Model) -> transcribe_cpp::Result { + let session = model.session()?; + Ok(Self { + model, + session: Some(session), + }) + } + + pub(crate) fn run( + &mut self, + samples: &[f32], + options: &transcribe_cpp::RunOptions, + ) -> transcribe_cpp::Result { + // transcribe-cpp 0.1.x retains input-sized scheduler buffers for the + // session lifetime. Drop each used session before creating its + // successor so a long inference cannot pin that high-water mark or + // overlap two sessions' persistent decoder state. + let mut session = match self.session.take() { + Some(session) => session, + None => self.model.session()?, + }; + let result = session.run(samples, options); + drop(session); + match self.model.session() { + Ok(session) => { + self.session = Some(session); + result + } + Err(recovery_error) => match result { + Ok(_) => Err(recovery_error), + Err(run_error) => Err(run_error), + }, + } + } +} diff --git a/src/parakeet.rs b/src/parakeet.rs index 7d32dde..f1a1e1a 100644 --- a/src/parakeet.rs +++ b/src/parakeet.rs @@ -18,11 +18,12 @@ use crate::dictation::{DictationClip, DictationProtocol, pad_for_parakeet}; use crate::dictation_processor::ProcessingObservation; use crate::history::{History, HistoryDraft, HistoryKind}; use crate::meeting::{self, TranscriptEntry, TranscriptPublication}; +use crate::offline_gguf::OfflineGgufSession; use crate::paste::{PasteMode, Paster}; use crate::suppression::InputActivity; #[cfg(test)] use crate::text_replacements::ReplacementSet; -use crate::transcription::{OfflineGgufSession, Transcriber, WarmTranscriber}; +use crate::transcription::{Transcriber, WarmTranscriber}; use crate::transcription_models::{ TranscriptionModelId, TranscriptionSelection, model_path, validate, }; diff --git a/src/transcription.rs b/src/transcription.rs index d4aea2b..bb22ad1 100644 --- a/src/transcription.rs +++ b/src/transcription.rs @@ -19,11 +19,6 @@ pub struct WarmTranscriber { active: Option, } -pub(crate) struct OfflineGgufSession { - model: transcribe_cpp::Model, - session: Option, -} - #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TranscriptSegment { @@ -127,43 +122,6 @@ impl WarmTranscriber { } } -impl OfflineGgufSession { - pub(crate) fn new(model: transcribe_cpp::Model) -> transcribe_cpp::Result { - let session = model.session()?; - Ok(Self { - model, - session: Some(session), - }) - } - - pub(crate) fn run( - &mut self, - samples: &[f32], - options: &transcribe_cpp::RunOptions, - ) -> transcribe_cpp::Result { - // transcribe-cpp 0.1.x retains input-sized scheduler buffers for the - // session lifetime. Drop each used session before creating its - // successor so a long inference cannot pin that high-water mark or - // overlap two sessions' persistent decoder state. - let mut session = match self.session.take() { - Some(session) => session, - None => self.model.session()?, - }; - let result = session.run(samples, options); - drop(session); - match self.model.session() { - Ok(session) => { - self.session = Some(session); - result - } - Err(recovery_error) => match result { - Ok(_) => Err(recovery_error), - Err(run_error) => Err(run_error), - }, - } - } -} - #[cfg(test)] mod tests { use super::{Transcript, UNIFIED_ENGLISH_TRAILING_SILENCE_SAMPLES, prepare_gguf_samples};