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    /// Pin a symbol for the coming turn. A pulsed plan needs the stream length
79    /// on the turn carrying the last, partial pulse: the input tensor is padded
80    /// to a full pulse, so its shape can not carry it.
81    fn resolve_symbol(&mut self, symbol: &Symbol, value: i64) -> TractResult<()> {
82        let _ = (symbol, value);
83        bail!("{self:?} can not resolve a symbol")
84    }
85
86    /// Seat the lanes carrying the coming turn's streams, one lane per row of
87    /// axis 0 of its tensors. A turn seating more than one lane needs axis 0 of
88    /// every stateful node to be the model's batch axis.
89    fn seat(&mut self, seating: Seating) -> TractResult<()> {
90        let _ = seating;
91        bail!("{self:?} can not seat lanes")
92    }
93
94    /// Drop the session state `lanes` hold, handing them to new streams.
95    fn reset_lanes(&mut self, lanes: &[LaneId]) -> TractResult<()> {
96        let _ = lanes;
97        bail!("{self:?} can not reset lanes")
98    }
99
100    fn runnable(&self) -> &dyn Runnable;
101
102    fn input_count(&self) -> usize {
103        self.runnable().input_count()
104    }
105
106    fn output_count(&self) -> usize {
107        self.runnable().output_count()
108    }
109}
110impl_downcast!(State);
111dyn_clone::clone_trait_object!(State);
112
113#[derive(Debug)]
114pub struct DefaultRuntime;
115
116impl Runtime for DefaultRuntime {
117    fn name(&self) -> StaticName {
118        Cow::Borrowed("cpu")
119    }
120
121    fn prepare_with_options(
122        &self,
123        model: TypedModel,
124        options: &RunOptions,
125    ) -> TractResult<Box<dyn Runnable>> {
126        let model = model.into_optimized()?;
127        Ok(Box::new(TypedSimplePlan::new_with_options(model, options)?))
128    }
129
130    fn check(&self) -> TractResult<()> {
131        Ok(())
132    }
133}
134
135impl Runnable for Arc<TypedRunnableModel> {
136    fn spawn(&self) -> TractResult<Box<dyn State>> {
137        Ok(Box::new(self.spawn()?))
138    }
139
140    fn typed_plan(&self) -> Option<&Self> {
141        Some(self)
142    }
143
144    fn typed_model(&self) -> Option<&Arc<TypedModel>> {
145        Some(&self.model)
146    }
147
148    fn input_count(&self) -> usize {
149        self.model.inputs.len()
150    }
151
152    fn output_count(&self) -> usize {
153        self.model.outputs.len()
154    }
155
156    fn input_fact(&self, ix: usize) -> TractResult<&TypedFact> {
157        self.model.input_fact(ix)
158    }
159    fn output_fact(&self, ix: usize) -> TractResult<&TypedFact> {
160        self.model.output_fact(ix)
161    }
162}
163
164impl State for TypedSimpleState {
165    fn run(&mut self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
166        self.run(inputs)
167    }
168
169    fn resolve_symbol(&mut self, symbol: &Symbol, value: i64) -> TractResult<()> {
170        self.turn_state.resolved_symbols.set(symbol, value);
171        Ok(())
172    }
173
174    fn seat(&mut self, seating: Seating) -> TractResult<()> {
175        self.seat(seating);
176        Ok(())
177    }
178
179    fn reset_lanes(&mut self, lanes: &[LaneId]) -> TractResult<()> {
180        self.reset_lanes(lanes)
181    }
182
183    fn runnable(&self) -> &dyn Runnable {
184        &self.plan
185    }
186}
187
188pub struct InventorizedRuntime(pub &'static dyn Runtime);
189
190impl Runtime for InventorizedRuntime {
191    fn name(&self) -> StaticName {
192        self.0.name()
193    }
194
195    fn prepare_with_options(
196        &self,
197        model: TypedModel,
198        options: &RunOptions,
199    ) -> TractResult<Box<dyn Runnable>> {
200        self.0.prepare_with_options(model, options)
201    }
202
203    fn check(&self) -> TractResult<()> {
204        self.0.check()
205    }
206}
207
208impl Debug for InventorizedRuntime {
209    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210        self.0.fmt(f)
211    }
212}
213
214inventory::collect!(InventorizedRuntime);
215
216pub fn runtimes() -> impl Iterator<Item = &'static dyn Runtime> {
217    inventory::iter::<InventorizedRuntime>().filter(|rt| rt.check().is_ok()).map(|ir| ir.0)
218}
219
220/// Known GPU backends, tried in order when resolving the virtual `gpu`
221/// (strict) / `gpu-or-cpu` (best-effort) names.
222const GPU_RUNTIME_NAMES: &[&str] = &["metal", "cuda"];
223
224pub fn runtime_for_name(s: &str) -> TractResult<Option<&'static dyn Runtime>> {
225    // Back-compat: `default` was the original name for the CPU runtime
226    // before it was renamed.  Keep it working as a plain alias.
227    let s = if s == "default" { "cpu" } else { s };
228    if s == "gpu" || s == "gpu-or-cpu" {
229        let mut last_check_err: Option<TractError> = None;
230        for name in GPU_RUNTIME_NAMES {
231            let Some(rt) = inventory::iter::<InventorizedRuntime>().find(|rt| rt.name() == *name)
232            else {
233                continue;
234            };
235            match rt.check() {
236                Ok(()) => return Ok(Some(rt.0)),
237                Err(e) => last_check_err = Some(e),
238            }
239        }
240        if s == "gpu" {
241            let detail =
242                last_check_err.map(|e| format!(" (last backend error: {e:#})")).unwrap_or_default();
243            bail!("Runtime `gpu` requested but no GPU backend is available{detail}");
244        }
245        // gpu-or-cpu: fall through to the cpu runtime.
246        return runtime_for_name("cpu");
247    }
248    rule_if_some!(rt = inventory::iter::<InventorizedRuntime>().find(|rt| rt.name() == s));
249    rt.check()?;
250    Ok(Some(rt.0))
251}
252
253#[macro_export]
254macro_rules! register_runtime {
255    ($type: ty= $val:expr) => {
256        static D: $type = $val;
257        inventory::submit! { $crate::runtime::InventorizedRuntime(&D) }
258    };
259}
260
261register_runtime!(DefaultRuntime = DefaultRuntime);