Skip to main content

transcribe_cpp/
backend.rs

1//! Backend module loading and compute-device discovery.
2//!
3//! In a static (or plain `shared`) build the compiled-in backends are already
4//! registered and [`init_backends`] is a harmless no-op. In a `dynamic-backends`
5//! build the compute backends are loadable modules; a host points the library at
6//! the provider directory ONCE, before the first model load — use
7//! [`init_backends_default`] when the modules sit next to libtranscribe, or
8//! [`init_backends`] with an explicit bundled provider directory. See the C
9//! header's backend-module section for the degradation contract.
10
11use std::ffi::CString;
12use std::path::Path;
13
14use transcribe_cpp_sys as sys;
15
16use crate::error::{check, Result};
17use crate::result::owned_str;
18use crate::types::Backend;
19
20/// One registered compute device.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct Device {
23    /// ggml device name, e.g. "Metal".
24    pub name: String,
25    /// Human-readable description, e.g. "Apple M4 Max".
26    pub description: String,
27    /// Classified kind: "cpu", "accel", "metal", "vulkan", "cuda", "sycl",
28    /// "gpu", or "unknown".
29    pub kind: String,
30}
31
32/// Load backend modules from `dir` (dynamic builds) and register their
33/// devices. A no-op in static builds. Call once, before the first model load.
34///
35/// Errors with [`crate::Error::Backend`] if, after loading, the process has no
36/// registered compute device (a dynamic build pointed at a directory with no
37/// usable modules). This call is idempotent per directory and NOT retryable in
38/// the same process.
39pub fn init_backends(dir: impl AsRef<Path>) -> Result<()> {
40    let dir = dir.as_ref();
41    // Pass the path bytes through faithfully (Unix) / reject non-UTF-8 (Windows),
42    // matching model loading — never lossily mangle a path with to_string_lossy.
43    let c_dir = CString::new(crate::model::path_bytes(dir)?)?;
44    let status = unsafe { sys::transcribe_init_backends(c_dir.as_ptr()) };
45    check(status, "init_backends")
46}
47
48/// Load the backend modules the way the build expects, with no caller-supplied
49/// path.
50///
51/// - **Compiled-in builds** (the default static build, or a plain `shared`
52///   build): a no-op returning `Ok(())` — the backends are already registered.
53/// - **`dynamic-backends` builds**: resolves the directory containing the
54///   loaded libtranscribe and scans only that directory. Ship the backend
55///   modules next to libtranscribe for this helper to find them.
56///
57/// If your app uses a different layout, call [`init_backends`] with that
58/// resolved module directory instead. Like [`init_backends`], this is
59/// idempotent and must run once before the first model load.
60pub fn init_backends_default() -> Result<()> {
61    let status = unsafe { sys::transcribe_init_backends_default() };
62    check(status, "init_backends_default")
63}
64
65/// The number of compute devices currently registered.
66pub fn device_count() -> usize {
67    let n = unsafe { sys::transcribe_backend_device_count() };
68    n.max(0) as usize
69}
70
71/// Every registered compute device.
72pub fn devices() -> Vec<Device> {
73    let mut out = Vec::with_capacity(device_count());
74    for i in 0..device_count() as i32 {
75        let mut raw: sys::transcribe_backend_device = unsafe { std::mem::zeroed() };
76        unsafe { sys::transcribe_backend_device_init(&mut raw) };
77        let status = unsafe { sys::transcribe_get_backend_device(i, &mut raw) };
78        if status == sys::transcribe_status::TRANSCRIBE_OK {
79            out.push(Device {
80                name: owned_str(raw.name),
81                description: owned_str(raw.description),
82                kind: owned_str(raw.kind),
83            });
84        }
85    }
86    out
87}
88
89/// Whether a backend request can be satisfied by some registered device. This
90/// is the probe to turn `Backend::Vulkan` on a machine without Vulkan into a
91/// clear error instead of a failed model load.
92pub fn backend_available(backend: Backend) -> bool {
93    unsafe { sys::transcribe_backend_available(backend.to_raw()) }
94}