Skip to main content

tract_core/ops/
element_wise.rs

1use crate::internal::*;
2use crate::ops::array::MultiBroadcastTo;
3use downcast_rs::Downcast;
4use dyn_eq::DynEq;
5use std::fmt;
6
7pub trait ElementWiseMiniOp:
8    fmt::Debug + dyn_clone::DynClone + dyn_eq::DynEq + Send + Sync + 'static + Downcast
9{
10    fn name(&self) -> String;
11    fn prefix(&self) -> &'static str {
12        ""
13    }
14    fn validation(&self) -> Validation {
15        Validation::Accurate
16    }
17    #[allow(unused_variables)]
18    fn output_type(&self, input_type: DatumType) -> Option<DatumType> {
19        None
20    }
21    #[allow(unused_variables)]
22    fn eval_in_place(&self, t: &mut Tensor, out_dt: Option<DatumType>) -> TractResult<()> {
23        bail!("Element wise eval in-place not defined");
24    }
25    #[allow(unused_variables)]
26    fn eval_out_of_place(&self, t: &Tensor, out_dt: Option<DatumType>) -> TractResult<Tensor> {
27        bail!("Element wise eval out-of-place place not defined");
28    }
29    #[allow(unused_variables)]
30    fn cost_per_element(&self, dt: DatumType) -> TVec<(Cost, usize)> {
31        tvec!()
32    }
33    #[allow(unused_variables)]
34    fn operating_datum_type(&self, dt: DatumType) -> DatumType {
35        dt
36    }
37    #[allow(unused_variables)]
38    fn declutter(
39        &self,
40        model: &TypedModel,
41        node: &TypedNode,
42    ) -> TractResult<Option<TypedModelPatch>> {
43        Ok(None)
44    }
45
46    #[allow(unused_variables)]
47    fn quantize(
48        &self,
49        dt: DatumType,
50        scale: f32,
51        zero_point: i32,
52    ) -> TractResult<Option<Box<dyn ElementWiseMiniOp>>> {
53        Ok(None)
54    }
55    #[allow(unused_variables)]
56    fn info(&self) -> TractResult<Vec<String>> {
57        Ok(vec![])
58    }
59}
60
61dyn_clone::clone_trait_object!(ElementWiseMiniOp);
62dyn_eq::eq_trait_object!(ElementWiseMiniOp);
63downcast_rs::impl_downcast!(ElementWiseMiniOp);
64
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct ElementWiseOp(pub Box<dyn ElementWiseMiniOp>, pub Option<DatumType>);
67
68impl ElementWiseOp {
69    fn output_datum_type(&self, input_dt: DatumType) -> DatumType {
70        self.1.unwrap_or(self.0.operating_datum_type(input_dt))
71    }
72}
73
74impl Op for ElementWiseOp {
75    fn name(&self) -> StaticName {
76        self.0.name().into()
77    }
78
79    fn info(&self) -> TractResult<Vec<String>> {
80        self.0.info()
81    }
82
83    fn validation(&self) -> Validation {
84        self.0.validation()
85    }
86
87    op_as_typed_op!();
88}
89
90impl EvalOp for ElementWiseOp {
91    op_out_of_plan!();
92
93    fn eval(&self, _ctx: &EvalContext, mut inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
94        if let Some(_dt) = self.0.output_type(inputs[0].datum_type()) {
95            Ok(tvec!(self.0.eval_out_of_place(&inputs[0], self.1)?.into_tvalue()))
96        } else {
97            let mut m = inputs.remove(0).into_tensor();
98            self.0.eval_in_place(&mut m, self.1)?;
99            Ok(tvec!(m.into()))
100        }
101    }
102}
103
104impl TypedOp for ElementWiseOp {
105    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
106        let mut fact = inputs[0].clone().without_value();
107        let dt = self.output_datum_type(fact.datum_type);
108        if let Some(dt) = self.1 {
109            fact.datum_type = dt;
110        } else if let Some(dt) = self.0.output_type(dt) {
111            fact.datum_type = dt;
112        }
113        // Propagate uniform_tdim through this op.
114        if let Some(tdim) = &inputs[0].uniform_tdim {
115            // Logical NOT on bool tensors: NOT(x) = 1 - x for 0/1 values.
116            // Not is bool-only by definition. BitNot is bitwise (valid on integers
117            // where ~x ≠ 1-x), so only apply this for bool input.
118            let is_logical_not = self.0.downcast_ref::<crate::ops::logic::Not>().is_some()
119                || (self.0.downcast_ref::<crate::ops::logic::BitNot>().is_some()
120                    && inputs[0].datum_type == bool::datum_type());
121            if is_logical_not {
122                fact.uniform_tdim = Some((TDim::Val(1) - tdim.clone()).reduce());
123            } else {
124                // General path: evaluate the op on a TDim scalar.
125                // Ops with a TDim arm (e.g. Floor → identity) pass the value through;
126                // ops without one return an error and uniform_tdim stays None.
127                let mut tmp = tensor0(tdim.clone());
128                if self.0.eval_in_place(&mut tmp, None).is_ok() {
129                    fact.uniform_tdim = tmp
130                        .try_as_plain()
131                        .ok()
132                        .and_then(|d| d.as_slice::<TDim>().ok())
133                        .and_then(|s| s.first())
134                        .cloned()
135                        .map(|d| d.reduce());
136                }
137            }
138        }
139        Ok(tvec!(fact))
140    }
141
142    fn input_roi(
143        &self,
144        model: &TypedModel,
145        node: &TypedNode,
146    ) -> TractResult<Option<TVec<Option<TDim>>>> {
147        crate::optim::propagate_roi::bubble_roi(model, node)
148    }
149
150    fn change_axes(
151        &self,
152        model: &TypedModel,
153        node: &TypedNode,
154        _io: InOut,
155        change: &AxisOp,
156    ) -> TractResult<Option<AxisChangeConsequence>> {
157        Ok(Some(AxisChangeConsequence::new(model, node, None, change)))
158    }
159
160    fn declutter(
161        &self,
162        model: &TypedModel,
163        node: &TypedNode,
164    ) -> TractResult<Option<TypedModelPatch>> {
165        // linear_prec (fan-in=1, fan-out=1) rather than single_prec: swapping
166        // through a fan-out predecessor clones it, and the clone can break
167        // downstream pattern detectors (e.g. Square+Reduce<Sum>+Mul fusion
168        // into Reduce<MeanOfSquares> feeding RmsNorm detection).
169        if let Some(prec) = model.linear_prec(node.id)?
170            && (prec.op_is::<AxisOp>()
171                || prec.op_is::<IntoShape>()
172                || prec.op_is::<MultiBroadcastTo>())
173        {
174            let mut patch = TypedModelPatch::default();
175            let mut wire = tvec!(patch.tap_model(model, prec.inputs[0])?);
176            wire = patch.wire_node(&node.name, &node.op, &wire)?;
177            wire = patch.wire_node(&prec.name, &prec.op, &wire)?;
178            patch.shunt_outside(model, node.id.into(), wire[0])?;
179            return Ok(Some(patch));
180        }
181        self.0.declutter(model, node)
182    }
183
184    fn axes_mapping(
185        &self,
186        inputs: &[&TypedFact],
187        outputs: &[&TypedFact],
188    ) -> TractResult<AxesMapping> {
189        AxesMapping::natural(inputs, outputs)
190    }
191
192    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {
193        let count: TDim = inputs[0].shape.iter().product();
194        Ok(self
195            .0
196            .cost_per_element(inputs[0].datum_type)
197            .into_iter()
198            .map(|(c, n)| (c, count.clone() * n))
199            .collect())
200    }
201
202    fn quantize(
203        &self,
204        _model: &TypedModel,
205        _node: &TypedNode,
206        dt: DatumType,
207        scale: f32,
208        zero_point: i32,
209    ) -> TractResult<Option<Box<dyn TypedOp>>> {
210        if let Some(mini) = self.0.quantize(dt, scale, zero_point)? {
211            Ok(Some(Box::new(ElementWiseOp(mini, self.1))))
212        } else {
213            Ok(None)
214        }
215    }
216
217    fn slice(
218        &self,
219        patch: &mut TypedModelPatch,
220        _model: &TypedModel,
221        node: &TypedNode,
222        _prefix: &str,
223        inputs: &[OutletId],
224        _output_axis: usize,
225        _start: &TDim,
226        _end: &TDim,
227    ) -> TractResult<Option<TVec<OutletId>>> {
228        patch.wire_node(&node.name, &node.op, inputs).map(Some)
229    }
230
231    as_op!();
232}
233
234#[macro_export]
235macro_rules! element_wise {
236    ($func:ident, $Op:ident $({$( $(#[$meta: meta])? $var: ident : $var_typ: path),*})?,
237        $([$($typ:ident),*] => $f:expr ),*
238        $(; q: $( [$($typ_dt:ident),*] => $f_f32:expr),*)?
239        $(; cost: $cost:expr )?
240        $(; declutter: $declutter:expr )?
241        $(; operating_datum_type: $operating_datum_type:expr )?
242        $(; prefix: $prefix:expr )?
243        $(; quantize: $quantize:expr )?
244        $(; validation: $validation:expr )?
245    ) => {
246        #[derive(Debug, Clone, PartialEq)]
247        pub struct $Op { $( $( $(#[$meta])? pub $var: $var_typ),* )? }
248        impl Eq for $Op {}
249        impl $crate::ops::element_wise::ElementWiseMiniOp for $Op {
250            fn name(&self) -> String {
251                format!("{}{}", self.prefix(), stringify!($Op))
252            }
253            fn eval_in_place(&self, t: &mut Tensor, out_dt: Option<DatumType>) -> TractResult<()> {
254                $(
255                    $(if out_dt.unwrap_or(t.datum_type()) == $typ::datum_type() {
256                        let mut t_plain = t.try_as_plain_mut()?;
257                        let t: &mut[$typ] = t_plain.as_slice_mut::<$typ>()?;
258                        let f: fn(&Self, &mut[$typ]) -> TractResult<()> = $f;
259                        let len = t.len();
260                        tract_linalg::multithread::par_chunks_mut(t, 1, len, |_, chunk| f(self, chunk))?;
261                        return Ok(())
262                    }
263                    )*
264                )*
265                $(
266                    $(
267                       $(
268                        let mut input_dt = t.datum_type();
269                        let sout_dt = out_dt.unwrap_or(input_dt);
270                        if sout_dt.unquantized() == <$typ_dt>::datum_type().unquantized() {
271                           if input_dt.unquantized() != sout_dt.unquantized() {
272                               // align unquantized input type to unquantized output type
273                               *t = match input_dt.unquantized() {
274                                   DatumType::U8 => t.clone().into_arc_tensor().offset_u8_as_i8(),
275                                   DatumType::I8 => t.clone().into_arc_tensor().offset_i8_as_u8(),
276                                   unknown_dt => bail!("unexpected quantization input dt {:?}", unknown_dt)
277                               }.into_tensor();
278                               input_dt = t.datum_type(); // because zero_point change
279                           }
280                           unsafe { t.set_datum_type(sout_dt) } // force cast
281                           let mut t_plain = t.try_as_plain_mut()?;
282                           let t: &mut[$typ_dt] = t_plain.as_slice_mut::<$typ_dt>()?;
283                           let f: fn(&Self, &mut[$typ_dt], DatumType, DatumType) -> TractResult<()> = |_, xs, input_dt, out_dt| {
284                               let (izp, iscale) = input_dt.zp_scale();
285                               let (ozp, oscale) = out_dt.zp_scale();
286                               xs.iter_mut().for_each(|x| {
287                                   let x_f32 = (*x as f32 - izp as f32) * iscale;
288                                   *x = (($f_f32(x_f32) / oscale) + ozp as f32).as_()
289                               });
290                               Ok(())
291                           };
292                           let len = t.len();
293                           tract_linalg::multithread::par_chunks_mut(t, 1, len, |_, chunk| f(self, chunk, input_dt, sout_dt))?;
294                           return Ok(())
295                       }
296                       )*
297                   )*
298                )?
299                bail!("{} does not support {:?}", self.name(), out_dt.unwrap_or(t.datum_type()));
300            }
301            $(
302            fn cost_per_element(&self, dt: DatumType) -> TVec<(Cost, usize)> {
303                $cost(dt)
304            }
305            )?
306            $(
307                fn declutter(
308                    &self,
309                    model: &TypedModel,
310                    node: &TypedNode,
311                ) -> TractResult<Option<TypedModelPatch>> {
312                    $declutter(model, node)
313                }
314            )?
315            $(
316            fn prefix(&self) -> &'static str {
317                $prefix
318            }
319            )?
320            $(
321            fn quantize(
322                &self,
323                dt: DatumType,
324                scale: f32,
325                zero_point: i32) -> TractResult<Option<Box<dyn ElementWiseMiniOp>>> {
326                    $quantize(&self, dt, scale, zero_point)
327            }
328            )?
329            $(
330            fn validation(&self) -> Validation {
331                $validation
332            }
333            )?
334            $(
335            fn operating_datum_type(&self, dt: DatumType) -> DatumType {
336                ($operating_datum_type)(dt)
337            }
338            )?
339        }
340        pub fn $func($( $($var: $var_typ),* )?) -> $crate::ops::element_wise::ElementWiseOp {
341            $crate::ops::element_wise::ElementWiseOp(Box::new($Op { $( $($var),* )? }), None)
342        }
343    }
344}
345
346#[macro_export]
347macro_rules! element_wise_oop {
348    ($(#[$fmeta:meta])* $func:ident, $Op:ident $({$( $(#[$meta: meta])? $var: ident : $var_typ: path),*})?,
349        $( [$($typ:ident),*] => $typ_dst:ident $f:expr ),*
350        $(; cost: $cost:expr )?
351        $(; info: $info:expr )?
352        $(; operating_datum_type: $operating_datum_type:expr )?
353        $(; prefix: $prefix:expr )?
354        $(; quantize: $quantize:expr )?
355        $(; validation: $validation:expr )?
356    ) => {
357        #[derive(Debug, Clone)]
358        pub struct $Op { $( $($(#[$meta])? pub $var: $var_typ),* )? }
359        impl PartialEq for $Op {
360            #[allow(unused_variables)]
361            fn eq(&self, other: &Self) -> bool {
362                $( $( if &self.$var != &other.$var { return false; })* )?
363                true
364            }
365        }
366        impl Eq for $Op {}
367        impl $crate::ops::element_wise::ElementWiseMiniOp for $Op {
368            fn name(&self) -> String {
369                format!("{}{}", self.prefix(), stringify!($Op))
370            }
371            fn output_type(&self, input_type: DatumType) -> Option<DatumType> {
372                $(
373                    $(if input_type == $typ::datum_type() {
374                        return Some(<$typ_dst>::datum_type())
375                    }
376                    )*
377                )*
378                None
379            }
380            fn eval_out_of_place(&self, t: &Tensor, _out_dt: Option<DatumType>) -> TractResult<Tensor> {
381                $(
382                    let mut dst = unsafe { Tensor::uninitialized_dt(<$typ_dst>::datum_type(), &t.shape())? };
383                    $(if t.datum_type() == $typ::datum_type() {
384                        let f: fn(&Self, &[$typ], &mut[$typ_dst]) -> TractResult<()> = $f;
385                        let t_plain = t.try_as_plain()?;
386                        let in_slice: &[$typ] = t_plain.as_slice::<$typ>()?;
387                        let mut dst_plain = dst.try_as_plain_mut()?;
388                        let dst_slice: &mut[$typ_dst] = dst_plain.as_slice_mut::<$typ_dst>()?;
389                        let len = dst_slice.len();
390                        tract_linalg::multithread::par_chunks_mut(dst_slice, 1, len, |first_row, chunk| {
391                            f(self, &in_slice[first_row..first_row + chunk.len()], chunk)
392                        })?;
393                        return Ok(dst)
394                    }
395                    )*
396                )*
397                bail!("{} does not support {:?}", self.name(), t.datum_type());
398            }
399            $(
400            fn cost_per_element(&self, dt: DatumType) -> TVec<(Cost, usize)> {
401                $cost(dt)
402            }
403            )?
404            $(
405            fn info(&self) -> TractResult<Vec<String>> {
406                $info(self)
407            }
408            )?
409            $(
410            fn prefix(&self) -> &'static str {
411                $prefix
412            }
413            )?
414            $(
415            fn quantize(
416                &self,
417                dt: DatumType,
418                scale: f32,
419                zero_point: i32) -> TractResult<Option<Box<dyn ElementWiseMiniOp>>> {
420                    $quantize(ft, scale, zero_point)
421            }
422            )?
423            $(
424            fn validation(&self) -> Validation {
425                $validation
426            }
427            )?
428            $(
429            fn operating_datum_type(&self, dt: DatumType) -> DatumType {
430                ($operating_datum_type)(dt)
431            }
432            )?
433        }
434        $(#[$fmeta])*
435        pub fn $func($( $($var: $var_typ),* )?) -> $crate::ops::element_wise::ElementWiseOp {
436            $crate::ops::element_wise::ElementWiseOp(Box::new($Op { $( $($var),* )? }), None)
437        }
438    }
439}