use voxora_engine::BackendKind;
use crate::capabilities::Capabilities;
use crate::error::BackendError;
pub fn best_device() -> BackendKind {
#[cfg(feature = "candle")]
{
best_device_candle().unwrap_or(BackendKind::Cpu)
}
#[cfg(not(feature = "candle"))]
{
BackendKind::Cpu
}
}
#[cfg(feature = "candle")]
fn best_device_candle() -> Option<BackendKind> {
use candle_core::Device;
if let Ok(Device::Cuda(_)) = Device::new_cuda(0) {
return Some(BackendKind::Cuda);
}
if let Ok(Device::Metal(_)) = Device::new_metal(0) {
return Some(BackendKind::Metal);
}
Some(BackendKind::Cpu)
}
pub fn detect() -> Capabilities {
#[cfg(feature = "candle")]
{
let compiled = candle_compiled_backends();
let mut caps = Capabilities::new(compiled);
caps.update_available_with(best_device_candle().unwrap_or(BackendKind::Cpu));
caps
}
#[cfg(not(feature = "candle"))]
{
Capabilities::new(vec![BackendKind::Cpu])
}
}
#[cfg(feature = "candle")]
fn candle_compiled_backends() -> Vec<BackendKind> {
let mut compiled = vec![BackendKind::Cpu];
if std::env::var("VOXORA_ENABLE_CUDA").is_ok() {
compiled.push(BackendKind::Cuda);
}
if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
compiled.push(BackendKind::Metal);
}
compiled
}
#[cfg(feature = "candle")]
impl Capabilities {
pub(crate) fn update_available_with(&mut self, best: BackendKind) {
self.available.retain(|b| *b != best);
self.available.insert(0, best);
}
}
pub fn best_device_or_error() -> Result<BackendKind, BackendError> {
let caps = detect();
if caps.is_available(BackendKind::Cpu) {
Ok(BackendKind::Cpu)
} else {
let compiled: Vec<String> = caps
.iter_compiled()
.map(|k| k.as_config().to_string())
.collect();
let available: Vec<String> = caps
.iter_available()
.map(|k| k.as_config().to_string())
.collect();
Err(BackendError::NoUsableBackend {
compiled,
available,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn best_device_returns_cpu_when_candle_feature_off() {
assert_eq!(best_device(), BackendKind::Cpu);
}
#[test]
fn detect_reports_cpu_as_compiled_and_available() {
let caps = detect();
assert!(caps.is_compiled(BackendKind::Cpu));
assert!(caps.is_available(BackendKind::Cpu));
}
#[test]
fn best_device_or_error_is_infallible_when_cpu_available() {
let kind = best_device_or_error().expect("cpu is always available");
assert_eq!(kind, BackendKind::Cpu);
}
}