Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ flash_attn = true
threads = 0
# How long the hidden ASR worker stays warm without requests (0 = never expire)
idle_timeout_ms = 120000
# Custom vocabulary injected as the whisper initial_prompt to bias decoding
# (whisper_cpp backend only).
vocabulary = []

[postprocess]
# "raw" (default), "rewrite", or "legacy_basic" for deprecated cleanup configs
Expand Down
4 changes: 3 additions & 1 deletion src/asr_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,8 @@ mod tests {
fn large_v3_turbo_is_marked_recommended() {
let info = find_model("large-v3-turbo").expect("turbo info");
assert_eq!(info.support_tier, SupportTier::Recommended);
assert!(setup_label(info).contains("[Recommended ]"));
// setup_label embeds ANSI styling (tier_token); strip it so the
// assertion holds on color-forced terminals (CLICOLOR_FORCE=1).
assert!(console::strip_ansi_codes(&setup_label(info)).contains("[Recommended ]"));
}
}
3 changes: 3 additions & 0 deletions src/config/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ flash_attn = true
threads = 0
# How long the hidden ASR worker stays warm without requests (0 = never expire)
idle_timeout_ms = 120000
# Custom vocabulary injected as the whisper initial_prompt to bias decoding
# (whisper_cpp backend only).
vocabulary = []

[postprocess]
# "raw" (default), "rewrite", or "legacy_basic" for deprecated cleanup configs
Expand Down
3 changes: 3 additions & 0 deletions src/config/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ pub struct TranscriptionConfig {
pub flash_attn: bool,
pub threads: u16,
pub idle_timeout_ms: u64,
/// Custom vocabulary injected as the whisper initial_prompt to bias decoding.
pub vocabulary: Vec<String>,
}

#[derive(Debug, Clone, Deserialize)]
Expand Down Expand Up @@ -261,6 +263,7 @@ impl Default for TranscriptionConfig {
flash_attn: true,
threads: 0,
idle_timeout_ms: 120000,
vocabulary: Vec::new(),
}
}
}
Expand Down
29 changes: 27 additions & 2 deletions src/transcribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub struct WhisperLocal {
ctx: WhisperContext,
language: String,
threads: i32,
initial_prompt: String,
}

