fix(rb-07): runtime capabilities derive accelerators honestly
get_runtime_capabilities was returning `accelerators = ["cpu", "vulkan"]` and `whisper.supports_gpu = true` regardless of build config or runtime state. On a macOS build it falsely advertised "vulkan" (the backend actually resolves via MoltenVK as Metal); on a whisper-disabled build it claimed GPU support for an engine that hadn't been linked. Added `compose_accelerators(whisper_enabled, loader_available, target)` — a pure helper that always emits "cpu" first and appends the platform-appropriate GPU name only when whisper is compiled in AND the Vulkan loader resolves. `supported_accelerators()` wraps it with the live `cfg!(feature = "whisper")`, loader probe, and target OS. `get_runtime_capabilities` now calls `supported_accelerators()` and sets `whisper.supports_gpu = cfg!(feature = "whisper")`. Parakeet stays CPU-only. Five tests in `commands::models::tests` cover the permutation matrix: whisper on/off, loader present/missing, macOS vs other. Both feature configurations (`--features whisper` and `--no-default-features`) build and pass tests. Macos Metal-loader resolution on real hardware stays on the ship-gate checklist — the detection logic is verifiable from Linux but runtime behaviour is not. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -341,6 +341,51 @@ fn vulkan_loader_available() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Compile-time target signalling used by `compose_accelerators`.
|
||||
/// Split out so the pure-function behaviour is testable without `cfg!`
|
||||
/// appearing in the test matrix.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum AcceleratorTarget {
|
||||
Macos,
|
||||
Other,
|
||||
}
|
||||
|
||||
/// Pure helper: produce the `accelerators` list from the
|
||||
/// whisper-compiled-in flag, runtime loader availability, and target
|
||||
/// family. Always starts with `cpu`; appends the platform-appropriate
|
||||
/// GPU name only when whisper is compiled in AND the Vulkan loader
|
||||
/// resolves. RB-07 replaced a hard-coded `["cpu", "vulkan"]`.
|
||||
fn compose_accelerators(
|
||||
whisper_enabled: bool,
|
||||
loader_available: bool,
|
||||
target: AcceleratorTarget,
|
||||
) -> Vec<String> {
|
||||
let mut accelerators = vec!["cpu".into()];
|
||||
if whisper_enabled && loader_available {
|
||||
let gpu = match target {
|
||||
AcceleratorTarget::Macos => "metal",
|
||||
AcceleratorTarget::Other => "vulkan",
|
||||
};
|
||||
accelerators.push(gpu.into());
|
||||
}
|
||||
accelerators
|
||||
}
|
||||
|
||||
/// Public wrapper around `compose_accelerators` that reads the real
|
||||
/// `cfg(feature = "whisper")`, runtime loader probe, and target OS.
|
||||
fn supported_accelerators() -> Vec<String> {
|
||||
let target = if cfg!(target_os = "macos") {
|
||||
AcceleratorTarget::Macos
|
||||
} else {
|
||||
AcceleratorTarget::Other
|
||||
};
|
||||
compose_accelerators(
|
||||
cfg!(feature = "whisper"),
|
||||
vulkan_loader_available(),
|
||||
target,
|
||||
)
|
||||
}
|
||||
|
||||
/// Report which backend whisper.cpp was actually able to initialise
|
||||
/// against. The whisper-rs build here is compiled with the `vulkan`
|
||||
/// feature unconditionally; on macOS that's still Metal via MoltenVK,
|
||||
@@ -453,12 +498,14 @@ pub fn get_runtime_capabilities(
|
||||
let active_compute_device = detect_active_compute_device();
|
||||
let cpu_features: CpuFeaturesInfo = hardware::probe_cpu_features().into();
|
||||
|
||||
// Kon's desktop build links transcribe-rs with the `whisper-vulkan`
|
||||
// feature unconditionally (see crates/transcription/Cargo.toml), so
|
||||
// whisper.cpp boots with Vulkan backend on any machine with a Vulkan
|
||||
// loader + ICD. Parakeet (ONNX) still runs on CPU. Reflect both.
|
||||
// Accelerator list is now derived from the live build configuration
|
||||
// and runtime loader probe — see `compose_accelerators`. Parakeet
|
||||
// (ONNX) stays CPU-only; Whisper advertises `supports_gpu` only
|
||||
// when it was actually compiled in for this binary.
|
||||
let whisper_supports_gpu = cfg!(feature = "whisper");
|
||||
|
||||
Ok(RuntimeCapabilities {
|
||||
accelerators: vec!["cpu".into(), "vulkan".into()],
|
||||
accelerators: supported_accelerators(),
|
||||
engines: vec![
|
||||
EngineRuntimeCapabilities {
|
||||
id: "whisper".into(),
|
||||
@@ -466,7 +513,7 @@ pub fn get_runtime_capabilities(
|
||||
loaded_model_id: whisper
|
||||
.loaded_model_id()
|
||||
.map(|model_id| model_id.to_string()),
|
||||
supports_gpu: true,
|
||||
supports_gpu: whisper_supports_gpu,
|
||||
models: whisper_models,
|
||||
},
|
||||
EngineRuntimeCapabilities {
|
||||
@@ -594,3 +641,66 @@ pub async fn load_parakeet_model(
|
||||
pub fn check_parakeet_engine(state: tauri::State<'_, AppState>) -> Result<bool, String> {
|
||||
Ok(state.parakeet_engine.is_loaded())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// RB-07: the accelerator list is now derived from compile flags +
|
||||
// runtime loader probe + target. These cover the permutations.
|
||||
//
|
||||
// Pre-fix behaviour was `vec!["cpu".into(), "vulkan".into()]`
|
||||
// regardless of whisper feature, loader availability, or platform —
|
||||
// so a macOS build falsely advertised "vulkan", and a no-whisper
|
||||
// build falsely advertised a GPU path with no engine to use it.
|
||||
|
||||
#[test]
|
||||
fn cpu_only_when_whisper_disabled() {
|
||||
assert_eq!(
|
||||
compose_accelerators(false, true, AcceleratorTarget::Macos),
|
||||
vec!["cpu".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
compose_accelerators(false, true, AcceleratorTarget::Other),
|
||||
vec!["cpu".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cpu_only_when_loader_missing() {
|
||||
assert_eq!(
|
||||
compose_accelerators(true, false, AcceleratorTarget::Macos),
|
||||
vec!["cpu".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
compose_accelerators(true, false, AcceleratorTarget::Other),
|
||||
vec!["cpu".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_with_loader_advertises_metal() {
|
||||
assert_eq!(
|
||||
compose_accelerators(true, true, AcceleratorTarget::Macos),
|
||||
vec!["cpu".to_string(), "metal".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_macos_with_loader_advertises_vulkan() {
|
||||
assert_eq!(
|
||||
compose_accelerators(true, true, AcceleratorTarget::Other),
|
||||
vec!["cpu".to_string(), "vulkan".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cpu_is_always_first_entry() {
|
||||
// Frontend relies on index-0 being the fallback; preserve that
|
||||
// contract regardless of which GPU extras are added.
|
||||
for target in [AcceleratorTarget::Macos, AcceleratorTarget::Other] {
|
||||
let full = compose_accelerators(true, true, target);
|
||||
assert_eq!(full.first(), Some(&"cpu".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user