use ort::ep::ExecutionProviderDispatch;
use std::{collections::BTreeMap, path::PathBuf};
#[derive(Clone, Debug)]
pub enum Backend {
TensorRt,
Cuda,
#[cfg(target_os = "macos")]
CoreMl,
Cpu,
Custom {
name: String,
provider: ExecutionProviderDispatch,
},
}
impl Backend {
pub fn name(&self) -> &str {
match self {
Self::TensorRt => "TensorRT",
Self::Cuda => "CUDA",
#[cfg(target_os = "macos")]
Self::CoreMl => "CoreML",
Self::Cpu => "CPU",
Self::Custom { name, .. } => name,
}
}
}
#[derive(Clone, Debug)]
pub struct TensorRtOptions {
pub device_id: i32,
pub fp16: bool,
pub tf32: bool,
pub cuda_graph: bool,
pub workspace_bytes: usize,
pub builder_optimization_level: u8,
pub auxiliary_streams: i8,
pub sparsity: bool,
pub min_shapes: Option<String>,
pub opt_shapes: Option<String>,
pub max_shapes: Option<String>,
}
impl Default for TensorRtOptions {
fn default() -> Self {
Self {
device_id: 0,
fp16: false,
tf32: true,
cuda_graph: true,
workspace_bytes: usize::try_from(4_u64 * 1024 * 1024 * 1024).unwrap_or(usize::MAX),
builder_optimization_level: 3,
auxiliary_streams: -1,
sparsity: true,
min_shapes: None,
opt_shapes: None,
max_shapes: None,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct CudaOptions {
pub device_id: i32,
pub tf32: bool,
pub cuda_graph: bool,
}
impl Default for CudaOptions {
fn default() -> Self {
Self {
device_id: 0,
tf32: true,
cuda_graph: true,
}
}
}
impl CudaOptions {
pub(crate) fn validate(&self) -> crate::Result<()> {
anyhow::ensure!(self.device_id >= 0, "Negative CUDA device id");
Ok(())
}
}
#[derive(Clone, Debug)]
pub enum CompileTarget {
TensorRt(TensorRtOptions),
Cuda(CudaOptions),
#[cfg(target_os = "macos")]
CoreMl,
Cpu,
Custom {
name: String,
provider: ExecutionProviderDispatch,
},
}
#[derive(Clone, Debug)]
pub struct CompileOptions {
pub target: CompileTarget,
pub intra_threads: usize,
pub inter_threads: usize,
pub parallel_execution: bool,
pub dimension_overrides: BTreeMap<String, usize>,
}
impl CompileOptions {
pub fn new(target: CompileTarget) -> Self {
Self {
target,
intra_threads: 1,
inter_threads: 1,
parallel_execution: false,
dimension_overrides: BTreeMap::new(),
}
}
pub(crate) fn into_runtime_options(self) -> crate::Result<OnnxOptions> {
let (backend, tensorrt, cuda) = match self.target {
CompileTarget::TensorRt(config) => {
config.validate()?;
(Backend::TensorRt, Some(config), None)
}
CompileTarget::Cuda(config) => {
config.validate()?;
(Backend::Cuda, None, Some(config))
}
#[cfg(target_os = "macos")]
CompileTarget::CoreMl => (Backend::CoreMl, None, None),
CompileTarget::Cpu => (Backend::Cpu, None, None),
CompileTarget::Custom { name, provider } => {
(Backend::Custom { name, provider }, None, None)
}
};
Ok(OnnxOptions {
backend: BackendSelection::Require(backend),
tensorrt,
cuda,
intra_threads: self.intra_threads,
inter_threads: self.inter_threads,
parallel_execution: self.parallel_execution,
dimension_overrides: self.dimension_overrides,
profiling: None,
})
}
}
#[derive(Clone, Debug)]
pub enum BackendSelection {
Require(Backend),
Auto(Vec<Backend>),
}
impl BackendSelection {
pub(crate) fn candidates(&self) -> &[Backend] {
match self {
Self::Require(backend) => std::slice::from_ref(backend),
Self::Auto(backends) => backends,
}
}
}
impl Default for BackendSelection {
fn default() -> Self {
#[cfg(target_os = "macos")]
{
Self::Auto(vec![Backend::CoreMl, Backend::Cpu])
}
#[cfg(cuda_platform)]
{
Self::Auto(vec![Backend::TensorRt, Backend::Cuda, Backend::Cpu])
}
#[cfg(not(any(target_os = "macos", cuda_platform)))]
{
Self::Auto(vec![Backend::Cpu])
}
}
}
#[derive(Clone, Debug)]
pub struct OnnxOptions {
pub backend: BackendSelection,
pub tensorrt: Option<TensorRtOptions>,
pub cuda: Option<CudaOptions>,
pub intra_threads: usize,
pub inter_threads: usize,
pub parallel_execution: bool,
pub dimension_overrides: BTreeMap<String, usize>,
pub profiling: Option<PathBuf>,
}
impl Default for OnnxOptions {
fn default() -> Self {
Self {
backend: BackendSelection::default(),
tensorrt: None,
cuda: None,
intra_threads: 1,
inter_threads: 1,
parallel_execution: false,
dimension_overrides: BTreeMap::new(),
profiling: None,
}
}
}
impl OnnxOptions {
pub fn cpu() -> Self {
Self {
backend: BackendSelection::Require(Backend::Cpu),
..Self::default()
}
}
pub(crate) fn validate(&self) -> crate::Result<()> {
anyhow::ensure!(
!self.backend.candidates().is_empty(),
"At least one backend is required"
);
anyhow::ensure!(
self.intra_threads <= i32::MAX as usize && self.inter_threads <= i32::MAX as usize,
"Thread counts must fit in a signed 32-bit integer"
);
anyhow::ensure!(
self.dimension_overrides
.values()
.all(|&d| d > 0 && i64::try_from(d).is_ok()),
"Invalid dimension override"
);
Ok(())
}
}
impl TensorRtOptions {
pub(crate) fn validate(&self) -> crate::Result<()> {
anyhow::ensure!(self.device_id >= 0, "Negative TensorRT device id");
anyhow::ensure!(
self.builder_optimization_level <= 5,
"TensorRT build level must be 0..=5"
);
anyhow::ensure!(self.workspace_bytes > 0, "Workspace must be positive");
anyhow::ensure!(
self.auxiliary_streams >= -1,
"TensorRT auxiliary streams must be -1 or nonnegative"
);
let profiles = [&self.min_shapes, &self.opt_shapes, &self.max_shapes];
anyhow::ensure!(
profiles.iter().all(|x| x.is_some()) || profiles.iter().all(|x| x.is_none()),
"Set all three TensorRT profiles or none"
);
Ok(())
}
}