impl WhisperLocal {
Expand All @@ -77,14 +78,34 @@ impl WhisperLocal {

tracing::info!("loading whisper model from {}", model_path.display());

let initial_prompt = config
.vocabulary
.iter()
.map(|word| word.trim())
.filter(|word| !word.is_empty())
.collect::<Vec<_>>()
.join(", ");

// whisper.cpp Vulkan flash-attention mis-decodes (immediate EOT, empty
// output) whenever a KV prefill from initial_prompt is present; repro'd
// on RADV RDNA4 with vendored whisper.cpp. Skip FA when biasing is active.
let flash_attn = config.flash_attn && initial_prompt.is_empty();

let mut ctx_params = WhisperContextParameters::default();
ctx_params.use_gpu(config.use_gpu);
ctx_params.flash_attn(config.use_gpu && config.flash_attn);
ctx_params.flash_attn(config.use_gpu && flash_attn);
tracing::info!(
"whisper acceleration: use_gpu={}, flash_attn={}",
config.use_gpu,
config.use_gpu && config.flash_attn
config.use_gpu && flash_attn
);
if !initial_prompt.is_empty() {
tracing::info!(
"whisper vocabulary prompt: {} terms (flash_attn forced off: {})",
config.vocabulary.len(),
config.flash_attn
);
}

let model_path_str = model_path.to_str().ok_or_else(|| {
WhsprError::Transcription(format!(
Expand Down Expand Up @@ -117,6 +138,7 @@ impl WhisperLocal {
ctx,
language: config.language.clone(),
threads,
initial_prompt,
})
}
}
Expand Down Expand Up @@ -220,6 +242,9 @@ impl WhisperLocal {
params.set_suppress_blank(true);
params.set_suppress_nst(true);
params.set_n_threads(self.threads);
if !self.initial_prompt.is_empty() {
params.set_initial_prompt(&self.initial_prompt);
}

let mut state = self.ctx.create_state().map_err(|e| {
WhsprError::Transcription(format!("failed to create whisper state: {e}"))
Expand Down
8 changes: 4 additions & 4 deletions src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ impl Theme for ConfirmTheme {
write!(
f,
"{} {} ",
&self.base.prompt_prefix,
self.base.prompt_prefix,
self.base.prompt_style.apply_to(prompt)
)?;
}
Expand All @@ -220,7 +220,7 @@ impl Theme for ConfirmTheme {
brackets.apply_to("/"),
no,
brackets.apply_to("]"),
&self.base.prompt_suffix
self.base.prompt_suffix
),
Some(true) => write!(
f,
Expand All @@ -230,7 +230,7 @@ impl Theme for ConfirmTheme {
brackets.apply_to("/"),
no,
brackets.apply_to("]"),
&self.base.prompt_suffix,
self.base.prompt_suffix,
self.base.defaults_style.apply_to("yes")
),
Some(false) => write!(
Expand All @@ -241,7 +241,7 @@ impl Theme for ConfirmTheme {
brackets.apply_to("/"),
no_default,
brackets.apply_to("]"),
&self.base.prompt_suffix,
self.base.prompt_suffix,
self.base.defaults_style.apply_to("no")
),
}
Expand Down
16 changes: 15 additions & 1 deletion vendor/whisper-rs-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,21 @@ fn main() {
});
}

if env::var("WHISPER_FORCE_GENERATE_BINDINGS").is_ok() || cfg!(feature = "vulkan") {
// Bindgen output is untrustworthy on modern libclang: with clang >= 22
// (23.1.3 reproduces here), bindgen cannot lay out whisper_full_params
// (nested anonymous structs) and emits an opaque `{ _address: u8 }` stub,
// which corrupts the params ABI passed to whisper_full. See e.g.
// https://github.com/Zackriya-Solutions/meetily/issues/428. The bundled
// bindings (src/bindings.rs) are complete and already include the
// ggml_backend_vk_* symbols from ggml-vulkan.h, so vulkan builds use
// them directly instead of regenerating at build time.
//
// To regenerate the bundled bindings (requires libclang < 22, e.g. 18):
// LIBCLANG_PATH=<clang18> WHISPER_FORCE_GENERATE_BINDINGS=1 \
// cargo check --no-default-features --features vulkan
// then copy OUT_DIR/bindings.rs over src/bindings.rs. Verify the diff
// only contains expected header changes.
if env::var("WHISPER_FORCE_GENERATE_BINDINGS").is_ok() {
let bindings = bindgen::Builder::default().header("wrapper.h");

#[cfg(feature = "metal")]
Expand Down
48 changes: 44 additions & 4 deletions vendor/whisper-rs-sys/src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,15 @@ pub const __USE_ISOC99: u32 = 1;
pub const __USE_ISOC95: u32 = 1;
pub const __USE_POSIX_IMPLICITLY: u32 = 1;
pub const _POSIX_SOURCE: u32 = 1;
pub const _POSIX_C_SOURCE: u32 = 200809;
pub const _POSIX_C_SOURCE: u32 = 202405;
pub const __USE_POSIX: u32 = 1;
pub const __USE_POSIX2: u32 = 1;
pub const __USE_POSIX199309: u32 = 1;
pub const __USE_POSIX199506: u32 = 1;
pub const __USE_XOPEN2K: u32 = 1;
pub const __USE_XOPEN2K8: u32 = 1;
pub const _ATFILE_SOURCE: u32 = 1;
pub const __USE_XOPEN2K24: u32 = 1;
pub const __WORDSIZE: u32 = 64;
pub const __WORDSIZE_TIME64_COMPAT32: u32 = 1;
pub const __SYSCALL_WORDSIZE: u32 = 64;
Expand All @@ -183,7 +184,7 @@ pub const __STDC_IEC_60559_COMPLEX__: u32 = 201404;
pub const __STDC_ISO_10646__: u32 = 201706;
pub const __GNU_LIBRARY__: u32 = 6;
pub const __GLIBC__: u32 = 2;
pub const __GLIBC_MINOR__: u32 = 41;
pub const __GLIBC_MINOR__: u32 = 44;
pub const _SYS_CDEFS_H: u32 = 1;
pub const __glibc_c99_flexarr_available: u32 = 1;
pub const __LDOUBLE_REDIRECTS_TO_FLOAT128_ABI: u32 = 0;
Expand Down Expand Up @@ -312,6 +313,8 @@ pub const WHISPER_SAMPLE_RATE: u32 = 16000;
pub const WHISPER_N_FFT: u32 = 400;
pub const WHISPER_HOP_LENGTH: u32 = 160;
pub const WHISPER_CHUNK_SIZE: u32 = 30;
pub const GGML_VK_NAME: &[u8; 7] = b"Vulkan\0";
pub const GGML_VK_MAX_DEVICES: u32 = 16;
pub type wchar_t = ::std::os::raw::c_int;
#[repr(C)]
#[repr(align(16))]
Expand Down Expand Up @@ -534,7 +537,9 @@ pub struct _IO_FILE {
pub _freeres_buf: *mut ::std::os::raw::c_void,
pub _prevchain: *mut *mut _IO_FILE,
pub _mode: ::std::os::raw::c_int,
pub _unused2: [::std::os::raw::c_char; 20usize],
pub _unused3: ::std::os::raw::c_int,
pub _total_written: __uint64_t,
pub _unused2: [::std::os::raw::c_char; 8usize],
}
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
Expand Down Expand Up @@ -588,7 +593,10 @@ const _: () = {
["Offset of field: _IO_FILE::_prevchain"]
[::std::mem::offset_of!(_IO_FILE, _prevchain) - 184usize];
["Offset of field: _IO_FILE::_mode"][::std::mem::offset_of!(_IO_FILE, _mode) - 192usize];
["Offset of field: _IO_FILE::_unused2"][::std::mem::offset_of!(_IO_FILE, _unused2) - 196usize];
["Offset of field: _IO_FILE::_unused3"][::std::mem::offset_of!(_IO_FILE, _unused3) - 196usize];
["Offset of field: _IO_FILE::_total_written"]
[::std::mem::offset_of!(_IO_FILE, _total_written) - 200usize];
["Offset of field: _IO_FILE::_unused2"][::std::mem::offset_of!(_IO_FILE, _unused2) - 208usize];
};
impl _IO_FILE {
#[inline]
Expand Down Expand Up @@ -5669,6 +5677,38 @@ unsafe extern "C" {
i_segment: ::std::os::raw::c_int,
) -> f32;
}
unsafe extern "C" {
pub fn ggml_backend_vk_init(dev_num: usize) -> ggml_backend_t;
}
unsafe extern "C" {
pub fn ggml_backend_is_vk(backend: ggml_backend_t) -> bool;
}
unsafe extern "C" {
pub fn ggml_backend_vk_get_device_count() -> ::std::os::raw::c_int;
}
unsafe extern "C" {
pub fn ggml_backend_vk_get_device_description(
device: ::std::os::raw::c_int,
description: *mut ::std::os::raw::c_char,
description_size: usize,
);
}
unsafe extern "C" {
pub fn ggml_backend_vk_get_device_memory(
device: ::std::os::raw::c_int,
free: *mut usize,
total: *mut usize,
);
}
unsafe extern "C" {
pub fn ggml_backend_vk_buffer_type(dev_num: usize) -> ggml_backend_buffer_type_t;
}
unsafe extern "C" {
pub fn ggml_backend_vk_host_buffer_type() -> ggml_backend_buffer_type_t;
}
unsafe extern "C" {
pub fn ggml_backend_vk_reg() -> ggml_backend_reg_t;
}
pub type __builtin_va_list = [__va_list_tag; 1usize];
#[repr(C)]
#[derive(Debug, Copy, Clone)]
Expand Down
2 changes: 2 additions & 0 deletions vendor/whisper-rs-sys/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
// rustc 1.99 warns on bindgen's bitfield transmutes; harmless generated code.
#![allow(unnecessary_transmutes)]

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));