diff --git a/tensorflow/lite/micro/kernels/reduce.cc b/tensorflow/lite/micro/kernels/reduce.cc index a689d3b934b..1ad364f6c56 100644 --- a/tensorflow/lite/micro/kernels/reduce.cc +++ b/tensorflow/lite/micro/kernels/reduce.cc @@ -1,4 +1,4 @@ -/* Copyright 2022 The TensorFlow Authors. All Rights Reserved. +/* Copyright 2025 The TensorFlow Authors. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -28,15 +28,17 @@ limitations under the License. namespace tflite { +namespace { + void* InitReduce(TfLiteContext* context, const char* buffer, size_t length) { void* op_data = context->AllocatePersistentBuffer(context, sizeof(OpDataReduce)); return new (op_data) OpDataReduce(); } -TfLiteStatus PrepareMax(TfLiteContext* context, TfLiteNode* node) { - return PrepareMaxHelper(context, node, - static_cast(node->user_data)); +TfLiteStatus PrepareMinMax(TfLiteContext* context, TfLiteNode* node) { + return PrepareMinMaxHelper(context, node, + static_cast(node->user_data)); } TfLiteStatus PrepareMeanOrSum(TfLiteContext* context, TfLiteNode* node) { @@ -54,17 +56,28 @@ TfLiteStatus EvalMax(TfLiteContext* context, TfLiteNode* node) { return EvalMaxHelper(context, node, op_data); } +TfLiteStatus EvalMin(TfLiteContext* context, TfLiteNode* node) { + OpDataReduce* op_data = static_cast(node->user_data); + return EvalMinHelper(context, node, op_data); +} + TfLiteStatus EvalSum(TfLiteContext* context, TfLiteNode* node) { return EvalSumHelper(context, node, static_cast(node->user_data)); } +} // namespace + TFLMRegistration Register_MEAN() { return tflite::micro::RegisterOp(InitReduce, PrepareMeanOrSum, EvalMean); } TFLMRegistration Register_REDUCE_MAX() { - return tflite::micro::RegisterOp(InitReduce, PrepareMax, EvalMax); + return tflite::micro::RegisterOp(InitReduce, PrepareMinMax, EvalMax); +} + +TFLMRegistration Register_REDUCE_MIN() { + return tflite::micro::RegisterOp(InitReduce, PrepareMinMax, EvalMin); } TFLMRegistration Register_SUM() { diff --git a/tensorflow/lite/micro/kernels/reduce.h b/tensorflow/lite/micro/kernels/reduce.h index 2daeef5feeb..15c05f122cc 100644 --- a/tensorflow/lite/micro/kernels/reduce.h +++ b/tensorflow/lite/micro/kernels/reduce.h @@ -1,4 +1,4 @@ -/* Copyright 2022 The TensorFlow Authors. All Rights Reserved. +/* Copyright 2025 The TensorFlow Authors. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -40,24 +40,24 @@ struct OpDataReduce { int num_axis; }; -TfLiteStatus PrepareMaxHelper(TfLiteContext* context, TfLiteNode* node, - OpDataReduce* op_data); +TfLiteStatus PrepareMinMaxHelper(TfLiteContext* context, TfLiteNode* node, + OpDataReduce* op_data); TfLiteStatus PrepareMeanOrSumHelper(TfLiteContext* context, TfLiteNode* node, OpDataReduce* op_data); TfLiteStatus EvalMaxHelper(TfLiteContext* context, TfLiteNode* node, OpDataReduce* op_data); +TfLiteStatus EvalMinHelper(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); -void ReduceResolveAxis(const int* axis_data, int axis_count, - MeanParams* op_params); - TFLMRegistration Register_MEAN(); TFLMRegistration Register_REDUCE_MAX(); +TFLMRegistration Register_REDUCE_MIN(); TFLMRegistration Register_SUM(); } // namespace tflite diff --git a/tensorflow/lite/micro/kernels/reduce_common.cc b/tensorflow/lite/micro/kernels/reduce_common.cc index 2c1a92a5062..5a26f6abc51 100644 --- a/tensorflow/lite/micro/kernels/reduce_common.cc +++ b/tensorflow/lite/micro/kernels/reduce_common.cc @@ -1,4 +1,4 @@ -/* Copyright 2023 The TensorFlow Authors. All Rights Reserved. +/* Copyright 2025 The TensorFlow Authors. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -31,6 +31,8 @@ namespace tflite { const int kMaxNumberOfAxis = 5; const int kMaxNumberOfReducedAxis = 2; +namespace { + TfLiteStatus PrepareSimple(TfLiteContext* context, TfLiteNode* node, int32_t* multiplier, int* shift) { MicroContext* micro_context = GetMicroContext(context); @@ -64,8 +66,138 @@ TfLiteStatus PrepareSimple(TfLiteContext* context, TfLiteNode* node, return kTfLiteOk; } -TfLiteStatus PrepareMaxHelper(TfLiteContext* context, TfLiteNode* node, - OpDataReduce* op_data) { +void ResolveAxis(const int* axis_data, int axis_count, + tflite::MeanParams* op_params) { + int i = 0; + for (; i < axis_count; ++i) { + op_params->axis[i] = static_cast(axis_data[i]); + } + for (; i < 4; ++i) { + op_params->axis[i] = 1; + } + op_params->axis_count = axis_count; +} + +template +TfLiteStatus QuantizedMeanOrSum(TfLiteContext* context, TfLiteNode* node, + int* temp_index, int* resolved_axis, + int32_t* temp_sum, OpDataReduce* op_data, + bool compute_sum) { + 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::QuantizedMeanOrSumExtraArgs( + 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_sum, compute_sum); + TF_LITE_ENSURE(context, result); + + return kTfLiteOk; +} + +template +TfLiteStatus EvalIntegerMean(TfLiteContext* context, TfLiteNode* node, + int num_axis, OpDataReduce* op_data, + int* temp_index, int* resolved_axis) { + int32_t* temp_sum = static_cast( + context->GetScratchBuffer(context, op_data->temp_buffer_idx)); + + QuantizedMeanOrSum(context, node, temp_index, resolved_axis, + temp_sum, op_data, /*compute_sum=*/false); + + return kTfLiteOk; +} + +enum MinMaxEvalType { kEvalMin, kEvalMax }; + +template +struct MinMaxReducerParams { + MinMaxReducerParams() = delete; + MinMaxReducerParams(MinMaxEvalType evalType) : type_(evalType){}; + + constexpr T initialValue() const { + return (type_ == kEvalMin) ? std::numeric_limits::max() + : std::numeric_limits::lowest(); + } + + // should be able to use "auto" keyword here, but GCC and Clang blow a fuse + T (*compare())(const T, const T) { + if (type_ == kEvalMin) { + return [](const T current, const T in) -> T { + return (in < current) ? in : current; + }; + } else { + return [](const T current, const T in) -> T { + return (in > current) ? in : current; + }; + } + } + + private: + MinMaxEvalType type_; +}; + +TfLiteStatus EvalMinMaxHelper(TfLiteContext* context, TfLiteNode* node, + OpDataReduce* op_data, MinMaxEvalType evalType) { + 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_buffer = static_cast( + context->GetScratchBuffer(context, op_data->temp_buffer_idx)); + int* resolved_axis = static_cast( + context->GetScratchBuffer(context, op_data->resolved_axis_idx)); + switch (input->type) { + case kTfLiteFloat32: { + MinMaxReducerParams reducer(evalType); + 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_buffer, resolved_axis, + reducer.initialValue(), reducer.compare())); + } break; + case kTfLiteInt8: { + MinMaxReducerParams reducer(evalType); + TF_LITE_ENSURE_EQ(context, static_cast(op_data->input_scale), + static_cast(op_data->output_scale)); + TF_LITE_ENSURE_EQ(context, op_data->input_zp, op_data->output_zp); + 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_buffer, resolved_axis, + reducer.initialValue(), reducer.compare())); + } break; + default: + MicroPrintf("Only float32 and int8 types are supported."); + return kTfLiteError; + } + return kTfLiteOk; +} + +} // namespace + +TfLiteStatus PrepareMinMaxHelper(TfLiteContext* context, TfLiteNode* node, + OpDataReduce* op_data) { TF_LITE_ENSURE_OK(context, PrepareSimple(context, node, &op_data->multiplier, &op_data->shift)); @@ -126,55 +258,6 @@ TfLiteStatus PrepareMeanOrSumHelper(TfLiteContext* context, TfLiteNode* node, return kTfLiteOk; } -void ResolveAxis(const int* axis_data, int axis_count, - tflite::MeanParams* op_params) { - int i = 0; - for (; i < axis_count; ++i) { - op_params->axis[i] = static_cast(axis_data[i]); - } - for (; i < 4; ++i) { - op_params->axis[i] = 1; - } - op_params->axis_count = axis_count; -} - -template -TfLiteStatus QuantizedMeanOrSum(TfLiteContext* context, TfLiteNode* node, - int* temp_index, int* resolved_axis, - int32_t* temp_sum, OpDataReduce* op_data, - bool compute_sum) { - 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::QuantizedMeanOrSumExtraArgs( - 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_sum, compute_sum); - TF_LITE_ENSURE(context, result); - - return kTfLiteOk; -} - -template -TfLiteStatus EvalIntegerMean(TfLiteContext* context, TfLiteNode* node, - int num_axis, OpDataReduce* op_data, - int* temp_index, int* resolved_axis) { - int32_t* temp_sum = static_cast( - context->GetScratchBuffer(context, op_data->temp_buffer_idx)); - - QuantizedMeanOrSum(context, node, temp_index, resolved_axis, - temp_sum, op_data, /*compute_sum=*/false); - - return kTfLiteOk; -} - TfLiteStatus EvalMeanHelper(TfLiteContext* context, TfLiteNode* node, OpDataReduce* op_data) { const TfLiteEvalTensor* input = tflite::micro::GetEvalInput(context, node, 0); @@ -238,56 +321,12 @@ TfLiteStatus EvalMeanHelper(TfLiteContext* context, TfLiteNode* node, TfLiteStatus EvalMaxHelper(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); + return EvalMinMaxHelper(context, node, op_data, kEvalMax); +} - // Interpret an axis tensor with null dimensions as a scalar - int num_axis = static_cast(ElementCount(*axis->dims)); - int* temp_buffer = static_cast( - context->GetScratchBuffer(context, op_data->temp_buffer_idx)); - int* resolved_axis = static_cast( - context->GetScratchBuffer(context, op_data->resolved_axis_idx)); - 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_buffer, resolved_axis, - std::numeric_limits::lowest(), - [](const float current, const float in) -> float { - return (in > current) ? in : current; - })); - break; - case kTfLiteInt8: - TF_LITE_ENSURE_EQ(context, static_cast(op_data->input_scale), - static_cast(op_data->output_scale)); - TF_LITE_ENSURE_EQ(context, op_data->input_zp, op_data->output_zp); - 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_buffer, resolved_axis, - std::numeric_limits::lowest(), - [](const int8_t current, const int8_t in) -> int8_t { - return (in > current) ? in : current; - })); - break; - default: - MicroPrintf("Only float32 and int8 types are supported."); - return kTfLiteError; - } - return kTfLiteOk; +TfLiteStatus EvalMinHelper(TfLiteContext* context, TfLiteNode* node, + OpDataReduce* op_data) { + return EvalMinMaxHelper(context, node, op_data, kEvalMin); } TfLiteStatus EvalSumHelper(TfLiteContext* context, TfLiteNode* node, diff --git a/tensorflow/lite/micro/kernels/reduce_test.cc b/tensorflow/lite/micro/kernels/reduce_test.cc index 8e532376fc3..d05ba0ed3fb 100644 --- a/tensorflow/lite/micro/kernels/reduce_test.cc +++ b/tensorflow/lite/micro/kernels/reduce_test.cc @@ -1,4 +1,4 @@ -/* Copyright 2019 The TensorFlow Authors. All Rights Reserved. +/* Copyright 2025 The TensorFlow Authors. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -484,7 +484,7 @@ TF_LITE_MICRO_TEST(Int8MaxOpTestKeepDims) { int input_shape[] = {3, 1, 3, 2}; const float input_data[] = {0.4, 0.2, 0.3, 0.4, 0.5, 0.6}; int axis_shape[] = {1, 1}; - const int32_t axis_data[] = {1, 1}; + const int32_t axis_data[] = {1}; int output_shape[] = {1, 2}; const float expected_output_data[] = {0.5, 0.6}; @@ -508,7 +508,7 @@ TF_LITE_MICRO_TEST(Int8MaxOpTestWithoutKeepDims) { int input_shape[] = {3, 1, 3, 2}; const float input_data[] = {0.4, 0.2, 0.3, 0.4, 0.5, 0.6}; int axis_shape[] = {1, 1}; - const int32_t axis_data[] = {1, 1}; + const int32_t axis_data[] = {1}; int output_shape[] = {1, 2}; const float expected_output_data[] = {0.5, 0.6}; @@ -530,6 +530,92 @@ TF_LITE_MICRO_TEST(Int8MaxOpTestWithoutKeepDims) { tflite::Register_REDUCE_MAX(), ¶ms); } +TF_LITE_MICRO_TEST(FloatMinOpTestNotKeepDims) { + int input_shape[] = {3, 4, 3, 2}; + const 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}; + int axis_shape[] = {1, 4}; + const int32_t axis_data[] = {1, 0, -3, -3}; + int output_shape[] = {1, 2}; + const float expected_output_data[] = {1, 2}; + float output_data[2]; + + TfLiteReducerParams params = {false}; + + tflite::testing::TestReduceOpFloat( + input_shape, input_data, axis_shape, axis_data, output_shape, output_data, + expected_output_data, tflite::Register_REDUCE_MIN(), ¶ms); +} + +TF_LITE_MICRO_TEST(FloatMinOpTestKeepDims) { + int input_shape[] = {3, 4, 3, 2}; + const 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}; + int axis_shape[] = {1, 2}; + const int32_t axis_data[] = {0, 2}; + int output_shape[] = {1, 3}; + const float expected_output_data[] = {1, 3, 5}; + float output_data[3]; + + TfLiteReducerParams params = {true}; + + tflite::testing::TestReduceOpFloat( + input_shape, input_data, axis_shape, axis_data, output_shape, output_data, + expected_output_data, tflite::Register_REDUCE_MIN(), ¶ms); +} + +TF_LITE_MICRO_TEST(Int8MinOpTestKeepDims) { + int input_shape[] = {3, 1, 3, 2}; + const float input_data[] = {0.4, 0.2, 0.3, 0.4, 0.5, 0.6}; + int axis_shape[] = {1, 1}; + const int32_t axis_data[] = {1}; + int output_shape[] = {1, 2}; + const float expected_output_data[] = {0.3, 0.2}; + + float input_scale = 2 / 255.0; + int input_zp = 0; + + TfLiteReducerParams params = {true}; + + int8_t input_data_quant[6]; + int8_t output_data_quant[2]; + int8_t expected_output_data_quant[2]; + + tflite::testing::TestReduceOpQuantized( + input_shape, input_data, input_data_quant, input_scale, input_zp, + axis_shape, axis_data, output_shape, expected_output_data, + output_data_quant, expected_output_data_quant, input_scale, input_zp, + tflite::Register_REDUCE_MIN(), ¶ms); +} + +TF_LITE_MICRO_TEST(Int8MinOpTestWithoutKeepDims) { + int input_shape[] = {3, 1, 3, 2}; + const float input_data[] = {0.4, 0.2, 0.3, 0.4, 0.5, 0.6}; + int axis_shape[] = {1, 1}; + const int32_t axis_data[] = {1}; + int output_shape[] = {1, 2}; + const float expected_output_data[] = {0.3, 0.2}; + + float input_scale = 2 / 255.0; + int input_zp = 0; + float output_scale = 2 / 255.0; + int output_zp = 0; + + TfLiteReducerParams params = {false}; + + int8_t input_data_quant[6]; + int8_t output_data_quant[2]; + int8_t expected_output_data_quant[2]; + + tflite::testing::TestReduceOpQuantized( + input_shape, input_data, input_data_quant, input_scale, input_zp, + axis_shape, axis_data, output_shape, expected_output_data, + output_data_quant, expected_output_data_quant, output_scale, output_zp, + tflite::Register_REDUCE_MIN(), ¶ms); +} + TF_LITE_MICRO_TEST(MeanInt84DWithoutKeepDimsWithPrecision) { int kInputShape4D[] = {4, 2, 2, 3, 1}; const float kInputData4D[] = {1.0, 24.0, 13.0, 3.0, 9.0, 17.0, diff --git a/tensorflow/lite/micro/kernels/xtensa/reduce.cc b/tensorflow/lite/micro/kernels/xtensa/reduce.cc index c7c507f70ee..255fa7c579a 100644 --- a/tensorflow/lite/micro/kernels/xtensa/reduce.cc +++ b/tensorflow/lite/micro/kernels/xtensa/reduce.cc @@ -1,4 +1,4 @@ -/* Copyright 2019 The TensorFlow Authors. All Rights Reserved. +/* Copyright 2025 The TensorFlow Authors. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -46,13 +46,21 @@ void* XtensaInitReduce(TfLiteContext* context, const char* buffer, TfLiteStatus XtensaPrepareMax(TfLiteContext* context, TfLiteNode* node) { OpDataReduce* op_data = &(static_cast(node->user_data)->reference_op_data); - TF_LITE_ENSURE_OK(context, PrepareMaxHelper(context, node, op_data)); + TF_LITE_ENSURE_OK(context, PrepareMinMaxHelper(context, node, op_data)); #if defined(VISION_P6) TF_LITE_ENSURE_OK(context, ReducePrepareVision(context, node)); #endif // VISION_P6 return kTfLiteOk; } +TfLiteStatus XtensaPrepareMin(TfLiteContext* context, TfLiteNode* node) { + OpDataReduce* op_data = + &(static_cast(node->user_data)->reference_op_data); + TF_LITE_ENSURE_OK(context, PrepareMinMaxHelper(context, node, op_data)); + // P6 FLK library does not support REDUCE_MIN + return kTfLiteOk; +} + TfLiteStatus XtensaPrepareMeanOrSum(TfLiteContext* context, TfLiteNode* node) { OpDataReduce* op_data = &(static_cast(node->user_data)->reference_op_data); @@ -94,6 +102,14 @@ TfLiteStatus XtensaEvalMax(TfLiteContext* context, TfLiteNode* node) { #endif } +TfLiteStatus XtensaEvalMin(TfLiteContext* context, TfLiteNode* node) { + XtensaReduceOpData* op_data_xtensa = + static_cast(node->user_data); + OpDataReduce* op_data = &(op_data_xtensa->reference_op_data); + // P6 FLK library does not support REDUCE_MIN + return EvalMinHelper(context, node, op_data); +} + TfLiteStatus XtensaEvalSum(TfLiteContext* context, TfLiteNode* node) { OpDataReduce* op_data = &(static_cast(node->user_data)->reference_op_data); @@ -110,6 +126,11 @@ TFLMRegistration Register_REDUCE_MAX() { XtensaEvalMax); } +TFLMRegistration Register_REDUCE_MIN() { + return tflite::micro::RegisterOp(XtensaInitReduce, XtensaPrepareMin, + XtensaEvalMin); +} + TFLMRegistration Register_SUM() { return tflite::micro::RegisterOp(XtensaInitReduce, XtensaPrepareMeanOrSum, XtensaEvalSum);