Skip to main content

tract_core/ops/array/
tile.rs

1use crate::internal::*;
2
3use super::MultiBroadcastTo;
4
5#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]
6pub struct Tile {
7    pub multipliers: TVec<TDim>,
8}
9
10impl Op for Tile {
11    fn name(&self) -> StaticName {
12        "Tile".into()
13    }
14
15    fn info(&self) -> TractResult<Vec<String>> {
16        Ok(vec![format!("multipliers: {:?}", self.multipliers)])
17    }
18
19    op_as_typed_op!();
20}
21
22impl EvalOp for Tile {
23    op_out_of_plan!();
24
25    fn eval(&self, ctx: &EvalContext, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
26        let multipliers: TVec<usize> = self
27            .multipliers
28            .iter()
29            .map(|m| m.eval(ctx.symbols).to_usize())
30            .collect::<Result<_, _>>()?;
31        Ok(tvec!(tile(&inputs[0], &multipliers)?))
32    }
33}
34
35impl TypedOp for Tile {
36    as_op!();
37
38    fn set_symbols(
39        &self,
40        _source: &TypedModel,
41        node: &TypedNode,
42        target: &mut TypedModel,
43        mapping: &HashMap<OutletId, OutletId>,
44        subs: &HashMap<Symbol, TDim>,
45    ) -> TractResult<TVec<OutletId>> {
46        let multipliers =
47            self.multipliers.iter().map(|m| m.substitute_all(subs)).collect::<TractResult<_>>()?;
48        target.wire_node(&node.name, Self { multipliers }, &[mapping[&node.inputs[0]]])
49    }
50
51    fn declutter(
52        &self,
53        model: &TypedModel,
54        node: &TypedNode,
55    ) -> TractResult<Option<TypedModelPatch>> {
56        let input_fact = model.outlet_fact(node.inputs[0])?;
57        if input_fact
58            .shape
59            .iter()
60            .zip(self.multipliers.iter())
61            .all(|(i, m)| i.is_one() || m.is_one())
62        {
63            let output_fact = self.output_facts(&[input_fact])?.remove(0);
64            TypedModelPatch::replace_single_op(
65                model,
66                node,
67                &node.inputs[0..1],
68                MultiBroadcastTo { shape: output_fact.shape },
69            )
70            .map(Some)
71        } else {
72            Ok(None)
73        }
74    }
75
76    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
77        let shape = inputs[0]
78            .shape
79            .iter()
80            .zip(self.multipliers.iter())
81            .map(|(a, b)| a.clone() * b)
82            .collect::<TVec<_>>();
83        Ok(tvec!(inputs[0].datum_type.fact(shape)))
84    }
85}
86
87#[derive(Debug, Clone, Hash, PartialEq, Eq)]
88pub struct DynTile {
89    pub multiplier_placeholders: TVec<TDim>,
90}
91
92impl DynTile {
93    pub fn new(scope: &SymbolScope, rank: usize) -> DynTile {
94        let multiplier_placeholders =
95            (0..rank).map(|_| scope.new_with_prefix("_tile_mult_").to_dim()).collect();
96        DynTile { multiplier_placeholders }
97    }
98}
99
100impl Op for DynTile {
101    fn name(&self) -> StaticName {
102        "DynTile".into()
103    }
104
105    op_as_typed_op!();
106}
107
108impl EvalOp for DynTile {
109    op_out_of_plan!();
110
111    fn eval(&self, ctx: &EvalContext, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
112        let multipliers = inputs[1].cast_to::<TDim>()?;
113        let multipliers: TVec<usize> = multipliers
114            .try_as_plain()?
115            .as_slice::<TDim>()?
116            .iter()
117            .map(|m| Ok(m.eval_to_i64(ctx.symbols)? as usize))
118            .collect::<TractResult<_>>()?;
119        Ok(tvec!(tile(&inputs[0], &multipliers)?))
120    }
121}
122
123impl TypedOp for DynTile {
124    as_op!();
125
126    fn declutter(
127        &self,
128        model: &TypedModel,
129        node: &TypedNode,
130    ) -> TractResult<Option<TypedModelPatch>> {
131        if let Some(mult) = &model.outlet_fact(node.inputs[1])?.konst {
132            let multipliers = mult
133                .cast_to::<TDim>()?
134                .try_as_plain()?
135                .as_slice::<TDim>()?
136                .iter()
137                .cloned()
138                .collect();
139            return TypedModelPatch::replace_single_op(
140                model,
141                node,
142                &node.inputs,
143                Tile { multipliers },
144            )
145            .map(Some);
146        }
147        Ok(None)
148    }
149
150    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
151        let multipliers = if let Some(k) = &inputs[1].konst {
152            k.cast_to::<TDim>()?.try_as_plain()?.as_slice::<TDim>()?.iter().cloned().collect()
153        } else {
154            self.multiplier_placeholders.clone()
155        };
156        let shape =
157            inputs[0].shape.iter().zip(multipliers).map(|(a, b)| b * a).collect::<TVec<_>>();
158        Ok(tvec!(inputs[0].datum_type.fact(shape)))
159    }
160}
161
162/// Tile one axis at a time: a tiled axis repeats whole blocks of the tensor, so
163/// each repeat is one assignment into a slice of the growing output.
164fn tile(data: &TValue, multipliers: &[usize]) -> TractResult<TValue> {
165    ensure!(multipliers.len() == data.rank(), "Tiling {data:?} by {multipliers:?}");
166    let mut current = None;
167    for (axis, &m) in multipliers.iter().enumerate() {
168        let source: &Tensor = current.as_ref().unwrap_or(data);
169        if m == 1 {
170            continue;
171        }
172        let dim = source.shape()[axis];
173        let mut shape: TVec<usize> = source.shape().into();
174        shape[axis] = dim * m;
175        let mut output = Tensor::zero_dt(source.datum_type(), &shape)?;
176        for repeat in 0..m {
177            output.assign_slice(repeat * dim..(repeat + 1) * dim, source, .., axis)?;
178        }
179        current = Some(output);
180    }
181    Ok(current.map(|t| t.into_tvalue()).unwrap_or_else(|| data.clone()))
182}