1use crate::internal::*;
5
6#[derive(Clone, Debug)]
11pub struct UnbatchedRunnable {
12 inner: Arc<dyn Runnable>,
13 interface: Arc<Interface>,
14}
15
16#[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 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 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
131fn 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 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}