Skip to main content

tract_core/
unbatched.rs

1//! A runnable answering to the contract of the model it was prepared from as
2//! that model was before batchify gave it a batch axis.
3
4use crate::internal::*;
5
6/// `inner`, prepared from a model batchified on axis 0, answering to the model
7/// as it was before: facts without the batch axis, the properties it had (its
8/// `pulse.*_axes` counting without the axis), and states that add axis 0 to
9/// the batched inputs and drop it from the batched outputs.
10#[derive(Clone, Debug)]
11pub struct UnbatchedRunnable {
12    inner: Arc<dyn Runnable>,
13    interface: Arc<Interface>,
14}
15
16/// What the runnable answers to, and which inputs and outputs carry the batch
17/// axis the states add and drop.
18#[derive(Debug)]
19struct Interface {
20    inputs: Vec<TypedFact>,
21    outputs: Vec<TypedFact>,
22    properties: HashMap<String, Arc<Tensor>>,
23    batch_in: Vec<bool>,
24    batch_out: Vec<bool>,
25}
26
27impl UnbatchedRunnable {
28    /// `inner` answering to `original`'s contract, when every input and output
29    /// of `inner` is `original`'s with one batch symbol prepended on axis 0 or
30    /// as it was, one of them batched at least. `inner` itself otherwise: no
31    /// batch axis was added, or the model changed some other way.
32    pub fn hide(inner: Box<dyn Runnable>, original: &TypedModel) -> TractResult<Box<dyn Runnable>> {
33        match Interface::of(&*inner, original)? {
34            Some(interface) => Ok(Box::new(UnbatchedRunnable {
35                inner: inner.into(),
36                interface: Arc::new(interface),
37            })),
38            None => Ok(inner),
39        }
40    }
41
42    /// The runnable as prepared, batch axis and all.
43    pub fn inner(&self) -> &Arc<dyn Runnable> {
44        &self.inner
45    }
46}
47
48impl Interface {
49    fn of(inner: &dyn Runnable, original: &TypedModel) -> TractResult<Option<Interface>> {
50        if original.inputs.len() != inner.input_count()
51            || original.outputs.len() != inner.output_count()
52        {
53            return Ok(None);
54        }
55        let mut batch: Option<TDim> = None;
56        let mut unbatched = |fact: &TypedFact, before: &TypedFact| -> Option<(TypedFact, bool)> {
57            let dims = fact.shape.dims();
58            if dims == before.shape.dims() {
59                return Some((fact.clone(), false));
60            }
61            let (first, rest) = dims.split_first()?;
62            if !matches!(first, TDim::Sym(_)) || rest != before.shape.dims() {
63                return None;
64            }
65            if batch.get_or_insert_with(|| first.clone()) != first {
66                return None;
67            }
68            let mut fact = fact.clone();
69            fact.shape = rest.into();
70            Some((fact, true))
71        };
72        let (mut inputs, mut batch_in) = (vec![], vec![]);
73        for ix in 0..inner.input_count() {
74            let Some((fact, batched)) = unbatched(inner.input_fact(ix)?, original.input_fact(ix)?)
75            else {
76                return Ok(None);
77            };
78            inputs.push(fact);
79            batch_in.push(batched);
80        }
81        let (mut outputs, mut batch_out) = (vec![], vec![]);
82        for ix in 0..inner.output_count() {
83            let Some((fact, batched)) =
84                unbatched(inner.output_fact(ix)?, original.output_fact(ix)?)
85            else {
86                return Ok(None);
87            };
88            outputs.push(fact);
89            batch_out.push(batched);
90        }
91        if batch.is_none() {
92            return Ok(None);
93        }
94        let properties = original.properties.clone();
95        Ok(Some(Interface { inputs, outputs, properties, batch_in, batch_out }))
96    }
97}
98
99impl Runnable for UnbatchedRunnable {
100    fn spawn(&self) -> TractResult<Box<dyn State>> {
101        Ok(Box::new(UnbatchedState { inner: self.inner.spawn()?, runnable: self.clone() }))
102    }
103
104    fn input_fact(&self, ix: usize) -> TractResult<&TypedFact> {
105        self.interface.inputs.get(ix).with_context(|| format!("No input {ix}"))
106    }
107
108    fn output_fact(&self, ix: usize) -> TractResult<&TypedFact> {
109        self.interface.outputs.get(ix).with_context(|| format!("No output {ix}"))
110    }
111
112    fn properties(&self) -> &HashMap<String, Arc<Tensor>> {
113        &self.interface.properties
114    }
115
116    fn typed_plan(&self) -> Option<&Arc<TypedSimplePlan>> {
117        self.inner.typed_plan()
118    }
119
120    fn typed_model(&self) -> Option<&Arc<TypedModel>> {
121        self.inner.typed_model()
122    }
123}
124
125#[derive(Clone, Debug)]
126struct UnbatchedState {
127    inner: Box<dyn State>,
128    runnable: UnbatchedRunnable,
129}
130
131/// Apply `edit` at axis 0 of the `values` flagged in `batched`.
132fn reshape_batched(
133    values: TVec<TValue>,
134    batched: &[bool],
135    edit: fn(&mut Tensor, usize) -> TractResult<()>,
136) -> TractResult<TVec<TValue>> {
137    values
138        .into_iter()
139        .enumerate()
140        .map(|(ix, value)| {
141            if !batched.get(ix).copied().unwrap_or(false) {
142                return Ok(value);
143            }
144            let mut tensor = value.into_tensor();
145            edit(&mut tensor, 0)?;
146            Ok(tensor.into_tvalue())
147        })
148        .collect()
149}
150
151impl State for UnbatchedState {
152    fn run(&mut self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
153        let interface = &self.runnable.interface;
154        let inputs = reshape_batched(inputs, &interface.batch_in, Tensor::insert_axis)?;
155        let outputs = self.inner.run(inputs)?;
156        reshape_batched(outputs, &interface.batch_out, Tensor::remove_axis)
157    }
158
159    fn resolve_symbol(&mut self, symbol: &Symbol, value: i64) -> TractResult<()> {
160        self.inner.resolve_symbol(symbol, value)
161    }
162
163    fn seat(&mut self, seating: Seating) -> TractResult<()> {
164        self.inner.seat(seating)
165    }
166
167    fn reset_lanes(&mut self, lanes: &[LaneId]) -> TractResult<()> {
168        self.inner.reset_lanes(lanes)
169    }
170
171    fn runnable(&self) -> &dyn Runnable {
172        &self.runnable
173    }
174}
175
176#[cfg(test)]
177mod test {
178    use super::*;
179    use crate::ops::math::mul;
180
181    /// `[3] * 2`, its `pulse.input_axes` claiming axis 0.
182    fn doubler() -> TractResult<TypedModel> {
183        let mut model = TypedModel::default();
184        let input = model.add_source("input", f32::fact([3]))?;
185        let two = model.add_const("two", tensor1(&[2f32]))?;
186        let doubled = model.wire_node("doubled", mul(), &[input, two])?;
187        model.select_output_outlets(&doubled)?;
188        model.properties.insert("pulse.input_axes".into(), rctensor1(&[0i64]));
189        Ok(model)
190    }
191
192    #[test]
193    fn a_batchified_model_keeps_the_contract_it_was_handed_over_with() -> TractResult<()> {
194        let model = doubler()?;
195        let batch = model.symbols.sym("B");
196        let batched = crate::batchify::batchify(&model, &batch, &[])?;
197        assert_eq!(*batched.properties["pulse.input_axes"], tensor1(&[1i64]));
198        let inner = DefaultRuntime.prepare(batched)?;
199        let runnable = UnbatchedRunnable::hide(inner, &model)?;
200        assert!(runnable.is::<UnbatchedRunnable>());
201        assert_eq!(runnable.input_fact(0)?.shape.dims(), &[3.to_dim()]);
202        assert_eq!(runnable.output_fact(0)?.shape.dims(), &[3.to_dim()]);
203        assert_eq!(*runnable.properties()["pulse.input_axes"], tensor1(&[0i64]));
204        let output = runnable.run(tvec!(tensor1(&[1f32, 2., 3.]).into_tvalue()))?;
205        assert_eq!(*output[0], tensor1(&[2f32, 4., 6.]));
206        Ok(())
207    }
208
209    #[test]
210    fn a_model_left_as_it_was_is_not_hidden() -> TractResult<()> {
211        let model = doubler()?;
212        let inner = DefaultRuntime.prepare(model.clone())?;
213        assert!(!UnbatchedRunnable::hide(inner, &model)?.is::<UnbatchedRunnable>());
214        Ok(())
215    }
216}