/* Copyright 2023 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 #include #include #include #include "absl/algorithm/container.h" #include "absl/types/span.h" #include "tensorflow/lite/c/c_api_types.h" #include "tensorflow/lite/core/c/common.h" #include "tensorflow/lite/kernels/test_util.h" #include "tensorflow/lite/schema/schema_generated.h" namespace tflite { namespace { using testing::ElementsAre; template struct TensorTypeFor; #define TENSOR_TYPE_ASSOC(CPP_TYPE, TENSORTYPE_VALUE) \ template <> \ struct TensorTypeFor { \ static constexpr TensorType value = TENSORTYPE_VALUE; \ }; TENSOR_TYPE_ASSOC(int8_t, TensorType_INT8); TENSOR_TYPE_ASSOC(int16_t, TensorType_INT16); TENSOR_TYPE_ASSOC(int32_t, TensorType_INT32); TENSOR_TYPE_ASSOC(int64_t, TensorType_INT64); TENSOR_TYPE_ASSOC(uint8_t, TensorType_UINT8); TENSOR_TYPE_ASSOC(uint16_t, TensorType_UINT16); TENSOR_TYPE_ASSOC(uint32_t, TensorType_UINT32); TENSOR_TYPE_ASSOC(uint64_t, TensorType_UINT64); TENSOR_TYPE_ASSOC(float, TensorType_FLOAT32); static_assert(sizeof(float) == 4, "float type is expected to be 32 bit long"); TENSOR_TYPE_ASSOC(double, TensorType_FLOAT64); static_assert(sizeof(double) == 8, "double type is expected to be 64 bit long"); template int32_t intsize(const Container& c) { return static_cast(c.size()); } template class DilateOpModel : public SingleOpModel { static constexpr TensorType kTensorType = TensorTypeFor::value; public: void SetInput(absl::Span shape, absl::Span data = {}) { input_shape_.assign(shape.begin(), shape.end()); if (data.empty()) { input_data_.resize(absl::c_accumulate(shape, 1, std::multiplies())); absl::c_iota(input_data_, 1); } else { input_data_.assign(data.begin(), data.end()); } } void SetWindowShape(absl::Span shape) { window_shape_data_.assign(shape.begin(), shape.end()); } // Note: the strides are counted in elements on the tensor grid not in the // underlying buffer. // // For instance, {2,2} on the following matrix strting at element 1 will reach // elements 3 (+2 horizontally), 7 (+2 vertically) and 9 (+2 vertically, +2 // horizontally): // // 1 2 3 // 4 5 6 // 7 8 9 void SetWindowStrides(absl::Span strides) { window_strides_data_.assign(strides.begin(), strides.end()); } void SetWindowDilations(absl::Span dilations) { window_dilations_data_.assign(dilations.begin(), dilations.end()); } void SetInitValue(const T& val) { init_value_data_ = val; } void Build() { input_ = AddInput({kTensorType, input_shape_}); init_value_ = AddConstInput(kTensorType, {init_value_data_}, {1}); window_shape_ = AddConstInput(TensorType_INT64, window_shape_data_, {intsize(window_shape_data_)}); window_strides_ = AddConstInput(TensorType_INT64, window_strides_data_, {intsize(window_strides_data_)}); window_dilations_ = AddConstInput(TensorType_INT64, window_dilations_data_, {intsize(window_dilations_data_)}); output_ = AddOutput(kTensorType); SetBuiltinOp( BuiltinOperator_REDUCE_WINDOW, BuiltinOptions2_ReduceWindowOptions, CreateReduceWindowOptions(builder_, ReduceWindowFunction_ADD).Union()); BuildInterpreter({input_shape_}); PopulateTensor(input_, input_data_); } TfLiteStatus BuildAndInvoke() { Build(); return Invoke(); } absl::Span GetOutputData() { return absl::Span(interpreter_->typed_tensor(output_), GetTensorSize(output_)); } absl::Span GetOutputShape() { const TfLiteIntArray& shape = *(interpreter_->tensor(output_)->dims); return absl::Span(shape.data, shape.size); } const std::vector& GetInput() const { return input_data_; } const std::vector& GetInputShape() const { return input_shape_; } const std::vector& GetWindowShape() const { return window_shape_data_; } const std::vector& GetWindowStrides() const { return window_strides_data_; } const std::vector& GetWindowDilations() const { return window_dilations_data_; } const T& GetInitValue() const { return init_value_data_; } protected: int input_ = -1; int window_shape_ = -1; int window_strides_ = -1; int window_dilations_ = -1; int init_value_ = -1; int output_ = -1; std::vector input_data_; T init_value_data_; std::vector input_shape_; std::vector window_shape_data_; std::vector window_strides_data_; std::vector window_dilations_data_; }; template class ReduceWindowTest : public testing::Test { protected: DilateOpModel model_; }; using TestList = testing::Types; TYPED_TEST_SUITE(ReduceWindowTest, TestList); TYPED_TEST(ReduceWindowTest, FullWindow) { auto& model = this->model_; model.SetInput(/*shape=*/{3, 3}); model.SetWindowShape({3, 3}); model.SetWindowStrides({1, 1}); model.SetWindowDilations({1, 1}); model.SetInitValue(0); EXPECT_EQ(this->model_.BuildAndInvoke(), kTfLiteOk); EXPECT_THAT(this->model_.GetOutputShape(), ElementsAre(1, 1)); EXPECT_THAT(this->model_.GetOutputData(), ElementsAre(45)); } TYPED_TEST(ReduceWindowTest, NoDilation) { auto& model = this->model_; model.SetInput(/*shape=*/{3, 3}); model.SetWindowShape({2, 2}); model.SetWindowStrides({1, 1}); model.SetWindowDilations({1, 1}); model.SetInitValue(0); EXPECT_EQ(this->model_.BuildAndInvoke(), kTfLiteOk); EXPECT_THAT(this->model_.GetOutputShape(), ElementsAre(2, 2)); EXPECT_THAT(this->model_.GetOutputData(), ElementsAre(12, 16, 24, 28)); } TYPED_TEST(ReduceWindowTest, FullWindowWithDilation) { auto& model = this->model_; model.SetInput(/*shape=*/{3, 3}); model.SetWindowShape({2, 2}); model.SetWindowStrides({1, 1}); model.SetWindowDilations({2, 2}); model.SetInitValue(0); EXPECT_EQ(this->model_.BuildAndInvoke(), kTfLiteOk); EXPECT_THAT(this->model_.GetOutputShape(), ElementsAre(1, 1)); EXPECT_THAT(this->model_.GetOutputData(), ElementsAre(20)); } TYPED_TEST(ReduceWindowTest, WithDilation) { auto& model = this->model_; model.SetInput(/*shape=*/{4, 4}); model.SetWindowShape({2, 2}); model.SetWindowStrides({1, 1}); model.SetWindowDilations({2, 2}); model.SetInitValue(0); EXPECT_EQ(this->model_.BuildAndInvoke(), kTfLiteOk); EXPECT_THAT(this->model_.GetOutputShape(), ElementsAre(2, 2)); EXPECT_THAT(this->model_.GetOutputData(), ElementsAre(24, 28, 40, 44)); } TYPED_TEST(ReduceWindowTest, WithStrides) { auto& model = this->model_; model.SetInput(/*shape=*/{4, 4}); model.SetWindowShape({2, 2}); model.SetWindowStrides({2, 2}); model.SetWindowDilations({1, 1}); model.SetInitValue(0); EXPECT_EQ(this->model_.BuildAndInvoke(), kTfLiteOk); EXPECT_THAT(this->model_.GetOutputShape(), ElementsAre(2, 2)); EXPECT_THAT(this->model_.GetOutputData(), ElementsAre(14, 22, 46, 54)); } TYPED_TEST(ReduceWindowTest, WithDilationAndStrides) { auto& model = this->model_; model.SetInput(/*shape=*/{5, 5}); model.SetWindowShape({2, 2}); model.SetWindowStrides({2, 2}); model.SetWindowDilations({2, 2}); model.SetInitValue(2); EXPECT_EQ(this->model_.BuildAndInvoke(), kTfLiteOk); EXPECT_THAT(this->model_.GetOutputShape(), ElementsAre(2, 2)); EXPECT_THAT(this->model_.GetOutputData(), ElementsAre(30, 38, 70, 78)); } TYPED_TEST(ReduceWindowTest, OutputShapeRoundingIsCorrect) { auto& model = this->model_; model.SetInput(/*shape=*/{1, 64, 114, 114}); model.SetWindowShape({1, 1, 3, 3}); model.SetWindowStrides({1, 1, 2, 2}); model.SetWindowDilations({1, 1, 1, 1}); model.SetInitValue(2); EXPECT_EQ(this->model_.BuildAndInvoke(), kTfLiteOk); EXPECT_THAT(this->model_.GetOutputShape(), ElementsAre(1, 64, 56, 56)); } } // namespace } // namespace tflite