diff --git a/core/src/ops/binary.rs b/core/src/ops/binary.rs index d5f133fa7f..2932f23719 100644 --- a/core/src/ops/binary.rs +++ b/core/src/ops/binary.rs @@ -593,7 +593,6 @@ macro_rules! bin_to_super_type { c_dt: &DatumType, accumulator_dt: DatumType ) -> TractResult> { - dbg!("generic call",a, b, c_dt, a.datum_type().qparams(), b.datum_type().qparams(), c_dt.qparams()); if let (Some(QParams::ZpScale {zero_point: a_zp, scale: a_scale}), Some(QParams::ZpScale {zero_point: b_zp, scale: b_scale}), Some(QParams::ZpScale {zero_point: c_zp, scale: c_scale})) = @@ -625,7 +624,6 @@ macro_rules! bin_to_super_type { if let Some(d) = generic_q_binary_as_float_op(a, b, c_dt, DatumType::F32)? { return Ok(Some(d)); } - dbg!("HU nobodoy called me, smell bad ..."); )? Ok(None) } diff --git a/core/src/ops/math/mod.rs b/core/src/ops/math/mod.rs index a32587e266..3192960b3e 100644 --- a/core/src/ops/math/mod.rs +++ b/core/src/ops/math/mod.rs @@ -722,182 +722,6 @@ mod tests { Ok(()) } - struct TestOpWithQU8 { - operator: crate::ops::binary::TypedBinOp, - tensor_mul_input_a: [u8; 4], - scalar_mul_input_b: u8, - output_qparams: QParams, - expected_output: [u8; 4], - a_qparams: Option, - b_qparams: Option, - } - impl TestOpWithQU8 { - fn check(&self) -> TractResult<()> { - let mut model = TypedModel::default(); - - let a_dt = DatumType::QU8(if let Some(a_qp) = self.a_qparams { - a_qp - } else { - self.output_qparams - }); - - let b_dt = DatumType::QU8(if let Some(b_qp) = self.b_qparams { - b_qp - } else { - self.output_qparams - }); - - let a = model.add_source("a", TypedFact::dt_shape(a_dt, [2_usize, 2]))?; - - let mut b_tensor = tensor0(self.scalar_mul_input_b).broadcast_into_rank(2)?; - unsafe { b_tensor.set_datum_type(b_dt) }; - let b = model.add_const("b", b_tensor.into_arc_tensor())?; - - // we need to wire correctly output to the mul { - let mut op = self.operator.clone(); - op.1 = Some(DatumType::QU8(self.output_qparams)); - // } - let c = model.wire_node("c", op, &[a, b])?[0]; - model.set_output_outlets(&[c])?; - - let mut a_data = Tensor::from_shape(&[2, 2], &self.tensor_mul_input_a)?; - unsafe { a_data.set_datum_type(a_dt) }; - - let result = SimplePlan::new(&model)?.run(tvec!(a_data.into()))?; - let arr = result[0].to_array_view::()?; - assert_eq!( - arr, - Tensor::from_shape(&[2, 2], &self.expected_output)?.to_array_view::()? - ); - Ok(()) - } - } - - #[test] - fn mul_as_qu8_overflow_clamp() -> TractResult<()> { - // last value in output tensor overflow hence is clamped - TestOpWithQU8 { - operator: mul(), - tensor_mul_input_a: [1_u8, 2, 3, 128], - scalar_mul_input_b: 4_u8, - output_qparams: QParams::ZpScale { scale: 1., zero_point: 0 }, - a_qparams: None, // aligned with output_qparams - b_qparams: None, // aligned with output_qparams - expected_output: [4_u8, 8, 12, 255], - } - .check() - } - - #[test] - fn mul_as_qu8_non_neutral_scale_and_offset() -> TractResult<()> { - // attempt with non neutral scale and offset - TestOpWithQU8 { - operator: mul(), - tensor_mul_input_a: [1_u8, 2, 3, 128], // real: -3, 0, 3, 378 - scalar_mul_input_b: 4_u8, // real: 6 - output_qparams: QParams::ZpScale { scale: 3., zero_point: 2 }, - // optima in non quantized output real: -18, 0, 18, 2268 - a_qparams: None, // aligned with output_qparams - b_qparams: None, // aligned with output_qparams - expected_output: [0_u8, 2, 8, 255], // approx obtained real: -6, 0, 18, 759 - } - .check() - } - - #[test] - fn mul_as_qu8_non_aligned_scale_and_offset() -> TractResult<()> { - // attempt with all scale and offset not aligned - TestOpWithQU8 { - operator: mul(), - tensor_mul_input_a: [3_u8, 4, 10, 25], // real: 0, 4.5, 31.5, 99 - scalar_mul_input_b: 6_u8, // real: 5 - output_qparams: QParams::ZpScale { scale: 1., zero_point: 0 }, - a_qparams: Some(QParams::ZpScale { scale: 4.5, zero_point: 3 }), - b_qparams: Some(QParams::ZpScale { scale: 2.5, zero_point: 4 }), - // optima in non quantized output real: 0, 22.5, 157.5, 495 - expected_output: [0_u8, 22, 158, 255], - } - .check() - } - - #[test] - fn add_as_qu8_non_aligned_scale_and_offset() -> TractResult<()> { - // attempt with all scale and offset not aligned - TestOpWithQU8 { - operator: add(), - tensor_mul_input_a: [3_u8, 4, 10, 25], // real: 0, 4.5, 31.5, 99 - scalar_mul_input_b: 6_u8, // real: 5 - output_qparams: QParams::ZpScale { scale: 1., zero_point: 0 }, - a_qparams: Some(QParams::ZpScale { scale: 4.5, zero_point: 3 }), - b_qparams: Some(QParams::ZpScale { scale: 2.5, zero_point: 4 }), - // optima in non quantized output real: 5, 9.5, 36.5, 104 - expected_output: [5_u8, 9, 37, 104], - } - .check() - } - - #[test] - fn div_as_qu8_non_aligned_scale_and_offset() -> TractResult<()> { - // attempt with all scale and offset not aligned - TestOpWithQU8 { - operator: div(), - tensor_mul_input_a: [3_u8, 5, 10, 25], // real: 0, 9, 31.5, 99 - scalar_mul_input_b: 6_u8, // real: 5 - output_qparams: QParams::ZpScale { scale: 1., zero_point: 0 }, - a_qparams: Some(QParams::ZpScale { scale: 4.5, zero_point: 3 }), - b_qparams: Some(QParams::ZpScale { scale: 2.5, zero_point: 4 }), - // optima in non quantized output real: 0, 1.8, 6.3, 19.8 - expected_output: [0_u8, 2, 5, 20], - } - .check() - } - - #[test] - fn max_0_as_qu8_non_aligned_scale_and_offset() -> TractResult<()> { - // relu in qu8 - TestOpWithQU8 { - operator: max(), - tensor_mul_input_a: [100_u8, 5, 110, 99], // real: 0, −427.5, 45, -4.5 - scalar_mul_input_b: 100_u8, // real: 0 - output_qparams: QParams::ZpScale { scale: 1., zero_point: 0 }, - a_qparams: Some(QParams::ZpScale { scale: 4.5, zero_point: 100 }), - b_qparams: Some(QParams::ZpScale { scale: 4.5, zero_point: 100 }), - // optima in non quantized output real: 0, 0, 45, 0 - expected_output: [0_u8, 0, 45, 0], - } - .check() - } - - #[test] - fn max_15_as_qu8_non_aligned_scale_and_offset() -> TractResult<()> { - TestOpWithQU8 { - operator: max(), - tensor_mul_input_a: [5_u8, 9, 8, 20], // real: 0, 16, 12, 60 - scalar_mul_input_b: 15_u8, // real: 15 - output_qparams: QParams::ZpScale { scale: 1., zero_point: 0 }, - a_qparams: Some(QParams::ZpScale { scale: 4., zero_point: 5 }), - b_qparams: Some(QParams::ZpScale { scale: 3., zero_point: 10 }), - // optima in non quantized output real: 15, 16, 15, 60 - expected_output: [15_u8, 16, 15, 60], - } - .check() - } - - #[test] - fn min_15_as_qu8_non_aligned_scale_and_offset() -> TractResult<()> { - TestOpWithQU8 { - operator: min(), - tensor_mul_input_a: [5_u8, 9, 8, 20], // real: 0, 16, 12, 60 - scalar_mul_input_b: 15_u8, // real: 15 - output_qparams: QParams::ZpScale { scale: 1., zero_point: 0 }, - a_qparams: Some(QParams::ZpScale { scale: 4., zero_point: 5 }), - b_qparams: Some(QParams::ZpScale { scale: 3., zero_point: 10 }), - // optima in non quantized output real: 0, 15, 12, 15 - expected_output: [0_u8, 15, 12, 15], - } - .check() - } - #[test] fn div_as_shift() -> TractResult<()> { let mut model = TypedModel::default(); diff --git a/test-rt/suite-unit/src/q_binary.rs b/test-rt/suite-unit/src/q_binary.rs index 084e534073..d7292d8411 100644 --- a/test-rt/suite-unit/src/q_binary.rs +++ b/test-rt/suite-unit/src/q_binary.rs @@ -148,8 +148,12 @@ impl Test for QBinaryOpProblem { let mut reference = self.reference_float_ops()?; let (zero_point, scale) = self.c_dt.zp_scale(); - let min_repr_val = (*self.c_dt.min_value().to_scalar::()? - zero_point as f32) * scale; - let max_repr_val = (*self.c_dt.max_value().to_scalar::()? - zero_point as f32) * scale; + let min_repr_val = (self.c_dt.unquantized().min_value().cast_to_scalar::()? + - zero_point as f32) + * scale; + let max_repr_val = (self.c_dt.unquantized().max_value().cast_to_scalar::()? + - zero_point as f32) + * scale; reference.to_array_view_mut()?.iter_mut().for_each(|x: &mut f32| { *x = round_ties_to_even((*x).clamp(min_repr_val, max_repr_val)) @@ -159,17 +163,17 @@ impl Test for QBinaryOpProblem { comparison.to_array_view_mut()?.iter_mut().for_each(|x: &mut f32| { *x = round_ties_to_even((*x).clamp(min_repr_val, max_repr_val)) }); - - dbg!(min_repr_val, max_repr_val); comparison.close_enough(&reference, approx) } } pub fn suite() -> TractResult { let mut suite = TestSuite::default(); - // suite.add_arbitrary::("proptest", ()); + //suite.add_arbitrary::("proptest", ()); + + // simplification 0 at declutter constant suite.add( - "trivial_0", + "trivial_mul_0_case", QBinaryOpProblem { operator: tract_core::ops::math::mul(), tensor_a: tensor0(0u8) @@ -187,5 +191,165 @@ pub fn suite() -> TractResult { c_dt: DatumType::QU8(QParams::ZpScale { zero_point: 0, scale: 1. }), }, ); + + suite.add( + "trivial_mul_as_qu8_overflow_clamp", + QBinaryOpProblem { + operator: tract_core::ops::math::mul(), + tensor_a: tensor1(&[1_u8, 2, 3, 128]) + .cast_to_dt( + u8::datum_type().quantize(QParams::ZpScale { zero_point: 0, scale: 1. }), + ) + .unwrap() + .into_owned(), + tensor_b: tensor1(&[4u8]) + .cast_to_dt( + u8::datum_type().quantize(QParams::ZpScale { zero_point: 0, scale: 1. }), + ) + .unwrap() + .into_owned(), + c_dt: DatumType::QU8(QParams::ZpScale { zero_point: 0, scale: 1. }), + }, + ); + + suite.add( + "trivial_mul_as_qu8_non_neutral_scale_and_offset", + QBinaryOpProblem { + operator: tract_core::ops::math::mul(), + tensor_a: tensor1(&[1_u8, 2, 3, 128]) + .cast_to_dt( + u8::datum_type().quantize(QParams::ZpScale { zero_point: 3, scale: 2. }), + ) + .unwrap() + .into_owned(), + tensor_b: tensor1(&[4u8]) + .cast_to_dt( + u8::datum_type().quantize(QParams::ZpScale { zero_point: 3, scale: 2. }), + ) + .unwrap() + .into_owned(), + c_dt: DatumType::QU8(QParams::ZpScale { zero_point: 3, scale: 2. }), + }, + ); + + suite.add( + "trivial_mul_as_qu8_non_aligned_scale_and_offset", + QBinaryOpProblem { + operator: tract_core::ops::math::mul(), + tensor_a: tensor1(&[3_u8, 4, 10, 25]) + .cast_to_dt( + u8::datum_type().quantize(QParams::ZpScale { zero_point: 3, scale: 4.5 }), + ) + .unwrap() + .into_owned(), + tensor_b: tensor1(&[6u8]) + .cast_to_dt( + u8::datum_type().quantize(QParams::ZpScale { zero_point: 4, scale: 2.5 }), + ) + .unwrap() + .into_owned(), + c_dt: DatumType::QU8(QParams::ZpScale { zero_point: 0, scale: 1. }), + }, + ); + + suite.add( + "trivial_max_0_as_qu8_non_aligned_scale_and_offset", + QBinaryOpProblem { + operator: tract_core::ops::math::max(), + tensor_a: tensor1(&[100_u8, 5, 110, 99]) + .cast_to_dt( + u8::datum_type().quantize(QParams::ZpScale { zero_point: 100, scale: 4.5 }), + ) + .unwrap() + .into_owned(), + tensor_b: tensor1(&[100u8]) + .cast_to_dt( + u8::datum_type().quantize(QParams::ZpScale { zero_point: 100, scale: 4.5 }), + ) + .unwrap() + .into_owned(), + c_dt: DatumType::QU8(QParams::ZpScale { zero_point: 0, scale: 1. }), + }, + ); + + suite.add( + "trivial_min_15_as_qu8_non_aligned_scale_and_offset", + QBinaryOpProblem { + operator: tract_core::ops::math::min(), + tensor_a: tensor1(&[5_u8, 9, 8, 20]) + .cast_to_dt( + u8::datum_type().quantize(QParams::ZpScale { zero_point: 5, scale: 4. }), + ) + .unwrap() + .into_owned(), + tensor_b: tensor1(&[15u8]) + .cast_to_dt( + u8::datum_type().quantize(QParams::ZpScale { zero_point: 10, scale: 3. }), + ) + .unwrap() + .into_owned(), + c_dt: DatumType::QU8(QParams::ZpScale { zero_point: 0, scale: 1. }), + }, + ); + + // suite.add( + // "trivial_max_15_as_qu8_non_aligned_scale_and_offset", + // QBinaryOpProblem { + // operator: tract_core::ops::math::max(), + // tensor_a: tensor1(&[5_u8, 9, 8, 20]) + // .cast_to_dt( + // u8::datum_type().quantize(QParams::ZpScale { zero_point: 5, scale: 4. }), + // ) + // .unwrap() + // .into_owned(), + // tensor_b: tensor1(&[15u8]) + // .cast_to_dt( + // u8::datum_type().quantize(QParams::ZpScale { zero_point: 10, scale: 3. }), + // ) + // .unwrap() + // .into_owned(), + // c_dt: DatumType::QU8(QParams::ZpScale { zero_point: 0, scale: 1. }), + // }, + // ); + // suite.add( + // "trivial_add_as_qu8_non_aligned_scale_and_offset", + // QBinaryOpProblem { + // operator: tract_core::ops::math::add(), + // tensor_a: tensor1(&[3_u8, 4, 10, 25]) + // .cast_to_dt( + // u8::datum_type().quantize(QParams::ZpScale { zero_point: 3, scale: 4.5 }), + // ) + // .unwrap() + // .into_owned(), + // tensor_b: tensor1(&[6u8]) + // .cast_to_dt( + // u8::datum_type().quantize(QParams::ZpScale { zero_point: 4, scale: 2.5 }), + // ) + // .unwrap() + // .into_owned(), + // c_dt: DatumType::QU8(QParams::ZpScale { zero_point: 0, scale: 1. }), + // }, + // ); + // + // suite.add( + // "trivial_div_as_qu8_non_aligned_scale_and_offset", + // QBinaryOpProblem { + // operator: tract_core::ops::math::div(), + // tensor_a: tensor1(&[3_u8, 4, 10, 25]) + // .cast_to_dt( + // u8::datum_type().quantize(QParams::ZpScale { zero_point: 3, scale: 4.5 }), + // ) + // .unwrap() + // .into_owned(), + // tensor_b: tensor1(&[6u8]) + // .cast_to_dt( + // u8::datum_type().quantize(QParams::ZpScale { zero_point: 4, scale: 2.5 }), + // ) + // .unwrap() + // .into_owned(), + // c_dt: DatumType::QU8(QParams::ZpScale { zero_point: 0, scale: 1. }), + // }, + // ); + Ok(suite) }