Skip to main content

tract_core/
runtime.rs

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    /// Use the simple ordering instead of the newer memory friendly one
14    pub skip_order_opt_ram: bool,
15
16    /// Override default global executor
17    pub executor: Option<Executor>,
18
19    /// Memory sizing hints
20    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    /// Consuming freeze: moves data instead of cloning.
90    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
219/// Known GPU backends, tried in order when resolving the virtual `gpu`
220/// (strict) / `gpu-or-cpu` (best-effort) names.
221const GPU_RUNTIME_NAMES: &[&str] = &["metal", "cuda"];
222
223pub fn runtime_for_name(s: &str) -> TractResult<Option<&'static dyn Runtime>> {
224    // Back-compat: `default` was the original name for the CPU runtime
225    // before it was renamed.  Keep it working as a plain alias.
226    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        // gpu-or-cpu: fall through to the cpu runtime.
245        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);