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
42    /// The mnemonic this runnable is known by, from its model's [`TRACT_NAME`]
43    /// property.
44    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    /// Pin a symbol for the coming turn. A pulsed plan needs the stream length
86    /// on the turn carrying the last, partial pulse: the input tensor is padded
87    /// to a full pulse, so its shape can not carry it.
88    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    /// Seat the lanes carrying the coming turn's streams, one lane per seat of
94    /// axis 0 of its tensors. A turn seating more than one lane needs axis 0 of
95    /// every stateful node to be the model's batch axis.
96    fn seat(&mut self, seating: Seating) -> TractResult<()> {
97        let _ = seating;
98        bail!("{self:?} can not seat lanes")
99    }
100
101    /// Drop the session state `lanes` hold, handing them to new streams.
102    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
227/// Known GPU backends, tried in order when resolving the virtual `gpu`
228/// (strict) / `gpu-or-cpu` (best-effort) names.
229const GPU_RUNTIME_NAMES: &[&str] = &["metal", "cuda"];
230
231pub fn runtime_for_name(s: &str) -> TractResult<Option<&'static dyn Runtime>> {
232    // Back-compat: `default` was the original name for the CPU runtime
233    // before it was renamed.  Keep it working as a plain alias.
234    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        // gpu-or-cpu: fall through to the cpu runtime.
253        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);