Skip to main content

tract_core/ops/math/
mod.rs

1#![allow(clippy::clone_on_copy)]
2#![allow(clippy::unnecessary_cast)]
3#![allow(clippy::blocks_in_conditions)]
4
5use super::array::MultiBroadcastTo;
6use super::binary::TypedBinOp;
7use crate::internal::*;
8use crate::ops::quant::scale_by;
9use num_traits::bounds::Bounded;
10use num_traits::int::PrimInt;
11use num_traits::{Float, One, Zero};
12use tract_data::internal::ClampCast;
13pub use tract_data::prelude::round_ties_to_even;
14use tract_linalg::{ScaleShiftAndRound, Scaler};
15use tract_num_traits::AsPrimitive;
16
17#[cfg(feature = "complex")]
18mod complex;
19#[cfg(feature = "complex")]
20pub use complex::{ComplexToInnerDim, InnerDimToComplex};
21use tract_linalg::routines::Func;
22
23bin_to_super_type!(add, Add,
24                   linalg: Add,
25                   neutral_element: 0,
26                   validation: Validation::Rounding,
27                   q: [i8, u8, i32, i32] => add_quant;
28                   q_op_on_f32: |a: f32, b: f32| -> f32 {a+b},
29                   [f32, i8, i16, i32, i64, u8, u16, u32, u64, f16, f64, TDim, String] => |c, a, b| *c = a.clone() + b);
30
31fn add_quant<T>(c: &mut T, a: &T, b: &T, zp: i32, _: f32)
32where
33    T: PrimInt + Bounded + AsPrimitive<i64> + Datum,
34    i64: AsPrimitive<T>,
35{
36    *c = (a.as_() + b.as_() - zp as i64).clamp_cast()
37}
38
39bin_to_super_type!(sub, Sub,
40                   linalg:Sub,
41                   is_commutative: false,
42                   neutral_element: 0,
43                   q: [i8, u8, i32, i32] => sub_quant;
44                   q_op_on_f32: |a: f32, b: f32| -> f32 {a-b},
45                   [f32, i8, i16, i32, i64, u8, u16, u32, u64, f16, f64, TDim] => |c, a, b| *c = a.clone() - b);
46
47bin_to_super_type!(subf, SubF,
48                   linalg:SubF,
49                   is_commutative: false,
50                   neutral_element: 0,
51                   q: [i8, u8, i32, i32] => subf_quant;
52                   q_op_on_f32: |a: f32, b: f32| -> f32 {b - a},
53                   [f32, i8, i16, i32, i64, u8, u16, u32, u64, f16, f64, TDim] => |c, a, b| *c = b.clone() - a);
54
55fn sub_quant<T>(c: &mut T, a: &T, b: &T, zp: i32, _: f32)
56where
57    T: PrimInt + Bounded + AsPrimitive<i16> + Datum,
58    i16: AsPrimitive<T>,
59{
60    *c = (a.as_() - b.as_() + zp as i16).clamp_cast()
61}
62
63fn subf_quant<T>(c: &mut T, a: &T, b: &T, zp: i32, _: f32)
64where
65    T: PrimInt + Bounded + AsPrimitive<i16> + Datum,
66    i16: AsPrimitive<T>,
67{
68    *c = (b.as_() - a.as_() + zp as i16).clamp_cast()
69}
70
71bin_to_super_type!(mul, Mul,
72                   cost: |dt| tvec!((Cost::FMA(dt), 1)),
73                   declutter: declutter_mul,
74                   eval_override: |a:TValue, b: TValue, c_dt: DatumType| -> TractResult<Tensor> {
75                    // we apply only if type is QU8 zp_scale datum type
76                    if let (DatumType::QU8(QParams::ZpScale {zero_point: a_zp, scale: a_scale}),
77                            DatumType::QU8(QParams::ZpScale {zero_point: b_zp, scale: b_scale}),
78                            DatumType::QU8(QParams::ZpScale {zero_point: c_zp, scale: c_scale})) =
79                        (a.datum_type(), b.datum_type(), c_dt)
80                    {
81                           let multiplier = a_scale  * b_scale * (1.0/ c_scale);
82                           let a = a.to_plain_array_view::<u8>()?;
83                           let b = b.to_plain_array_view::<u8>()?;
84                           let c_shape = crate::broadcast::multi_broadcast(&[a.shape(), b.shape()]).context("no broadcast solution")?;
85                           let mut c = Tensor::zero_dt(c_dt, &c_shape)?;
86                           let mut c_plain = c.try_as_plain_mut()?;
87                           let view = c_plain.to_array_view_mut::<u8>()?;
88                           crate::ndarray::Zip::from(view)
89                               .and_broadcast(a)
90                               .and_broadcast(b)
91                               .for_each(|c,a,b| *c = (scale_by((*a as i32 - a_zp as i32) * (*b as i32 - b_zp as i32), multiplier) + c_zp as i32).clamp_cast());
92                           Ok(c)
93                        } else {
94                            Mul.generic_eval(a, b, c_dt)
95                        }
96                    },
97                   linalg: Mul,
98                   neutral_element: 1,
99                   absorbing_element: 0,
100                   out_of_place: |c:&mut Tensor, a:&Tensor, b: &Tensor| -> TractResult<bool> {
101                       if c.datum_type() == TDim::datum_type() &&
102                           a.datum_type() == TDim::datum_type() && b.datum_type() == TDim::datum_type() {
103                               let a = a.to_plain_array_view::<TDim>()?;
104                               let b = b.cast_to::<i32>()?;
105                               let b = b.to_plain_array_view::<i32>()?;
106                               let mut c_plain = c.try_as_plain_mut()?;
107                               let c = c_plain.to_array_view_mut::<TDim>()?;
108                               crate::ndarray::Zip::from(c).and_broadcast(a).and_broadcast(b).for_each(|c,a,b| *c = a.clone() * *b);
109                               Ok(true)
110                           }
111                       else {
112                           match c.datum_type() {
113                               DatumType::QI8(params) => {
114                                   let (zp, scale) = params.zp_scale();
115                                   let a = a.to_plain_array_view::<i8>()?;
116                                   let b = b.to_plain_array_view::<i8>()?;
117                                   let mut c_plain = c.try_as_plain_mut()?;
118                                   let c = c_plain.to_array_view_mut::<i8>()?;
119                                   crate::ndarray::Zip::from(c)
120                                       .and_broadcast(a)
121                                       .and_broadcast(b)
122                                       .for_each(|c,a,b| *c = (scale_by((*a as i16 - zp as i16) * (*b as i16 - zp as i16), scale) + zp as i16).clamp_cast());
123                                   Ok(true)
124                               }
125                               DatumType::QU8(params) => {
126                                   let (zp, scale) = params.zp_scale();
127                                   let a = a.to_plain_array_view::<u8>()?;
128                                   let b = b.to_plain_array_view::<u8>()?;
129                                   let mut c_plain = c.try_as_plain_mut()?;
130                                   let c = c_plain.to_array_view_mut::<u8>()?;
131                                   crate::ndarray::Zip::from(c)
132                                       .and_broadcast(a)
133                                       .and_broadcast(b)
134                                       .for_each(|c,a,b| *c = (scale_by((*a as i32 - zp as i32) * (*b as i32 - zp as i32), scale) + zp as i32).clamp_cast());
135                                   Ok(true)
136                               }
137                               _ => Ok(false)
138                           }
139                       }
140                   },
141                   q: [i8, u8, i32] => |c, a, b, zp, scale| {
142                    *c = (scale_by((a.clone() as i32 - zp as i32) * (*b as i32 - zp as i32) , scale) + zp as i32).clamp_cast()
143                   };
144                   q_op_on_f32: |a: f32, b: f32| a * b,
145                   [i8, i16, i32, i64, u8, u16, u32, u64] => |c, a, b| *c = a.wrapping_mul(*b),
146                   [f32, f16, f64] => |c, a, b| *c = a * b,
147                   [TDim] => |c, a, b| *c = a.clone() * b
148);
149
150bin_to_super_type!(div, Div,
151cost: |dt| tvec!((Cost::Div(dt), 1)),
152declutter: declutter_div,
153eval_override: |a:TValue, b: TValue, c_dt: DatumType| -> TractResult<Tensor> {
154    if
155        a.datum_type() == TDim::datum_type() && b.datum_type() == TDim::datum_type() {
156            let a = a.to_plain_array_view::<TDim>()?;
157            let b = b.to_plain_array_view::<TDim>()?;
158            let c_shape = crate::broadcast::multi_broadcast(&[a.shape(), b.shape()]).context("no broadcast solution")?;
159            unsafe {
160                let a = a.broadcast(&*c_shape).unwrap();
161                let b = b.broadcast(&*c_shape).unwrap();
162                let mut c = Tensor::uninitialized_dt(DatumType::TDim, &c_shape)?;
163                let mut c_plain = c.try_as_plain_mut()?;
164                let mut view = c_plain.to_array_view_mut::<TDim>()?;
165                for coords in crate::ndarray::indices(&*c_shape) {
166                    let (p, q) = a[&coords].maybe_div(&b[&coords])?;
167                    view[&coords] = p/q;
168                }
169                Ok(c)
170            }
171        } else if let (DatumType::QU8(QParams::ZpScale {zero_point: a_zp, scale: a_scale}),
172                       DatumType::QU8(QParams::ZpScale {zero_point: b_zp, scale: b_scale}),
173                       DatumType::QU8(QParams::ZpScale {zero_point: c_zp, scale: c_scale})) =
174                (a.datum_type(), b.datum_type(), c_dt) {
175
176               let multiplier = a_scale / (b_scale * c_scale);
177                let a = a.to_plain_array_view::<u8>()?;
178                let b = b.to_plain_array_view::<u8>()?;
179                let c_shape = crate::broadcast::multi_broadcast(&[a.shape(), b.shape()]).context("no broadcast solution")?;
180                let mut c = Tensor::zero_dt(c_dt, &c_shape)?;
181                let mut c_plain = c.try_as_plain_mut()?;
182                let view = c_plain.to_array_view_mut::<u8>()?;
183                crate::ndarray::Zip::from(view)
184                    .and_broadcast(a)
185                    .and_broadcast(b)
186                    // maintain division in f32 before rescale to maintain high accuracy
187                    .for_each(|c,a,b| *c = (
188                            scale_by(
189                                (*a as i32 - a_zp as i32) as f32 / (*b as i32 - b_zp as i32) as f32, multiplier
190                            ) as i32 + c_zp as i32
191                        ).clamp_cast());
192                Ok(c)
193        } else {
194            Div.generic_eval(a, b, c_dt)
195        }
196},
197is_commutative: false,
198neutral_element: 1,
199out_of_place: |c:&mut Tensor, a:&Tensor, b: &Tensor| -> TractResult<bool> {
200    if c.datum_type() == TDim::datum_type() &&
201        a.datum_type() == TDim::datum_type() && b.datum_type() == TDim::datum_type() {
202            let a = a.to_plain_array_view::<TDim>()?;
203            let b = b.cast_to::<i32>()?;
204            let b = b.to_plain_array_view::<i32>()?;
205            let mut c_plain = c.try_as_plain_mut()?;
206            let c = c_plain.to_array_view_mut::<TDim>()?;
207            crate::ndarray::Zip::from(c).and_broadcast(a).and_broadcast(b).for_each(|c,a,b| *c = a.clone() / *b);
208            Ok(true)
209        } else if c.datum_type().is_quantized() || b.datum_type().is_quantized() || a.datum_type().is_quantized() {
210            let a_f32 = a.cast_to::<f32>()?;
211            let a_f32 = a_f32.to_plain_array_view::<f32>()?;
212            let b_f32 = b.cast_to::<f32>()?;
213            let b_f32 = b_f32.to_plain_array_view::<f32>()?;
214            let c_f32 = &a_f32 / &b_f32;
215            *c = c_f32.into_tensor().cast_to_dt(c.datum_type())?.into_owned();
216            Ok(true)
217        } else {
218            Ok(false)
219        }
220},
221q_op_on_f32: |a: f32, b: f32| a / b,
222// Rust checks division overflow in every profile, not just where overflow-checks is on, so
223// `MIN / -1` panics even in release. Wrap it, as Mul above already does with wrapping_mul, and
224// as onnx.reference and ONNX Runtime both do. A zero divisor still panics; the references
225// disagree on what it should produce, so that is left alone.
226[i8, i16, i32, i64, u8, u16, u32, u64] => |c, a, b| *c = a.wrapping_div(*b),
227[f32, f16, f64] => |c, a, b| *c = a.clone() / b
228);
229
230bin_to_super_type!(rem, Rem,
231                                      eval_override: |a:TValue, b: TValue, c_dt: DatumType| -> TractResult<Tensor> {
232                                          if
233                                              a.datum_type() == TDim::datum_type() && b.datum_type() == TDim::datum_type() {
234                                                  let a = a.to_plain_array_view::<TDim>()?;
235                                                  let b = b.cast_to::<i32>()?;
236                                                  let b = b.to_plain_array_view::<i32>()?;
237                                                  let c_shape = crate::broadcast::multi_broadcast(&[a.shape(), b.shape()]).context("no broadcast solution")?;
238                                                  unsafe {
239                                                      let mut c = Tensor::uninitialized_dt(DatumType::TDim, &c_shape)?;
240                                                      let mut c_plain = c.try_as_plain_mut()?;
241                                                      let view = c_plain.to_array_view_mut::<TDim>()?;
242                                                      crate::ndarray::Zip::from(view).and_broadcast(a).and_broadcast(b).for_each(|c,a,b| *c = a.clone() % *b);
243                                                      Ok(c)
244                                                  }
245                                              } else {
246                                                  Rem.generic_eval(a,b, c_dt)
247                                              }
248                                      },
249                                      out_of_place: |c:&mut Tensor, a:&Tensor, b: &Tensor| -> TractResult<bool> {
250                                          if c.datum_type() == TDim::datum_type() &&
251                                              a.datum_type() == TDim::datum_type() && b.datum_type() == TDim::datum_type() {
252                                                  let a = a.to_plain_array_view::<TDim>()?;
253                                                  let b = b.cast_to::<i32>()?;
254                                                  let b = b.to_plain_array_view::<i32>()?;
255                                                  let mut c_plain = c.try_as_plain_mut()?;
256                                                  let c = c_plain.to_array_view_mut::<TDim>()?;
257                                                  crate::ndarray::Zip::from(c).and_broadcast(a).and_broadcast(b).for_each(|c,a,b| *c = a.clone() % *b);
258                                                  Ok(true)
259                                              } else {
260                                                  Ok(false)
261                                              }
262                                      },
263                                      // As for Div: `MIN % -1` panics in every profile without this.
264                                      [i8, i16, i32, i64, u8, u16, u32, u64] => |c, a, b| *c = a.wrapping_rem(*b),
265                                      [f32, f16, f64] => |c, a, b| *c = a.clone() % b);
266
267bin_to_super_type!(min, Min, linalg:Min,
268                   q: [i8, u8, i32] => |c, a, b, _, _| *c = if a < b { *a } else { *b };
269                   q_op_on_f32: |a: f32, b: f32| a.min(b),
270                   [f16, f32, f64] => |c,a,b| *c = a.min(*b),
271                   [TDim] => |c,a,b| *c = a.clone().mini(b.clone()),
272                   [i8, i16, i32, i64, u8, u16, u32, u64] => |c, a, b| *c = *a.min(b));
273
274bin_to_super_type!(max, Max,
275                   eval_override: |a:TValue, b: TValue, c_dt: DatumType| -> TractResult<Tensor> {
276                   // Attempt to optimize relu case
277                    if let (DatumType::QU8(QParams::ZpScale {zero_point: a_zp, scale: a_scale}),
278                            DatumType::QU8(QParams::ZpScale {zero_point: b_zp, scale: b_scale}),
279                            DatumType::QU8(QParams::ZpScale {zero_point: c_zp, scale: c_scale})) =
280                        (a.datum_type(), b.datum_type(), c_dt)
281                        && (a.is_uniform() || b.is_uniform()) {
282                            // select e between a and b as uniform if exist
283                            // and d remaining a or b
284                            let (d, d_zp, d_scale, e, e_zp, e_scale) = if a.is_uniform() && !b.is_uniform() {
285                                (&b, &b_zp, &b_scale, &a, &a_zp, &a_scale)
286                            } else {
287                                (&a, &a_zp, &a_scale, &b, &b_zp, &b_scale)
288                            };
289                            if e.is_uniform() { // may be relu or any scalar
290                                let e = e.cast_to::<u8>()?.try_as_plain()?.as_slice::<u8>()?[0];
291                                let e_val_as_d_aligned: i32 = scale_by(e as i32 - e_zp, e_scale / d_scale);
292                                let multiplier = d_scale  * (1.0/ c_scale);
293                                let d = d.to_plain_array_view::<u8>()?;
294                                let mut c = Tensor::zero_dt(c_dt, d.shape())?;
295                                let mut c_plain = c.try_as_plain_mut()?;
296                                let view = c_plain.to_array_view_mut::<u8>()?;
297                                crate::ndarray::Zip::from(view)
298                                    .and_broadcast(d)
299                                    .for_each(|c,d| {
300                                        let d_min_zp = *d as i32 - *d_zp as i32;
301                                        let c_val: i32 = if d_min_zp < e_val_as_d_aligned {
302                                            e_val_as_d_aligned
303                                        } else {
304                                            d_min_zp
305                                        };
306                                        *c = (scale_by(c_val, multiplier) + c_zp as i32).clamp_cast();
307                                    });
308                                return Ok(c)
309                            }
310                        }
311                    Max.generic_eval(a, b, c_dt)
312                   },
313                   linalg:Max,
314                   q: [i8, u8, i32] => |c, a, b, _, _| *c = if a < b { *b } else { *a };
315                   q_op_on_f32: |a: f32, b: f32| -> f32 {a.max(b)},
316                   [f16, f32, f64] => |c,a,b| *c = a.max(*b),
317                   [TDim] => |c,a,b| *c = a.clone().maxi(b.clone()),
318                   [i8, i16, i32, i64, u8, u16, u32, u64] => |c, a, b| *c = *a.max(b));
319
320bin_to_super_type!(pow, Pow,
321                   declutter: declutter_pow,
322                   is_commutative: false,
323                   neutral_element: 1,
324                   q_op_on_f32: |a: f32, b: f32| -> f32 {a.powf(b)},
325                   [f16, f32, f64] => |c,a,b| *c = a.powf(*b),
326                   [i32, i64] => |c,a,b| *c = a.pow(*b as u32));
327
328bin_to_super_type!(shift_left, ShiftLeft,
329                   is_commutative: false,
330                   [i8, i16, i32, i64, u8, u16, u32, u64] => |c, a, b| *c = *a << *b);
331bin_to_super_type!(shift_right, ShiftRight,
332                   is_commutative: false,
333                   [i8, i16, i32, i64, u8, u16, u32, u64] => |c, a, b| *c = *a >> *b);
334
335fn declutter_mul(
336    _op: &Mul,
337    model: &TypedModel,
338    node: &TypedNode,
339) -> TractResult<Option<TypedModelPatch>> {
340    if node.inputs[0] == node.inputs[1] && !node.outputs[0].fact.datum_type.is_quantized() {
341        return Ok(Some(TypedModelPatch::replace_single_op(
342            model,
343            node,
344            &node.inputs[0..1],
345            square(),
346        )?));
347    }
348
349    if let Some(uniform) = crate::ops::binary::one_input_is_uniform(model, node)? {
350        let var_fact = model.outlet_fact(uniform.var)?;
351        if uniform.uni.cast_to_scalar::<f64>()? == 0.0 {
352            let shapes =
353                model.node_input_facts(node.id)?.iter().map(|f| &f.shape).collect::<TVec<_>>();
354            let shape: ShapeFact =
355                crate::broadcast::multi_broadcast(&shapes).context("Failed to broadcast")?.into();
356            return Ok(Some(TypedModelPatch::rewire(
357                model,
358                &[],
359                &[node.id.into()],
360                &|patch, _| {
361                    let scalar = patch.add_const(
362                        format!("{}.zero", node.name),
363                        if uniform.uni.datum_type().is_quantized() {
364                            let output_dt = node.outputs[0].fact.datum_type;
365                            Arc::new(uniform.uni.clone().cast_to_dt(output_dt)?.into_owned())
366                        } else {
367                            uniform.uni.clone()
368                        },
369                    )?;
370                    let op = MultiBroadcastTo::new(shape.clone());
371                    patch.wire_node(&node.name, op, &[scalar])
372                },
373            )?));
374        }
375        let dt = uniform.uni.datum_type();
376        if !dt.is_quantized() {
377            // avoid cast potential with Q tensor
378            let integer = uniform.uni.cast_to_scalar::<i64>()?;
379            if tensor0(integer)
380                .cast_to_dt(uniform.uni.datum_type())?
381                .close_enough(&uniform.uni, false)
382                .is_ok()
383                && uniform.uni.cast_to_scalar::<i64>()?.count_ones() == 1
384                && dt.is_integer()
385            {
386                let shift = integer.trailing_zeros();
387                return Ok(Some(TypedModelPatch::rewire(
388                    model,
389                    &[uniform.var],
390                    &[node.id.into()],
391                    &|patch, taps| {
392                        let shift = patch.add_const(
393                            format!("{}.shift", node.name),
394                            tensor0(shift)
395                                .cast_to_dt(dt)?
396                                .into_owned()
397                                .broadcast_into_rank(var_fact.rank())?,
398                        )?;
399                        patch.wire_node(&node.name, shift_left(), &[taps[0], shift])
400                    },
401                )?));
402            }
403        }
404    }
405    if let Some(patch) = declutter_mul_const_mul_const(model, node)? {
406        return Ok(Some(patch));
407    }
408    Ok(None)
409}
410
411fn declutter_mul_const_mul_const(
412    model: &TypedModel,
413    node: &TypedNode,
414) -> TractResult<Option<TypedModelPatch>> {
415    let input_facts = model.node_input_facts(node.id)?;
416    rule_if_some!(const_slot = input_facts.iter().position(|f| f.konst.is_some()));
417    let prec = model.node(node.inputs[1 - const_slot].node);
418    rule_if_some!(prec_mul = prec.op_as::<TypedBinOp>());
419    rule_if!(prec.outputs[0].successors.len() <= 1);
420    rule_if!(prec_mul.0.is::<Mul>());
421    let prec_input_facts = model.node_input_facts(prec.id)?;
422    rule_if_some!(prec_const_slot = prec_input_facts.iter().position(|f| f.konst.is_some()));
423
424    let const_fact = model.outlet_fact(node.inputs[const_slot])?;
425    let prec_const_fact = model.outlet_fact(prec.inputs[prec_const_slot])?;
426    // todo: extend to anything broadcast compatible
427    rule_if!(const_fact.shape.volume().is_one() || prec_const_fact.shape.volume().is_one());
428    rule_if!(const_fact.datum_type.is_float());
429    let result = mul()
430        .eval(
431            &EvalContext::out_of_plan(),
432            tvec!(
433                const_fact.konst.clone().unwrap().into_tvalue(),
434                prec_const_fact.konst.clone().unwrap().into_tvalue()
435            ),
436        )?
437        .remove(0)
438        .into_arc_tensor();
439    let mut patch = TypedModelPatch::default();
440    let konst = patch.add_const(&prec.name, result)?;
441    let input_tap = patch.tap_model(model, prec.inputs[1 - prec_const_slot])?;
442    let wire = patch.wire_node(&node.name, mul(), &[konst, input_tap])?;
443    patch.shunt_outside(model, node.id.into(), wire[0])?;
444    Ok(Some(patch))
445}
446
447fn declutter_div(
448    _op: &Div,
449    model: &TypedModel,
450    node: &TypedNode,
451) -> TractResult<Option<TypedModelPatch>> {
452    if let &[p, q] = &*model.node_input_facts(node.id)? {
453        let dt = q.datum_type;
454        if let Some(q) = &q.uniform
455            && let Ok(integer) = q.cast_to_scalar::<i64>()
456            && tensor0(integer).cast_to_dt(dt)?.close_enough(q, false).is_ok()
457            && dt.is_integer()
458            && q.cast_to_scalar::<i64>()?.count_ones() == 1
459        {
460            let shift = integer.trailing_zeros();
461            return Ok(Some(TypedModelPatch::rewire(
462                model,
463                &[node.inputs[0]],
464                &[node.id.into()],
465                &|patch, taps| {
466                    let shift = patch.add_const(
467                        format!("{}.shift", node.name),
468                        tensor0(shift)
469                            .cast_to_dt(dt)?
470                            .into_owned()
471                            .broadcast_into_rank(p.rank())?,
472                    )?;
473                    patch.wire_node(&node.name, shift_right(), &[taps[0], shift])
474                },
475            )?));
476        }
477        if dt.is_float() {
478            return Ok(Some(TypedModelPatch::rewire(
479                model,
480                &node.inputs,
481                &[node.id.into()],
482                &|patch, taps| {
483                    let q =
484                        patch.wire_node(format!("{}-recip", node.name), recip(), &[taps[1]])?[0];
485                    patch.wire_node(&node.name, mul(), &[taps[0], q])
486                },
487            )?));
488        }
489    }
490    Ok(None)
491}
492
493fn declutter_pow(
494    _op: &Pow,
495    model: &TypedModel,
496    node: &TypedNode,
497) -> TractResult<Option<TypedModelPatch>> {
498    let b = model.outlet_fact(node.inputs[1])?;
499    if let Some(b) = &b.uniform {
500        let b = b.cast_to_scalar::<f32>()?;
501        let dt = model.outlet_fact(node.inputs[0])?.datum_type;
502        let unary: Option<Box<dyn TypedOp>> = if b == 2.0 {
503            Some(Box::new(square()))
504        } else if b == 0.5 {
505            Some(Box::new(sqrt()))
506        } else if matches!(dt, DatumType::F16 | DatumType::F32) {
507            Some(Box::new(pow_const(b)))
508        } else {
509            None
510        };
511        if let Some(unary) = unary {
512            return Ok(Some(TypedModelPatch::replace_single_op(
513                model,
514                node,
515                &[node.inputs[0]],
516                unary,
517            )?));
518        }
519    }
520    crate::ops::nn::gelu_approximate::detect_gelu_approx(_op, model, node)
521}
522
523element_wise!(abs, Abs, [i8, i16, i32, i64, f16, f32, f64] => |_, xs| {
524    xs.iter_mut().for_each(|x| *x = x.abs());
525    Ok(())
526}, [u8, u16, u32, u64] => |_, _| Ok(());
527q: [i8, u8, i32, i32] => f32::abs;
528operating_datum_type: |dt| if dt == TDim::datum_type() { i64::datum_type() } else { dt }
529);
530
531element_wise!(exp, Exp,
532[f32] => |_, xs| { Func::Exp.ew_f32()?.run(xs) },
533[f16, f64] => |_, xs| {
534    xs.iter_mut().for_each(|x| *x = x.exp());
535    Ok(())
536};
537q: [i8, u8, i32, i32] => f32::exp;
538validation: Validation::Rounding
539);
540
541element_wise!(ln, Ln,
542[f32] => |_, xs| { Func::Ln.ew_f32()?.run(xs) },
543[f16, f64] => |_, xs| {
544    xs.iter_mut().for_each(|x| *x = x.ln());
545    Ok(())
546};
547q: [i8, u8, i32, i32] => f32::ln;
548validation: Validation::Rounding
549);
550
551// x^c for a constant exponent: the unary form of Pow against a uniform operand,
552// going through the same powf so results match the binary op exactly.
553element_wise!(pow_const, PowConst { exponent: f32 },
554    [f16] => |op, xs| {
555        let e = op.exponent;
556        xs.iter_mut().for_each(|x| *x = f16::from_f32(x.to_f32().powf(e)));
557        Ok(())
558    },
559    [f32] => |op, xs| {
560        let e = op.exponent;
561        xs.iter_mut().for_each(|x| *x = x.powf(e));
562        Ok(())
563    };
564    validation: Validation::Rounding
565);
566
567element_wise!(square, Square, [f16, f32, f64] => |_, xs| {
568    xs.iter_mut().for_each(|x| *x = x.powi(2));
569    Ok(())
570};
571q: [i8, u8, i32, i32] => |f : f32| f.powi(2);
572declutter: declutter_square;
573validation: Validation::Rounding
574);
575
576fn declutter_square(model: &TypedModel, node: &TypedNode) -> TractResult<Option<TypedModelPatch>> {
577    use super::element_wise::*;
578    // Square(Sqrt(x)) → x (Sqrt output is non-negative, so Square is exact inverse)
579    if let Some(prec) = model.linear_prec(node.id)?
580        && let Some(ew) = prec.op_as::<ElementWiseOp>()
581        && ew.0.is::<Sqrt>()
582    {
583        let mut patch = TypedModelPatch::default();
584        let tap = patch.tap_model(model, prec.inputs[0])?;
585        patch.shunt_outside(model, node.id.into(), tap)?;
586        return Ok(Some(patch));
587    }
588    Ok(None)
589}
590
591element_wise!(sqrt, Sqrt, [f16, f32, f64] => |_, xs| {
592    xs.iter_mut().for_each(|x| *x = x.sqrt());
593    Ok(())
594};
595q: [i8, u8, i32, i32] => f32::sqrt;
596validation: Validation::Rounding
597);
598
599element_wise!(recip, Recip, [f16, f32, f64] => |_, xs| {
600    xs.iter_mut().for_each(|x| *x = x.recip());
601    Ok(())
602};
603q: [i8, u8, i32, i32] => f32::recip;
604cost: |dt| {tvec!((Cost::Div(dt), 1))};
605declutter: declutter_recip;
606validation: Validation::Rounding
607);
608
609fn declutter_recip(model: &TypedModel, node: &TypedNode) -> TractResult<Option<TypedModelPatch>> {
610    use super::element_wise::*;
611    if let Some(prec) = model.linear_prec(node.id)?
612        && let Some(ew) = prec.op_as::<ElementWiseOp>()
613    {
614        let repl = if ew.0.is::<Sqrt>() {
615            Some(rsqrt())
616        } else if ew.0.is::<Rsqrt>() {
617            Some(sqrt())
618        } else {
619            None
620        };
621        if let Some(repl) = repl {
622            let mut patch = TypedModelPatch::default();
623            let mut wire = patch.tap_model(model, prec.inputs[0])?;
624            wire = patch.wire_node(&node.name, repl, &[wire])?[0];
625            patch.shunt_outside(model, node.id.into(), wire)?;
626            return Ok(Some(patch));
627        }
628    }
629    Ok(None)
630}
631
632element_wise!(rsqrt, Rsqrt, [f16, f32, f64] => |_, xs| {
633    xs.iter_mut().for_each(|x| *x = x.sqrt().recip());
634    Ok(())
635};
636q: [i8, u8, i32] => |x : f32| x.sqrt().recip();
637validation: Validation::Rounding
638);
639
640element_wise!(ceil, Ceil, [f16, f32, f64] => |_, xs| {
641    xs.iter_mut().for_each(|x| *x = x.ceil());
642    Ok(())
643}, [i8, i16,i32, i64, u8, u16, u32, u64, TDim] => |_, _| Ok(());
644q: [i8, u8, i32] => f32::recip);
645
646element_wise!(floor, Floor, [f16, f32, f64] => |_, xs| {
647    xs.iter_mut().for_each(|x| *x = x.floor());
648    Ok(())
649}, [i8, i16,i32, i64, u8, u16, u32, u64, TDim] => |_, _| Ok(());
650q: [i8, u8, i32] => f32::floor);
651
652element_wise!(round, Round, [f16, f32, f64] => |_, xs| {
653    xs.iter_mut().for_each(|x| *x = x.round());
654    Ok(())
655}, [i8, i16,i32, i64, u8, u16, u32, u64, TDim] => |_, _| Ok(());
656q: [i8, u8, i32] => f32::round);
657
658element_wise!(q_scale, QScale{scaler: Scaler},[i32] => |op, xs| {
659    xs.iter_mut().for_each(|x| *x = x.q_scale(op.scaler));
660    Ok(())
661});
662
663element_wise!(round_half_to_even, RoundHalfToEven,
664[f32] => |_, xs| {
665    xs.iter_mut().for_each(|x| *x = round_ties_to_even(*x));
666    Ok(())
667},
668[f16] => |_, xs| {
669    xs.iter_mut().for_each(|x| *x = f16::from_f32(round_ties_to_even(x.to_f32())));
670    Ok(())
671};
672q: [i8, u8, i32] => round_ties_to_even);
673
674element_wise!(cos, Cos, [f16, f32, f64] => |_, xs| {
675    xs.iter_mut().for_each(|x| *x = x.cos());
676    Ok(())
677};
678q: [i8, u8, i32] => f32::cos);
679
680element_wise!(sin, Sin, [f16, f32, f64] => |_, xs| {
681    xs.iter_mut().for_each(|x| *x = x.sin());
682    Ok(())
683};
684q: [i8, u8, i32] => f32::sin);
685
686element_wise!(tan, Tan, [f16, f32, f64] => |_, xs| {
687    xs.iter_mut().for_each(|x| *x = x.tan());
688    Ok(())
689};
690q: [i8, u8, i32] => f32::tan);
691
692element_wise!(acos, Acos, [f16, f32, f64] => |_, xs| {
693    xs.iter_mut().for_each(|x| *x = x.acos());
694    Ok(())
695};
696q: [i8, u8, i32] => f32::acos);
697
698element_wise!(asin, Asin, [f16, f32, f64] => |_, xs| {
699    xs.iter_mut().for_each(|x| *x = x.asin());
700    Ok(())
701};
702q: [i8, u8, i32] => f32::asin);
703
704element_wise!(atan, Atan, [f16, f32, f64] => |_, xs| {
705    xs.iter_mut().for_each(|x| *x = x.atan());
706    Ok(())
707};
708q: [i8, u8, i32] => f32::atan);
709
710element_wise!(cosh, Cosh, [f16, f32, f64] => |_, xs| {
711    xs.iter_mut().for_each(|x| *x = x.cosh());
712    Ok(())
713};
714q: [i8, u8, i32] => f32::cosh);
715
716element_wise!(sinh, Sinh, [f16, f32, f64] => |_, xs| {
717    xs.iter_mut().for_each(|x| *x = x.sinh());
718    Ok(())
719};
720q: [i8, u8, i32] => f32::sinh);
721
722element_wise!(tanh, Tanh,
723 [f16] => |_, xs| { Func::Tanh.ew_f16()?.run(xs) },
724 [f32] => |_, xs| { Func::Tanh.ew_f32()?.run(xs) },
725 [f64] => |_, xs| { xs.iter_mut().for_each(|x| *x = x.tanh()); Ok(()) };
726 q: [i8, u8, i32] => f32::tanh;
727 cost: |dt| {tvec!((Cost::FMA(dt), 11), (Cost::Div(dt), 1))}
728);
729
730/// Every f16 bit pattern mapped through the registered f32 erf kernel and rounded
731/// back, so f16 `Erf` is one load per element. Built from that kernel rather than
732/// from a formula, so the table matches whatever kernel this host dispatches to.
733/// 128 KiB, built on first use.
734fn erf_f16_lut() -> &'static [u16; 1 << 16] {
735    static LUT: std::sync::OnceLock<Box<[u16; 1 << 16]>> = std::sync::OnceLock::new();
736    LUT.get_or_init(|| {
737        let mut values: Vec<f32> =
738            (0..=u16::MAX).map(|bits| f16::from_bits(bits).to_f32()).collect();
739        Func::Erf
740            .ew_f32()
741            .expect("no erf kernel to build the f16 lookup table with")
742            .run(&mut values)
743            .expect("erf kernel failed on the lookup table domain");
744        let mut lut = Box::new([0u16; 1 << 16]);
745        lut.iter_mut().zip(values).for_each(|(slot, v)| *slot = f16::from_f32(v).to_bits());
746        lut
747    })
748}
749
750element_wise!(erf, Erf,
751 [f32] => |_, xs| { Func::Erf.ew_f32()?.run(xs) },
752 [f16] => |_, xs| {
753     let lut = erf_f16_lut();
754     xs.iter_mut().for_each(|x| *x = f16::from_bits(lut[x.to_bits() as usize]));
755     Ok(())
756};
757 cost: |dt| {tvec!((Cost::FMA(dt), 11), (Cost::Div(dt), 1))};
758 declutter: declutter_erf
759);
760
761fn declutter_erf(model: &TypedModel, node: &TypedNode) -> TractResult<Option<TypedModelPatch>> {
762    crate::ops::nn::gelu_exact::detect_gelu_exact(model, node)
763}
764
765element_wise!(acosh, Acosh, [f16, f32, f64] => |_, xs| {
766    xs.iter_mut().for_each(|x| *x = x.acosh());
767    Ok(())
768};
769q: [i8, u8, i32] => f32::acosh);
770element_wise!(asinh, Asinh, [f16, f32, f64] => |_, xs| {
771    xs.iter_mut().for_each(|x| *x = x.asinh());
772    Ok(())
773};
774q: [i8, u8, i32] => f32::asinh);
775element_wise!(atanh, Atanh, [f16, f32, f64] => |_, xs| {
776    xs.iter_mut().for_each(|x| *x = x.atanh());
777    Ok(())
778};
779q: [i8, u8, i32] => f32::atanh);
780
781element_wise!(neg, Neg, [i8, i16, i32, i64, f16, f32, f64, TDim] => |_, xs| {
782    xs.iter_mut().for_each(|x| *x = -x.clone());
783    Ok(())
784};
785q: [i8, u8, i32] => |x: f32| -x);
786
787element_wise!(sign, Sign, [i8, i16, i32, i64, f16, f32, f64] => |_, xs| {
788    xs.iter_mut().for_each(|x| *x = if x.is_zero() { Zero::zero() } else { x.signum() });
789    Ok(())
790}, [u8, u16, u32, u64] => |_, xs| {
791    xs.iter_mut().for_each(|x| *x = if x.is_zero() { *x } else { One::one() });
792    Ok(())
793};
794q: [i8, u8, i32] => |x: f32| if x.is_zero() { 0.0 } else { x.signum() });
795
796element_wise_oop!(is_inf, IsInf { detect_positive: bool, detect_negative: bool },
797    [f32] => bool |op, xs, ys| {
798        xs.iter().zip(ys.iter_mut()).for_each(|(x,y)|
799            *y = (op.detect_positive && *x == f32::INFINITY) || (op.detect_negative && *x == f32::NEG_INFINITY)
800        );
801        Ok(())
802    },
803    [f16] => bool |op, xs, ys| {
804        xs.iter().zip(ys.iter_mut()).for_each(|(x,y)|
805            *y = (op.detect_positive && *x == f16::INFINITY) || (op.detect_negative && *x == f16::NEG_INFINITY)
806        );
807        Ok(())
808    }
809);
810
811element_wise_oop!(is_nan, IsNan,
812    [f16, f32] => bool |_, xs, ys| {
813        xs.iter().zip(ys.iter_mut()).for_each(|(x,y)| *y = x.is_nan());
814        Ok(())
815    }
816);
817
818#[cfg(test)]
819mod tests {
820    use crate::ops::binary::TypedBinOp;
821
822    use super::*;
823    use ndarray::arr2;
824
825    // Rust checks integer division and remainder overflow in every profile, not only where
826    // overflow-checks is on, so `MIN / -1` panicked even in a release build. These pin the
827    // wrapping result, which is what onnx.reference and ONNX Runtime both produce.
828
829    #[test]
830    fn integer_div_wraps_at_min_over_minus_one() {
831        assert_eq!(
832            div()
833                .0
834                .eval(tensor1(&[i32::MIN]).into(), tensor1(&[-1i32]).into(), i32::datum_type())
835                .unwrap(),
836            tensor1(&[i32::MIN])
837        );
838        assert_eq!(
839            div()
840                .0
841                .eval(tensor1(&[i8::MIN]).into(), tensor1(&[-1i8]).into(), i8::datum_type())
842                .unwrap(),
843            tensor1(&[i8::MIN])
844        );
845        assert_eq!(
846            div()
847                .0
848                .eval(tensor1(&[i64::MIN]).into(), tensor1(&[-1i64]).into(), i64::datum_type())
849                .unwrap(),
850            tensor1(&[i64::MIN])
851        );
852    }
853
854    #[test]
855    fn integer_rem_wraps_at_min_over_minus_one() {
856        assert_eq!(
857            rem()
858                .0
859                .eval(tensor1(&[i32::MIN]).into(), tensor1(&[-1i32]).into(), i32::datum_type())
860                .unwrap(),
861            tensor1(&[0i32])
862        );
863        assert_eq!(
864            rem()
865                .0
866                .eval(tensor1(&[i16::MIN]).into(), tensor1(&[-1i16]).into(), i16::datum_type())
867                .unwrap(),
868            tensor1(&[0i16])
869        );
870    }
871
872    #[test]
873    fn integer_div_and_rem_are_otherwise_unchanged() {
874        // Controls: wrapping only differs from `/` and `%` at MIN over -1.
875        assert_eq!(
876            div()
877                .0
878                .eval(
879                    tensor1(&[-7i32, 7, 9]).into(),
880                    tensor1(&[2i32, -2, 4]).into(),
881                    i32::datum_type()
882                )
883                .unwrap(),
884            tensor1(&[-3i32, -3, 2])
885        );
886        assert_eq!(
887            rem()
888                .0
889                .eval(
890                    tensor1(&[-7i32, 7, 9]).into(),
891                    tensor1(&[2i32, -2, 4]).into(),
892                    i32::datum_type()
893                )
894                .unwrap(),
895            tensor1(&[-1i32, 1, 1])
896        );
897        assert_eq!(
898            div()
899                .0
900                .eval(tensor1(&[255u8]).into(), tensor1(&[2u8]).into(), u8::datum_type())
901                .unwrap(),
902            tensor1(&[127u8])
903        );
904    }
905
906    #[test]
907    fn test_mul() {
908        let a = arr2(&[[1., 2.], [3., 4.]]);
909        let b = arr2(&[[1., 0.], [0., 0.]]);
910        assert_eq!(a * b, arr2(&[[1., 0.], [0., 0.]]));
911    }
912
913    #[test]
914    fn erf_f16_lut_matches_the_f32_kernel_on_every_f16() {
915        let all: Vec<f16> = (0..=u16::MAX).map(f16::from_bits).collect();
916
917        let mut reference: Vec<f32> = all.iter().map(|x| x.to_f32()).collect();
918        Func::Erf.ew_f32().unwrap().run(&mut reference).unwrap();
919        let reference: Vec<f16> = reference.into_iter().map(f16::from_f32).collect();
920
921        let mut lut = Tensor::from_shape(&[all.len()], &all).unwrap();
922        erf().0.eval_in_place(&mut lut, None).unwrap();
923
924        let lut = lut.to_plain_array_view::<f16>().unwrap();
925        let mismatch = lut.iter().zip(&reference).position(|(a, b)| a.to_bits() != b.to_bits());
926        assert_eq!(mismatch, None);
927    }
928
929    #[test]
930    fn dot() {
931        let a = arr2(&[[1., 2.], [3., 4.]]);
932        let b = arr2(&[[1., 0.], [0., 0.]]);
933        assert_eq!(a.dot(&b), arr2(&[[1., 0.], [3., 0.]]));
934    }
935
936    #[test]
937    fn mul_as_shift_left() -> TractResult<()> {
938        let mut model = TypedModel::default();
939        let x = model.add_source("x", i32::fact([2usize, 2]))?;
940        let a = model.add_const("a", tensor0(4i32).broadcast_into_rank(2)?.into_arc_tensor())?;
941        let y = model.wire_node("y", mul(), &[x, a])?[0];
942        model.select_output_outlets(&[y])?;
943        let result =
944            SimplePlan::new(model.clone())?.run(tvec!(tensor2(&[[1, 2], [3, 4]]).into()))?;
945        assert_eq!(*result[0], tensor2(&[[4, 8], [12, 16]]));
946        let decluttered = model.into_decluttered()?;
947        let result =
948            SimplePlan::new(decluttered.clone())?.run(tvec!(tensor2(&[[1, 2], [3, 4]]).into()))?;
949        assert_eq!(*result[0], tensor2(&[[4, 8], [12, 16]]));
950        let op = decluttered
951            .node(decluttered.output_outlets()?[0].node)
952            .op()
953            .downcast_ref::<TypedBinOp>()
954            .unwrap();
955        assert!(op.0.downcast_ref::<ShiftLeft>().is_some());
956        Ok(())
957    }
958
959    #[test]
960    fn div_as_shift() -> TractResult<()> {
961        let mut model = TypedModel::default();
962        let x = model.add_source("a", i32::fact([2usize, 2]))?;
963        let s = model.add_const("shift", tensor2(&[[4]]))?;
964        let y = model.wire_node("c", div(), [x, s].as_ref())?[0];
965        model.select_output_outlets(&[y])?;
966        let result =
967            SimplePlan::new(model.clone())?.run(tvec!(tensor2(&[[16, 32], [64, 68]]).into()))?;
968        assert_eq!(*result[0], tensor2(&[[4, 8], [16, 17]]));
969        let decluttered = model.into_decluttered()?;
970        let result = SimplePlan::new(decluttered.clone())?
971            .run(tvec!(tensor2(&[[16, 32], [64, 68]]).into()))?;
972        assert_eq!(*result[0], tensor2(&[[4, 8], [16, 17]]));
973        let op = decluttered
974            .node(decluttered.output_outlets()?[0].node)
975            .op()
976            .downcast_ref::<TypedBinOp>()
977            .unwrap();
978        assert!(op.0.downcast_ref::<ShiftRight>().is_some());
979        Ok(())
980    }
981
982    #[test]
983    fn sign_of_negative_zero_is_positive_zero() -> TractResult<()> {
984        let mut t = tensor1(&[-0.0f32, 0.0, -2.0, 2.0]);
985        Sign {}.eval_in_place(&mut t, None)?;
986        let got = t.try_as_plain()?.as_slice::<f32>()?;
987        // Compared as bit patterns: -0.0 == 0.0 is true, so an equality check on the
988        // values would pass even when -0.0 is returned.
989        assert_eq!(got[0].to_bits(), 0f32.to_bits());
990        assert_eq!(got[1].to_bits(), 0f32.to_bits());
991        assert_eq!(got[2], -1.0);
992        assert_eq!(got[3], 1.0);
993        Ok(())
994    }
995}