gigastt_core/runtime/ort/
factory.rs1#![allow(dead_code)]
2
3use std::path::{Path, PathBuf};
4use std::sync::{Arc, OnceLock};
5
6use crate::runtime::{
7 error::RuntimeError,
8 factory::{Runtime, RuntimeFactory},
9};
10
11use super::session::OrtRuntime;
12
13#[cfg(all(feature = "coreml", feature = "cuda"))]
14compile_error!("features `coreml` and `cuda` are mutually exclusive");
15
16#[derive(Clone, Copy)]
25pub enum OrtExecutionProvider {
26 Cpu,
27 #[cfg(feature = "coreml")]
28 CoreML,
29 #[cfg(feature = "cuda")]
30 Cuda,
31 #[cfg(feature = "nnapi")]
32 Nnapi,
33}
34
35impl OrtExecutionProvider {
36 pub(crate) fn execution_providers(
41 self,
42 model_path: &Path,
43 ) -> Vec<ort::ep::ExecutionProviderDispatch> {
44 #[cfg(not(feature = "coreml"))]
50 let _ = model_path;
51 match self {
52 Self::Cpu => vec![ort::ep::CPU::default().build()],
53 #[cfg(feature = "coreml")]
54 Self::CoreML => {
55 let cache_dir = match model_path.parent() {
61 Some(p) => crate::model::coreml_cache_dir(p),
62 None => crate::model::coreml_cache_dir(Path::new(".")),
63 };
64 let coreml_ep = ort::ep::CoreML::default()
65 .with_model_format(ort::ep::coreml::ModelFormat::MLProgram)
66 .with_static_input_shapes(true)
67 .with_compute_units(ort::ep::coreml::ComputeUnits::CPUAndNeuralEngine)
68 .with_specialization_strategy(
69 ort::ep::coreml::SpecializationStrategy::FastPrediction,
70 )
71 .with_model_cache_dir(cache_dir.to_string_lossy())
72 .build();
73 vec![coreml_ep, ort::ep::CPU::default().build()]
74 }
75 #[cfg(feature = "cuda")]
76 Self::Cuda => vec![
77 ort::ep::CUDA::default().build(),
78 ort::ep::CPU::default().build(),
79 ],
80 #[cfg(feature = "nnapi")]
81 Self::Nnapi => vec![
82 ort::ep::NNAPI::default().build(),
83 ort::ep::CPU::default().build(),
84 ],
85 }
86 }
87
88 pub(crate) fn is_cpu(self) -> bool {
90 matches!(self, Self::Cpu)
91 }
92}
93
94pub struct OrtFactory {
96 provider: OrtExecutionProvider,
97 prepacked: Option<Arc<ort::session::builder::PrepackedWeights>>,
98 optimized_cache_dir: Option<PathBuf>,
99}
100
101impl OrtFactory {
102 fn with_provider(provider: OrtExecutionProvider) -> Self {
103 Self {
104 provider,
105 prepacked: None,
106 optimized_cache_dir: None,
107 }
108 }
109
110 pub fn cpu() -> Self {
111 Self::with_provider(OrtExecutionProvider::Cpu)
112 }
113
114 #[cfg(feature = "coreml")]
115 pub fn coreml() -> Self {
116 Self::with_provider(OrtExecutionProvider::CoreML)
117 }
118
119 #[cfg(feature = "cuda")]
120 pub fn cuda() -> Self {
121 Self::with_provider(OrtExecutionProvider::Cuda)
122 }
123
124 #[cfg(feature = "nnapi")]
125 pub fn nnapi() -> Self {
126 Self::with_provider(OrtExecutionProvider::Nnapi)
127 }
128
129 pub fn with_prepacked_weights(
130 mut self,
131 prepacked: Arc<ort::session::builder::PrepackedWeights>,
132 ) -> Self {
133 self.prepacked = Some(prepacked);
134 self
135 }
136
137 pub fn with_optimized_cache_dir(mut self, dir: impl Into<PathBuf>) -> Self {
138 self.optimized_cache_dir = Some(dir.into());
139 self
140 }
141}
142
143static ORT_INIT: OnceLock<bool> = OnceLock::new();
144
145fn ensure_ort_initialized() {
146 let initialized_by_us = ORT_INIT.get_or_init(|| ort::init().with_name("gigastt").commit());
147 if !initialized_by_us {
148 tracing::warn!(
149 "ort environment was already configured before gigastt initialization; execution provider settings may not apply"
150 );
151 }
152}
153
154impl RuntimeFactory for OrtFactory {
155 fn create(&self, intra_threads: usize) -> Result<Box<dyn Runtime>, RuntimeError> {
156 ensure_ort_initialized();
157 Ok(Box::new(OrtRuntime::new(
158 intra_threads,
159 self.provider,
160 self.prepacked.clone(),
161 self.optimized_cache_dir.clone(),
162 )))
163 }
164
165 fn cpu_fallback(&self) -> Box<dyn RuntimeFactory> {
166 Box::new(OrtFactory::cpu())
167 }
168}
169
170pub fn default_factory() -> Box<dyn RuntimeFactory> {
183 #[cfg(feature = "candle")]
184 {
185 Box::new(crate::runtime::candle::factory::CandleFactory::new())
186 }
187 #[cfg(all(feature = "ane", target_os = "macos"))]
188 {
189 Box::new(crate::runtime::coreml::factory::AneFactory::new())
190 }
191 #[cfg(not(any(feature = "candle", all(feature = "ane", target_os = "macos"))))]
198 {
199 #[cfg(feature = "coreml")]
200 {
201 Box::new(OrtFactory::coreml())
202 }
203 #[cfg(all(feature = "cuda", not(feature = "coreml")))]
204 {
205 Box::new(OrtFactory::cuda())
206 }
207 #[cfg(all(feature = "nnapi", not(feature = "coreml"), not(feature = "cuda")))]
208 {
209 Box::new(OrtFactory::nnapi())
210 }
211 #[cfg(not(any(feature = "coreml", feature = "cuda", feature = "nnapi")))]
212 {
213 Box::new(OrtFactory::cpu())
214 }
215 }
216}
217
218pub fn cpu_factory() -> Box<dyn RuntimeFactory> {
220 Box::new(OrtFactory::cpu())
221}
222
223pub fn production_factory(model_dir: &Path) -> Box<dyn RuntimeFactory> {
231 production_factory_variant(
232 model_dir,
233 crate::model::ModelVariant::detect_in_dir(model_dir),
234 )
235}
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq)]
239pub(crate) enum BackendKind {
240 Ort,
242 Candle,
244 Ane,
246}
247
248pub(crate) fn select_backend(variant: Option<crate::model::ModelVariant>) -> BackendKind {
254 let is_rnnt = variant == Some(crate::model::ModelVariant::Rnnt);
255 #[cfg(feature = "candle")]
256 if is_rnnt {
257 return BackendKind::Candle;
258 }
259 #[cfg(all(feature = "ane", target_os = "macos"))]
260 if is_rnnt {
261 return BackendKind::Ane;
262 }
263 let _ = is_rnnt;
264 BackendKind::Ort
265}
266
267pub(crate) fn production_factory_variant(
275 model_dir: &Path,
276 variant: Option<crate::model::ModelVariant>,
277) -> Box<dyn RuntimeFactory> {
278 let backend = select_backend(variant);
279 #[cfg(feature = "candle")]
283 if backend == BackendKind::Candle {
284 return Box::new(crate::runtime::candle::factory::CandleFactory::new());
285 }
286 #[cfg(all(feature = "ane", target_os = "macos"))]
287 if backend == BackendKind::Ane {
288 return Box::new(crate::runtime::coreml::factory::AneFactory::new());
289 }
290 let _ = backend;
291
292 #[cfg(feature = "coreml")]
298 let factory = OrtFactory::coreml();
299 #[cfg(all(feature = "cuda", not(feature = "coreml")))]
300 let factory = OrtFactory::cuda();
301 #[cfg(not(any(feature = "coreml", feature = "cuda")))]
302 let factory = {
303 let prepacked = std::sync::Arc::new(ort::session::builder::PrepackedWeights::new());
309 OrtFactory::cpu()
310 .with_optimized_cache_dir(model_dir.join("optimized_cache"))
311 .with_prepacked_weights(prepacked)
312 };
313 #[cfg(any(feature = "coreml", feature = "cuda"))]
314 let _ = model_dir;
315 Box::new(factory)
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use crate::model::ModelVariant;
322
323 #[test]
328 fn select_backend_non_rnnt_and_none_are_always_ort() {
329 assert_eq!(
330 select_backend(Some(ModelVariant::E2eRnnt)),
331 BackendKind::Ort
332 );
333 assert_eq!(select_backend(Some(ModelVariant::MlCtc)), BackendKind::Ort);
334 assert_eq!(
335 select_backend(Some(ModelVariant::MlCtcLarge)),
336 BackendKind::Ort
337 );
338 assert_eq!(select_backend(None), BackendKind::Ort);
339 }
340
341 #[cfg(not(any(feature = "candle", all(feature = "ane", target_os = "macos"))))]
344 #[test]
345 fn select_backend_rnnt_is_ort_without_accelerated_backend() {
346 assert_eq!(select_backend(Some(ModelVariant::Rnnt)), BackendKind::Ort);
347 }
348
349 #[test]
350 fn test_cpu_factory_can_attach_prepacked_weights() {
351 let pw = std::sync::Arc::new(ort::session::builder::PrepackedWeights::new());
352 let f = OrtFactory::cpu().with_prepacked_weights(pw);
353 let rt = f.create(1).expect("cpu runtime with prepacked");
355 drop(rt);
356 }
357
358 #[cfg(feature = "candle")]
361 #[test]
362 fn select_backend_candle_only_for_rnnt() {
363 assert_eq!(
364 select_backend(Some(ModelVariant::Rnnt)),
365 BackendKind::Candle
366 );
367 assert_eq!(
368 select_backend(Some(ModelVariant::E2eRnnt)),
369 BackendKind::Ort
370 );
371 assert_eq!(select_backend(Some(ModelVariant::MlCtc)), BackendKind::Ort);
372 assert_eq!(select_backend(None), BackendKind::Ort);
373 }
374
375 #[cfg(all(feature = "ane", target_os = "macos"))]
377 #[test]
378 fn select_backend_ane_only_for_rnnt() {
379 assert_eq!(select_backend(Some(ModelVariant::Rnnt)), BackendKind::Ane);
380 assert_eq!(
381 select_backend(Some(ModelVariant::E2eRnnt)),
382 BackendKind::Ort
383 );
384 assert_eq!(select_backend(None), BackendKind::Ort);
385 }
386}