diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 10257fe..0f9dfc4 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -12,6 +12,7 @@ sysinfo = "0.35" async-trait = "0.1" num_cpus = "1" tracing = "0.1" +libloading = "0.8" [dev-dependencies] tempfile = "3" diff --git a/crates/core/src/hardware.rs b/crates/core/src/hardware.rs index 749a1be..1fa2766 100644 --- a/crates/core/src/hardware.rs +++ b/crates/core/src/hardware.rs @@ -169,6 +169,40 @@ pub fn probe_system() -> SystemProfile { } } +/// Best-effort probe for the Vulkan loader shared library. +/// +/// whisper.cpp and llama.cpp Vulkan backends silently drop to CPU if +/// `libvulkan.so.1` (Linux) / `vulkan-1.dll` (Windows) / the MoltenVK +/// bundle (macOS) is missing at runtime. We probe via +/// `libloading::Library::new`; a successful open means the loader is +/// resolvable and we should treat the GPU path as live. +/// +/// Moved from `src-tauri/src/commands/models.rs` so non-Tauri crates +/// (transcription, llm) can call it without depending on the Tauri +/// binary. +pub fn vulkan_loader_available() -> bool { + #[cfg(target_os = "linux")] + let candidates: &[&str] = &["libvulkan.so.1", "libvulkan.so"]; + #[cfg(target_os = "windows")] + let candidates: &[&str] = &["vulkan-1.dll"]; + #[cfg(target_os = "macos")] + let candidates: &[&str] = &["libvulkan.1.dylib", "libMoltenVK.dylib"]; + #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))] + let candidates: &[&str] = &[]; + + for name in candidates { + // SAFETY: libloading::Library::new loads a shared library and + // returns a handle that is dropped at the end of this + // iteration. We do not call any symbols, so the open-for-probe + // pattern is sound. + match unsafe { libloading::Library::new(*name) } { + Ok(_lib) => return true, + Err(_) => continue, + } + } + false +} + #[cfg(test)] mod tests { use super::*; @@ -217,4 +251,11 @@ mod tests { #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] assert!(!features.has_ggml_baseline()); } + + #[test] + fn vulkan_loader_available_does_not_panic() { + // We can't assert the value (depends on host's libvulkan), + // but we can assert the call completes. + let _ = vulkan_loader_available(); + } }