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
42 fn name(&self) -> Option<&str> {
45 self.typed_model()?.name()
46 }
47
48 fn input_count(&self) -> usize {
49 self.typed_model().context("Fallback implementation on typed_model()").unwrap().inputs.len()
50 }
51 fn output_count(&self) -> usize {
52 self.typed_model()
53 .context("Fallback implementation on typed_model()")
54 .unwrap()
55 .outputs
56 .len()
57 }
58 fn input_fact(&self, ix: usize) -> TractResult<&TypedFact> {
59 self.typed_model()
60 .context("Fallback implementation on typed_model()")
61 .unwrap()
62 .input_fact(ix)
63 }
64 fn output_fact(&self, ix: usize) -> TractResult<&TypedFact> {
65 self.typed_model()
66 .context("Fallback implementation on typed_model()")
67 .unwrap()
68 .output_fact(ix)
69 }
70 fn properties(&self) -> &HashMap<String, Arc<Tensor>> {
71 lazy_static! {
72 static ref NO_PROPERTIES: HashMap<String, Arc<Tensor>> = Default::default();
73 };
74 self.typed_model().map(|model| &model.properties).unwrap_or(&NO_PROPERTIES)
75 }
76
77 fn typed_plan(&self) -> Option<&Arc<TypedSimplePlan>>;
78 fn typed_model(&self) -> Option<&Arc<TypedModel>>;
79}
80impl_downcast!(Runnable);
81
82pub trait State: Any + Downcast + Debug + Send + DynClone + 'static {
83 fn run(&mut self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>>;
84
85 fn resolve_symbol(&mut self, symbol: &Symbol, value: i64) -> TractResult<()> {
89 let _ = (symbol, value);
90 bail!("{self:?} can not resolve a symbol")
91 }
92
93 fn seat(&mut self, seating: Seating) -> TractResult<()> {
97 let _ = seating;
98 bail!("{self:?} can not seat lanes")
99 }
100
101 fn reset_lanes(&mut self, lanes: &[LaneId]) -> TractResult<()> {
103 let _ = lanes;
104 bail!("{self:?} can not reset lanes")
105 }
106
107 fn runnable(&self) -> &dyn Runnable;
108
109 fn input_count(&self) -> usize {
110 self.runnable().input_count()
111 }
112
113 fn output_count(&self) -> usize {
114 self.runnable().output_count()
115 }
116}
117impl_downcast!(State);
118dyn_clone::clone_trait_object!(State);
119
120#[derive(Debug)]
121pub struct DefaultRuntime;
122
123impl Runtime for DefaultRuntime {
124 fn name(&self) -> StaticName {
125 Cow::Borrowed("cpu")
126 }
127
128 fn prepare_with_options(
129 &self,
130 model: TypedModel,
131 options: &RunOptions,
132 ) -> TractResult<Box<dyn Runnable>> {
133 let model = model.into_optimized()?;
134 Ok(Box::new(TypedSimplePlan::new_with_options(model, options)?))
135 }
136
137 fn check(&self) -> TractResult<()> {
138 Ok(())
139 }
140}
141
142impl Runnable for Arc<TypedRunnableModel> {
143 fn spawn(&self) -> TractResult<Box<dyn State>> {
144 Ok(Box::new(self.spawn()?))
145 }
146
147 fn typed_plan(&self) -> Option<&Self> {
148 Some(self)
149 }
150
151 fn typed_model(&self) -> Option<&Arc<TypedModel>> {
152 Some(&self.model)
153 }
154
155 fn input_count(&self) -> usize {
156 self.model.inputs.len()
157 }
158
159 fn output_count(&self) -> usize {
160 self.model.outputs.len()
161 }
162
163 fn input_fact(&self, ix: usize) -> TractResult<&TypedFact> {
164 self.model.input_fact(ix)
165 }
166 fn output_fact(&self, ix: usize) -> TractResult<&TypedFact> {
167 self.model.output_fact(ix)
168 }
169}
170
171impl State for TypedSimpleState {
172 fn run(&mut self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
173 self.run(inputs)
174 }
175
176 fn resolve_symbol(&mut self, symbol: &Symbol, value: i64) -> TractResult<()> {
177 self.turn_state.resolved_symbols.set(symbol, value);
178 Ok(())
179 }
180
181 fn seat(&mut self, seating: Seating) -> TractResult<()> {
182 self.seat(seating);
183 Ok(())
184 }
185
186 fn reset_lanes(&mut self, lanes: &[LaneId]) -> TractResult<()> {
187 self.reset_lanes(lanes)
188 }
189
190 fn runnable(&self) -> &dyn Runnable {
191 &self.plan
192 }
193}
194
195pub struct InventorizedRuntime(pub &'static dyn Runtime);
196
197impl Runtime for InventorizedRuntime {
198 fn name(&self) -> StaticName {
199 self.0.name()
200 }
201
202 fn prepare_with_options(
203 &self,
204 model: TypedModel,
205 options: &RunOptions,
206 ) -> TractResult<Box<dyn Runnable>> {
207 self.0.prepare_with_options(model, options)
208 }
209
210 fn check(&self) -> TractResult<()> {
211 self.0.check()
212 }
213}
214
215impl Debug for InventorizedRuntime {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 self.0.fmt(f)
218 }
219}
220
221inventory::collect!(InventorizedRuntime);
222
223pub fn runtimes() -> impl Iterator<Item = &'static dyn Runtime> {
224 inventory::iter::<InventorizedRuntime>().filter(|rt| rt.check().is_ok()).map(|ir| ir.0)
225}
226
227const GPU_RUNTIME_NAMES: &[&str] = &["metal", "cuda"];
230
231pub fn runtime_for_name(s: &str) -> TractResult<Option<&'static dyn Runtime>> {
232 let s = if s == "default" { "cpu" } else { s };
235 if s == "gpu" || s == "gpu-or-cpu" {
236 let mut last_check_err: Option<TractError> = None;
237 for name in GPU_RUNTIME_NAMES {
238 let Some(rt) = inventory::iter::<InventorizedRuntime>().find(|rt| rt.name() == *name)
239 else {
240 continue;
241 };
242 match rt.check() {
243 Ok(()) => return Ok(Some(rt.0)),
244 Err(e) => last_check_err = Some(e),
245 }
246 }
247 if s == "gpu" {
248 let detail =
249 last_check_err.map(|e| format!(" (last backend error: {e:#})")).unwrap_or_default();
250 bail!("Runtime `gpu` requested but no GPU backend is available{detail}");
251 }
252 return runtime_for_name("cpu");
254 }
255 rule_if_some!(rt = inventory::iter::<InventorizedRuntime>().find(|rt| rt.name() == s));
256 rt.check()?;
257 Ok(Some(rt.0))
258}
259
260#[macro_export]
261macro_rules! register_runtime {
262 ($type: ty= $val:expr) => {
263 static D: $type = $val;
264 inventory::submit! { $crate::runtime::InventorizedRuntime(&D) }
265 };
266}
267
268register_runtime!(DefaultRuntime = DefaultRuntime);