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 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 .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[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 [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 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 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() { 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 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 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, [f16, f32, f64] => |_, xs| {
532 xs.iter_mut().for_each(|x| *x = x.exp());
533 Ok(())
534};
535q: [i8, u8, i32, i32] => f32::exp;
536validation: Validation::Rounding
537);
538
539element_wise!(ln, Ln, [f16, f32, f64] => |_, xs| {
540 xs.iter_mut().for_each(|x| *x = x.ln());
541 Ok(())
542};
543q: [i8, u8, i32, i32] => f32::ln;
544validation: Validation::Rounding
545);
546
547element_wise!(pow_const, PowConst { exponent: f32 },
550 [f16] => |op, xs| {
551 let e = op.exponent;
552 xs.iter_mut().for_each(|x| *x = f16::from_f32(x.to_f32().powf(e)));
553 Ok(())
554 },
555 [f32] => |op, xs| {
556 let e = op.exponent;
557 xs.iter_mut().for_each(|x| *x = x.powf(e));
558 Ok(())
559 };
560 validation: Validation::Rounding
561);
562
563element_wise!(square, Square, [f16, f32, f64] => |_, xs| {
564 xs.iter_mut().for_each(|x| *x = x.powi(2));
565 Ok(())
566};
567q: [i8, u8, i32, i32] => |f : f32| f.powi(2);
568declutter: declutter_square;
569validation: Validation::Rounding
570);
571
572fn declutter_square(model: &TypedModel, node: &TypedNode) -> TractResult<Option<TypedModelPatch>> {
573 use super::element_wise::*;
574 if let Some(prec) = model.linear_prec(node.id)?
576 && let Some(ew) = prec.op_as::<ElementWiseOp>()
577 && ew.0.is::<Sqrt>()
578 {
579 let mut patch = TypedModelPatch::default();
580 let tap = patch.tap_model(model, prec.inputs[0])?;
581 patch.shunt_outside(model, node.id.into(), tap)?;
582 return Ok(Some(patch));
583 }
584 Ok(None)
585}
586
587element_wise!(sqrt, Sqrt, [f16, f32, f64] => |_, xs| {
588 xs.iter_mut().for_each(|x| *x = x.sqrt());
589 Ok(())
590};
591q: [i8, u8, i32, i32] => f32::sqrt;
592validation: Validation::Rounding
593);
594
595element_wise!(recip, Recip, [f16, f32, f64] => |_, xs| {
596 xs.iter_mut().for_each(|x| *x = x.recip());
597 Ok(())
598};
599q: [i8, u8, i32, i32] => f32::recip;
600cost: |dt| {tvec!((Cost::Div(dt), 1))};
601declutter: declutter_recip;
602validation: Validation::Rounding
603);
604
605fn declutter_recip(model: &TypedModel, node: &TypedNode) -> TractResult<Option<TypedModelPatch>> {
606 use super::element_wise::*;
607 if let Some(prec) = model.linear_prec(node.id)?
608 && let Some(ew) = prec.op_as::<ElementWiseOp>()
609 {
610 let repl = if ew.0.is::<Sqrt>() {
611 Some(rsqrt())
612 } else if ew.0.is::<Rsqrt>() {
613 Some(sqrt())
614 } else {
615 None
616 };
617 if let Some(repl) = repl {
618 let mut patch = TypedModelPatch::default();
619 let mut wire = patch.tap_model(model, prec.inputs[0])?;
620 wire = patch.wire_node(&node.name, repl, &[wire])?[0];
621 patch.shunt_outside(model, node.id.into(), wire)?;
622 return Ok(Some(patch));
623 }
624 }
625 Ok(None)
626}
627
628element_wise!(rsqrt, Rsqrt, [f16, f32, f64] => |_, xs| {
629 xs.iter_mut().for_each(|x| *x = x.sqrt().recip());
630 Ok(())
631};
632q: [i8, u8, i32] => |x : f32| x.sqrt().recip();
633validation: Validation::Rounding
634);
635
636element_wise!(ceil, Ceil, [f16, f32, f64] => |_, xs| {
637 xs.iter_mut().for_each(|x| *x = x.ceil());
638 Ok(())
639}, [i8, i16,i32, i64, u8, u16, u32, u64, TDim] => |_, _| Ok(());
640q: [i8, u8, i32] => f32::recip);
641
642element_wise!(floor, Floor, [f16, f32, f64] => |_, xs| {
643 xs.iter_mut().for_each(|x| *x = x.floor());
644 Ok(())
645}, [i8, i16,i32, i64, u8, u16, u32, u64, TDim] => |_, _| Ok(());
646q: [i8, u8, i32] => f32::floor);
647
648element_wise!(round, Round, [f16, f32, f64] => |_, xs| {
649 xs.iter_mut().for_each(|x| *x = x.round());
650 Ok(())
651}, [i8, i16,i32, i64, u8, u16, u32, u64, TDim] => |_, _| Ok(());
652q: [i8, u8, i32] => f32::round);
653
654element_wise!(q_scale, QScale{scaler: Scaler},[i32] => |op, xs| {
655 xs.iter_mut().for_each(|x| *x = x.q_scale(op.scaler));
656 Ok(())
657});
658
659element_wise!(round_half_to_even, RoundHalfToEven,
660[f32] => |_, xs| {
661 xs.iter_mut().for_each(|x| *x = round_ties_to_even(*x));
662 Ok(())
663},
664[f16] => |_, xs| {
665 xs.iter_mut().for_each(|x| *x = f16::from_f32(round_ties_to_even(x.to_f32())));
666 Ok(())
667};
668q: [i8, u8, i32] => round_ties_to_even);
669
670element_wise!(cos, Cos, [f16, f32, f64] => |_, xs| {
671 xs.iter_mut().for_each(|x| *x = x.cos());
672 Ok(())
673};
674q: [i8, u8, i32] => f32::cos);
675
676element_wise!(sin, Sin, [f16, f32, f64] => |_, xs| {
677 xs.iter_mut().for_each(|x| *x = x.sin());
678 Ok(())
679};
680q: [i8, u8, i32] => f32::sin);
681
682element_wise!(tan, Tan, [f16, f32, f64] => |_, xs| {
683 xs.iter_mut().for_each(|x| *x = x.tan());
684 Ok(())
685};
686q: [i8, u8, i32] => f32::tan);
687
688element_wise!(acos, Acos, [f16, f32, f64] => |_, xs| {
689 xs.iter_mut().for_each(|x| *x = x.acos());
690 Ok(())
691};
692q: [i8, u8, i32] => f32::acos);
693
694element_wise!(asin, Asin, [f16, f32, f64] => |_, xs| {
695 xs.iter_mut().for_each(|x| *x = x.asin());
696 Ok(())
697};
698q: [i8, u8, i32] => f32::asin);
699
700element_wise!(atan, Atan, [f16, f32, f64] => |_, xs| {
701 xs.iter_mut().for_each(|x| *x = x.atan());
702 Ok(())
703};
704q: [i8, u8, i32] => f32::atan);
705
706element_wise!(cosh, Cosh, [f16, f32, f64] => |_, xs| {
707 xs.iter_mut().for_each(|x| *x = x.cosh());
708 Ok(())
709};
710q: [i8, u8, i32] => f32::cosh);
711
712element_wise!(sinh, Sinh, [f16, f32, f64] => |_, xs| {
713 xs.iter_mut().for_each(|x| *x = x.sinh());
714 Ok(())
715};
716q: [i8, u8, i32] => f32::sinh);
717
718element_wise!(tanh, Tanh,
719 [f16] => |_, xs| { Func::Tanh.ew_f16()?.run(xs) },
720 [f32] => |_, xs| { Func::Tanh.ew_f32()?.run(xs) },
721 [f64] => |_, xs| { xs.iter_mut().for_each(|x| *x = x.tanh()); Ok(()) };
722 q: [i8, u8, i32] => f32::tanh;
723 cost: |dt| {tvec!((Cost::FMA(dt), 11), (Cost::Div(dt), 1))}
724);
725
726fn erf_f16_lut() -> &'static [u16; 1 << 16] {
731 static LUT: std::sync::OnceLock<Box<[u16; 1 << 16]>> = std::sync::OnceLock::new();
732 LUT.get_or_init(|| {
733 let mut values: Vec<f32> =
734 (0..=u16::MAX).map(|bits| f16::from_bits(bits).to_f32()).collect();
735 Func::Erf
736 .ew_f32()
737 .expect("no erf kernel to build the f16 lookup table with")
738 .run(&mut values)
739 .expect("erf kernel failed on the lookup table domain");
740 let mut lut = Box::new([0u16; 1 << 16]);
741 lut.iter_mut().zip(values).for_each(|(slot, v)| *slot = f16::from_f32(v).to_bits());
742 lut
743 })
744}
745
746element_wise!(erf, Erf,
747 [f32] => |_, xs| { Func::Erf.ew_f32()?.run(xs) },
748 [f16] => |_, xs| {
749 let lut = erf_f16_lut();
750 xs.iter_mut().for_each(|x| *x = f16::from_bits(lut[x.to_bits() as usize]));
751 Ok(())
752};
753 cost: |dt| {tvec!((Cost::FMA(dt), 11), (Cost::Div(dt), 1))};
754 declutter: declutter_erf
755);
756
757fn declutter_erf(model: &TypedModel, node: &TypedNode) -> TractResult<Option<TypedModelPatch>> {
758 crate::ops::nn::gelu_exact::detect_gelu_exact(model, node)
759}
760
761element_wise!(acosh, Acosh, [f16, f32, f64] => |_, xs| {
762 xs.iter_mut().for_each(|x| *x = x.acosh());
763 Ok(())
764};
765q: [i8, u8, i32] => f32::acosh);
766element_wise!(asinh, Asinh, [f16, f32, f64] => |_, xs| {
767 xs.iter_mut().for_each(|x| *x = x.asinh());
768 Ok(())
769};
770q: [i8, u8, i32] => f32::asinh);
771element_wise!(atanh, Atanh, [f16, f32, f64] => |_, xs| {
772 xs.iter_mut().for_each(|x| *x = x.atanh());
773 Ok(())
774};
775q: [i8, u8, i32] => f32::atanh);
776
777element_wise!(neg, Neg, [i8, i16, i32, i64, f16, f32, f64, TDim] => |_, xs| {
778 xs.iter_mut().for_each(|x| *x = -x.clone());
779 Ok(())
780};
781q: [i8, u8, i32] => |x: f32| -x);
782
783element_wise!(sign, Sign, [i8, i16, i32, i64, f16, f32, f64] => |_, xs| {
784 xs.iter_mut().for_each(|x| *x = if x.is_zero() { Zero::zero() } else { x.signum() });
785 Ok(())
786}, [u8, u16, u32, u64] => |_, xs| {
787 xs.iter_mut().for_each(|x| *x = if x.is_zero() { *x } else { One::one() });
788 Ok(())
789};
790q: [i8, u8, i32] => |x: f32| if x.is_zero() { 0.0 } else { x.signum() });
791
792element_wise_oop!(is_inf, IsInf { detect_positive: bool, detect_negative: bool },
793 [f32] => bool |op, xs, ys| {
794 xs.iter().zip(ys.iter_mut()).for_each(|(x,y)|
795 *y = (op.detect_positive && *x == f32::INFINITY) || (op.detect_negative && *x == f32::NEG_INFINITY)
796 );
797 Ok(())
798 },
799 [f16] => bool |op, xs, ys| {
800 xs.iter().zip(ys.iter_mut()).for_each(|(x,y)|
801 *y = (op.detect_positive && *x == f16::INFINITY) || (op.detect_negative && *x == f16::NEG_INFINITY)
802 );
803 Ok(())
804 }
805);
806
807element_wise_oop!(is_nan, IsNan,
808 [f16, f32] => bool |_, xs, ys| {
809 xs.iter().zip(ys.iter_mut()).for_each(|(x,y)| *y = x.is_nan());
810 Ok(())
811 }
812);
813
814#[cfg(test)]
815mod tests {
816 use crate::ops::binary::TypedBinOp;
817
818 use super::*;
819 use ndarray::arr2;
820
821 #[test]
826 fn integer_div_wraps_at_min_over_minus_one() {
827 assert_eq!(
828 div()
829 .0
830 .eval(tensor1(&[i32::MIN]).into(), tensor1(&[-1i32]).into(), i32::datum_type())
831 .unwrap(),
832 tensor1(&[i32::MIN])
833 );
834 assert_eq!(
835 div()
836 .0
837 .eval(tensor1(&[i8::MIN]).into(), tensor1(&[-1i8]).into(), i8::datum_type())
838 .unwrap(),
839 tensor1(&[i8::MIN])
840 );
841 assert_eq!(
842 div()
843 .0
844 .eval(tensor1(&[i64::MIN]).into(), tensor1(&[-1i64]).into(), i64::datum_type())
845 .unwrap(),
846 tensor1(&[i64::MIN])
847 );
848 }
849
850 #[test]
851 fn integer_rem_wraps_at_min_over_minus_one() {
852 assert_eq!(
853 rem()
854 .0
855 .eval(tensor1(&[i32::MIN]).into(), tensor1(&[-1i32]).into(), i32::datum_type())
856 .unwrap(),
857 tensor1(&[0i32])
858 );
859 assert_eq!(
860 rem()
861 .0
862 .eval(tensor1(&[i16::MIN]).into(), tensor1(&[-1i16]).into(), i16::datum_type())
863 .unwrap(),
864 tensor1(&[0i16])
865 );
866 }
867
868 #[test]
869 fn integer_div_and_rem_are_otherwise_unchanged() {
870 assert_eq!(
872 div()
873 .0
874 .eval(
875 tensor1(&[-7i32, 7, 9]).into(),
876 tensor1(&[2i32, -2, 4]).into(),
877 i32::datum_type()
878 )
879 .unwrap(),
880 tensor1(&[-3i32, -3, 2])
881 );
882 assert_eq!(
883 rem()
884 .0
885 .eval(
886 tensor1(&[-7i32, 7, 9]).into(),
887 tensor1(&[2i32, -2, 4]).into(),
888 i32::datum_type()
889 )
890 .unwrap(),
891 tensor1(&[-1i32, 1, 1])
892 );
893 assert_eq!(
894 div()
895 .0
896 .eval(tensor1(&[255u8]).into(), tensor1(&[2u8]).into(), u8::datum_type())
897 .unwrap(),
898 tensor1(&[127u8])
899 );
900 }
901
902 #[test]
903 fn test_mul() {
904 let a = arr2(&[[1., 2.], [3., 4.]]);
905 let b = arr2(&[[1., 0.], [0., 0.]]);
906 assert_eq!(a * b, arr2(&[[1., 0.], [0., 0.]]));
907 }
908
909 #[test]
910 fn erf_f16_lut_matches_the_f32_kernel_on_every_f16() {
911 let all: Vec<f16> = (0..=u16::MAX).map(f16::from_bits).collect();
912
913 let mut reference: Vec<f32> = all.iter().map(|x| x.to_f32()).collect();
914 Func::Erf.ew_f32().unwrap().run(&mut reference).unwrap();
915 let reference: Vec<f16> = reference.into_iter().map(f16::from_f32).collect();
916
917 let mut lut = Tensor::from_shape(&[all.len()], &all).unwrap();
918 erf().0.eval_in_place(&mut lut, None).unwrap();
919
920 let lut = lut.to_plain_array_view::<f16>().unwrap();
921 let mismatch = lut.iter().zip(&reference).position(|(a, b)| a.to_bits() != b.to_bits());
922 assert_eq!(mismatch, None);
923 }
924
925 #[test]
926 fn dot() {
927 let a = arr2(&[[1., 2.], [3., 4.]]);
928 let b = arr2(&[[1., 0.], [0., 0.]]);
929 assert_eq!(a.dot(&b), arr2(&[[1., 0.], [3., 0.]]));
930 }
931
932 #[test]
933 fn mul_as_shift_left() -> TractResult<()> {
934 let mut model = TypedModel::default();
935 let x = model.add_source("x", i32::fact([2usize, 2]))?;
936 let a = model.add_const("a", tensor0(4i32).broadcast_into_rank(2)?.into_arc_tensor())?;
937 let y = model.wire_node("y", mul(), &[x, a])?[0];
938 model.select_output_outlets(&[y])?;
939 let result =
940 SimplePlan::new(model.clone())?.run(tvec!(tensor2(&[[1, 2], [3, 4]]).into()))?;
941 assert_eq!(*result[0], tensor2(&[[4, 8], [12, 16]]));
942 let decluttered = model.into_decluttered()?;
943 let result =
944 SimplePlan::new(decluttered.clone())?.run(tvec!(tensor2(&[[1, 2], [3, 4]]).into()))?;
945 assert_eq!(*result[0], tensor2(&[[4, 8], [12, 16]]));
946 let op = decluttered
947 .node(decluttered.output_outlets()?[0].node)
948 .op()
949 .downcast_ref::<TypedBinOp>()
950 .unwrap();
951 assert!(op.0.downcast_ref::<ShiftLeft>().is_some());
952 Ok(())
953 }
954
955 #[test]
956 fn div_as_shift() -> TractResult<()> {
957 let mut model = TypedModel::default();
958 let x = model.add_source("a", i32::fact([2usize, 2]))?;
959 let s = model.add_const("shift", tensor2(&[[4]]))?;
960 let y = model.wire_node("c", div(), [x, s].as_ref())?[0];
961 model.select_output_outlets(&[y])?;
962 let result =
963 SimplePlan::new(model.clone())?.run(tvec!(tensor2(&[[16, 32], [64, 68]]).into()))?;
964 assert_eq!(*result[0], tensor2(&[[4, 8], [16, 17]]));
965 let decluttered = model.into_decluttered()?;
966 let result = SimplePlan::new(decluttered.clone())?
967 .run(tvec!(tensor2(&[[16, 32], [64, 68]]).into()))?;
968 assert_eq!(*result[0], tensor2(&[[4, 8], [16, 17]]));
969 let op = decluttered
970 .node(decluttered.output_outlets()?[0].node)
971 .op()
972 .downcast_ref::<TypedBinOp>()
973 .unwrap();
974 assert!(op.0.downcast_ref::<ShiftRight>().is_some());
975 Ok(())
976 }
977
978 #[test]
979 fn sign_of_negative_zero_is_positive_zero() -> TractResult<()> {
980 let mut t = tensor1(&[-0.0f32, 0.0, -2.0, 2.0]);
981 Sign {}.eval_in_place(&mut t, None)?;
982 let got = t.try_as_plain()?.as_slice::<f32>()?;
983 assert_eq!(got[0].to_bits(), 0f32.to_bits());
986 assert_eq!(got[1].to_bits(), 0f32.to_bits());
987 assert_eq!(got[2], -1.0);
988 assert_eq!(got[3], 1.0);
989 Ok(())
990 }
991}