1use std::any::Any;
2use std::fmt::Debug;
3
4use downcast_rs::Downcast;
5use dyn_clone::DynClone;
6use lazy_static::lazy_static;
7use tract_linalg::multithread::Executor;
8
9use crate::internal::*;
10
11#[derive(Clone, Debug, Default)]
12pub struct RunOptions {
13 pub skip_order_opt_ram: bool,
15
16 pub executor: Option<Executor>,
18
19 pub memory_sizing_hints: Option<SymbolValues>,
21}
22
23pub trait Runtime: Debug + Send + Sync + 'static {
24 fn name(&self) -> StaticName;
25 fn prepare(&self, model: TypedModel) -> TractResult<Box<dyn Runnable>> {
26 self.prepare_with_options(model, &Default::default())
27 }
28 fn check(&self) -> TractResult<()>;
29 fn prepare_with_options(
30 &self,
31 model: TypedModel,
32 options: &RunOptions,
33 ) -> TractResult<Box<dyn Runnable>>;
34}
35
36pub trait Runnable: Any + Downcast + Debug + Send + Sync + 'static {
37 fn run(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
38 self.spawn()?.run(inputs)
39 }
40 fn spawn(&self) -> TractResult<Box<dyn State>>;
41 fn input_count(&self) -> usize {
42 self.typed_model().context("Fallback implementation on typed_model()").unwrap().inputs.len()
43 }
44 fn output_count(&self) -> usize {
45 self.typed_model()
46 .context("Fallback implementation on typed_model()")
47 .unwrap()
48 .outputs
49 .len()
50 }
51 fn input_fact(&self, ix: usize) -> TractResult<&TypedFact> {
52 self.typed_model()
53 .context("Fallback implementation on typed_model()")
54 .unwrap()
55 .input_fact(ix)
56 }
57 fn output_fact(&self, ix: usize) -> TractResult<&TypedFact> {
58 self.typed_model()
59 .context("Fallback implementation on typed_model()")
60 .unwrap()
61 .output_fact(ix)
62 }
63 fn properties(&self) -> &HashMap<String, Arc<Tensor>> {
64 lazy_static! {
65 static ref NO_PROPERTIES: HashMap<String, Arc<Tensor>> = Default::default();
66 };
67 self.typed_model().map(|model| &model.properties).unwrap_or(&NO_PROPERTIES)
68 }
69
70 fn typed_plan(&self) -> Option<&Arc<TypedSimplePlan>>;
71 fn typed_model(&self) -> Option<&Arc<TypedModel>>;
72}
73impl_downcast!(Runnable);
74
75pub trait State: Any + Downcast + Debug + Send + DynClone + 'static {
76 fn run(&mut self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>>;
77
78 fn runnable(&self) -> &dyn Runnable;
79
80 fn input_count(&self) -> usize {
81 self.runnable().input_count()
82 }
83
84 fn output_count(&self) -> usize {
85 self.runnable().output_count()
86 }
87
88 fn freeze(&self) -> Box<dyn FrozenState>;
89 fn freeze_into(self: Box<Self>) -> Box<dyn FrozenState> {
91 self.freeze()
92 }
93}
94impl_downcast!(State);
95dyn_clone::clone_trait_object!(State);
96
97pub trait FrozenState: Any + Debug + DynClone + Send {
98 fn unfreeze(&self) -> Box<dyn State>;
99 fn input_count(&self) -> usize;
100 fn output_count(&self) -> usize;
101}
102dyn_clone::clone_trait_object!(FrozenState);
103
104#[derive(Debug)]
105pub struct DefaultRuntime;
106
107impl Runtime for DefaultRuntime {
108 fn name(&self) -> StaticName {
109 Cow::Borrowed("cpu")
110 }
111
112 fn prepare_with_options(
113 &self,
114 model: TypedModel,
115 options: &RunOptions,
116 ) -> TractResult<Box<dyn Runnable>> {
117 let model = model.into_optimized()?;
118 Ok(Box::new(TypedSimplePlan::new_with_options(model, options)?))
119 }
120
121 fn check(&self) -> TractResult<()> {
122 Ok(())
123 }
124}
125
126impl Runnable for Arc<TypedRunnableModel> {
127 fn spawn(&self) -> TractResult<Box<dyn State>> {
128 Ok(Box::new(self.spawn()?))
129 }
130
131 fn typed_plan(&self) -> Option<&Self> {
132 Some(self)
133 }
134
135 fn typed_model(&self) -> Option<&Arc<TypedModel>> {
136 Some(&self.model)
137 }
138
139 fn input_count(&self) -> usize {
140 self.model.inputs.len()
141 }
142
143 fn output_count(&self) -> usize {
144 self.model.outputs.len()
145 }
146
147 fn input_fact(&self, ix: usize) -> TractResult<&TypedFact> {
148 self.model.input_fact(ix)
149 }
150 fn output_fact(&self, ix: usize) -> TractResult<&TypedFact> {
151 self.model.output_fact(ix)
152 }
153}
154
155impl State for TypedSimpleState {
156 fn run(&mut self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
157 self.run(inputs)
158 }
159
160 fn runnable(&self) -> &dyn Runnable {
161 &self.plan
162 }
163
164 fn freeze(&self) -> Box<dyn FrozenState> {
165 Box::new(TypedSimpleState::freeze(self))
166 }
167
168 fn freeze_into(self: Box<Self>) -> Box<dyn FrozenState> {
169 Box::new(TypedSimpleState::freeze_into(*self))
170 }
171}
172
173impl FrozenState for TypedFrozenSimpleState {
174 fn unfreeze(&self) -> Box<dyn State> {
175 Box::new(TypedFrozenSimpleState::unfreeze(self))
176 }
177
178 fn input_count(&self) -> usize {
179 self.plan().model().input_outlets().unwrap().len()
180 }
181
182 fn output_count(&self) -> usize {
183 self.plan().model().output_outlets().unwrap().len()
184 }
185}
186
187pub struct InventorizedRuntime(pub &'static dyn Runtime);
188
189impl Runtime for InventorizedRuntime {
190 fn name(&self) -> StaticName {
191 self.0.name()
192 }
193
194 fn prepare_with_options(
195 &self,
196 model: TypedModel,
197 options: &RunOptions,
198 ) -> TractResult<Box<dyn Runnable>> {
199 self.0.prepare_with_options(model, options)
200 }
201
202 fn check(&self) -> TractResult<()> {
203 self.0.check()
204 }
205}
206
207impl Debug for InventorizedRuntime {
208 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209 self.0.fmt(f)
210 }
211}
212
213inventory::collect!(InventorizedRuntime);
214
215pub fn runtimes() -> impl Iterator<Item = &'static dyn Runtime> {
216 inventory::iter::<InventorizedRuntime>().filter(|rt| rt.check().is_ok()).map(|ir| ir.0)
217}
218
219const GPU_RUNTIME_NAMES: &[&str] = &["metal", "cuda"];
222
223pub fn runtime_for_name(s: &str) -> TractResult<Option<&'static dyn Runtime>> {
224 let s = if s == "default" { "cpu" } else { s };
227 if s == "gpu" || s == "gpu-or-cpu" {
228 let mut last_check_err: Option<TractError> = None;
229 for name in GPU_RUNTIME_NAMES {
230 let Some(rt) = inventory::iter::<InventorizedRuntime>().find(|rt| rt.name() == *name)
231 else {
232 continue;
233 };
234 match rt.check() {
235 Ok(()) => return Ok(Some(rt.0)),
236 Err(e) => last_check_err = Some(e),
237 }
238 }
239 if s == "gpu" {
240 let detail =
241 last_check_err.map(|e| format!(" (last backend error: {e:#})")).unwrap_or_default();
242 bail!("Runtime `gpu` requested but no GPU backend is available{detail}");
243 }
244 return runtime_for_name("cpu");
246 }
247 rule_if_some!(rt = inventory::iter::<InventorizedRuntime>().find(|rt| rt.name() == s));
248 rt.check()?;
249 Ok(Some(rt.0))
250}
251
252#[macro_export]
253macro_rules! register_runtime {
254 ($type: ty= $val:expr) => {
255 static D: $type = $val;
256 inventory::submit! { $crate::runtime::InventorizedRuntime(&D) }
257 };
258}
259
260register_runtime!(DefaultRuntime = DefaultRuntime);