diff --git a/tensorflow/lite/kernels/internal/reference/reduce.h b/tensorflow/lite/kernels/internal/reference/reduce.h index 5b795ea8ff0..5afd11f67f7 100644 --- a/tensorflow/lite/kernels/internal/reference/reduce.h +++ b/tensorflow/lite/kernels/internal/reference/reduce.h @@ -484,6 +484,20 @@ inline bool QuantizedReduceProd(const T* input_data, int32_t input_zero_point, return true; } +template +inline bool QuantizedProdExtraArgs( + const T* input_data, int32_t input_zero_point, float input_scale, + const int* input_dims, const int input_num_dims, T* output_data, + float output_scale, int32_t output_multiplier, int output_shift, + int32_t output_zero_point, const int* output_dims, + const int output_num_dims, const int* axis, const int num_axis_dimensions, + bool keep_dims, int* temp_index, int* resolved_axis, U* temp_prod) { + + return QuantizedReduceProd (input_data, input_zero_point, RuntimeShape(input_num_dims, input_dims), output_data, + output_zero_point, RuntimeShape(output_num_dims, output_dims), axis, num_axis_dimensions, keep_dims, temp_index, + resolved_axis, temp_prod, output_multiplier, output_shift); +} + } // namespace reference_ops } // namespace tflite diff --git a/tensorflow/lite/micro/kernels/micro_ops.h b/tensorflow/lite/micro/kernels/micro_ops.h index 2e33a6730bd..61af25f073a 100644 --- a/tensorflow/lite/micro/kernels/micro_ops.h +++ b/tensorflow/lite/micro/kernels/micro_ops.h @@ -100,6 +100,7 @@ TFLMRegistration Register_PRELU(); TFLMRegistration Register_QUANTIZE(); TFLMRegistration Register_READ_VARIABLE(); TFLMRegistration Register_REDUCE_MAX(); +TFLMRegistration Register_REDUCE_PROD(); TFLMRegistration Register_RELU(); TFLMRegistration Register_RELU6(); TFLMRegistration Register_RESHAPE(); diff --git a/tensorflow/lite/micro/kernels/reduce.cc b/tensorflow/lite/micro/kernels/reduce.cc index a689d3b934b..53f64ca7f09 100644 --- a/tensorflow/lite/micro/kernels/reduce.cc +++ b/tensorflow/lite/micro/kernels/reduce.cc @@ -44,6 +44,11 @@ TfLiteStatus PrepareMeanOrSum(TfLiteContext* context, TfLiteNode* node) { static_cast(node->user_data)); } +TfLiteStatus PrepareProd(TfLiteContext* context, TfLiteNode* node) { + return PrepareProdHelper(context, node, + static_cast(node->user_data)); +} + TfLiteStatus EvalMean(TfLiteContext* context, TfLiteNode* node) { return EvalMeanHelper(context, node, static_cast(node->user_data)); @@ -59,6 +64,11 @@ TfLiteStatus EvalSum(TfLiteContext* context, TfLiteNode* node) { static_cast(node->user_data)); } +TfLiteStatus EvalProd(TfLiteContext* context, TfLiteNode* node) { + return EvalProdHelper(context, node, + static_cast(node->user_data)); +} + TFLMRegistration Register_MEAN() { return tflite::micro::RegisterOp(InitReduce, PrepareMeanOrSum, EvalMean); } @@ -71,4 +81,8 @@ TFLMRegistration Register_SUM() { return tflite::micro::RegisterOp(InitReduce, PrepareMeanOrSum, EvalSum); } +TFLMRegistration Register_PROD() { + return tflite::micro::RegisterOp(InitReduce, PrepareProd, EvalProd); +} + } // namespace tflite diff --git a/tensorflow/lite/micro/kernels/reduce.h b/tensorflow/lite/micro/kernels/reduce.h index 2daeef5feeb..7251136901e 100644 --- a/tensorflow/lite/micro/kernels/reduce.h +++ b/tensorflow/lite/micro/kernels/reduce.h @@ -46,12 +46,17 @@ TfLiteStatus PrepareMaxHelper(TfLiteContext* context, TfLiteNode* node, TfLiteStatus PrepareMeanOrSumHelper(TfLiteContext* context, TfLiteNode* node, OpDataReduce* op_data); +TfLiteStatus PrepareProdHelper(TfLiteContext* context, TfLiteNode* node, + OpDataReduce* op_data); + TfLiteStatus EvalMaxHelper(TfLiteContext* context, TfLiteNode* node, OpDataReduce* op_data); TfLiteStatus EvalMeanHelper(TfLiteContext* context, TfLiteNode* node, OpDataReduce* op_data); TfLiteStatus EvalSumHelper(TfLiteContext* context, TfLiteNode* node, OpDataReduce* op_data); +TfLiteStatus EvalProdHelper(TfLiteContext* context, TfLiteNode* node, + OpDataReduce* op_data); void ReduceResolveAxis(const int* axis_data, int axis_count, MeanParams* op_params); @@ -59,6 +64,7 @@ void ReduceResolveAxis(const int* axis_data, int axis_count, TFLMRegistration Register_MEAN(); TFLMRegistration Register_REDUCE_MAX(); TFLMRegistration Register_SUM(); +TFLMRegistration Register_PROD(); } // namespace tflite diff --git a/tensorflow/lite/micro/kernels/reduce_common.cc b/tensorflow/lite/micro/kernels/reduce_common.cc index 2c1a92a5062..c5ce346cd81 100644 --- a/tensorflow/lite/micro/kernels/reduce_common.cc +++ b/tensorflow/lite/micro/kernels/reduce_common.cc @@ -92,12 +92,63 @@ TfLiteStatus PrepareMaxHelper(TfLiteContext* context, TfLiteNode* node, return kTfLiteOk; } +double GetQuantProdScaling(double input_scale, double output_scale, + int reduced_axis_size) { + // The scaling after taking the product of all the quantized values should + // be (input_scale**reduced_axis_size)/output_scale but to avoid overflowing + // the accumulator we instead scale each multiplication by + // input_scale/nth_root(output_scale, reduced_axis_size). + return input_scale / std::pow(output_scale, 1.0 / reduced_axis_size); +} + +TfLiteStatus PrepareProdHelper(TfLiteContext* context, TfLiteNode* node, + OpDataReduce* op_data) { + MicroContext* micro_context = GetMicroContext(context); + TfLiteTensor* input = micro_context->AllocateTempInputTensor(node, 0); + TfLiteTensor* output = micro_context->AllocateTempOutputTensor(node, 0); + TfLiteTensor* axis = micro_context->AllocateTempInputTensor(node, 1); + + if (input->type == kTfLiteInt8 || input->type == kTfLiteInt16) { + + const int input_size = NumElements(input); + const int output_size = NumElements(output); + const int reduced_axis_size = input_size / output_size; + const double scaling = GetQuantProdScaling( + static_cast(input->params.scale), + static_cast(output->params.scale), + reduced_axis_size); + QuantizeMultiplier(scaling, &op_data->multiplier, &op_data->shift); + } + + int output_size = NumElements(output); + op_data->num_axis = NumElements(axis); + + if (input->type == kTfLiteInt8 || input->type == kTfLiteInt16) { + context->RequestScratchBufferInArena(context, output_size * sizeof(int32_t), + &op_data->temp_buffer_idx); + op_data->input_zp = input->params.zero_point; + op_data->input_scale = input->params.scale; + op_data->output_zp = output->params.zero_point; + op_data->output_scale = output->params.scale; + } + + TF_LITE_ENSURE_OK( + context, + PrepareSimple(context, node, &(op_data->multiplier), &(op_data->shift))); + // TODO(b/144955155): Support uint8_t(b/144955155) and int8_t(b/144955018) + micro_context->DeallocateTempTfLiteTensor(input); + micro_context->DeallocateTempTfLiteTensor(output); + micro_context->DeallocateTempTfLiteTensor(axis); + return kTfLiteOk; +} + TfLiteStatus PrepareMeanOrSumHelper(TfLiteContext* context, TfLiteNode* node, OpDataReduce* op_data) { MicroContext* micro_context = GetMicroContext(context); TfLiteTensor* input = micro_context->AllocateTempInputTensor(node, 0); TfLiteTensor* output = micro_context->AllocateTempOutputTensor(node, 0); TfLiteTensor* axis = micro_context->AllocateTempInputTensor(node, 1); + if (input->type == kTfLiteInt8 || input->type == kTfLiteInt16) { const double real_multiplier = static_cast(input->params.scale) / static_cast(output->params.scale); @@ -162,6 +213,29 @@ TfLiteStatus QuantizedMeanOrSum(TfLiteContext* context, TfLiteNode* node, return kTfLiteOk; } +template +TfLiteStatus QuantizedProd(TfLiteContext* context, TfLiteNode* node, + int* temp_index, int* resolved_axis, + int32_t* temp_prod, OpDataReduce* op_data) { + const TfLiteEvalTensor* input = tflite::micro::GetEvalInput(context, node, 0); + const TfLiteEvalTensor* axis = tflite::micro::GetEvalInput(context, node, 1); + TfLiteEvalTensor* output = tflite::micro::GetEvalOutput(context, node, 0); + TfLiteReducerParams* params = + static_cast(node->builtin_data); + + bool result = reference_ops::QuantizedProdExtraArgs( + tflite::micro::GetTensorData(input), op_data->input_zp, + op_data->input_scale, &input->dims->data[0], input->dims->size, + tflite::micro::GetTensorData(output), op_data->output_scale, + op_data->multiplier, op_data->shift, op_data->output_zp, + &output->dims->data[0], output->dims->size, + tflite::micro::GetTensorData(axis), op_data->num_axis, + params->keep_dims, temp_index, resolved_axis, temp_prod); + TF_LITE_ENSURE(context, result); + + return kTfLiteOk; +} + template TfLiteStatus EvalIntegerMean(TfLiteContext* context, TfLiteNode* node, int num_axis, OpDataReduce* op_data, @@ -337,4 +411,52 @@ TfLiteStatus EvalSumHelper(TfLiteContext* context, TfLiteNode* node, return kTfLiteOk; } +TfLiteStatus EvalProdHelper(TfLiteContext* context, TfLiteNode* node, + OpDataReduce* op_data) { + const TfLiteEvalTensor* input = tflite::micro::GetEvalInput(context, node, 0); + const TfLiteEvalTensor* axis = tflite::micro::GetEvalInput(context, node, 1); + TfLiteEvalTensor* output = tflite::micro::GetEvalOutput(context, node, 0); + TF_LITE_ENSURE_TYPES_EQ(context, input->type, output->type); + TfLiteReducerParams* params = + static_cast(node->builtin_data); + + + // Interpret an axis tensor with null dimensions as a scalar. + int num_axis = static_cast(ElementCount(*axis->dims)); + int temp_index[kMaxNumberOfAxis]; + int resolved_axis[kMaxNumberOfReducedAxis]; + + switch (input->type) { + case kTfLiteFloat32: { + TF_LITE_ENSURE( + context, + reference_ops::ReduceGeneric( + tflite::micro::GetTensorData(input), input->dims->data, + input->dims->size, tflite::micro::GetTensorData(output), + output->dims->data, output->dims->size, + tflite::micro::GetTensorData(axis), num_axis, + params->keep_dims, temp_index, resolved_axis, /*init_value=*/1.f, + [](const float current, const float in) -> float { + return in * current; + })); + } break; + case kTfLiteInt8: { + int32_t* temp_prod = static_cast( + context->GetScratchBuffer(context, op_data->temp_buffer_idx)); + QuantizedProd(context, node, temp_index, resolved_axis, + temp_prod, op_data); + } break; + case kTfLiteInt16: { + int32_t* temp_prod = static_cast( + context->GetScratchBuffer(context, op_data->temp_buffer_idx)); + QuantizedProd(context, node, temp_index, resolved_axis, + temp_prod, op_data); + } break; + default: + MicroPrintf("Only float32, int8, and int16 types are supported."); + return kTfLiteError; + } + return kTfLiteOk; +} + } // namespace tflite diff --git a/tensorflow/lite/micro/kernels/reduce_test.cc b/tensorflow/lite/micro/kernels/reduce_test.cc index 8e532376fc3..f6292f3f222 100644 --- a/tensorflow/lite/micro/kernels/reduce_test.cc +++ b/tensorflow/lite/micro/kernels/reduce_test.cc @@ -38,6 +38,7 @@ static int kOutputShape2D[] = {2, 1, 2}; static const float kGoldenData2D[] = {2.5, 6.5}; static const float kGoldenDataSum2D[] = {10.0, 26.0}; +static const float kGoldenDataProd2D[] = {24.0, 1680.0}; // Common 3D inputs, outputs and axis. static const int kInputElements3D = 8; @@ -52,6 +53,7 @@ static int kOutputShape3D[] = {2, 1, 2}; static const float kGoldenData3D[] = {2.5, 6.5}; static const float kGoldenDataSum3D[] = {10.0, 26.0}; +static const float kGoldenDataProd3D[] = {24.0, 1680.0}; // Common 4D inputs, outputs and axis. static const int kInputElements4D = 24; @@ -68,6 +70,7 @@ static int kOutputShape4D[] = {4, 2, 1, 1, 2}; static const float kGoldenData4D[] = {6, 7, 18, 19}; static const float kGoldenDataSum4D[] = {36, 42, 108, 114}; +static const float kGoldenDataProd4D[] = {10395.0, 46080.0, 30421755.0, 42577920.0}; template TfLiteStatus ValidateReduceGoldens(TfLiteTensor* tensors, int tensors_size, @@ -556,6 +559,386 @@ TF_LITE_MICRO_TEST(MeanInt84DWithoutKeepDimsWithPrecision) { output_zero_point, tflite::Register_MEAN(), ¶ms, 1.0); } +TF_LITE_MICRO_TEST(ProdFloatSmallInput) { + int input_shape[] = {4, 3, 2, 2, 2}; + int output_shape[] = {2, 3, 2}; + int axis_shape[] = {2}; + int32_t axis_data[] = {2, 3}; + float input_data[] = {1.0, 2.0, 3.0, 4.0, 8.0, 7.0, 6.0, 5.0, + 10.0, 9.0, 11.0, 12.0, 1.0, 2.0, 3.0, 4.0, + 8.0, 7.0, 6.0, 5.0, 10.0, 9.0, 11.0, 12.0}; + float expected_output[] = {24.0, 1680.0, 11880.0, 24.0, 1680.0, 11880.0}; + float actual_output_data[6]; + + TfLiteReducerParams params = {false}; + + tflite::testing::TestReduceOpFloat( + input_shape, input_data, axis_shape, axis_data, output_shape, + actual_output_data, expected_output, tflite::Register_PROD(), ¶ms); +} + +TF_LITE_MICRO_TEST(ProdFloatFlatten2ReduceDims) { + int input_shape[] = {3, 4, 3, 2}; + int output_shape[] = {1, 4}; + int axis_shape[] = {1, 2}; + int32_t axis_data[] = {2, 1}; + float input_data[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, + 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0}; + float expected_output[] = {720.0, 665280.0, 13366080.0, 96909120.0}; + float actual_output_data[4]; + + TfLiteReducerParams params = {false}; + + tflite::testing::TestReduceOpFloat( + input_shape, input_data, axis_shape, axis_data, output_shape, + actual_output_data, expected_output, tflite::Register_PROD(), ¶ms); +} + +TF_LITE_MICRO_TEST(ProdFloatFlatten2NonReduceDims) { + int input_shape[] = {3, 4, 3, 2}; + int output_shape[] = {1, 12}; + int axis_shape[] = {1, 1}; + int32_t axis_data[] = {2}; + float input_data[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, + 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0}; + float expected_output[] = {2.0, 12.0, 30.0, 56.0, 90.0, 132.0, + 182.0, 240.0, 306.0, 380.0, 462.0, 552.0}; + float actual_output_data[12]; + + TfLiteReducerParams params = {false}; + + tflite::testing::TestReduceOpFloat( + input_shape, input_data, axis_shape, axis_data, output_shape, + actual_output_data, expected_output, tflite::Register_PROD(), ¶ms); +} + +TF_LITE_MICRO_TEST(ProdFloatFlatten2MiddleDims) { + int input_shape[] = {4, 2, 2, 3, 2}; + int output_shape[] = {2, 2, 2}; + int axis_shape[] = {1, 2}; + int32_t axis_data[] = {1, 2}; + float input_data[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, + 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0}; + float expected_output[] = {10395.0, 46080.0, 30421755.0, 42577920.0}; + float actual_output_data[4]; + + TfLiteReducerParams params = {false}; + + tflite::testing::TestReduceOpFloat( + input_shape, input_data, axis_shape, axis_data, output_shape, + actual_output_data, expected_output, tflite::Register_PROD(), ¶ms); +} + +TF_LITE_MICRO_TEST(ProdFloat2DKeepDims) { + float output_data[tflite::testing::kOutputElements2D]; + + TfLiteReducerParams params = {true}; + + tflite::testing::TestReduceOpFloat( + tflite::testing::kInputShape2D, tflite::testing::kInputData2D, + tflite::testing::kAxisShape2D, tflite::testing::kAxisData2D, + tflite::testing::kOutputShape2D, output_data, + tflite::testing::kGoldenDataProd2D, tflite::Register_PROD(), ¶ms); +} + +TF_LITE_MICRO_TEST(ProdInt82DKeepDims) { + int8_t expected_output_data_quant[tflite::testing::kOutputElements2D]; + int8_t output_data_quant[tflite::testing::kOutputElements2D]; + int8_t input_data_quant[tflite::testing::kInputElements2D]; + + float input_scale = 0.5f; + int input_zero_point = 0; + float output_scale = 0.5f; + int output_zero_point = 0; + + TfLiteReducerParams params = { + true // keep_dims + }; + + tflite::testing::TestReduceOpQuantized( + tflite::testing::kInputShape2D, tflite::testing::kInputData2D, + input_data_quant, input_scale, input_zero_point, + tflite::testing::kAxisShape2D, tflite::testing::kAxisData2D, + tflite::testing::kOutputShape2D, tflite::testing::kGoldenDataProd2D, + output_data_quant, expected_output_data_quant, output_scale, + output_zero_point, tflite::Register_PROD(), ¶ms, 1.0); +} + +TF_LITE_MICRO_TEST(ProdInt162DKeepDims) { + int16_t expected_output_data_quant[tflite::testing::kOutputElements2D]; + int16_t output_data_quant[tflite::testing::kOutputElements2D]; + int16_t input_data_quant[tflite::testing::kInputElements2D]; + + float input_scale = 0.5f; + int input_zero_point = 0; + float output_scale = 0.5f; + int output_zero_point = 0; + + TfLiteReducerParams params = { + true // keep_dims + }; + + tflite::testing::TestReduceOpQuantized( + tflite::testing::kInputShape2D, tflite::testing::kInputData2D, + input_data_quant, input_scale, input_zero_point, + tflite::testing::kAxisShape2D, tflite::testing::kAxisData2D, + tflite::testing::kOutputShape2D, tflite::testing::kGoldenDataProd2D, + output_data_quant, expected_output_data_quant, output_scale, + output_zero_point, tflite::Register_PROD(), ¶ms, 1.0); +} + +TF_LITE_MICRO_TEST(ProdFloat3DKeepDims) { + float output_data[tflite::testing::kOutputElements3D]; + + TfLiteReducerParams params = {true}; + + tflite::testing::TestReduceOpFloat( + tflite::testing::kInputShape3D, tflite::testing::kInputData3D, + tflite::testing::kAxisShape3D, tflite::testing::kAxisData3D, + tflite::testing::kOutputShape3D, output_data, + tflite::testing::kGoldenDataProd3D, tflite::Register_PROD(), ¶ms); +} + +TF_LITE_MICRO_TEST(ProdInt83DKeepDims) { + int8_t expected_output_data_quant[tflite::testing::kOutputElements3D]; + int8_t output_data_quant[tflite::testing::kOutputElements3D]; + int8_t input_data_quant[tflite::testing::kInputElements3D]; + + float input_scale = 0.5f; + int input_zero_point = 0; + float output_scale = 0.5f; + int output_zero_point = 0; + + TfLiteReducerParams params = { + true // keep_dims + }; + + tflite::testing::TestReduceOpQuantized( + tflite::testing::kInputShape3D, tflite::testing::kInputData3D, + input_data_quant, input_scale, input_zero_point, + tflite::testing::kAxisShape3D, tflite::testing::kAxisData3D, + tflite::testing::kOutputShape3D, tflite::testing::kGoldenDataProd3D, + output_data_quant, expected_output_data_quant, output_scale, + output_zero_point, tflite::Register_PROD(), ¶ms, 1.0); +} + +TF_LITE_MICRO_TEST(ProdInt163DKeepDims) { + int16_t expected_output_data_quant[tflite::testing::kOutputElements3D]; + int16_t output_data_quant[tflite::testing::kOutputElements3D]; + int16_t input_data_quant[tflite::testing::kInputElements3D]; + + float input_scale = 0.5f; + int input_zero_point = 0; + float output_scale = 0.5f; + int output_zero_point = 0; + + TfLiteReducerParams params = { + true // keep_dims + }; + + tflite::testing::TestReduceOpQuantized( + tflite::testing::kInputShape3D, tflite::testing::kInputData3D, + input_data_quant, input_scale, input_zero_point, + tflite::testing::kAxisShape3D, tflite::testing::kAxisData3D, + tflite::testing::kOutputShape3D, tflite::testing::kGoldenDataProd3D, + output_data_quant, expected_output_data_quant, output_scale, + output_zero_point, tflite::Register_PROD(), ¶ms, 1.0); +} + +TF_LITE_MICRO_TEST(ProdFloat4DKeepDims) { + float output_data[tflite::testing::kOutputElements4D]; + + TfLiteReducerParams params = { + true // keep_dims + }; + + tflite::testing::TestReduceOpFloat( + tflite::testing::kInputShape4D, tflite::testing::kInputData4D, + tflite::testing::kAxisShape4D, tflite::testing::kAxisData4D, + tflite::testing::kOutputShape4D, output_data, + tflite::testing::kGoldenDataProd4D, tflite::Register_PROD(), ¶ms); +} + +TF_LITE_MICRO_TEST(ProdInt84DKeepDims) { + int8_t expected_output_data_quant[tflite::testing::kOutputElements4D]; + int8_t output_data_quant[tflite::testing::kOutputElements4D]; + int8_t input_data_quant[tflite::testing::kInputElements4D]; + + float input_scale = 1.f; + int input_zero_point = 0; + float output_scale = 1.f; + int output_zero_point = 0; + + TfLiteReducerParams params = { + true // keep_dims + }; + + tflite::testing::TestReduceOpQuantized( + tflite::testing::kInputShape4D, tflite::testing::kInputData4D, + input_data_quant, input_scale, input_zero_point, + tflite::testing::kAxisShape4D, tflite::testing::kAxisData4D, + tflite::testing::kOutputShape4D, tflite::testing::kGoldenDataProd4D, + output_data_quant, expected_output_data_quant, output_scale, + output_zero_point, tflite::Register_PROD(), ¶ms, 1.0); +} + +TF_LITE_MICRO_TEST(ProdInt164DKeepDims) { + int16_t expected_output_data_quant[tflite::testing::kOutputElements4D]; + int16_t output_data_quant[tflite::testing::kOutputElements4D]; + int16_t input_data_quant[tflite::testing::kInputElements4D]; + + float input_scale = 0.5f; + int input_zero_point = 0; + float output_scale = 0.5f; + int output_zero_point = 0; + + TfLiteReducerParams params = { + true // keep_dims + }; + + tflite::testing::TestReduceOpQuantized( + tflite::testing::kInputShape4D, tflite::testing::kInputData4D, + input_data_quant, input_scale, input_zero_point, + tflite::testing::kAxisShape4D, tflite::testing::kAxisData4D, + tflite::testing::kOutputShape4D, tflite::testing::kGoldenDataProd4D, + output_data_quant, expected_output_data_quant, output_scale, + output_zero_point, tflite::Register_PROD(), ¶ms, 1.0); +} + +TF_LITE_MICRO_TEST(ProdFloat4DWithoutKeepDims) { + int kOutputShape4D[] = {2, 2, 2}; + float output_data[tflite::testing::kOutputElements4D]; + TfLiteReducerParams params = { + false // keep_dims + }; + + tflite::testing::TestReduceOpFloat( + tflite::testing::kInputShape4D, tflite::testing::kInputData4D, + tflite::testing::kAxisShape4D, tflite::testing::kAxisData4D, + kOutputShape4D, output_data, tflite::testing::kGoldenDataProd4D, + tflite::Register_PROD(), ¶ms); +} + +TF_LITE_MICRO_TEST(ProdInt84DWithoutKeepDims) { + int8_t expected_output_data_quant[tflite::testing::kOutputElements4D]; + int8_t output_data_quant[tflite::testing::kOutputElements4D]; + int8_t input_data_quant[tflite::testing::kInputElements4D]; + + int kOutputShape4D[] = {2, 2, 2}; + TfLiteReducerParams params = { + false // keep_dims + }; + float input_scale = 1.f; + int input_zero_point = 0; + float output_scale = 1.f; + int output_zero_point = 0; + + tflite::testing::TestReduceOpQuantized( + tflite::testing::kInputShape4D, tflite::testing::kInputData4D, + input_data_quant, input_scale, input_zero_point, + tflite::testing::kAxisShape4D, tflite::testing::kAxisData4D, + kOutputShape4D, tflite::testing::kGoldenDataProd4D, output_data_quant, + expected_output_data_quant, output_scale, output_zero_point, + tflite::Register_PROD(), ¶ms, 1.0); +} + +TF_LITE_MICRO_TEST(ProdInt164DWithoutKeepDims) { + int16_t expected_output_data_quant[tflite::testing::kOutputElements4D]; + int16_t output_data_quant[tflite::testing::kOutputElements4D]; + int16_t input_data_quant[tflite::testing::kInputElements4D]; + + int kOutputShape4D[] = {2, 2, 2}; + TfLiteReducerParams params = { + false // keep_dims + }; + float input_scale = 0.5f; + int input_zero_point = 0; + float output_scale = 0.5f; + int output_zero_point = 0; + + tflite::testing::TestReduceOpQuantized( + tflite::testing::kInputShape4D, tflite::testing::kInputData4D, + input_data_quant, input_scale, input_zero_point, + tflite::testing::kAxisShape4D, tflite::testing::kAxisData4D, + kOutputShape4D, tflite::testing::kGoldenDataProd4D, output_data_quant, + expected_output_data_quant, output_scale, output_zero_point, + tflite::Register_PROD(), ¶ms, 1.0); +} + +TF_LITE_MICRO_TEST(ProdInt164DWithoutKeepDimsDifferentScaleAndZeroPoint) { + int16_t expected_output_data_quant[tflite::testing::kOutputElements4D]; + int16_t output_data_quant[tflite::testing::kOutputElements4D]; + int16_t input_data_quant[tflite::testing::kInputElements4D]; + + int kOutputShape4D[] = {2, 2, 2}; + TfLiteReducerParams params = { + false // keep_dims + }; + float input_scale = 0.5f; + int input_zero_point = 0; + float output_scale = 0.7f; + int output_zero_point = 0; + + tflite::testing::TestReduceOpQuantized( + tflite::testing::kInputShape4D, tflite::testing::kInputData4D, + input_data_quant, input_scale, input_zero_point, + tflite::testing::kAxisShape4D, tflite::testing::kAxisData4D, + kOutputShape4D, tflite::testing::kGoldenDataProd4D, output_data_quant, + expected_output_data_quant, output_scale, output_zero_point, + tflite::Register_PROD(), ¶ms, 1.0); +} + +TF_LITE_MICRO_TEST(ProdFloatSize1) { + int input_shape[] = {1, 1}; + int output_shape[] = {1, 1}; + int axis_shape[] = {1, 1}; + int32_t axis_data[] = {0}; + float input_data[] = {1.0}; + float expected_output[] = {1.0}; + float actual_output_data[1]; + + TfLiteReducerParams params = {false}; + + tflite::testing::TestReduceOpFloat( + input_shape, input_data, axis_shape, axis_data, output_shape, + actual_output_data, expected_output, tflite::Register_PROD(), ¶ms); +} + +TF_LITE_MICRO_TEST(ProdFloat2DRedundantDims) { + int input_shape[] = {3, 1, 2, 4}; + int output_shape[] = {2, 1, 4}; + int axis_shape[] = {1, 1}; + int32_t axis_data[] = {1}; + float input_data[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}; + float expected_output[] = {5.0, 12.0, 21.0, 32.0}; + float actual_output_data[4]; + + TfLiteReducerParams params = {false}; + + tflite::testing::TestReduceOpFloat( + input_shape, input_data, axis_shape, axis_data, output_shape, + actual_output_data, expected_output, tflite::Register_PROD(), ¶ms); +} + +TF_LITE_MICRO_TEST(ProdFloatScalar) { + int input_shape[] = {1, 1}; + int output_shape[] = {1, 1}; + int axis_shape[] = {1, 0}; + int32_t axis_data[] = {}; + float input_data[] = {1.0}; + float expected_output[] = {1.0}; + float actual_output_data[1]; + + TfLiteReducerParams params = {false}; + + tflite::testing::TestReduceOpFloat( + input_shape, input_data, axis_shape, axis_data, output_shape, + actual_output_data, expected_output, tflite::Register_PROD(), ¶ms); +} + TF_LITE_MICRO_TEST(SumFloatFlatten2ReduceDims) { int input_shape[] = {3, 4, 3, 2}; int output_shape[] = {1, 4};