diff --git a/python/tflite_micro/python_ops_resolver.cc b/python/tflite_micro/python_ops_resolver.cc index f5d6e636c16..f7171d7e0f7 100644 --- a/python/tflite_micro/python_ops_resolver.cc +++ b/python/tflite_micro/python_ops_resolver.cc @@ -104,6 +104,7 @@ PythonOpsResolver::PythonOpsResolver() { AddReshape(); AddResizeBilinear(); AddResizeNearestNeighbor(); + AddReverseV2(); AddRfft(); AddRound(); AddRsqrt(); diff --git a/tensorflow/lite/micro/kernels/BUILD b/tensorflow/lite/micro/kernels/BUILD index 7b5ddc7b306..babdb7b6406 100644 --- a/tensorflow/lite/micro/kernels/BUILD +++ b/tensorflow/lite/micro/kernels/BUILD @@ -292,6 +292,7 @@ tflm_kernel_cc_library( "reshape_common.cc", "resize_bilinear.cc", "resize_nearest_neighbor.cc", + "reverse.cc", "round.cc", "select.cc", "shape.cc", @@ -1224,6 +1225,19 @@ tflm_cc_test( ], ) +tflm_cc_test( + name = "reverse_test", + srcs = [ + "reverse_test.cc", + ], + deps = [ + ":kernel_runner", + "//tensorflow/lite/c:common", + "//tensorflow/lite/micro:test_helpers", + "//tensorflow/lite/micro/testing:micro_test", + ], +) + tflm_cc_test( name = "round_test", srcs = [ diff --git a/tensorflow/lite/micro/kernels/Makefile.inc b/tensorflow/lite/micro/kernels/Makefile.inc index f4456242fef..a62432daa1b 100644 --- a/tensorflow/lite/micro/kernels/Makefile.inc +++ b/tensorflow/lite/micro/kernels/Makefile.inc @@ -160,6 +160,7 @@ $(TENSORFLOW_ROOT)tensorflow/lite/micro/kernels/reduce_test.cc \ $(TENSORFLOW_ROOT)tensorflow/lite/micro/kernels/reshape_test.cc \ $(TENSORFLOW_ROOT)tensorflow/lite/micro/kernels/resize_bilinear_test.cc \ $(TENSORFLOW_ROOT)tensorflow/lite/micro/kernels/resize_nearest_neighbor_test.cc \ +$(TENSORFLOW_ROOT)tensorflow/lite/micro/kernels/reverse_test.cc \ $(TENSORFLOW_ROOT)tensorflow/lite/micro/kernels/round_test.cc \ $(TENSORFLOW_ROOT)tensorflow/lite/micro/kernels/select_test.cc \ $(TENSORFLOW_ROOT)tensorflow/lite/micro/kernels/shape_test.cc \ diff --git a/tensorflow/lite/micro/kernels/micro_ops.h b/tensorflow/lite/micro/kernels/micro_ops.h index 2e33a6730bd..13a0798b159 100644 --- a/tensorflow/lite/micro/kernels/micro_ops.h +++ b/tensorflow/lite/micro/kernels/micro_ops.h @@ -105,6 +105,7 @@ TFLMRegistration Register_RELU6(); TFLMRegistration Register_RESHAPE(); TFLMRegistration Register_RESIZE_BILINEAR(); TFLMRegistration Register_RESIZE_NEAREST_NEIGHBOR(); +TFLMRegistration Register_REVERSE_V2(); TFLMRegistration Register_ROUND(); TFLMRegistration Register_RSQRT(); TFLMRegistration Register_SELECT_V2(); diff --git a/tensorflow/lite/micro/kernels/reverse.cc b/tensorflow/lite/micro/kernels/reverse.cc new file mode 100644 index 00000000000..17bf444346f --- /dev/null +++ b/tensorflow/lite/micro/kernels/reverse.cc @@ -0,0 +1,201 @@ +/* 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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include + +#include "tensorflow/lite/c/common.h" +#include "tensorflow/lite/kernels/internal/tensor_ctypes.h" +#include "tensorflow/lite/kernels/kernel_util.h" +#include "tensorflow/lite/micro/kernels/kernel_util.h" +#include "tensorflow/lite/micro/micro_log.h" +#include "tensorflow/lite/micro/micro_utils.h" + +namespace tflite { + +constexpr int kMaxDimensions = 8; + +namespace { + +constexpr int kInputTensor = 0; +constexpr int kAxisTensor = 1; +constexpr int kOutputTensor = 0; + +int comp(const void* a, const void* b) { return (*(int*)a - *(int*)b); } + +TfLiteStatus ReverseV2Prepare(TfLiteContext* context, TfLiteNode* node) { + MicroContext* micro_context = GetMicroContext(context); + + TF_LITE_ENSURE_EQ(context, NumInputs(node), 2); + TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1); + + // Ensure inputs and outputs exist. + TfLiteTensor* input = + micro_context->AllocateTempInputTensor(node, kInputTensor); + TF_LITE_ENSURE(context, input != nullptr); + TfLiteTensor* axis = + micro_context->AllocateTempInputTensor(node, kAxisTensor); + TF_LITE_ENSURE(context, axis != nullptr); + TfLiteTensor* output = + micro_context->AllocateTempOutputTensor(node, kOutputTensor); + TF_LITE_ENSURE(context, output != nullptr); + TF_LITE_ENSURE_EQ(context, NumDimensions(axis), 1); + TF_LITE_ENSURE(context, NumDimensions(input) <= 8); + TF_LITE_ENSURE(context, NumDimensions(input) >= NumElements(axis)); + + if (input->type != kTfLiteInt32 && input->type != kTfLiteFloat32 && + input->type != kTfLiteUInt8 && input->type != kTfLiteInt8 && + input->type != kTfLiteInt16) { + MicroPrintf("Type '%s' is not supported by reverse.", + TfLiteTypeGetName(input->type)); + return kTfLiteError; + } + + if (axis->type != kTfLiteInt32) { + MicroPrintf("Axis Type '%s' is not supported by reverse.", + TfLiteTypeGetName(axis->type)); + return kTfLiteError; + } + // The value type and output type must match. + TF_LITE_ENSURE_EQ(context, input->type, output->type); + + micro_context->DeallocateTempTfLiteTensor(input); + micro_context->DeallocateTempTfLiteTensor(axis); + micro_context->DeallocateTempTfLiteTensor(output); + return kTfLiteOk; +} + +template +void ReverseImpl(int32_t* axes, int num_axes, const RuntimeShape& input_shape, + const T* input_data, T* output_data) { + bool is_upper = (axes[num_axes - 1] == input_shape.DimensionsCount() - 1); + bool is_lower = (axes[0] == 0); + int rank = input_shape.DimensionsCount(); + if (is_upper && is_lower) { + std::reverse_copy(input_data, input_data + input_shape.FlatSize(), + output_data); + return; + } else { + int32_t min_dim = axes[0]; + int32_t max_dim = axes[num_axes - 1]; + int upper_size = 1; + for (int i = 0; i < min_dim; ++i) { + upper_size *= input_shape.Dims(i); + } + int lower_size = 1; + for (int i = max_dim + 1; i < rank; ++i) { + lower_size *= input_shape.Dims(i); + } + int middle_size = 1; + for (int i = min_dim; i <= max_dim; ++i) { + middle_size *= input_shape.Dims(i); + } + + if (lower_size > 1) { + for (int i = 0; i < upper_size; ++i) { + for (int j = 0; j < middle_size; ++j) { + T* src = (T*)input_data + (i * (middle_size) + j) * lower_size; + T* dst = (T*)output_data + + (i * (middle_size) + (middle_size - j - 1)) * lower_size; + memcpy(dst, src, lower_size * sizeof(T)); + } + } + } else { + for (int i = 0; i < upper_size; ++i) { + std::reverse_copy(input_data + i * (middle_size), + input_data + i * middle_size + middle_size, + output_data + i * (middle_size)); + } + } + } +} + +TfLiteStatus ReverseV2Eval(TfLiteContext* context, TfLiteNode* node) { + const TfLiteEvalTensor* input = + micro::GetEvalInput(context, node, kInputTensor); + const TfLiteEvalTensor* axis = + micro::GetEvalInput(context, node, kAxisTensor); + TfLiteEvalTensor* output = micro::GetEvalOutput(context, node, kOutputTensor); + + TF_LITE_ENSURE_EQ(context, axis->type, kTfLiteInt32); + const int num_axes = static_cast(ElementCount(*axis->dims)); + TF_LITE_ENSURE(context, num_axes <= 8); + + int32_t axes_data[kMaxDimensions]; + std::memcpy(axes_data, axis->data.i32, sizeof(int32_t) * num_axes); + const int rank = tflite::micro::GetTensorShape(input).DimensionsCount(); + for (int i = 0; i < num_axes; ++i) { + if (axes_data[i] < 0) { + axes_data[i] += rank; + } + TF_LITE_ENSURE(context, axes_data[i] >= 0 && axes_data[i] < rank); + } + qsort(axes_data, num_axes, sizeof(int32_t), comp); + + bool is_contiguous = true; + for (int i = 1; i < num_axes; ++i) { + if (axes_data[i - 1] + 1 != axes_data[i]) { + is_contiguous = false; + break; + } + } + if (!is_contiguous) { + MicroPrintf("Non-contiguous `axes` not supported"); + return kTfLiteError; + } + + switch (output->type) { + case kTfLiteFloat32: + ReverseImpl(axes_data, num_axes, + tflite::micro::GetTensorShape(input), + tflite::micro::GetTensorData(input), + tflite::micro::GetTensorData(output)); + break; + case kTfLiteInt32: + ReverseImpl(axes_data, num_axes, + tflite::micro::GetTensorShape(input), + tflite::micro::GetTensorData(input), + tflite::micro::GetTensorData(output)); + break; + case kTfLiteInt16: + ReverseImpl(axes_data, num_axes, + tflite::micro::GetTensorShape(input), + tflite::micro::GetTensorData(input), + tflite::micro::GetTensorData(output)); + break; + case kTfLiteInt8: + case kTfLiteUInt8: + ReverseImpl(axes_data, num_axes, + tflite::micro::GetTensorShape(input), + tflite::micro::GetTensorData(input), + tflite::micro::GetTensorData(output)); + break; + default: + MicroPrintf( + "Reverse currently supports float32, int16, " + "int8 and uint8 for output, got %d.", + TfLiteTypeGetName(output->type)); + return kTfLiteError; + } + + return kTfLiteOk; +} + +} // namespace + +TFLMRegistration Register_REVERSE_V2() { + return tflite::micro::RegisterOp(nullptr, ReverseV2Prepare, ReverseV2Eval); +} + +} // namespace tflite diff --git a/tensorflow/lite/micro/kernels/reverse_test.cc b/tensorflow/lite/micro/kernels/reverse_test.cc new file mode 100644 index 00000000000..d92bf3156aa --- /dev/null +++ b/tensorflow/lite/micro/kernels/reverse_test.cc @@ -0,0 +1,408 @@ +/* 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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include + +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/c/common.h" +#include "tensorflow/lite/micro/kernels/kernel_runner.h" +#include "tensorflow/lite/micro/test_helpers.h" +#include "tensorflow/lite/micro/testing/micro_test.h" + +namespace tflite { +namespace testing { +namespace { + +constexpr int kNumInputs = 2; +constexpr int kNumOutputs = 1; +constexpr int kInputTensorIndex_0 = 0; +constexpr int kInputTensorIndex_1 = 1; +constexpr int kOutputTensorIndex = 2; + +void ExecuteReverseTest(TfLiteTensor* tensors, int tensors_count) { + int kInputArrayData[] = {kNumInputs, kInputTensorIndex_0, + kInputTensorIndex_1}; + TfLiteIntArray* inputs_array = IntArrayFromInts(kInputArrayData); + int kOutputArrayData[] = {kNumOutputs, kOutputTensorIndex}; + TfLiteIntArray* outputs_array = IntArrayFromInts(kOutputArrayData); + + const TFLMRegistration registration = tflite::Register_REVERSE_V2(); + micro::KernelRunner runner(registration, tensors, tensors_count, inputs_array, + outputs_array, nullptr); + + TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, runner.InitAndPrepare()); + TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, runner.Invoke()); +} + +template +void TestReverse(int* input_dims_data[kNumInputs], const T* input_data_0, + const int32_t* input_data_1, int* expected_dims, + const T* expected_data, T* output_data) { + TfLiteIntArray* input_dims_0 = IntArrayFromInts(input_dims_data[0]); + TfLiteIntArray* input_dims_1 = IntArrayFromInts(input_dims_data[1]); + TfLiteIntArray* output_dims = IntArrayFromInts(expected_dims); + const int output_count = ElementCount(*output_dims); + + TfLiteTensor tensors[] = { + CreateTensor(input_data_0, input_dims_0), + CreateTensor(input_data_1, input_dims_1), + CreateTensor(output_data, output_dims), + }; + constexpr int tensors_count = std::extent::value; + ExecuteReverseTest(tensors, tensors_count); + + // check output data against expected + for (int i = 0; i < output_count; i++) { + TF_LITE_MICRO_EXPECT_NEAR(expected_data[i], output_data[i], 0); + } + + // check output dimensions (relocated) against original dimensions + TF_LITE_MICRO_EXPECT_EQ(output_dims->size, + tensors[kOutputTensorIndex].dims->size); + for (int i = 0; i < output_dims->size; i++) { + TF_LITE_MICRO_EXPECT_EQ(output_dims->data[i], + tensors[kOutputTensorIndex].dims->data[i]); + } +} + +} // namespace +} // namespace testing +} // namespace tflite + +TF_LITE_MICRO_TESTS_BEGIN + +// float32 tests. +TF_LITE_MICRO_TEST(ReverseOpTestFloatOneDimension) { + int kInputDims_0[] = {1, 4}; + int kInputDims_1[] = {1, 1}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {1, 4}; + + const float kInput_0[] = {1, 2, 3, 4}; + const int32_t kInput_1[] = {0}; + const float kExpect[] = {4, 3, 2, 1}; + const int kOutputCount = std::extent::value; + float output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +TF_LITE_MICRO_TEST(ReverseOpTestFloatMultiDimensions) { + int kInputDims_0[] = {3, 4, 3, 2}; + int kInputDims_1[] = {1, 1}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {3, 4, 3, 2}; + + const float kInput_0[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}; + const int32_t kInput_1[] = {1}; + const float kExpect[] = {5, 6, 3, 4, 1, 2, 11, 12, 9, 10, 7, 8, + 17, 18, 15, 16, 13, 14, 23, 24, 21, 22, 19, 20}; + const int kOutputCount = std::extent::value; + float output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +// int32 tests +TF_LITE_MICRO_TEST(ReverseOpTestInt32OneDimension) { + int kInputDims_0[] = {1, 4}; + int kInputDims_1[] = {1, 1}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {1, 4}; + + const int32_t kInput_0[] = {1, 2, 3, 4}; + const int32_t kInput_1[] = {0}; + const int32_t kExpect[] = {4, 3, 2, 1}; + const int kOutputCount = std::extent::value; + int32_t output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +TF_LITE_MICRO_TEST(ReverseOpTestInt32MultiDimensions) { + int kInputDims_0[] = {3, 4, 3, 2}; + int kInputDims_1[] = {1, 1}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {3, 4, 3, 2}; + + const int32_t kInput_0[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}; + const int32_t kInput_1[] = {1}; + const int32_t kExpect[] = {5, 6, 3, 4, 1, 2, 11, 12, 9, 10, 7, 8, + 17, 18, 15, 16, 13, 14, 23, 24, 21, 22, 19, 20}; + const int kOutputCount = std::extent::value; + int32_t output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +TF_LITE_MICRO_TEST(ReverseOpTestInt32MultiDimensionsFirst) { + int kInputDims_0[] = {3, 3, 3, 3}; + int kInputDims_1[] = {1, 1}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {3, 3, 3, 3}; + + const int32_t kInput_0[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27}; + const int32_t kInput_1[] = {0}; + const int32_t kExpect[] = {19, 20, 21, 22, 23, 24, 25, 26, 27, + 10, 11, 12, 13, 14, 15, 16, 17, 18, + 1, 2, 3, 4, 5, 6, 7, 8, 9}; + const int kOutputCount = std::extent::value; + int32_t output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +TF_LITE_MICRO_TEST(ReverseOpTestInt32MultiDimensionsSecond) { + int kInputDims_0[] = {3, 3, 3, 3}; + int kInputDims_1[] = {1, 1}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {3, 3, 3, 3}; + + const int32_t kInput_0[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27}; + const int32_t kInput_1[] = {1}; + const int32_t kExpect[] = {7, 8, 9, 4, 5, 6, 1, 2, 3, + 16, 17, 18, 13, 14, 15, 10, 11, 12, + 25, 26, 27, 22, 23, 24, 19, 20, 21}; + const int kOutputCount = std::extent::value; + int32_t output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +TF_LITE_MICRO_TEST(ReverseOpTestInt32MultiDimensionsThird) { + int kInputDims_0[] = {3, 3, 3, 3}; + int kInputDims_1[] = {1, 1}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {3, 3, 3, 3}; + + const int32_t kInput_0[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27}; + const int32_t kInput_1[] = {2}; + const int32_t kExpect[] = {3, 2, 1, 6, 5, 4, 9, 8, 7, + 12, 11, 10, 15, 14, 13, 18, 17, 16, + 21, 20, 19, 24, 23, 22, 27, 26, 25}; + const int kOutputCount = std::extent::value; + int32_t output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +TF_LITE_MICRO_TEST(ReverseOpTestInt32MultiDimensionsFirstSecond) { + int kInputDims_0[] = {3, 3, 3, 3}; + int kInputDims_1[] = {1, 2}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {3, 3, 3, 3}; + + const int32_t kInput_0[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27}; + const int32_t kInput_1[] = {0, 1}; + + const int32_t kExpect[] = {25, 26, 27, 22, 23, 24, 19, 20, 21, + 16, 17, 18, 13, 14, 15, 10, 11, 12, + 7, 8, 9, 4, 5, 6, 1, 2, 3}; + + const int kOutputCount = std::extent::value; + int32_t output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +TF_LITE_MICRO_TEST(ReverseOpTestInt32MultiDimensionsSecondThird) { + int kInputDims_0[] = {3, 3, 3, 3}; + int kInputDims_1[] = {1, 2}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {3, 3, 3, 3}; + + const int32_t kInput_0[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27}; + const int32_t kInput_1[] = {1, 2}; + + const int32_t kExpect[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, + 18, 17, 16, 15, 14, 13, 12, 11, 10, + 27, 26, 25, 24, 23, 22, 21, 20, 19}; + const int kOutputCount = std::extent::value; + int32_t output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +TF_LITE_MICRO_TEST(ReverseOpTestInt32MultiDimensionsSecondFirst) { + int kInputDims_0[] = {3, 3, 3, 3}; + int kInputDims_1[] = {1, 2}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {3, 3, 3, 3}; + + const int32_t kInput_0[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27}; + const int32_t kInput_1[] = {1, 0}; + + const int32_t kExpect[] = {25, 26, 27, 22, 23, 24, 19, 20, 21, + 16, 17, 18, 13, 14, 15, 10, 11, 12, + 7, 8, 9, 4, 5, 6, 1, 2, 3}; + + const int kOutputCount = std::extent::value; + int32_t output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +TF_LITE_MICRO_TEST(ReverseOpTestInt32MultiDimensionsAll) { + int kInputDims_0[] = {3, 3, 3, 3}; + int kInputDims_1[] = {1, 3}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {3, 3, 3, 3}; + + const int32_t kInput_0[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27}; + const int32_t kInput_1[] = {0, 1, 2}; + const int32_t kExpect[] = {27, 26, 25, 24, 23, 22, 21, 20, 19, + 18, 17, 16, 15, 14, 13, 12, 11, 10, + 9, 8, 7, 6, 5, 4, 3, 2, 1}; + const int kOutputCount = std::extent::value; + int32_t output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +// uint8 tests +TF_LITE_MICRO_TEST(ReverseOpTestUint8OneDimension) { + int kInputDims_0[] = {1, 4}; + int kInputDims_1[] = {1, 1}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {1, 4}; + + const uint8_t kInput_0[] = {1, 2, 3, 4}; + const int32_t kInput_1[] = {0}; + const uint8_t kExpect[] = {4, 3, 2, 1}; + + const int kOutputCount = std::extent::value; + uint8_t output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +TF_LITE_MICRO_TEST(ReverseOpTestUint8MultiDimensions) { + int kInputDims_0[] = {3, 4, 3, 2}; + int kInputDims_1[] = {1, 1}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {3, 4, 3, 2}; + + const uint8_t kInput_0[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}; + const int32_t kInput_1[] = {1}; + const uint8_t kExpect[] = {5, 6, 3, 4, 1, 2, 11, 12, 9, 10, 7, 8, + 17, 18, 15, 16, 13, 14, 23, 24, 21, 22, 19, 20}; + const int kOutputCount = std::extent::value; + uint8_t output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +// int8 tests +TF_LITE_MICRO_TEST(ReverseOpTestInt8OneDimension) { + int kInputDims_0[] = {1, 4}; + int kInputDims_1[] = {1, 1}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {1, 4}; + + const int8_t kInput_0[] = {1, 2, -1, -2}; + const int32_t kInput_1[] = {0}; + const int8_t kExpect[] = {-2, -1, 2, 1}; + const int kOutputCount = std::extent::value; + int8_t output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +TF_LITE_MICRO_TEST(ReverseOpTestInt8MultiDimensions) { + int kInputDims_0[] = {3, 4, 3, 2}; + int kInputDims_1[] = {1, 1}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {3, 4, 3, 2}; + + const int8_t kInput_0[] = {-1, -2, -3, -4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, -21, -22, -23, -24}; + const int32_t kInput_1[] = {1}; + const int8_t kExpect[] = {5, 6, -3, -4, -1, -2, 11, 12, 9, 10, 7, 8, + 17, 18, 15, 16, 13, 14, -23, -24, -21, -22, 19, 20}; + const int kOutputCount = std::extent::value; + int8_t output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +// int16 tests +TF_LITE_MICRO_TEST(ReverseOpTestInt16OneDimension) { + int kInputDims_0[] = {1, 4}; + int kInputDims_1[] = {1, 1}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {1, 4}; + + const int16_t kInput_0[] = {1, 2, 3, 4}; + const int32_t kInput_1[] = {0}; + const int16_t kExpect[] = {4, 3, 2, 1}; + const int kOutputCount = std::extent::value; + int16_t output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +TF_LITE_MICRO_TEST(ReverseOpTestInt16MultiDimensions) { + int kInputDims_0[] = {3, 4, 3, 2}; + int kInputDims_1[] = {1, 1}; + int* kInputDims[tflite::testing::kNumInputs] = {kInputDims_0, kInputDims_1}; + int kOutputDims[] = {3, 4, 3, 2}; + + const int16_t kInput_0[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}; + const int32_t kInput_1[] = {1}; + const int16_t kExpect[] = {5, 6, 3, 4, 1, 2, 11, 12, 9, 10, 7, 8, + 17, 18, 15, 16, 13, 14, 23, 24, 21, 22, 19, 20}; + const int kOutputCount = std::extent::value; + int16_t output_data[kOutputCount]; + + tflite::testing::TestReverse(kInputDims, kInput_0, kInput_1, kOutputDims, + kExpect, output_data); +} + +TF_LITE_MICRO_TESTS_END diff --git a/tensorflow/lite/micro/micro_mutable_op_resolver.h b/tensorflow/lite/micro/micro_mutable_op_resolver.h index f3f2080f0aa..e6af3b0a7dd 100644 --- a/tensorflow/lite/micro/micro_mutable_op_resolver.h +++ b/tensorflow/lite/micro/micro_mutable_op_resolver.h @@ -525,6 +525,11 @@ class MicroMutableOpResolver : public MicroOpResolver { ParseResizeNearestNeighbor); } + TfLiteStatus AddReverseV2() { + return AddBuiltin(BuiltinOperator_REVERSE_V2, Register_REVERSE_V2(), + ParseReverseV2); + } + TfLiteStatus AddRfft(const TFLMRegistration* registration = tflite::tflm_signal::Register_RFFT()) { // TODO(b/286250473): change back name and remove namespace diff --git a/tensorflow/lite/micro/tools/benchmarking/op_resolver.h b/tensorflow/lite/micro/tools/benchmarking/op_resolver.h index 9b98849c472..03aa5890ef0 100644 --- a/tensorflow/lite/micro/tools/benchmarking/op_resolver.h +++ b/tensorflow/lite/micro/tools/benchmarking/op_resolver.h @@ -109,6 +109,7 @@ inline TfLiteStatus CreateOpResolver(TflmOpResolver& op_resolver) { TF_LITE_ENSURE_STATUS(op_resolver.AddReshape()); TF_LITE_ENSURE_STATUS(op_resolver.AddResizeBilinear()); TF_LITE_ENSURE_STATUS(op_resolver.AddResizeNearestNeighbor()); + TF_LITE_ENSURE_STATUS(op_resolver.AddReverseV2()); TF_LITE_ENSURE_STATUS(op_resolver.AddRfft()); TF_LITE_ENSURE_STATUS(op_resolver.AddRound()); TF_LITE_ENSURE_STATUS(op_resolver.AddRsqrt()); diff --git a/tensorflow/lite/micro/tools/make/Makefile b/tensorflow/lite/micro/tools/make/Makefile index 0bf5532badf..a21765b3454 100644 --- a/tensorflow/lite/micro/tools/make/Makefile +++ b/tensorflow/lite/micro/tools/make/Makefile @@ -447,6 +447,7 @@ $(TENSORFLOW_ROOT)tensorflow/lite/micro/kernels/reshape.cc \ $(TENSORFLOW_ROOT)tensorflow/lite/micro/kernels/reshape_common.cc \ $(TENSORFLOW_ROOT)tensorflow/lite/micro/kernels/resize_bilinear.cc \ $(TENSORFLOW_ROOT)tensorflow/lite/micro/kernels/resize_nearest_neighbor.cc \ +$(TENSORFLOW_ROOT)tensorflow/lite/micro/kernels/reverse.cc \ $(TENSORFLOW_ROOT)tensorflow/lite/micro/kernels/round.cc \ $(TENSORFLOW_ROOT)tensorflow/lite/micro/kernels/select.cc \ $(TENSORFLOW_ROOT)tensorflow/lite/micro/kernels/shape.cc \