Files
tensorflow--tensorflow/tensorflow/lite/kernels/dilate_test.cc
T
wehub-resource-sync 8a852e4b4e
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s
chore: import upstream snapshot with attribution
2026-07-13 12:14:16 +08:00

361 lines
13 KiB
C++

/* 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 <cstddef>
#include <cstdint>
#include <functional>
#include <numeric>
#include <type_traits>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/algorithm/container.h"
#include "absl/types/span.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
namespace tflite {
namespace {
// A reference implementation of the dilation operation.
template <class T>
std::vector<T> DilateReference(const std::vector<T>& input,
const std::vector<int32_t>& shape,
const std::vector<int32_t>& dilations,
const T padding_value) {
// Compute the output shape.
std::vector<int> output_shape(shape.size(), 0);
for (size_t i = 0; i < shape.size(); ++i) {
output_shape[i] = (shape[i] - 1) * dilations[i] + 1;
}
// Compute the input strides.
std::vector<int> strides(shape.size(), 0);
strides[shape.size() - 1] = 1;
for (size_t i = shape.size() - 1; i > 0; --i) {
strides[i - 1] = shape[i] * strides[i];
}
// Compute the output strides.
std::vector<int> output_strides(shape.size(), 0);
output_strides[shape.size() - 1] = 1;
for (size_t i = shape.size() - 1; i > 0; --i) {
output_strides[i - 1] = output_shape[i] * output_strides[i];
}
// Create a buffer that can hold the output data filled with 0.
std::vector<T> output(
std::accumulate(output_shape.begin(), output_shape.end(), 1,
std::multiplies<>()),
padding_value);
for (int input_index = 0; input_index < input.size(); ++input_index) {
int remaining_index = input_index;
int output_index = 0;
for (int dim = 0; dim < shape.size(); ++dim) {
const int coordinate = remaining_index / strides[dim];
remaining_index %= strides[dim];
output_index += coordinate * dilations[dim] * output_strides[dim];
}
output[output_index] = input[input_index];
}
return output;
}
template <class T>
struct TensorTypeFor;
#define TENSOR_TYPE_ASSOC(CPP_TYPE, TENSORTYPE_VALUE) \
template <> \
struct TensorTypeFor<CPP_TYPE> { \
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 <class T, bool IsDilationTensorConst>
class DilateOpModel : public SingleOpModel {
static constexpr TensorType kTensorType = TensorTypeFor<T>::value;
public:
void SetInput(absl::Span<const int32_t> shape,
absl::Span<const T> data = {}) {
input_shape_.assign(shape.begin(), shape.end());
if (data.empty()) {
input_data_.resize(absl::c_accumulate(shape, 1, std::multiplies<int>()));
absl::c_iota(input_data_, 1);
} else {
input_data_.assign(data.begin(), data.end());
}
}
void SetDilations(absl::Span<const int32_t> dilations) {
dilations_shape_ = std::vector<int>(1, dilations.size());
dilations_data_.assign(dilations.begin(), dilations.end());
}
void SetPaddingValue(const T& val) { padding_value_data_ = val; }
void Build() {
input_ = AddInput({kTensorType, input_shape_});
if (IsDilationTensorConst) {
dilations_ = AddConstInput(TensorType_INT32, dilations_data_,
{static_cast<int>(dilations_data_.size())});
} else {
dilations_ = AddInput({TensorType_INT32, dilations_shape_});
}
padding_value_ = AddConstInput(kTensorType, &padding_value_data_, {1});
output_ = AddOutput(kTensorType);
SetBuiltinOp(BuiltinOperator_DILATE, BuiltinOptions2_DilateOptions,
CreateDilateOptions(builder_).Union());
BuildInterpreter({input_shape_});
PopulateTensor(input_, input_data_);
if (!IsDilationTensorConst) {
PopulateTensor(dilations_, dilations_data_);
}
}
TfLiteStatus BuildAndInvoke() {
Build();
return Invoke();
}
absl::Span<const T> GetOutputData() {
return absl::Span<const T>(interpreter_->typed_tensor<T>(output_),
GetTensorSize(output_));
}
absl::Span<const int> GetOutputShape() {
const TfLiteIntArray& shape = *(interpreter_->tensor(output_)->dims);
return absl::Span<const int>(shape.data, shape.size);
}
const std::vector<T>& GetInput() const { return input_data_; }
const std::vector<int>& GetInputShape() const { return input_shape_; }
const std::vector<int>& GetDilations() const { return dilations_data_; }
const T& GetPaddingValue() const { return padding_value_data_; }
protected:
int input_ = -1;
int dilations_ = -1;
int padding_value_ = -1;
int output_ = -1;
std::vector<T> input_data_;
std::vector<int32_t> input_shape_;
std::vector<int32_t> dilations_data_;
std::vector<int32_t> dilations_shape_;
T padding_value_data_ = 0;
};
template <class Configuration>
class DilateTest;
template <class StorageType, class IsDilationTensorConst>
class DilateTest<testing::Types<StorageType, IsDilationTensorConst>>
: public testing::Test {
protected:
DilateOpModel<StorageType, IsDilationTensorConst::value> model_;
};
struct ConstantDilation : std::true_type {};
struct VariableDilation : std::false_type {};
using TestList = testing::Types<testing::Types<int8_t, ConstantDilation>,
testing::Types<int16_t, ConstantDilation>,
testing::Types<int32_t, ConstantDilation>,
testing::Types<int64_t, ConstantDilation>,
testing::Types<uint8_t, ConstantDilation>,
testing::Types<uint16_t, ConstantDilation>,
testing::Types<uint32_t, ConstantDilation>,
testing::Types<uint64_t, ConstantDilation>,
testing::Types<float, ConstantDilation>,
testing::Types<double, ConstantDilation>,
testing::Types<int8_t, VariableDilation>,
testing::Types<int16_t, VariableDilation>,
testing::Types<int32_t, VariableDilation>,
testing::Types<int64_t, VariableDilation>,
testing::Types<uint8_t, VariableDilation>,
testing::Types<uint16_t, VariableDilation>,
testing::Types<uint32_t, VariableDilation>,
testing::Types<uint64_t, VariableDilation>,
testing::Types<float, VariableDilation>,
testing::Types<double, VariableDilation>>;
TYPED_TEST_SUITE(DilateTest, TestList);
TYPED_TEST(DilateTest, DilationManualTest) {
this->model_.SetInput(/*shape=*/{2, 2});
this->model_.SetDilations(/*dilations=*/{2, 3});
const std::vector<int> expected{
/* clang-format off */
1, 0, 0, 2,
0, 0, 0, 0,
3, 0, 0, 4
/* clang-format on */
};
EXPECT_EQ(this->model_.BuildAndInvoke(), kTfLiteOk);
EXPECT_THAT(this->model_.GetOutputShape(), ElementsAre(3, 4));
EXPECT_THAT(this->model_.GetOutputData(), ElementsAreArray(expected));
}
TYPED_TEST(DilateTest, DilationManualTest2) {
this->model_.SetInput(/*shape=*/{2, 3});
this->model_.SetDilations(/*dilations=*/{2, 3});
const std::vector<int> expected{
/* clang-format off */
1, 0, 0, 2, 0, 0, 3,
0, 0, 0, 0, 0, 0, 0,
4, 0, 0, 5, 0, 0, 6
/* clang-format on */
};
EXPECT_EQ(this->model_.BuildAndInvoke(), kTfLiteOk);
EXPECT_THAT(this->model_.GetOutputShape(), ElementsAre(3, 7));
EXPECT_THAT(this->model_.GetOutputData(), ElementsAreArray(expected));
}
TYPED_TEST(DilateTest, DilationManualTest3) {
this->model_.SetInput(/*shape=*/{4, 2, 3});
this->model_.SetDilations({2, 3, 4});
const std::vector<int> expected{
/* clang-format off */
1, 0, 0, 0, 2, 0, 0, 0, 3,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
4, 0, 0, 0, 5, 0, 0, 0, 6,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
7, 0, 0, 0, 8, 0, 0, 0, 9,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
10, 0, 0, 0, 11, 0, 0, 0, 12,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
13, 0, 0, 0, 14, 0, 0, 0, 15,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
16, 0, 0, 0, 17, 0, 0, 0, 18,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
19, 0, 0, 0, 20, 0, 0, 0, 21,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
22, 0, 0, 0, 23, 0, 0, 0, 24,
/* clang-format on */
};
EXPECT_EQ(this->model_.BuildAndInvoke(), kTfLiteOk);
EXPECT_THAT(this->model_.GetOutputShape(), ElementsAre(7, 4, 9));
EXPECT_THAT(this->model_.GetOutputData(), ElementsAreArray(expected));
}
TYPED_TEST(DilateTest, TrailingDilationOptimizationWorks) {
this->model_.SetInput(/*shape=*/{2, 2, 2, 2});
this->model_.SetDilations(/*dilations=*/{2, 1, 1, 1});
const std::vector<int> expected{
/* clang-format off */
1, 2, 3, 4, 5, 6, 7, 8,
0, 0, 0, 0, 0, 0, 0, 0,
9, 10, 11, 12, 13, 14, 15, 16
/* clang-format on */
};
EXPECT_EQ(this->model_.BuildAndInvoke(), kTfLiteOk);
EXPECT_THAT(this->model_.GetOutputShape(), ElementsAre(3, 2, 2, 2));
EXPECT_THAT(this->model_.GetOutputData(), ElementsAreArray(expected));
}
TYPED_TEST(DilateTest, TrailingDilationOptimizationDegenerateCaseWorks) {
this->model_.SetInput(/*shape=*/{2, 2, 2, 2});
this->model_.SetDilations(/*dilations=*/{1, 1, 1, 1});
const std::vector<int> expected{
/* clang-format off */
1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16
/* clang-format on */
};
EXPECT_EQ(this->model_.BuildAndInvoke(), kTfLiteOk);
EXPECT_THAT(this->model_.GetOutputShape(), ElementsAre(2, 2, 2, 2));
EXPECT_THAT(this->model_.GetOutputData(), ElementsAreArray(expected));
}
TYPED_TEST(DilateTest, CheckAgainstReferenceImplementation) {
auto& model = this->model_;
model.SetInput(/*shape=*/{5, 4, 2});
model.SetDilations(/*dilations=*/{2, 3, 5});
model.SetPaddingValue(-1);
const auto expected =
DilateReference(model.GetInput(), model.GetInputShape(),
model.GetDilations(), model.GetPaddingValue());
EXPECT_EQ(model.BuildAndInvoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputData(), ElementsAreArray(expected));
}
TYPED_TEST(DilateTest, RankSeven) {
auto& model = this->model_;
model.SetInput(/*shape=*/{2, 1, 2, 1, 2, 1, 2});
model.SetDilations(/*dilations=*/{2, 1, 2, 1, 1, 1, 2});
model.SetPaddingValue(-1);
const auto expected =
DilateReference(model.GetInput(), model.GetInputShape(),
model.GetDilations(), model.GetPaddingValue());
EXPECT_EQ(model.BuildAndInvoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({3, 1, 3, 1, 2, 1, 3}));
EXPECT_THAT(model.GetOutputData(), ElementsAreArray(expected));
}
} // namespace
} // namespace tflite