use std::sync::OnceLock;
use ort::ep::ExecutionProviderDispatch;
use ort::session::builder::SessionBuilder;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum Ep {
Cpu,
Cuda,
TensorRt,
DirectMl,
CoreMl,
Auto,
}
pub(crate) fn parse(v: &str) -> Option<Ep> {
match v.trim().to_ascii_lowercase().as_str() {
"" | "cpu" => Some(Ep::Cpu),
"cuda" => Some(Ep::Cuda),
"tensorrt" | "trt" => Some(Ep::TensorRt),
"directml" | "dml" => Some(Ep::DirectMl),
"coreml" => Some(Ep::CoreMl),
"auto" => Some(Ep::Auto),
_ => None,
}
}
fn compiled(ep: Ep) -> bool {
match ep {
Ep::Cpu => true,
Ep::Cuda => cfg!(feature = "cuda"),
Ep::TensorRt => cfg!(feature = "tensorrt"),
Ep::DirectMl => cfg!(feature = "directml"),
Ep::CoreMl => cfg!(feature = "coreml"),
Ep::Auto => true,
}
}
fn any_gpu_compiled() -> bool {
[Ep::Cuda, Ep::TensorRt, Ep::DirectMl, Ep::CoreMl]
.into_iter()
.any(compiled)
}
fn default_choice() -> Ep {
if any_gpu_compiled() {
Ep::Auto
} else {
Ep::Cpu
}
}
pub(crate) fn choice() -> Ep {
static CHOICE: OnceLock<Ep> = OnceLock::new();
*CHOICE.get_or_init(|| {
let raw = std::env::var("DOCLING_RS_EP").unwrap_or_default();
if raw.trim().is_empty() {
return default_choice();
}
let Some(ep) = parse(&raw) else {
eprintln!(
"docling-pdf: DOCLING_RS_EP={raw:?} names no known execution provider \
(cpu|cuda|tensorrt|directml|coreml|auto); using CPU"
);
return Ep::Cpu;
};
if !compiled(ep) {
eprintln!(
"docling-pdf: DOCLING_RS_EP={raw:?} requested, but this binary was built \
without that provider — rebuild with `--features {}`; using CPU",
match ep {
Ep::Cuda => "cuda",
Ep::TensorRt => "tensorrt",
Ep::DirectMl => "directml",
Ep::CoreMl => "coreml",
Ep::Cpu | Ep::Auto => unreachable!("always compiled"),
}
);
return Ep::Cpu;
}
ep
})
}
pub(crate) fn prefers_fp32() -> bool {
match choice() {
Ep::Cpu => false,
Ep::Cuda | Ep::TensorRt | Ep::DirectMl | Ep::CoreMl => true,
Ep::Auto => any_gpu_compiled(),
}
}
fn dispatches() -> Option<Vec<ExecutionProviderDispatch>> {
let d = match choice() {
Ep::Cpu => return None,
Ep::Cuda => vec![ort::ep::CUDA::default().build().error_on_failure()],
Ep::TensorRt => vec![ort::ep::TensorRT::default().build().error_on_failure()],
Ep::DirectMl => vec![ort::ep::DirectML::default().build().error_on_failure()],
Ep::CoreMl => vec![ort::ep::CoreML::default().build().error_on_failure()],
Ep::Auto => {
let mut v = Vec::new();
if cfg!(feature = "tensorrt") {
v.push(ort::ep::TensorRT::default().build());
}
if cfg!(feature = "cuda") {
v.push(ort::ep::CUDA::default().build());
}
if cfg!(feature = "coreml") {
v.push(ort::ep::CoreML::default().build());
}
if cfg!(feature = "directml") {
v.push(ort::ep::DirectML::default().build());
}
if v.is_empty() {
return None; }
v
}
};
Some(d)
}
pub fn apply(builder: SessionBuilder) -> Result<SessionBuilder, String> {
let Some(eps) = dispatches() else {
return Ok(builder);
};
static LOGGED: OnceLock<()> = OnceLock::new();
LOGGED.get_or_init(|| {
if crate::timing::enabled() {
eprintln!("docling-pdf: execution providers: {eps:?}");
}
});
builder
.with_execution_providers(eps)
.map_err(|e| format!("execution provider registration: {e}"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_known_names_and_aliases() {
assert_eq!(parse(""), Some(Ep::Cpu));
assert_eq!(parse("cpu"), Some(Ep::Cpu));
assert_eq!(parse("CUDA"), Some(Ep::Cuda));
assert_eq!(parse(" cuda "), Some(Ep::Cuda));
assert_eq!(parse("tensorrt"), Some(Ep::TensorRt));
assert_eq!(parse("trt"), Some(Ep::TensorRt));
assert_eq!(parse("directml"), Some(Ep::DirectMl));
assert_eq!(parse("dml"), Some(Ep::DirectMl));
assert_eq!(parse("CoreML"), Some(Ep::CoreMl));
assert_eq!(parse("auto"), Some(Ep::Auto));
}
#[test]
fn parse_rejects_unknown() {
assert_eq!(parse("gpu"), None);
assert_eq!(parse("rocm"), None);
assert_eq!(parse("cuda:0"), None);
}
#[test]
fn cpu_and_auto_are_always_compiled() {
assert!(compiled(Ep::Cpu));
assert!(compiled(Ep::Auto));
}
#[test]
fn unset_defaults_to_auto_exactly_in_gpu_builds() {
#[cfg(any(
feature = "cuda",
feature = "tensorrt",
feature = "directml",
feature = "coreml"
))]
assert_eq!(default_choice(), Ep::Auto);
#[cfg(not(any(
feature = "cuda",
feature = "tensorrt",
feature = "directml",
feature = "coreml"
)))]
assert_eq!(default_choice(), Ep::Cpu);
}
}