use std::fmt;
#[cfg(feature = "ort")]
use std::path::{Path, PathBuf};
#[cfg(feature = "ort")]
use std::sync::Arc;
#[cfg(feature = "ort")]
use crate::error::Result;
#[cfg(feature = "ort")]
use ort::environment::Environment;
#[cfg(feature = "ort")]
use ort::session::Session;
#[cfg(feature = "ort")]
use ort::session::builder::SessionBuilder;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum ExecutionProvider {
#[cfg(feature = "ort")]
#[default]
Cpu,
#[cfg(feature = "cuda")]
Cuda,
#[cfg(feature = "tensorrt")]
TensorRT,
#[cfg(feature = "coreml")]
CoreML,
#[cfg(feature = "directml")]
DirectML,
#[cfg(feature = "migraphx")]
MIGraphX,
#[cfg(feature = "openvino")]
OpenVINO,
#[cfg(feature = "webgpu")]
WebGPU,
#[cfg(feature = "nnapi")]
NNAPI,
#[cfg(feature = "burn")]
#[cfg_attr(not(feature = "ort"), default)]
BurnCpu,
#[cfg(feature = "wgpu")]
BurnWgpu,
#[cfg(feature = "burn-cuda")]
BurnCuda,
#[cfg(feature = "burn-rocm")]
BurnRocm,
}
impl ExecutionProvider {
pub fn is_burn(self) -> bool {
match self {
#[cfg(feature = "burn")]
ExecutionProvider::BurnCpu => true,
#[cfg(feature = "wgpu")]
ExecutionProvider::BurnWgpu => true,
#[cfg(feature = "burn-cuda")]
ExecutionProvider::BurnCuda => true,
#[cfg(feature = "burn-rocm")]
ExecutionProvider::BurnRocm => true,
#[allow(unreachable_patterns)]
_ => false,
}
}
pub(crate) fn per_token_provider(self) -> Self {
match self {
#[cfg(feature = "wgpu")]
ExecutionProvider::BurnWgpu => ExecutionProvider::BurnCpu,
#[cfg(feature = "burn-cuda")]
ExecutionProvider::BurnCuda => ExecutionProvider::BurnCpu,
#[cfg(feature = "burn-rocm")]
ExecutionProvider::BurnRocm => ExecutionProvider::BurnCpu,
other => other,
}
}
}
#[cfg(feature = "ort")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CoreMLComputeUnits {
All,
CpuAndNeuralEngine,
#[default]
CpuAndGpu,
CpuOnly,
}
#[derive(Clone)]
#[non_exhaustive]
pub struct ModelConfig {
pub execution_provider: ExecutionProvider,
pub intra_threads: usize,
pub inter_threads: usize,
#[cfg(feature = "ort")]
pub configure: Option<Arc<dyn Fn(SessionBuilder) -> ort::Result<SessionBuilder> + Send + Sync>>,
#[cfg(feature = "ort")]
pub coreml_cache_dir: Option<PathBuf>,
#[cfg(feature = "ort")]
pub coreml_compute_units: CoreMLComputeUnits,
#[cfg(feature = "ort")]
pub environment: Option<Environment>,
}
impl fmt::Debug for ModelConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("ModelConfig");
s.field("execution_provider", &self.execution_provider)
.field("intra_threads", &self.intra_threads)
.field("inter_threads", &self.inter_threads);
#[cfg(feature = "ort")]
s.field(
"configure",
&if self.configure.is_some() {
"<fn>"
} else {
"None"
},
)
.field("coreml_cache_dir", &self.coreml_cache_dir)
.field("coreml_compute_units", &self.coreml_compute_units)
.field("environment", &self.environment);
s.finish()
}
}
impl Default for ModelConfig {
fn default() -> Self {
Self {
execution_provider: ExecutionProvider::default(),
intra_threads: 4,
inter_threads: 1,
#[cfg(feature = "ort")]
configure: None,
#[cfg(feature = "ort")]
coreml_cache_dir: None,
#[cfg(feature = "ort")]
coreml_compute_units: CoreMLComputeUnits::default(),
#[cfg(feature = "ort")]
environment: None,
}
}
}
impl ModelConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_execution_provider(mut self, provider: ExecutionProvider) -> Self {
self.execution_provider = provider;
self
}
pub fn with_intra_threads(mut self, threads: usize) -> Self {
self.intra_threads = threads;
self
}
pub fn with_inter_threads(mut self, threads: usize) -> Self {
self.inter_threads = threads;
self
}
#[cfg(feature = "ort")]
pub fn with_custom_configure(
mut self,
configure: impl Fn(SessionBuilder) -> ort::Result<SessionBuilder> + Send + Sync + 'static,
) -> Self {
self.configure = Some(Arc::new(configure));
self
}
#[cfg(feature = "ort")]
pub fn with_coreml_cache_dir(mut self, path: impl Into<PathBuf>) -> Self {
self.coreml_cache_dir = Some(path.into());
self
}
#[cfg(feature = "ort")]
pub fn with_coreml_compute_units(mut self, units: CoreMLComputeUnits) -> Self {
self.coreml_compute_units = units;
self
}
#[cfg(feature = "ort")]
pub fn with_environment(mut self, environment: Environment) -> Self {
self.environment = Some(environment);
self
}
#[cfg(feature = "ort")]
pub fn build_session(&self, path: &Path) -> Result<Session> {
let Some(environment) = &self.environment else {
return Err(crate::error::Error::Config(
"ONNX Runtime needs an environment: create one with \
`parakeet_rs::ort::init().build()?` and pass it with \
`ExecutionConfig::new().with_environment(env)`"
.into(),
));
};
let builder = Session::builder(environment)?;
let mut builder = self.apply_to_session_builder(builder)?;
Ok(builder.commit_from_file(path)?)
}
#[cfg(feature = "ort")]
pub(crate) fn apply_to_session_builder(
&self,
builder: SessionBuilder,
) -> Result<SessionBuilder> {
#[cfg(any(
feature = "cuda",
feature = "tensorrt",
feature = "coreml",
feature = "directml",
feature = "migraphx",
feature = "openvino",
feature = "webgpu",
feature = "nnapi"
))]
use ort::ep::CPU as CPUExecutionProvider;
use ort::session::builder::GraphOptimizationLevel;
let mut builder = builder
.with_optimization_level(GraphOptimizationLevel::Level3)?
.with_intra_threads(self.intra_threads)?
.with_inter_threads(self.inter_threads)?;
builder = match self.execution_provider {
ExecutionProvider::Cpu => {
builder.with_auto_device(ort::session::builder::AutoDevicePolicy::PreferCPU)?
}
#[cfg(feature = "cuda")]
ExecutionProvider::Cuda => builder.with_execution_providers([
ort::ep::CUDA::default().build(),
CPUExecutionProvider::default().build().error_on_failure(),
])?,
#[cfg(feature = "tensorrt")]
ExecutionProvider::TensorRT => builder.with_execution_providers([
ort::ep::TensorRT::default().build(),
CPUExecutionProvider::default().build().error_on_failure(),
])?,
#[cfg(feature = "coreml")]
ExecutionProvider::CoreML => {
use ort::ep::coreml::{ComputeUnits, CoreML};
let units = match self.coreml_compute_units {
CoreMLComputeUnits::All => ComputeUnits::All,
CoreMLComputeUnits::CpuAndNeuralEngine => ComputeUnits::CPUAndNeuralEngine,
CoreMLComputeUnits::CpuAndGpu => ComputeUnits::CPUAndGPU,
CoreMLComputeUnits::CpuOnly => ComputeUnits::CPUOnly,
};
let mut coreml = CoreML::default().with_compute_units(units);
if let Some(cache_dir) = &self.coreml_cache_dir {
coreml = coreml.with_model_cache_dir(cache_dir.to_string_lossy());
}
builder.with_execution_providers([
coreml.build(),
CPUExecutionProvider::default().build().error_on_failure(),
])?
}
#[cfg(feature = "directml")]
ExecutionProvider::DirectML => builder.with_execution_providers([
ort::ep::DirectML::default().build(),
CPUExecutionProvider::default().build().error_on_failure(),
])?,
#[cfg(feature = "migraphx")]
ExecutionProvider::MIGraphX => builder.with_execution_providers([
ort::ep::MIGraphX::default().build(),
CPUExecutionProvider::default().build().error_on_failure(),
])?,
#[cfg(feature = "openvino")]
ExecutionProvider::OpenVINO => builder.with_execution_providers([
ort::ep::OpenVINO::default().build(),
CPUExecutionProvider::default().build().error_on_failure(),
])?,
#[cfg(feature = "webgpu")]
ExecutionProvider::WebGPU => builder.with_execution_providers([
ort::ep::WebGPU::default().build(),
CPUExecutionProvider::default().build().error_on_failure(),
])?,
#[cfg(feature = "nnapi")]
ExecutionProvider::NNAPI => builder.with_execution_providers([
ort::ep::NNAPI::default().build(),
CPUExecutionProvider::default().build().error_on_failure(),
])?,
#[allow(unreachable_patterns)]
provider => {
return Err(crate::error::Error::Config(format!(
"{provider:?} is a burn provider; an ONNX Runtime session needs an ONNX Runtime provider"
)));
}
};
if let Some(configure) = self.configure.as_ref() {
builder = configure(builder)?;
}
Ok(builder)
}
}
#[cfg(all(test, feature = "ort"))]
mod tests {
use super::*;
#[test]
fn a_session_without_an_environment_is_an_error() {
let err = ModelConfig::new()
.build_session(Path::new("missing.onnx"))
.unwrap_err();
assert!(err.to_string().contains("with_environment"), "{err}");
}
}