use crate::error::{oe, Result};
use crate::probe::cuda_available;
use anyhow::anyhow;
use ort::session::builder::{GraphOptimizationLevel, SessionBuilder};
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum DeviceReq {
Auto,
Cpu,
Cuda,
}
impl DeviceReq {
pub fn parse(s: &str) -> Result<Self> {
match s.to_ascii_lowercase().as_str() {
"auto" => Ok(Self::Auto),
"cpu" => Ok(Self::Cpu),
"cuda" | "gpu" => Ok(Self::Cuda),
other => Err(anyhow!("device must be 'auto', 'cpu' or 'cuda', got '{other}'")),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct SessionPolicy {
pub opt_level: Option<GraphOptimizationLevel>,
pub intra_threads: Option<usize>,
pub inter_threads: Option<usize>,
}
impl SessionPolicy {
pub fn text_embed() -> Self {
let threads = std::thread::available_parallelism()
.map(|p| p.get())
.unwrap_or(1);
Self {
opt_level: Some(ort::session::builder::GraphOptimizationLevel::Level3),
intra_threads: Some(std::cmp::max(1, threads / 2)), inter_threads: Some(1),
}
}
pub fn ort_defaults() -> Self {
Self { opt_level: None, intra_threads: None, inter_threads: None }
}
pub fn apply(&self, mut builder: SessionBuilder) -> Result<SessionBuilder> {
if let Some(level) = self.opt_level {
builder = oe(builder.with_optimization_level(level))?;
}
if let Some(n) = self.intra_threads {
builder = oe(builder.with_intra_threads(n))?;
}
if let Some(n) = self.inter_threads {
builder = oe(builder.with_inter_threads(n))?;
}
Ok(builder)
}
}
pub(crate) fn resolve_provider_names(device: DeviceReq) -> (Vec<String>, bool) {
let cuda = cuda_available();
let used_cuda = cuda && !matches!(device, DeviceReq::Cpu);
let names = match device {
DeviceReq::Cpu => vec![],
DeviceReq::Auto => {
if cuda {
vec!["cuda".to_string()]
} else {
vec![]
}
}
DeviceReq::Cuda if cuda => vec!["cuda".to_string()],
DeviceReq::Cuda => {
eprintln!(
"embroider: CUDA requested but no CUDA execution provider in the loaded \
ONNX Runtime — falling back to CPU. Install onnxruntime-gpu for acceleration."
);
vec![]
}
};
(names, used_cuda)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn device_parse_accepts_aliases_case_insensitively() {
assert_eq!(DeviceReq::parse("auto").unwrap(), DeviceReq::Auto);
assert_eq!(DeviceReq::parse("CPU").unwrap(), DeviceReq::Cpu);
assert_eq!(DeviceReq::parse("cuda").unwrap(), DeviceReq::Cuda);
assert_eq!(DeviceReq::parse("GPU").unwrap(), DeviceReq::Cuda);
}
#[test]
fn device_parse_rejects_unknown_with_message() {
let err = DeviceReq::parse("tpu").unwrap_err().to_string();
assert!(err.contains("device must be"), "{err}");
assert!(err.contains("'tpu'"), "{err}");
}
}