Skip to main content

tract_core/ops/scan/
optimized.rs

1use super::*;
2use tract_data::internal::*;
3
4#[derive(Debug, Clone, new)]
5pub struct ScanOpParams {
6    pub skip: usize,
7    pub reset_every_turn: bool,
8    pub plan: Arc<TypedSimplePlan>,
9    pub input_mapping: Vec<InputMapping>,
10    pub output_mapping: Vec<OutputMapping<TDim>>,
11}
12
13#[derive(Debug, Clone, new)]
14pub struct OptScan(Arc<ScanOpParams>);
15
16impl std::ops::Deref for OptScan {
17    type Target = ScanOpParams;
18    fn deref(&self) -> &ScanOpParams {
19        &self.0
20    }
21}
22
23impl PartialEq for OptScan {
24    fn eq(&self, _other: &Self) -> bool {
25        false
26    }
27}
28impl Eq for OptScan {}
29
30impl OptScan {
31    pub fn iteration_count(&self, inputs: &[&TypedFact]) -> Option<TDim> {
32        super::iteration_count(&self.input_mapping, inputs)
33    }
34}
35
36impl Op for OptScan {
37    fn name(&self) -> StaticName {
38        "Scan".into()
39    }
40
41    fn info(&self) -> TractResult<Vec<String>> {
42        let mut lines = vec![];
43        for (ix, im) in self.input_mapping.iter().enumerate() {
44            lines.push(format!("Model input  #{ix}: {im:?}"));
45        }
46        for (ix, om) in self.output_mapping.iter().enumerate() {
47            lines.push(format!("Model output #{ix}: {om:?}"));
48        }
49        Ok(lines)
50    }
51
52    op_as_typed_op!();
53}
54
55impl EvalOp for OptScan {
56    not_out_of_plan!();
57
58    fn state(&self, _ctx: &EvalContext) -> TractResult<Option<Box<dyn OpState>>> {
59        Ok(Some(Box::new(State {
60            position: 0,
61            hidden_state: tvec!(),
62            model_state: self.plan.spawn()?,
63            op: Arc::clone(&self.0),
64        })))
65    }
66}
67
68#[derive(Clone, Debug)]
69pub struct State {
70    op: Arc<ScanOpParams>,
71    position: usize,
72    hidden_state: TVec<TValue>,
73    pub model_state: TypedSimpleState,
74}
75
76impl State {
77    pub fn iteration_count(&self, inputs: &TVec<TValue>) -> usize {
78        let (slot, info) = self
79            .op
80            .input_mapping
81            .iter()
82            .enumerate()
83            .find_map(|(ix, it)| it.as_scan().map(|scan| (ix, scan)))
84            .unwrap();
85        inputs[slot].shape()[info.axis].divceil(info.chunk.unsigned_abs())
86    }
87
88    pub(super) fn slice_input(
89        input: &Tensor,
90        axis: usize,
91        chunk_ix: usize,
92        chunk_dim: isize,
93    ) -> TractResult<Tensor> {
94        unsafe {
95            let full_len = input.shape()[axis];
96            let mut shape: TVec<usize> = input.shape().into();
97            shape[axis] = chunk_dim.unsigned_abs();
98            let mut t = Tensor::uninitialized_dt(input.datum_type(), &shape)?;
99            if chunk_dim < 0 {
100                let chunk_dim = (-chunk_dim) as usize;
101                for i in 0..chunk_dim {
102                    if chunk_dim * chunk_ix + i < full_len {
103                        let dst_ix = chunk_dim - i - 1;
104                        let src_ix = full_len - 1 - (chunk_ix * chunk_dim + i);
105                        t.assign_slice_unchecked(dst_ix..=dst_ix, input, src_ix..=src_ix, axis);
106                    }
107                }
108            } else if (chunk_ix + 1) * chunk_dim as usize > full_len {
109                let chunk_dim = chunk_dim as usize;
110                let remain = full_len - chunk_ix * chunk_dim;
111                let mut shape: TVec<usize> = input.shape().into();
112                shape[axis] = chunk_dim;
113                t.assign_slice_unchecked(..remain, input, chunk_ix * chunk_dim.., axis);
114            } else {
115                let start = chunk_dim as usize * chunk_ix;
116                let end = start + chunk_dim as usize;
117                t.assign_slice_unchecked(.., input, start..end, axis);
118            }
119            Ok(t)
120        }
121    }
122
123    pub(super) fn assign_output(
124        output: &mut Tensor,
125        axis: usize,
126        element_value: &Tensor,
127        i: usize,
128        backward: bool,
129    ) {
130        let full_len = output.shape()[axis];
131        let offset = if backward {
132            full_len - 1 - i * element_value.shape()[axis]
133        } else {
134            i * element_value.shape()[axis]
135        };
136        let count = element_value.shape()[axis].min(output.shape()[axis] - offset);
137        unsafe {
138            output.assign_slice_unchecked(offset..offset + count, element_value, ..count, axis)
139        };
140    }
141}
142
143impl OpState for State {
144    fn eval(
145        &mut self,
146        ctx: &EvalContext,
147        _op: &dyn Op,
148        inputs: TVec<TValue>,
149    ) -> TractResult<TVec<TValue>> {
150        let iters = self.iteration_count(&inputs);
151
152        let &mut State { ref op, ref mut hidden_state, ref mut position, ref mut model_state } =
153            self;
154
155        // initialize state at first pass, or when forced
156        if op.reset_every_turn {
157            hidden_state.clear()
158        }
159        if hidden_state.len() == 0 {
160            for (slot, input) in op.input_mapping.iter().enumerate() {
161                if input.is_state() {
162                    hidden_state.push(inputs[slot].clone());
163                }
164            }
165        }
166
167        // One full-coverage iteration yields each scan output as-is, so the body
168        // tensor is handed through instead of copied into a preallocation. Warm-up
169        // calls (position <= skip) leave outputs unassigned and need the general path.
170        let runs_now = *position + 1 > op.skip;
171        let mut single_shot: TVec<bool> = tvec!();
172        let mut outputs = tvec!();
173        for (ix, output) in op.output_mapping.iter().enumerate() {
174            let mut one_shot = false;
175            if let Some((slot, info)) = output.scan {
176                let fact = op.plan.model().output_fact(ix)?;
177                let mut shape: TVec<usize> = fact.shape.eval_to_usize(ctx.symbols)?.into_owned();
178                let scanning_dim = output
179                    .full_dim_hint
180                    .as_ref()
181                    .and_then(|d| d.as_usize())
182                    .unwrap_or(shape[info.axis] * iters);
183                one_shot = iters == 1 && runs_now && scanning_dim == shape[info.axis];
184                shape[info.axis] = scanning_dim;
185                let t = if one_shot {
186                    Tensor::default()
187                } else {
188                    unsafe { Tensor::uninitialized_dt(fact.datum_type, &shape)? }
189                };
190                outputs.push((slot, t));
191            }
192            single_shot.push(one_shot);
193            if let Some(slot) = output.last_value_slot {
194                outputs.push((slot, Tensor::default()));
195            }
196        }
197        outputs.sort_by_key(|a| a.0);
198        let mut outputs: TVec<Tensor> = outputs.into_iter().map(|(_slot, v)| v).collect();
199
200        // The body runs the SAME plan with the SAME shapes every iteration, so
201        // resolve its symbols once and keep them across iters (light per-iter
202        // reset), and reuse one drained input buffer. This avoids the per-timestep
203        // symbol re-resolution + reallocation a plain `model_state.run()` would do.
204        // Cleared up front since the body state persists across outer Scan calls.
205        model_state.clear_resolved_symbols();
206        let mut iter_inputs: TVec<TValue> = tvec!();
207        let mut symbols_resolved = false;
208
209        for i in 0..iters {
210            *position += 1;
211            if *position <= op.skip {
212                continue;
213            }
214            hidden_state.reverse();
215
216            iter_inputs.clear();
217            for (slot, m) in op.input_mapping.iter().enumerate() {
218                iter_inputs.push(match m {
219                    InputMapping::State => hidden_state.pop().unwrap(),
220                    InputMapping::Scan(info) => {
221                        let input = &inputs[slot];
222                        // A chunk covering the whole axis slices to the input
223                        // itself, forward or backward.
224                        if i == 0 && input.shape()[info.axis] == info.chunk.unsigned_abs() {
225                            input.clone()
226                        } else {
227                            Self::slice_input(input, info.axis, i, info.chunk)?.into_tvalue()
228                        }
229                    }
230                    InputMapping::Full => inputs[slot].clone(),
231                });
232            }
233            trace!("iter_inputs #{i}: {iter_inputs:?}");
234
235            // Lighter equivalent of `model_state.run(iter_inputs)`: resolve body
236            // symbols only on the first iteration, and reset between iters without
237            // discarding them.
238            model_state.set_inputs_drain(&mut iter_inputs).context("Setting body inputs")?;
239            if !symbols_resolved {
240                model_state.resolve_symbols_with_states()?;
241                symbols_resolved = true;
242            }
243            model_state.exec().with_context(|| "Evaluating inner body")?;
244            let iter_outputs = model_state.outputs()?;
245            model_state.reset_turn_keep_symbols();
246            trace!("iter_outputs #{i}: {iter_outputs:?}");
247
248            for (ix, (v, mapping)) in iter_outputs.into_iter().zip(&op.output_mapping).enumerate() {
249                if let Some((slot, info)) = mapping.scan {
250                    if single_shot[ix] && !mapping.state && mapping.last_value_slot.is_none() {
251                        outputs[slot] = v.into_tensor();
252                        continue;
253                    } else if single_shot[ix] {
254                        outputs[slot] = v.clone().into_tensor();
255                    } else {
256                        Self::assign_output(&mut outputs[slot], info.axis, &v, i, info.chunk < 0);
257                    }
258                }
259                if i == iters - 1
260                    && let Some(slot) = mapping.last_value_slot
261                {
262                    outputs[slot] = v.clone().into_tensor();
263                }
264                if mapping.state {
265                    hidden_state.push(v);
266                }
267            }
268        }
269
270        Ok(outputs.into_iter().map(|t| t.into_tvalue()).collect())
271    }
272
273    fn reset_lanes(&mut self, _lanes: &[LaneId]) -> TractResult<()> {
274        bail!("Scan is not lane-aware: its body is a nested state")
275    }
276}
277
278impl TypedOp for OptScan {
279    as_op!();
280
281    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
282        let mut outputs = tvec!();
283        let iters = super::iteration_count(&self.input_mapping, inputs).unwrap();
284        for (ix, output) in self.output_mapping.iter().enumerate() {
285            let fact = self.plan.model().output_fact(ix)?;
286            if let Some(slot) = output.last_value_slot {
287                outputs.push((slot, fact.datum_type.fact(fact.shape.clone())));
288            }
289            if let Some((slot, info)) = output.scan {
290                let mut shape = fact.shape.clone();
291                let scanning_dim =
292                    output.full_dim_hint.clone().unwrap_or(shape[info.axis].clone() * &iters);
293                shape.set(info.axis, scanning_dim);
294                outputs.push((slot, fact.datum_type.fact(shape)));
295            }
296        }
297        outputs.sort_by_key(|a| a.0);
298        let outputs: TVec<_> = outputs.into_iter().map(|(_slot, v)| v).collect();
299        Ok(outputs)
300    }
301
302    fn nested_model_multipliers(&self, inputs: &[&TypedFact]) -> Vec<(StaticName, TDim)> {
303        vec![(
304            "loop".into(),
305            super::iteration_count(&self.input_mapping, inputs).unwrap_or_else(|| 1.to_dim()),
306        )]
307    }
308}