/* Copyright 2018 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 #include #include "tensorflow/lite/core/c/common.h" #include "tensorflow/lite/kernels/internal/portable_tensor_utils.h" #include "tensorflow/lite/kernels/internal/tensor_ctypes.h" #include "tensorflow/lite/kernels/kernel_util.h" #include "tensorflow/lite/kernels/test_util.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/string_type.h" #include "tensorflow/lite/types/half.h" namespace tflite { namespace { using ::testing::ElementsAreArray; enum class TestType { kConst = 0, kDynamic = 1, }; template class SliceOpModel : public SingleOpModel { public: SliceOpModel(std::initializer_list input_shape, std::initializer_list begin_shape, std::initializer_list begin_data, std::initializer_list size_shape, std::initializer_list size_data, TensorType tensor_index_type, TensorType tensor_input_type, TestType input_tensor_types, std::initializer_list output_shape = {}) { input_ = AddInput(tensor_input_type); if (input_tensor_types == TestType::kDynamic) { begin_ = AddInput(tensor_index_type); size_ = AddInput(tensor_index_type); } else { begin_ = AddConstInput(GetTensorType(), begin_data, begin_shape); size_ = AddConstInput(GetTensorType(), size_data, size_shape); } output_ = AddOutput(TensorData(tensor_input_type, output_shape)); SetBuiltinOp(BuiltinOperator_SLICE, BuiltinOptions_SliceOptions, CreateSliceOptions(builder_).Union()); BuildInterpreter({input_shape, begin_shape, size_shape}); if (input_tensor_types == TestType::kDynamic) { PopulateTensor(begin_, begin_data); PopulateTensor(size_, size_data); } } void SetInput(std::initializer_list data) { if constexpr (std::is_same::value) { if (interpreter_->tensor(input_)->type == kTfLiteInt4) { PopulateTensor4bit(input_, 0, data.begin(), data.end()); return; } } PopulateTensor(input_, data); } void SetStringInput(std::vector data) { PopulateStringTensor(input_, data); } std::vector GetOutput() { return ExtractVector(output_); } std::vector GetOutputShape() { return GetTensorShape(output_); } const TfLiteTensor* GetOutputTensor() { return interpreter_->tensor(output_); } private: int input_; int begin_; int size_; int output_; }; class SliceOpTest : public ::testing::TestWithParam {}; TEST_P(SliceOpTest, In1D) { SliceOpModel m({4}, {1}, {1}, {1}, {2}, TensorType_INT32, TensorType_FLOAT32, GetParam()); m.SetInput({1, 2, 3, 4}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({2, 3})); } TEST_P(SliceOpTest, In2D) { SliceOpModel m({2, 3}, {2}, {1, 0}, {2}, {1, 2}, TensorType_INT32, TensorType_FLOAT32, GetParam()); m.SetInput({1, 2, 3, 4, 5, 6}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({4, 5})); } TEST_P(SliceOpTest, In3D) { SliceOpModel m({2, 3, 2}, {3}, {0, 0, 0}, {3}, {2, 3, 2}, TensorType_INT32, TensorType_FLOAT32, GetParam()); m.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 3, 2})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12})); } TEST_P(SliceOpTest, In5D) { SliceOpModel m({5, 1, 1, 1, 1}, {5}, {1, 0, 0, 0, 0}, {5}, {3, 1, 1, 1, 1}, TensorType_INT32, TensorType_FLOAT32, GetParam()); m.SetInput({1, 2, 3, 4, 5}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({3, 1, 1, 1, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({2, 3, 4})); } TEST_P(SliceOpTest, In6D) { SliceOpModel m({2, 1, 1, 1, 1, 3}, {6}, {1, 0, 0, 0, 0, 1}, {6}, {1, 1, 1, 1, 1, 2}, TensorType_INT32, TensorType_FLOAT32, GetParam()); m.SetInput({1, 2, 3, 4, 5, 6}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 1, 1, 1, 1, 2})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({5, 6})); } TEST_P(SliceOpTest, InputFloat) { SliceOpModel m({4, 1, 1, 1}, {4}, {1, 0, 0, 0}, {4}, {3, 1, 1, 1}, TensorType_INT32, TensorType_FLOAT32, GetParam()); m.SetInput({1, 2, 3, 4}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({3, 1, 1, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({2, 3, 4})); } TEST_P(SliceOpTest, IndexInt64) { SliceOpModel m({4, 1, 1, 1}, {4}, {1, 0, 0, 0}, {4}, {3, 1, 1, 1}, TensorType_INT64, TensorType_FLOAT32, GetParam()); m.SetInput({1, 2, 3, 4}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({3, 1, 1, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({2, 3, 4})); } // See these test cases under: // https://www.tensorflow.org/versions/master/api_docs/python/tf/slice TEST_P(SliceOpTest, InputInteger1) { SliceOpModel m({3, 2, 3, 1}, {4}, {1, 0, 0, 0}, {4}, {1, 1, 3, 1}, TensorType_INT32, TensorType_INT32, GetParam()); m.SetInput({1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 1, 3, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({3, 3, 3})); } TEST_P(SliceOpTest, InputInteger2) { SliceOpModel m({3, 2, 3, 1}, {4}, {1, 0, 0, 0}, {4}, {1, 2, 3, 1}, TensorType_INT32, TensorType_INT32, GetParam()); m.SetInput({1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2, 3, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({3, 3, 3, 4, 4, 4})); } TEST_P(SliceOpTest, InputInteger3) { SliceOpModel m({3, 2, 3, 1}, {4}, {1, 0, 0, 0}, {4}, {2, 1, 3, 1}, TensorType_INT32, TensorType_INT32, GetParam()); m.SetInput({1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 3, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({3, 3, 3, 5, 5, 5})); } TEST_P(SliceOpTest, SizeMinus1) { SliceOpModel m({3, 2, 3, 1}, {4}, {1, 0, 0, 0}, {4}, {2, 1, -1, 1}, TensorType_INT32, TensorType_INT32, GetParam()); m.SetInput({1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 3, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({3, 3, 3, 5, 5, 5})); } TEST_P(SliceOpTest, BeginNonZeroSizeMinus1Axis1) { SliceOpModel m({3, 3, 2, 1}, {4}, {1, 1, 0, 0}, {4}, {2, -1, 1, 1}, TensorType_INT32, TensorType_INT32, GetParam()); m.SetInput({1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 2, 1, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({5, 6, 8, 9})); } TEST_P(SliceOpTest, BeginNonZeroSizeMinus1Axis2) { SliceOpModel m({3, 2, 3, 1}, {4}, {1, 0, 1, 0}, {4}, {2, 1, -1, 1}, TensorType_INT32, TensorType_INT32, GetParam()); m.SetInput({1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 2, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({3, 3, 5, 5})); } TEST_P(SliceOpTest, BeginNonZeroSizeMinus1Axis3) { SliceOpModel m({3, 1, 2, 3}, {4}, {1, 0, 0, 1}, {4}, {2, 1, 1, -1}, TensorType_INT32, TensorType_INT32, GetParam()); m.SetInput({1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 1, 2})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({3, 3, 5, 5})); } TEST_P(SliceOpTest, SliceUint8) { SliceOpModel m({3, 2, 3, 1}, {4}, {1, 0, 0, 0}, {4}, {2, 1, -1, 1}, TensorType_INT32, TensorType_UINT8, GetParam()); m.SetInput({1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 3, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({3, 3, 3, 5, 5, 5})); } TEST_P(SliceOpTest, SliceUint32) { SliceOpModel m({3, 2, 3, 1}, {4}, {1, 0, 0, 0}, {4}, {2, 1, -1, 1}, TensorType_INT32, TensorType_UINT32, GetParam()); m.SetInput({1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 3, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({3, 3, 3, 5, 5, 5})); } TEST_P(SliceOpTest, SliceInt8) { SliceOpModel m({3, 2, 3, 1}, {4}, {1, 0, 0, 0}, {4}, {2, 1, -1, 1}, TensorType_INT32, TensorType_INT8, GetParam()); m.SetInput({1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 3, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({3, 3, 3, 5, 5, 5})); } TEST_P(SliceOpTest, SliceInt4) { SliceOpModel m({3, 2, 3, 1}, {4}, {1, 0, 0, 0}, {4}, {2, 1, -1, 1}, TensorType_INT32, TensorType_INT4, GetParam()); m.SetInput({1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 3, 1})); const TfLiteTensor* output_tensor = m.GetOutputTensor(); int num_elements = NumElements(output_tensor); std::vector unpacked_output(num_elements); tensor_utils::UnpackPackedIntToInt8(GetTensorData(output_tensor), num_elements, /*bit_width=*/4, unpacked_output.data()); EXPECT_THAT(unpacked_output, ElementsAreArray({3, 3, 3, 5, 5, 5})); } TEST_P(SliceOpTest, SliceInt16) { SliceOpModel m({3, 2, 3, 1}, {4}, {1, 0, 0, 0}, {4}, {2, 1, -1, 1}, TensorType_INT32, TensorType_INT16, GetParam()); m.SetInput({1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 3, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({3, 3, 3, 5, 5, 5})); } TEST_P(SliceOpTest, SliceString) { SliceOpModel m({3, 2, 3, 1}, {4}, {1, 0, 0, 0}, {4}, {2, 1, -1, 1}, TensorType_INT32, TensorType_STRING, GetParam()); m.SetStringInput({"0,0,0,0", "0,0,1,0", "0,0,2,0", // "0,1,0,0", "0,1,1,0", "0,1,2,0", // "1,0,0,0", "1,0,1,0", "1,0,2,0", // "1,1,0,0", "1,1,1,0", "1,1,2,0", // "2,0,0,0", "2,0,1,0", "2,0,2,0", // "2,1,0,0", "2,1,1,0", "2,1,2,0"}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 3, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({"1,0,0,0", "1,0,1,0", "1,0,2,0", // "2,0,0,0", "2,0,1,0", "2,0,2,0"})); } TEST_P(SliceOpTest, SliceInt64) { SliceOpModel m({3, 2, 3, 1}, {4}, {1, 0, 0, 0}, {4}, {2, 1, -1, 1}, TensorType_INT32, TensorType_INT64, GetParam()); m.SetInput({1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 3, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({3, 3, 3, 5, 5, 5})); } TEST_P(SliceOpTest, SliceInt64StaticOutput) { SliceOpModel m({3, 2, 3, 1}, {4}, {1, 0, 0, 0}, {4}, {2, 1, -1, 1}, TensorType_INT32, TensorType_INT64, GetParam(), {2, 1, 3, 1}); m.SetInput({1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 3, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({3, 3, 3, 5, 5, 5})); EXPECT_NE(m.GetOutputTensor()->allocation_type, kTfLiteDynamic); } TEST_P(SliceOpTest, SliceBool) { SliceOpModel m({2, 3}, {2}, {1, 0}, {2}, {-1, 2}, TensorType_INT32, TensorType_BOOL, GetParam()); m.SetInput({true, false, true, false, true, true}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({false, true})); } TEST_P(SliceOpTest, SliceFloat16) { SliceOpModel m({3, 2, 3, 1}, {4}, {1, 0, 0, 0}, {4}, {2, 1, -1, 1}, TensorType_INT32, TensorType_FLOAT16, GetParam()); m.SetInput({half(1), half(1), half(1), half(2), half(2), half(2), half(3), half(3), half(3), half(4), half(4), half(4), half(5), half(5), half(5), half(6), half(6), half(6)}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 3, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({half(3), half(3), half(3), half(5), half(5), half(5)})); } TEST_P(SliceOpTest, SliceBFloat16) { SliceOpModel m({3, 2, 3, 1}, {4}, {1, 0, 0, 0}, {4}, {2, 1, -1, 1}, TensorType_INT32, TensorType_BFLOAT16, GetParam()); m.SetInput({Eigen::bfloat16(1), Eigen::bfloat16(1), Eigen::bfloat16(1), Eigen::bfloat16(2), Eigen::bfloat16(2), Eigen::bfloat16(2), Eigen::bfloat16(3), Eigen::bfloat16(3), Eigen::bfloat16(3), Eigen::bfloat16(4), Eigen::bfloat16(4), Eigen::bfloat16(4), Eigen::bfloat16(5), Eigen::bfloat16(5), Eigen::bfloat16(5), Eigen::bfloat16(6), Eigen::bfloat16(6), Eigen::bfloat16(6)}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 3, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({Eigen::bfloat16(3), Eigen::bfloat16(3), Eigen::bfloat16(3), Eigen::bfloat16(5), Eigen::bfloat16(5), Eigen::bfloat16(5)})); } TEST_P(SliceOpTest, BeginNonZeroSizeMinus1Axis1Float16) { SliceOpModel m({3, 3, 2, 1}, {4}, {1, 1, 0, 0}, {4}, {2, -1, 1, 1}, TensorType_INT32, TensorType_FLOAT16, GetParam()); m.SetInput({half(1), half(1), half(2), half(2), half(3), half(3), half(4), half(4), half(5), half(5), half(6), half(6), half(7), half(7), half(8), half(8), half(9), half(9)}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 2, 1, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({half(5), half(6), half(8), half(9)})); } TEST_P(SliceOpTest, BeginNonZeroSizeMinus1Axis1BFloat16) { SliceOpModel m({3, 3, 2, 1}, {4}, {1, 1, 0, 0}, {4}, {2, -1, 1, 1}, TensorType_INT32, TensorType_BFLOAT16, GetParam()); m.SetInput({Eigen::bfloat16(1), Eigen::bfloat16(1), Eigen::bfloat16(2), Eigen::bfloat16(2), Eigen::bfloat16(3), Eigen::bfloat16(3), Eigen::bfloat16(4), Eigen::bfloat16(4), Eigen::bfloat16(5), Eigen::bfloat16(5), Eigen::bfloat16(6), Eigen::bfloat16(6), Eigen::bfloat16(7), Eigen::bfloat16(7), Eigen::bfloat16(8), Eigen::bfloat16(8), Eigen::bfloat16(9), Eigen::bfloat16(9)}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 2, 1, 1})); EXPECT_THAT(m.GetOutput(), ElementsAreArray({Eigen::bfloat16(5), Eigen::bfloat16(6), Eigen::bfloat16(8), Eigen::bfloat16(9)})); } INSTANTIATE_TEST_SUITE_P(SliceOpTest, SliceOpTest, ::testing::Values(TestType::kConst, TestType::kDynamic)); } // namespace } // namespace tflite