#![allow(dead_code)]
use std::path::{Path, PathBuf};
use std::sync::{Arc, OnceLock};
use crate::runtime::{
error::RuntimeError,
factory::{Runtime, RuntimeFactory},
};
use super::session::OrtRuntime;
#[cfg(all(feature = "coreml", feature = "cuda"))]
compile_error!("features `coreml` and `cuda` are mutually exclusive");
#[derive(Clone, Copy)]
pub enum OrtExecutionProvider {
Cpu,
#[cfg(feature = "coreml")]
CoreML,
#[cfg(feature = "cuda")]
Cuda,
#[cfg(feature = "nnapi")]
Nnapi,
}
impl OrtExecutionProvider {
pub(crate) fn execution_providers(
self,
model_path: &Path,
) -> Vec<ort::ep::ExecutionProviderDispatch> {
#[cfg(not(feature = "coreml"))]
let _ = model_path;
match self {
Self::Cpu => vec![ort::ep::CPU::default().build()],
#[cfg(feature = "coreml")]
Self::CoreML => {
let cache_dir = match model_path.parent() {
Some(p) => crate::model::coreml_cache_dir(p),
None => crate::model::coreml_cache_dir(Path::new(".")),
};
let coreml_ep = ort::ep::CoreML::default()
.with_model_format(ort::ep::coreml::ModelFormat::MLProgram)
.with_static_input_shapes(true)
.with_compute_units(ort::ep::coreml::ComputeUnits::CPUAndNeuralEngine)
.with_specialization_strategy(
ort::ep::coreml::SpecializationStrategy::FastPrediction,
)
.with_model_cache_dir(cache_dir.to_string_lossy())
.build();
vec![coreml_ep, ort::ep::CPU::default().build()]
}
#[cfg(feature = "cuda")]
Self::Cuda => vec![
ort::ep::CUDA::default().build(),
ort::ep::CPU::default().build(),
],
#[cfg(feature = "nnapi")]
Self::Nnapi => vec![
ort::ep::NNAPI::default().build(),
ort::ep::CPU::default().build(),
],
}
}
pub(crate) fn is_cpu(self) -> bool {
matches!(self, Self::Cpu)
}
}
pub struct OrtFactory {
provider: OrtExecutionProvider,
prepacked: Option<Arc<ort::session::builder::PrepackedWeights>>,
optimized_cache_dir: Option<PathBuf>,
}
impl OrtFactory {
fn with_provider(provider: OrtExecutionProvider) -> Self {
Self {
provider,
prepacked: None,
optimized_cache_dir: None,
}
}
pub fn cpu() -> Self {
Self::with_provider(OrtExecutionProvider::Cpu)
}
#[cfg(feature = "coreml")]
pub fn coreml() -> Self {
Self::with_provider(OrtExecutionProvider::CoreML)
}
#[cfg(feature = "cuda")]
pub fn cuda() -> Self {
Self::with_provider(OrtExecutionProvider::Cuda)
}
#[cfg(feature = "nnapi")]
pub fn nnapi() -> Self {
Self::with_provider(OrtExecutionProvider::Nnapi)
}
pub fn with_prepacked_weights(
mut self,
prepacked: Arc<ort::session::builder::PrepackedWeights>,
) -> Self {
self.prepacked = Some(prepacked);
self
}
pub fn with_optimized_cache_dir(mut self, dir: impl Into<PathBuf>) -> Self {
self.optimized_cache_dir = Some(dir.into());
self
}
}
static ORT_INIT: OnceLock<bool> = OnceLock::new();
fn ensure_ort_initialized() {
let initialized_by_us = ORT_INIT.get_or_init(|| ort::init().with_name("gigastt").commit());
if !initialized_by_us {
tracing::warn!(
"ort environment was already configured before gigastt initialization; execution provider settings may not apply"
);
}
}
impl RuntimeFactory for OrtFactory {
fn create(&self, intra_threads: usize) -> Result<Box<dyn Runtime>, RuntimeError> {
ensure_ort_initialized();
Ok(Box::new(OrtRuntime::new(
intra_threads,
self.provider,
self.prepacked.clone(),
self.optimized_cache_dir.clone(),
)))
}
fn cpu_fallback(&self) -> Box<dyn RuntimeFactory> {
Box::new(OrtFactory::cpu())
}
}
pub fn default_factory() -> Box<dyn RuntimeFactory> {
#[cfg(feature = "candle")]
{
Box::new(crate::runtime::candle::factory::CandleFactory::new())
}
#[cfg(all(feature = "ane", target_os = "macos"))]
{
Box::new(crate::runtime::coreml::factory::AneFactory::new())
}
#[cfg(not(any(feature = "candle", all(feature = "ane", target_os = "macos"))))]
{
#[cfg(feature = "coreml")]
{
Box::new(OrtFactory::coreml())
}
#[cfg(all(feature = "cuda", not(feature = "coreml")))]
{
Box::new(OrtFactory::cuda())
}
#[cfg(all(feature = "nnapi", not(feature = "coreml"), not(feature = "cuda")))]
{
Box::new(OrtFactory::nnapi())
}
#[cfg(not(any(feature = "coreml", feature = "cuda", feature = "nnapi")))]
{
Box::new(OrtFactory::cpu())
}
}
}
pub fn cpu_factory() -> Box<dyn RuntimeFactory> {
Box::new(OrtFactory::cpu())
}
pub fn production_factory(model_dir: &Path) -> Box<dyn RuntimeFactory> {
production_factory_variant(
model_dir,
crate::model::ModelVariant::detect_in_dir(model_dir),
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum BackendKind {
Ort,
Candle,
Ane,
}
pub(crate) fn select_backend(variant: Option<crate::model::ModelVariant>) -> BackendKind {
let is_rnnt = variant == Some(crate::model::ModelVariant::Rnnt);
#[cfg(feature = "candle")]
if is_rnnt {
return BackendKind::Candle;
}
#[cfg(all(feature = "ane", target_os = "macos"))]
if is_rnnt {
return BackendKind::Ane;
}
let _ = is_rnnt;
BackendKind::Ort
}
pub(crate) fn production_factory_variant(
model_dir: &Path,
variant: Option<crate::model::ModelVariant>,
) -> Box<dyn RuntimeFactory> {
let backend = select_backend(variant);
#[cfg(feature = "candle")]
if backend == BackendKind::Candle {
return Box::new(crate::runtime::candle::factory::CandleFactory::new());
}
#[cfg(all(feature = "ane", target_os = "macos"))]
if backend == BackendKind::Ane {
return Box::new(crate::runtime::coreml::factory::AneFactory::new());
}
let _ = backend;
#[cfg(feature = "coreml")]
let factory = OrtFactory::coreml();
#[cfg(all(feature = "cuda", not(feature = "coreml")))]
let factory = OrtFactory::cuda();
#[cfg(not(any(feature = "coreml", feature = "cuda")))]
let factory = {
let prepacked = std::sync::Arc::new(ort::session::builder::PrepackedWeights::new());
OrtFactory::cpu()
.with_optimized_cache_dir(model_dir.join("optimized_cache"))
.with_prepacked_weights(prepacked)
};
#[cfg(any(feature = "coreml", feature = "cuda"))]
let _ = model_dir;
Box::new(factory)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::ModelVariant;
#[test]
fn select_backend_non_rnnt_and_none_are_always_ort() {
assert_eq!(
select_backend(Some(ModelVariant::E2eRnnt)),
BackendKind::Ort
);
assert_eq!(select_backend(Some(ModelVariant::MlCtc)), BackendKind::Ort);
assert_eq!(
select_backend(Some(ModelVariant::MlCtcLarge)),
BackendKind::Ort
);
assert_eq!(select_backend(None), BackendKind::Ort);
}
#[cfg(not(any(feature = "candle", all(feature = "ane", target_os = "macos"))))]
#[test]
fn select_backend_rnnt_is_ort_without_accelerated_backend() {
assert_eq!(select_backend(Some(ModelVariant::Rnnt)), BackendKind::Ort);
}
#[test]
fn test_cpu_factory_can_attach_prepacked_weights() {
let pw = std::sync::Arc::new(ort::session::builder::PrepackedWeights::new());
let f = OrtFactory::cpu().with_prepacked_weights(pw);
let rt = f.create(1).expect("cpu runtime with prepacked");
drop(rt);
}
#[cfg(feature = "candle")]
#[test]
fn select_backend_candle_only_for_rnnt() {
assert_eq!(
select_backend(Some(ModelVariant::Rnnt)),
BackendKind::Candle
);
assert_eq!(
select_backend(Some(ModelVariant::E2eRnnt)),
BackendKind::Ort
);
assert_eq!(select_backend(Some(ModelVariant::MlCtc)), BackendKind::Ort);
assert_eq!(select_backend(None), BackendKind::Ort);
}
#[cfg(all(feature = "ane", target_os = "macos"))]
#[test]
fn select_backend_ane_only_for_rnnt() {
assert_eq!(select_backend(Some(ModelVariant::Rnnt)), BackendKind::Ane);
assert_eq!(
select_backend(Some(ModelVariant::E2eRnnt)),
BackendKind::Ort
);
assert_eq!(select_backend(None), BackendKind::Ort);
}
}