chore: import upstream snapshot with attribution
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:14:16 +08:00
commit 8a852e4b4e
36502 changed files with 9277225 additions and 0 deletions
@@ -0,0 +1,106 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
load(":tests.bzl", "hexagon_op_tests")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = [
"//visibility:public",
],
licenses = ["notice"],
)
cc_library(
name = "hexagon_delegate_op_model",
testonly = 1,
hdrs = ["hexagon_delegate_op_model.h"],
deps = [
"//tensorflow/lite:framework",
"//tensorflow/lite/core:framework",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/core/kernels:builtin_ops",
"//tensorflow/lite/delegates/hexagon:hexagon_delegate",
"//tensorflow/lite/kernels:test_util",
"//tensorflow/lite/kernels/internal:reference_base",
"//tensorflow/lite/kernels/internal:tensor",
"//tensorflow/lite/kernels/internal:types",
"//tensorflow/lite/schema:schema_fbs",
"@com_google_googletest//:gtest",
],
)
hexagon_op_tests(
srcs = [
"activations_test.cc",
"arg_min_max_test.cc",
"arithmetic_test.cc",
"concat_test.cc",
"conv_test.cc",
"l2_norm_test.cc",
"matmul_test.cc",
"min_max_builder_test.cc",
"mirror_pad_test.cc",
"mul_test.cc",
"neg_test.cc",
"pack_test.cc",
"pad_test.cc",
"pool_test.cc",
"quantize_test.cc",
"reduce_test.cc",
"reshape_test.cc",
"resize_test.cc",
"rsqrt_test.cc",
"slice_test.cc",
"softmax_test.cc",
"space_to_depth_test.cc",
"split_test.cc",
"squared_difference_test.cc",
"strided_slice_test.cc",
"transpose_conv_test.cc",
"transpose_test.cc",
],
deps = [
":hexagon_delegate_op_model",
"//tensorflow/lite/c:c_api_types",
"//tensorflow/lite/c:common",
"//tensorflow/lite/core/c:c_api_types",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/kernels:kernel_util",
"//tensorflow/lite/kernels:reshape_test_common",
"//tensorflow/lite/kernels:test_util",
"//tensorflow/lite/kernels/internal:reference_base",
"//tensorflow/lite/kernels/internal:tensor",
"//tensorflow/lite/kernels/internal:tensor_ctypes",
"//tensorflow/lite/kernels/internal:test_util",
"//tensorflow/lite/kernels/internal:types",
"//tensorflow/lite/schema:schema_fbs",
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "batch_seq_config_test",
srcs = [
"batch_seq_config_test.cc",
],
tags = [
"no_oss",
"nobuilder",
"notap",
],
deps = [
"//tensorflow/core:framework_internal",
"//tensorflow/lite:framework",
"//tensorflow/lite:string",
"//tensorflow/lite/c:common",
"//tensorflow/lite/core/api:op_resolver",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/delegates/hexagon:hexagon_delegate",
"//tensorflow/lite/kernels:builtin_ops",
"//tensorflow/lite/kernels:kernel_util",
"//tensorflow/lite/testing:util",
"//tensorflow/lite/tools:logging",
"//tensorflow/lite/tools/benchmark:benchmark_utils",
"@com_google_googletest//:gtest",
],
)
@@ -0,0 +1,19 @@
# Hexagon Delegate Testing
This directory contains unit-tests for Op Builders for the hexagon delegate.
To Run the all the tests use the run_tests.sh under directory and pass
the path to the directory containing libhexagon_nn_skel*.so files.
The script will copy all files to the device and build all tests and execute
them.
The test should stop if one of the tests failed.
Example:
Follow the [Instructions](https://www.tensorflow.org/lite/performance/hexagon_delegate)
and download the hexagon_nn_skel and extract the files.
For example if files are extracted in /tmp/hexagon_skel, the sample command.
`
bash tensorflow/lite/delegates/hexagon/builders/tests/run_tests.sh /tmp/hexagon_skel
`
@@ -0,0 +1,330 @@
/* Copyright 2020 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 <algorithm>
#include <cstdarg>
#include <cstdint>
#include <limits>
#include <random>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
namespace {
void GenerateUniformRandomVector(int size, float min, float max,
std::minstd_rand* random_engine,
std::vector<float>* result) {
// Never use std::uniform_*_distribution in tests, it's
// implementation-defined. Likewise, don't use std::default_random_engine,
// implementation-defined. Implementation-defined is bad because it means that
// any toolchain update or new platform may run into test failures.
// std::minstd_rand is a standard instantiation of
// std::linear_congruential_engine, the cheapest generator in c++11 stdlib,
// it's good enough here.
result->resize(size);
for (int i = 0; i < size; i++) {
// We don't care whether the `max` value may ever be produced exactly.
// It may actually be thanks to rounding, as std::minstd_rand::modulus
// is 2^31 - 1 is greater than the inverse float epsilon.
float random_value_scaled_0_1 =
(*random_engine)() *
(1.0f / static_cast<float>(std::minstd_rand::modulus));
(*result)[i] = min + (max - min) * random_value_scaled_0_1;
}
}
} // namespace
class ActivationOpModel : public SingleOpModelWithHexagon {
public:
explicit ActivationOpModel(BuiltinOperator type, const TensorData& input,
const TensorData& output) {
input_ = AddInput(input);
output_ = AddOutput(output);
SetBuiltinOp(type, BuiltinOptions_NONE, 0);
BuildInterpreter({GetShape(input_)});
}
template <typename T>
void SetInput(const std::vector<float>& data) {
QuantizeAndPopulate<T>(input_, data);
}
template <typename T>
std::vector<float> GetDequantizedOutput() {
return Dequantize<T>(ExtractVector<T>(output_), GetScale(output_),
GetZeroPoint(output_));
}
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
protected:
BuiltinOperator op_code_;
int input_;
int output_;
};
template <typename integer_type, TensorType tensor_dtype>
void ReluTestImpl() {
const float kMin = -6;
const float kMax = 6;
ActivationOpModel model(BuiltinOperator_RELU,
/*input=*/{tensor_dtype, {1, 3}, kMin, kMax},
/*output=*/{tensor_dtype, {1, 3}, kMin, kMax});
model.SetInput<integer_type>({1, 5, 7});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 3}));
EXPECT_THAT(
model.GetDequantizedOutput<integer_type>(),
ElementsAreArray(ArrayFloatNear({1.0, 5.0, 6.0}, /*max_abs_err=*/0.03)));
}
template <typename integer_type, TensorType tensor_dtype>
void Relu6TestImpl() {
const float kMin = -8;
const float kMax = 8;
ActivationOpModel model(BuiltinOperator_RELU6,
/*input=*/{tensor_dtype, {1, 3}, kMin, kMax},
/*output=*/{tensor_dtype, {1, 3}, kMin, kMax});
model.SetInput<integer_type>({4, -1.0, 8});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 3}));
EXPECT_THAT(
model.GetDequantizedOutput<integer_type>(),
ElementsAreArray(ArrayFloatNear({4.0, 0.0, 6.0}, /*max_abs_err=*/0.03)));
}
template <typename integer_type, TensorType tensor_dtype>
void TanhTestImpl() {
// Tanh values are always in this range.
const float kMin = -1;
const float kMax = 127.f / 128.f;
ActivationOpModel model(BuiltinOperator_TANH,
/*input=*/{tensor_dtype, {1, 3}, 8 * kMin, 8 * kMax},
/*output=*/{tensor_dtype, {1, 3}, kMin, kMax});
model.SetInput<integer_type>({4, -1.0, 8});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 3}));
EXPECT_THAT(model.GetDequantizedOutput<integer_type>(),
ElementsAreArray(ArrayFloatNear({1.00392, -0.752941, 1.00392},
/*max_abs_err=*/0.03)));
}
template <typename integer_type, TensorType tensor_dtype>
void SigmoidTestImpl() {
const float kMin = -8;
const float kMax = 8;
TensorData output;
if (tensor_dtype == TensorType_UINT8) {
output = {tensor_dtype, {}, 0, 0, 1. / 256};
} else if (tensor_dtype == TensorType_INT8) {
output = {tensor_dtype, {}, 0, 0, 1. / 256, -128};
}
// Sigmoid requires output min/max to be set to these numbers.
ActivationOpModel model(BuiltinOperator_LOGISTIC,
/*input=*/{tensor_dtype, {1, 3}, kMin, kMax},
/*output=*/output);
model.SetInput<integer_type>({4, -1.0, 8});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 3}));
EXPECT_THAT(model.GetDequantizedOutput<integer_type>(),
ElementsAreArray(ArrayFloatNear({0.977, 0.266, 0.996},
/*max_abs_err=*/0.03)));
}
TEST(ActivationOpModel, ReluOutput_UInt8) {
ReluTestImpl<uint8_t, TensorType_UINT8>();
}
TEST(ActivationOpModel, ReluOutput_Int8) {
ReluTestImpl<int8_t, TensorType_INT8>();
}
TEST(ActivationOpModel, Relu6Output_UInt8) {
Relu6TestImpl<uint8_t, TensorType_UINT8>();
}
TEST(ActivationOpModel, Relu6Output_Int8) {
Relu6TestImpl<int8_t, TensorType_INT8>();
}
TEST(ActivationOpModel, SigmoidOutput_UInt8) {
SigmoidTestImpl<uint8_t, TensorType_UINT8>();
}
TEST(ActivationOpModel, SigmoidOutput_Int8) {
SigmoidTestImpl<int8_t, TensorType_INT8>();
}
TEST(ActivationOpModel, TanhOutput_UInt8) {
TanhTestImpl<uint8_t, TensorType_UINT8>();
}
TEST(ActivationOpModel, TanhOutput_Int8) {
TanhTestImpl<int8_t, TensorType_INT8>();
}
void EvalTestReferenceHardSwish(int size, const std::vector<float>& input,
std::vector<float>* result) {
result->resize(size);
for (int i = 0; i < size; i++) {
const float in = input[i];
(*result)[i] = in * std::min(6.0f, std::max(0.0f, in + 3)) * (1.0f / 6.0f);
}
}
template <TensorType Tensor_Type, typename input_type>
void TestQuantizedHardSwish(int size, float input_min, float input_max,
float output_min, float output_max,
std::minstd_rand* random_engine) {
std::vector<float> float_input_values;
GenerateUniformRandomVector(size, input_min, input_max, random_engine,
&float_input_values);
std::vector<float> float_ref_output_values;
EvalTestReferenceHardSwish(size, float_input_values,
&float_ref_output_values);
for (float& val : float_ref_output_values) {
val = std::min(output_max, std::max(output_min, val));
}
ActivationOpModel m(
BuiltinOperator_HARD_SWISH,
/*input=*/{Tensor_Type, {1, 1, 1, size}, input_min, input_max},
/*output=*/{Tensor_Type, {1, 1, 1, size}, output_min, output_max});
m.SetInput<input_type>(float_input_values);
m.ApplyDelegateAndInvoke();
const std::vector<float> dequantized_output =
m.GetDequantizedOutput<input_type>();
// QUANTIZATION-RECOMMENDED TOLERANCE:
// The numerical error for any 8bit quantized function is at least one half
// times the quantization step: 0.5 * (kOutMax - kOutMin) / 256.
// To that we add again the quantization step (kOutMax - kOutMin) / 256
// to allow for an off-by-one rounding error.
// TOLERANCE FOR HEXAGON:
// Hexagon also introduces some error, so we choose the max between that value
// & 0.03
const float quant_recommended_tolerance =
std::max(input_max - input_min, output_max - output_min) * (1.5f / 256.f);
const float kTolerance = std::max(0.03f, quant_recommended_tolerance);
EXPECT_THAT(dequantized_output, ElementsAreArray(ArrayFloatNear(
float_ref_output_values, kTolerance)));
}
template <TensorType Tensor_Type, typename input_type>
void HardSwishTestImpl() {
std::minstd_rand random_engine;
std::vector<std::pair<float, float>> minmax_pairs{{0.f, 1.f}, {-5.f, 10.f}};
for (const auto& input_minmax : minmax_pairs) {
for (const auto& output_minmax : minmax_pairs) {
float input_min = input_minmax.first;
float input_max = input_minmax.second;
float output_min = output_minmax.first;
float output_max = output_minmax.second;
for (int size : {1, 3, 40}) {
TestQuantizedHardSwish<Tensor_Type, input_type>(
size, input_min, input_max, output_min, output_max, &random_engine);
}
}
}
}
TEST(ActivationOpModel, HardSwishTestUInt8) {
HardSwishTestImpl<TensorType_UINT8, uint8_t>();
}
TEST(ActivationOpModel, HardSwishTestInt8) {
HardSwishTestImpl<TensorType_INT8, int8_t>();
}
template <TensorType Tensor_Type, typename input_type>
void HardSwishBiasTestImpl() {
float input_min = -11.654928f;
float input_max = 25.036512f;
float output_min = -0.3905796f;
float output_max = 24.50887f;
float tolerated_bias = 0.035;
const float quantized_type_range =
static_cast<float>(std::numeric_limits<int8_t>::max()) -
static_cast<float>(std::numeric_limits<int8_t>::min());
const float input_scale = (input_max - input_min) / quantized_type_range;
const float output_scale = (output_max - output_min) / quantized_type_range;
const float max_scale = std::max(output_scale, input_scale);
// In this bias-focused test case, no need for randomly generated input
// values.
ASSERT_LE(input_min, -3.0f);
ASSERT_GE(input_max, 3.0f);
const int quantized_input_negative_three =
std::round(std::numeric_limits<input_type>::min() +
(-3.0f - input_min) / input_scale);
const int quantized_input_positive_three =
std::round(std::numeric_limits<input_type>::min() +
(3.0f - input_min) / input_scale);
std::vector<float> float_input_values;
for (int i = quantized_input_negative_three;
i <= quantized_input_positive_three; i++) {
float_input_values.push_back(
input_min + (i - std::numeric_limits<int8_t>::min()) * input_scale);
}
const int size = float_input_values.size();
std::vector<float> float_ref_output_values;
EvalTestReferenceHardSwish(size, float_input_values,
&float_ref_output_values);
for (float& val : float_ref_output_values) {
val = std::min(output_max, std::max(output_min, val));
}
ActivationOpModel m(
BuiltinOperator_HARD_SWISH,
/*input=*/{Tensor_Type, {1, 1, 1, size}, input_min, input_max},
/*output=*/{Tensor_Type, {1, 1, 1, size}, output_min, output_max});
m.SetInput<input_type>(float_input_values);
m.ApplyDelegateAndInvoke();
const std::vector<float> dequantized_output =
m.GetDequantizedOutput<input_type>();
float sum_diff = 0;
for (int i = 0; i < size; i++) {
sum_diff += dequantized_output[i] - float_ref_output_values[i];
}
const float bias = sum_diff / (size * max_scale);
EXPECT_LE(std::abs(bias), tolerated_bias);
}
// See the comment in the reference implementation of quantized HardSwish:
// A numerical issue significantly affecting ImageNet classification accuracy
// with MobileNet v3 is only observable at the scale of HardSwish unit tests
// if we monitor specifically bias. This testcase is extracted from one of the
// HardSwish nodes in that MobileNet v3 that exhibited this issue.
TEST(ActivationOpModel, HardSwishBiasTest) {
HardSwishBiasTestImpl<TensorType_UINT8, uint8_t>();
}
TEST(ActivationOpModel, HardSwishBiasTestInt8) {
HardSwishBiasTestImpl<TensorType_INT8, int8_t>();
}
} // namespace tflite
@@ -0,0 +1,146 @@
/* Copyright 2020 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 <initializer_list>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
class ArgBaseOpModel : public SingleOpModelWithHexagon {
public:
explicit ArgBaseOpModel(TensorType input_type) {
input_ = AddInput(input_type);
output_ = AddOutput(TensorType_INT32);
}
int input() const { return input_; }
std::vector<int> GetInt32Output() const {
return ExtractVector<int>(output_);
}
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
protected:
using SingleOpModelWithHexagon::builder_;
int input_;
int output_;
};
class ArgMinOpModel : public ArgBaseOpModel {
public:
ArgMinOpModel(std::initializer_list<int> input_shape, TensorType input_type)
: ArgBaseOpModel(input_type /*input_type*/), input_shape_(input_shape) {}
void Build() {
SetBuiltinOp(BuiltinOperator_ARG_MIN, BuiltinOptions_ArgMinOptions,
CreateArgMinOptions(builder_, TensorType_INT32 /*output_type*/)
.Union());
BuildInterpreter({input_shape_, {1}});
}
private:
std::vector<int> input_shape_;
};
class ArgMaxOpModel : public ArgBaseOpModel {
public:
ArgMaxOpModel(std::initializer_list<int> input_shape, TensorType input_type)
: ArgBaseOpModel(input_type /*input_type*/), input_shape_(input_shape) {}
void Build() {
SetBuiltinOp(BuiltinOperator_ARG_MAX, BuiltinOptions_ArgMaxOptions,
CreateArgMaxOptions(builder_, TensorType_INT32 /*output_type*/)
.Union());
BuildInterpreter({input_shape_, {1}});
}
private:
std::vector<int> input_shape_;
};
template <typename integer_type, TensorType tensor_dtype>
void ArgMinTestImpl() {
ArgMinOpModel model({1, 1, 1, 4}, tensor_dtype);
model.AddConstInput(TensorType_INT32, {3}, {1});
model.Build();
if (tensor_dtype == TensorType_UINT8) {
model.SymmetricQuantizeAndPopulate(model.input(), {1, 5, 0, 7});
} else {
model.SignedSymmetricQuantizeAndPopulate(model.input(), {1, 5, 0, 7});
}
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetInt32Output(), ElementsAreArray({2}));
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 1, 1}));
}
template <typename integer_type, TensorType tensor_dtype>
void ArgMinNegativeTestImpl() {
ArgMinOpModel model({1, 1, 2, 4}, tensor_dtype);
model.AddConstInput(TensorType_INT32, {-2}, {1});
model.Build();
if (tensor_dtype == TensorType_UINT8) {
model.SymmetricQuantizeAndPopulate(model.input(), {1, 2, 7, 8, 1, 9, 7, 3});
} else {
model.SignedSymmetricQuantizeAndPopulate(model.input(),
{1, 2, 7, 8, 1, 9, 7, 3});
}
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetInt32Output(), ElementsAreArray({0, 0, 0, 1}));
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 1, 4}));
}
template <typename integer_type, TensorType tensor_dtype>
void ArgMaxTestImpl() {
ArgMaxOpModel model({1, 1, 1, 4}, tensor_dtype);
model.AddConstInput(TensorType_INT32, {3}, {1});
model.Build();
if (tensor_dtype == TensorType_UINT8) {
model.SymmetricQuantizeAndPopulate(model.input(), {1, 5, 0, 7});
} else {
model.SignedSymmetricQuantizeAndPopulate(model.input(), {1, 5, 0, 7});
}
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetInt32Output(), ElementsAreArray({3}));
}
TEST(ArgMinTest, GetArgMin_UInt8) {
ArgMinTestImpl<uint8_t, TensorType_UINT8>();
}
TEST(ArgMinTest, GetArgMin_Int8) { ArgMinTestImpl<int8_t, TensorType_INT8>(); }
TEST(ArgMinTest, GetArgMinNegative_UInt8) {
ArgMinNegativeTestImpl<uint8_t, TensorType_UINT8>();
}
TEST(ArgMinTest, GetArgMinNegative_Int8) {
ArgMinNegativeTestImpl<int8_t, TensorType_INT8>();
}
TEST(ArgMaxTest, GetArgMax_UInt8) {
ArgMaxTestImpl<uint8_t, TensorType_UINT8>();
}
TEST(ArgMaxTest, GetArgMax_Int8) { ArgMaxTestImpl<int8_t, TensorType_INT8>(); }
} // namespace tflite
@@ -0,0 +1,192 @@
/* Copyright 2020 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 <initializer_list>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
class ArithmeticOpBaseModel : public SingleOpModelWithHexagon {
public:
ArithmeticOpBaseModel(const TensorData& input1, const TensorData& input2,
const TensorData& output)
: SingleOpModelWithHexagon() {
input1_ = AddInput(input1);
input2_ = AddInput(input2);
output_ = AddOutput(output);
}
ArithmeticOpBaseModel(const TensorData& input1, const TensorData& input2,
const TensorData& output,
const std::initializer_list<uint8_t>& input1_data,
const std::initializer_list<uint8_t>& input2_data) {
if (input1_data.size() > 0)
input1_ = AddConstInput(input1, input1_data);
else
input1_ = AddInput(input1);
if (input2_data.size() > 0)
input2_ = AddConstInput(input2, input2_data);
else
input2_ = AddInput(input2);
output_ = AddOutput(output);
}
void InitInterpreter() {
BuildInterpreter({GetShape(input1_), GetShape(input2_)});
}
template <typename T>
void SetInput1(const std::vector<float>& data) {
QuantizeAndPopulate<T>(input1_, data);
}
template <typename T>
void SetInput2(const std::vector<float>& data) {
QuantizeAndPopulate<T>(input2_, data);
}
template <typename T>
std::vector<float> GetDequantizedOutput() {
return Dequantize<T>(ExtractVector<T>(output_), GetScale(output_),
GetZeroPoint(output_));
}
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
protected:
int input1_;
int input2_;
int output_;
};
class AddOpModel : public ArithmeticOpBaseModel {
public:
AddOpModel(const TensorData& input1, const TensorData& input2,
const TensorData& output, ActivationFunctionType activation_func)
: ArithmeticOpBaseModel(input1, input2, output),
activation_func_(activation_func) {}
AddOpModel(const TensorData& input1, const TensorData& input2,
const TensorData& output,
const std::initializer_list<uint8_t>& input1_data,
const std::initializer_list<uint8_t>& input2_data,
ActivationFunctionType activation_func)
: ArithmeticOpBaseModel(input1, input2, output, input1_data, input2_data),
activation_func_(activation_func) {}
void InitInterpreter() {
SetBuiltinOp(BuiltinOperator_ADD, BuiltinOptions_AddOptions,
CreateAddOptions(builder_, activation_func_).Union());
ArithmeticOpBaseModel::InitInterpreter();
}
private:
ActivationFunctionType activation_func_;
};
template <TensorType tensor_type, typename integer_dtype>
void QuantizedTestsNoActivation(ActivationFunctionType activation_func) {
const float kQuantizedTolerance = 2.0 / 255.0;
std::vector<std::vector<float>> inputs1 = {
{0.1, 0.2, 0.3, 0.4}, {-0.8, 0.2, 0.4, 0.7}, {-0.8, 0.2, 0.7, 0.3}};
std::vector<std::vector<float>> inputs2 = {
{0.6, 0.4, 0.3, 0.1}, {0.6, 0.4, 0.5, -0.8}, {0.6, 0.4, -0.8, 0.5}};
for (size_t i = 0; i < 1; ++i) {
AddOpModel m({tensor_type, {1, 2, 2, 1}, -1.0, 1.0},
{tensor_type, {1, 2, 2, 1}, -1.0, 1.0},
{tensor_type, {1, 2, 2, 1}, -1.0, 1.0}, activation_func);
m.InitInterpreter();
m.SetInput1<integer_dtype>(inputs1[i]);
m.SetInput2<integer_dtype>(inputs2[i]);
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<integer_dtype>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(
m.GetDequantizedOutput<integer_dtype>(),
ElementsAreArray(ArrayFloatNear(reference_output, kQuantizedTolerance)))
<< "With test number " << i;
}
}
class QuantizedAddOpModel
: public testing::TestWithParam<ActivationFunctionType> {};
TEST_P(QuantizedAddOpModel, QuantizedTestsNoActivationUInt8) {
QuantizedTestsNoActivation<TensorType_UINT8, uint8_t>(GetParam());
}
TEST_P(QuantizedAddOpModel, QuantizedTestsNoActivationInt8) {
QuantizedTestsNoActivation<TensorType_INT8, int8_t>(GetParam());
}
TEST(QuantizedAddOpModelNoActivation, TestUInt8_ConstInput_1) {
const float kQuantizedTolerance = 2.0 / 255.0;
AddOpModel m({TensorType_UINT8, {1, 2, 2, 1}, -1.0, 1.0},
{TensorType_UINT8, {1, 2, 2, 1}, -1.0, 1.0},
{TensorType_UINT8, {1, 2, 2, 1}, -1.0, 1.0},
{110, 142, 156, 171}, {}, ActivationFunctionType_NONE);
m.InitInterpreter();
m.SetInput1<uint8_t>({0.1, 0.2, 0.3, 0.4});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<uint8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(
m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output, kQuantizedTolerance)));
}
TEST(QuantizedAddOpModelNoActivation, TestUInt8_ConstInput_2) {
const float kQuantizedTolerance = 2.0 / 255.0;
AddOpModel m({TensorType_UINT8, {1, 2, 2, 1}, -1.0, 1.0},
{TensorType_UINT8, {1, 2, 2, 1}, -1.0, 1.0},
{TensorType_UINT8, {1, 2, 2, 1}, -1.0, 1.0}, {},
{110, 142, 156, 171}, ActivationFunctionType_NONE);
m.InitInterpreter();
m.SetInput2<uint8_t>({0.1, 0.2, 0.3, 0.4});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<uint8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(
m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output, kQuantizedTolerance)));
}
TEST(QuantizedAddOpModelNoActivation, TestInt8_ConstInput) {
const float kQuantizedTolerance = 2.0 / 255.0;
AddOpModel m({TensorType_INT8, {1, 2, 2, 1}, -1.0, 1.0},
{TensorType_INT8, {1, 2, 2, 1}, -1.0, 1.0},
{TensorType_INT8, {1, 2, 2, 1}, -1.0, 1.0}, {},
{110, 101, 105, 120}, ActivationFunctionType_NONE);
m.InitInterpreter();
m.SetInput2<int8_t>({0.1, 0.2, 0.3, 0.4});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<int8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(
m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output, kQuantizedTolerance)));
}
INSTANTIATE_TEST_SUITE_P(QuantizedAddOpModel, QuantizedAddOpModel,
testing::Values(ActivationFunctionType_NONE,
ActivationFunctionType_RELU,
ActivationFunctionType_RELU_N1_TO_1,
ActivationFunctionType_RELU6));
} // namespace tflite
@@ -0,0 +1,267 @@
/* Copyright 2020 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 <algorithm>
#include <memory>
#include <random>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/core/util/command_line_flags.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/core/api/op_resolver.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/interpreter_builder.h"
#include "tensorflow/lite/kernels/kernel_util.h"
#include "tensorflow/lite/kernels/register.h"
#include "tensorflow/lite/model_builder.h"
#include "tensorflow/lite/string_type.h"
#include "tensorflow/lite/testing/util.h"
#include "tensorflow/lite/tools/benchmark/benchmark_utils.h"
#include "tensorflow/lite/tools/logging.h"
namespace tflite {
namespace {
struct batch_seq_config_test_flags {
std::string* model_file_path;
std::string* model_input_shapes;
int max_batch_size = -1;
float error_epsilon = 0.2;
} batch_seq_config_test_flags_values;
// Returns a randomly generated data of size 'num_elements'.
std::vector<uint8_t> GetData(int num_elements) {
std::vector<uint8_t> result(num_elements);
std::random_device random_engine;
std::uniform_int_distribution<uint32_t> distribution(0, 254);
std::generate_n(result.data(), num_elements, [&]() {
return static_cast<uint8_t>(distribution(random_engine));
});
return result;
}
// Returns the total number of elements.
int NumElements(const std::vector<int>& shape) {
int num_elements = 1;
for (int dim : shape) num_elements *= dim;
return num_elements;
}
// Returns true if 'control' and 'exp' values match up to 'epsilon'
bool DiffOutput(const std::vector<float>& control,
const std::vector<float>& exp, double epsilon) {
if (control.size() != exp.size()) {
TFLITE_LOG(ERROR) << "Mismatch size Expected" << control.size() << " got "
<< exp.size();
return false;
}
bool has_diff = false;
for (int i = 0; i < control.size(); ++i) {
if (abs(control[i] - exp[i]) > epsilon) {
TFLITE_LOG(ERROR) << control[i] << " " << exp[i];
has_diff = true;
}
}
return !has_diff;
}
bool DiffOutput(const std::vector<float>& control,
const std::vector<float>& exp) {
return DiffOutput(control, exp,
tflite::batch_seq_config_test_flags_values.error_epsilon);
}
} // namespace
class TestModel {
public:
TestModel() : delegate_(nullptr, [](TfLiteDelegate* delegate) {}) {}
// Initialize the model by reading the model from file and build
// interpreter.
void Init() {
model_ = tflite::FlatBufferModel::BuildFromFile(
tflite::batch_seq_config_test_flags_values.model_file_path->c_str());
ASSERT_TRUE(model_ != nullptr);
resolver_ = std::make_unique<ops::builtin::BuiltinOpResolver>();
InterpreterBuilder(*model_, *resolver_)(&interpreter_);
ASSERT_TRUE(interpreter_ != nullptr);
}
// Add Hexagon delegate to the graph.
void ApplyDelegate(int max_batch_size,
const std::vector<int>& input_batch_dimensions,
const std::vector<int>& output_batch_dimensions) {
TfLiteIntArray* input_batch_dim =
TfLiteIntArrayCreate(input_batch_dimensions.size());
TfLiteIntArray* output_batch_dim =
TfLiteIntArrayCreate(output_batch_dimensions.size());
for (int i = 0; i < input_batch_dimensions.size(); ++i)
input_batch_dim->data[i] = input_batch_dimensions[i];
for (int i = 0; i < output_batch_dimensions.size(); ++i)
output_batch_dim->data[i] = output_batch_dimensions[i];
::TfLiteHexagonDelegateOptions options = {0};
options.enable_dynamic_batch_size = true;
options.max_batch_size = max_batch_size;
options.input_batch_dimensions = input_batch_dim;
options.output_batch_dimensions = output_batch_dim;
TfLiteDelegate* delegate = TfLiteHexagonDelegateCreate(&options);
ASSERT_TRUE(delegate != nullptr);
delegate_ = std::unique_ptr<TfLiteDelegate, void (*)(TfLiteDelegate*)>(
delegate, [](TfLiteDelegate* delegate) {
TfLiteHexagonDelegateDelete(delegate);
});
ASSERT_TRUE(interpreter_->ModifyGraphWithDelegate(delegate_.get()) ==
kTfLiteOk);
}
void Run(const std::vector<int>& input_shape,
const std::vector<uint8_t>& input_data) {
// Resize Inputs.
auto interpreter_inputs = interpreter_->inputs();
interpreter_->ResizeInputTensor(interpreter_inputs[0], input_shape);
ASSERT_EQ(kTfLiteOk, interpreter_->AllocateTensors());
TfLiteTensor* input_tensor =
interpreter_->tensor(interpreter_->inputs()[0]);
memcpy(input_tensor->data.raw, input_data.data(),
input_data.size() * sizeof(uint8_t));
ASSERT_EQ(kTfLiteOk, interpreter_->Invoke());
}
std::vector<float> GetOutput(int output_index) {
auto* tensor = interpreter_->output_tensor(output_index);
uint8_t* data = interpreter_->typed_output_tensor<uint8_t>(output_index);
std::vector<float> result;
result.resize(NumElements(tensor));
const auto scale =
reinterpret_cast<TfLiteAffineQuantization*>(tensor->quantization.params)
->scale->data[0];
const auto zero_point =
reinterpret_cast<TfLiteAffineQuantization*>(tensor->quantization.params)
->zero_point->data[0];
for (int i = 0; i < result.size(); ++i) {
result[i] = scale * (data[i] - zero_point);
}
return result;
}
private:
std::unique_ptr<TfLiteDelegate, void (*)(TfLiteDelegate*)> delegate_;
std::unique_ptr<FlatBufferModel> model_;
std::unique_ptr<tflite::OpResolver> resolver_;
std::unique_ptr<Interpreter> interpreter_;
};
std::vector<std::vector<int>> ParseInputShapes() {
std::vector<string> str_input_shapes;
benchmark::util::SplitAndParse(
*tflite::batch_seq_config_test_flags_values.model_input_shapes, ':',
&str_input_shapes);
std::vector<std::vector<int>> input_shapes(str_input_shapes.size());
for (int i = 0; i < str_input_shapes.size(); ++i) {
benchmark::util::SplitAndParse(str_input_shapes[i], ',', &input_shapes[i]);
}
return input_shapes;
}
TEST(HexagonDynamicBatch, MultipleResizes) {
int num_failed_tests = 0;
int num_test = 0;
auto test_input_shapes = ParseInputShapes();
auto default_model = std::make_unique<TestModel>();
auto delegated_model = std::make_unique<TestModel>();
TFLITE_LOG(INFO)
<< "model_file_path: "
<< *tflite::batch_seq_config_test_flags_values.model_file_path << "\n";
TFLITE_LOG(INFO)
<< "model_input_shapes: "
<< *tflite::batch_seq_config_test_flags_values.model_input_shapes << "\n";
TFLITE_LOG(INFO) << "max_batch_size: "
<< tflite::batch_seq_config_test_flags_values.max_batch_size
<< "\n";
TFLITE_LOG(INFO) << "error_epsilon: "
<< tflite::batch_seq_config_test_flags_values.error_epsilon
<< "\n";
default_model->Init();
delegated_model->Init();
delegated_model->ApplyDelegate(
tflite::batch_seq_config_test_flags_values.max_batch_size, {0}, {0});
for (const auto& input_shape : test_input_shapes) {
const auto input = GetData(NumElements(input_shape));
default_model->Run(input_shape, input);
delegated_model->Run(input_shape, input);
const auto default_output = default_model->GetOutput(0);
const auto delegated_output = delegated_model->GetOutput(0);
if (!DiffOutput(default_output, delegated_output)) {
TFLITE_LOG(ERROR) << "Failed for input " << num_test;
num_failed_tests++;
}
num_test++;
}
if (num_failed_tests == 0) {
TFLITE_LOG(INFO) << "All Tests PASSED";
} else {
TFLITE_LOG(INFO) << "Failed " << num_failed_tests << " out of " << num_test;
}
}
} // namespace tflite
int main(int argc, char** argv) {
::tflite::LogToStderr();
std::string FLAGS_model_file_path;
std::string FLAGS_model_input_shapes;
int FLAGS_max_batch_size = -1;
float FLAGS_error_epsilon = 0.2;
std::vector<tensorflow::Flag> flags = {
tensorflow::Flag("model_file_path", &FLAGS_model_file_path,
"Path to the test model file."),
tensorflow::Flag(
"model_input_shapes", &FLAGS_model_input_shapes,
"List of different input shapes for testing, the input will "
"resized for each one in order and tested. They Should be "
"separated by : and each shape has dimensions separated by ,"),
tensorflow::Flag("max_batch_size", &FLAGS_max_batch_size,
"Maximum batch size for a single run by hexagon."),
tensorflow::Flag("error_epsilon", &FLAGS_error_epsilon,
"Maximum error allowed while diffing the output."),
};
bool no_inputs = argc == 1;
bool success = tensorflow::Flags::Parse(&argc, argv, flags);
if (!success || no_inputs || (argc == 2 && !strcmp(argv[1], "--helpfull"))) {
fprintf(stderr, "%s", tensorflow::Flags::Usage(argv[0], flags).c_str());
return {};
} else if (FLAGS_model_file_path.empty() ||
FLAGS_model_input_shapes.empty()) {
fprintf(stderr, "%s", tensorflow::Flags::Usage(argv[0], flags).c_str());
return {};
}
tflite::batch_seq_config_test_flags_values.model_file_path =
&FLAGS_model_file_path;
tflite::batch_seq_config_test_flags_values.max_batch_size =
FLAGS_max_batch_size;
tflite::batch_seq_config_test_flags_values.model_input_shapes =
&FLAGS_model_input_shapes;
tflite::batch_seq_config_test_flags_values.error_epsilon =
FLAGS_error_epsilon;
testing::InitGoogleTest();
TfLiteHexagonInit();
int return_val = RUN_ALL_TESTS();
TfLiteHexagonTearDown();
return return_val;
}
@@ -0,0 +1,286 @@
/* Copyright 2020 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 <random>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
namespace {
void GenerateUniformRandomVector(int size, float min, float max,
std::minstd_rand* random_engine,
std::vector<float>* result) {
// Never use std::uniform_*_distribution in tests, it's
// implementation-defined. Likewise, don't use std::default_random_engine,
// implementation-defined. Implementation-defined is bad because it means that
// any toolchain update or new platform may run into test failures.
// std::minstd_rand is a standard instantiation of
// std::linear_congruential_engine, the cheapest generator in c++11 stdlib,
// it's good enough here.
result->resize(size);
for (int i = 0; i < size; i++) {
// We don't care whether the `max` value may ever be produced exactly.
// It may actually be thanks to rounding, as std::minstd_rand::modulus
// is 2^31 - 1 is greater than the inverse float epsilon.
float random_value_scaled_0_1 =
(*random_engine)() *
(1.0f / static_cast<float>(std::minstd_rand::modulus));
(*result)[i] = min + (max - min) * random_value_scaled_0_1;
}
}
} // namespace
class QuantizedConcatenationOpModel : public SingleOpModelWithHexagon {
public:
QuantizedConcatenationOpModel(const std::vector<TensorData>& input_template,
int axis, const TensorData& output_template) {
std::vector<std::vector<int>> all_input_shapes;
for (int i = 0; i < input_template.size(); ++i) {
all_input_shapes.push_back(input_template[i].shape);
AddInput(input_template[i]);
}
output_ = AddOutput({output_template.type, /*shape=*/{},
output_template.min, output_template.max});
SetBuiltinOp(
BuiltinOperator_CONCATENATION, BuiltinOptions_ConcatenationOptions,
CreateConcatenationOptions(builder_, axis, ActivationFunctionType_NONE)
.Union());
BuildInterpreter(all_input_shapes);
}
template <typename T>
void SetInput(int index, std::vector<float> data) {
QuantizeAndPopulate<T>(index, data);
}
template <typename T>
std::vector<float> GetDequantizedOutput() {
return Dequantize<T>(ExtractVector<T>(output_), GetScale(output_),
GetZeroPoint(output_));
}
template <typename T>
std::vector<T> GetOutput() {
return ExtractVector<T>(output_);
}
private:
int output_;
};
template <typename integer_type, TensorType tensor_dtype>
void FourInputsQuantizedSameRangeImpl() {
QuantizedConcatenationOpModel m0({{tensor_dtype, {2, 1, 1, 2}, -12.7, 12.8},
{tensor_dtype, {2, 1, 1, 2}, -12.7, 12.8},
{tensor_dtype, {2, 1, 1, 2}, -12.7, 12.8},
{tensor_dtype, {2, 1, 1, 2}, -12.7, 12.8}},
/*axis=*/3, {tensor_dtype, {}, -12.7, 12.8});
m0.SetInput<integer_type>(0, {1.0f, 3.0f, 4.0f, 7.0f});
m0.SetInput<integer_type>(1, {1.1f, 3.1f, 4.1f, 7.1f});
m0.SetInput<integer_type>(2, {1.2f, 3.2f, 4.2f, 7.2f});
m0.SetInput<integer_type>(3, {1.3f, 3.3f, 4.3f, 7.3f});
m0.ApplyDelegateAndInvoke();
EXPECT_THAT(m0.GetDequantizedOutput<integer_type>(),
ElementsAreArray(ArrayFloatNear(
{
1.0f,
3.0f,
1.1f,
3.1f,
1.2f,
3.2f,
1.3f,
3.3f, //
4.0f,
7.0f,
4.1f,
7.1f,
4.2f,
7.2f,
4.3f,
7.3f, //
},
/*max_abs_err=*/0.2)));
}
TEST(QuantizedConcatenationOpModel, FourInputsQuantizedSameRange_UInt8) {
FourInputsQuantizedSameRangeImpl<uint8_t, TensorType_UINT8>();
}
TEST(QuantizedConcatenationOpModel, FourInputsQuantizedSameRange_Int8) {
FourInputsQuantizedSameRangeImpl<int8_t, TensorType_INT8>();
}
template <typename integer_type, TensorType tensor_dtype>
void TwoInputsNegativeAxisImpl() {
auto tensor0 = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f};
auto tensor1 = {7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f};
QuantizedConcatenationOpModel m0({{tensor_dtype,
{2, 3},
std::numeric_limits<integer_type>::min(),
std::numeric_limits<integer_type>::max()},
{tensor_dtype,
{2, 3},
std::numeric_limits<integer_type>::min(),
std::numeric_limits<integer_type>::max()}},
/*axis=*/-2,
{tensor_dtype,
{},
std::numeric_limits<integer_type>::min(),
std::numeric_limits<integer_type>::max()});
m0.SetInput<integer_type>(0, tensor0);
m0.SetInput<integer_type>(1, tensor1);
m0.ApplyDelegateAndInvoke();
EXPECT_THAT(m0.GetOutput<integer_type>(),
ElementsAreArray({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}));
}
TEST(QuantizedConcatenationOpModel, TwoInputsNegativeAxis_UInt8) {
TwoInputsNegativeAxisImpl<uint8_t, TensorType_UINT8>();
}
TEST(QuantizedConcatenationOpModel, TwoInputsNegativeAxis_Int8) {
TwoInputsNegativeAxisImpl<int8_t, TensorType_INT8>();
}
// NOTE: Int8 Concat does not have mixed-range support.
TEST(QuantizedConcatenationOpModel, FourInputsQuantizedMixedRange) {
QuantizedConcatenationOpModel m0(
{{TensorType_UINT8, {2, 1, 1, 2}, -10.7, 10.8},
{TensorType_UINT8, {2, 1, 1, 2}, 0, 12.8},
{TensorType_UINT8, {2, 1, 1, 2}, -11, 11.8},
{TensorType_UINT8, {2, 1, 1, 2}, 0, 7.4}},
/*axis=*/3, {TensorType_UINT8, {}, -12.7, 12.8});
m0.SetInput<uint8_t>(0, {1.0f, 3.0f, 4.0f, 7.0f});
m0.SetInput<uint8_t>(1, {1.1f, 3.1f, 4.1f, 7.1f});
m0.SetInput<uint8_t>(2, {1.2f, 3.2f, 4.2f, 7.2f});
m0.SetInput<uint8_t>(3, {1.3f, 3.3f, 4.3f, 7.3f});
m0.ApplyDelegateAndInvoke();
EXPECT_THAT(m0.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(
{
1.0f,
3.0f,
1.1f,
3.1f,
1.2f,
3.2f,
1.3f,
3.3f, //
4.0f,
7.0f,
4.1f,
7.1f,
4.2f,
7.2f,
4.3f,
7.3f, //
},
/*max_abs_err=*/0.2)));
}
TEST(QuantizedConcatenationOpModel, FourInputsAxis2_UInt8) {
QuantizedConcatenationOpModel m0({{TensorType_UINT8, {2, 1, 2}, -10.7, 10.8},
{TensorType_UINT8, {2, 1, 2}, 0, 12.8},
{TensorType_UINT8, {2, 1, 2}, -11, 11.8},
{TensorType_UINT8, {2, 1, 2}, 0, 7.4}},
/*axis=*/2,
{TensorType_UINT8, {2, 1, 2}, -1., 1.});
m0.SetInput<uint8_t>(0, {1.0f, -3.0f, -4.0f, -7.0f});
m0.SetInput<uint8_t>(1, {1.1f, 3.1f, 4.1f, 7.1f});
m0.SetInput<uint8_t>(2, {1.2f, -3.2f, -4.2f, 7.2f});
m0.SetInput<uint8_t>(3, {1.3f, 3.3f, 4.3f, 7.3f});
m0.ApplyDelegateAndInvoke();
EXPECT_THAT(m0.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(
{
1.0f,
-1.0f,
1.0f,
1.0f,
1.0f,
-1.0f,
1.0f,
1.0f, //
-1.0f,
-1.0f,
1.0f,
1.0f,
-1.0f,
1.0f,
1.0f,
1.0f, //
},
/*max_abs_err=*/0.2)));
}
// If the input min/max (across all tensors) is same as the output min/max,
// Hexagon's Requantize causes errors in InceptionV3.
// So, we diable it for that case in the builder.
// This unit test ensures that the math still works.
TEST(QuantizedConcatenationOpModel, FourInputsQuantizedMixedRange_LargeData) {
// Problem specification.
// Adapted from CONCAT node at #15 in Inceptionv3 quantized.
std::vector<float> params1 = {0, 11.30514f};
std::vector<float> params2 = {0, 10.38416f};
std::vector<float> params3 = {0, 13.52495f};
std::vector<float> params4 = {0, 5.883808f};
std::vector<float> params_output = {0, 13.52495f};
QuantizedConcatenationOpModel m0(
{{TensorType_UINT8, {1, 35, 35, 64}, params1[0], params1[1]},
{TensorType_UINT8, {1, 35, 35, 64}, params2[0], params2[1]},
{TensorType_UINT8, {1, 35, 35, 96}, params3[0], params3[1]},
{TensorType_UINT8, {1, 35, 35, 32}, params4[0], params4[1]}},
/*axis=*/3, {TensorType_UINT8, {}, params_output[0], params_output[1]});
// Generate random data.
std::minstd_rand random_engine;
std::vector<float> data1, data2, data3, data4;
int num_elements_multiplier = 1 * 35 * 35;
GenerateUniformRandomVector(num_elements_multiplier * 64, params1[0],
params1[1], &random_engine, &data1);
GenerateUniformRandomVector(num_elements_multiplier * 64, params2[0],
params2[1], &random_engine, &data2);
GenerateUniformRandomVector(num_elements_multiplier * 96, params3[0],
params3[1], &random_engine, &data3);
GenerateUniformRandomVector(num_elements_multiplier * 32, params4[0],
params4[1], &random_engine, &data4);
m0.SetInput<uint8_t>(0, data1);
m0.SetInput<uint8_t>(1, data2);
m0.SetInput<uint8_t>(2, data3);
m0.SetInput<uint8_t>(3, data4);
// Reference output.
ASSERT_EQ(m0.Invoke(), kTfLiteOk);
std::vector<float> reference_output = m0.GetDequantizedOutput<uint8_t>();
m0.ApplyDelegateAndInvoke();
EXPECT_THAT(m0.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output,
/*max_abs_err=*/0.1)));
}
} // namespace tflite
@@ -0,0 +1,794 @@
/* Copyright 2020 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 <initializer_list>
#include <numeric>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/internal/test_util.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
int NumElements(const std::vector<int>& dims) {
return std::accumulate(dims.begin(), dims.end(), 1, std::multiplies<int>());
}
class QuantizedConvolutionOpModel : public SingleOpModelWithHexagon {
public:
QuantizedConvolutionOpModel(BuiltinOperator type, const TensorData& input,
const TensorData& filter,
const TensorData& output, Padding padding_type,
int dilation_factor = 1, int stride_length = 1,
ActivationFunctionType fused_activation_function =
ActivationFunctionType_NONE) {
input_ = AddInput(input);
filter_ = AddInput(filter);
int bias_size = GetShape(filter_)[0];
if (type == BuiltinOperator_DEPTHWISE_CONV_2D) {
bias_size = GetShape(filter_)[3];
}
if (filter.per_channel_quantization) {
// per channel quantization.
std::vector<float> bias_scale(
filter.per_channel_quantization_scales.size());
std::vector<int64_t> bias_zero_points(
filter.per_channel_quantization_scales.size());
for (size_t i = 0; i < filter.per_channel_quantization_scales.size();
++i) {
bias_scale[i] = input.scale * filter.per_channel_quantization_scales[i];
bias_zero_points[i] = 0;
}
TensorData bias{TensorType_INT32,
{bias_size},
/*min=*/0,
/*max=*/0,
/*scale=*/0,
/*zero_point=*/0,
true,
/*per_channel_quantization_scales=*/bias_scale,
/*per_channel_quantization_offsets=*/bias_zero_points,
/*channel_index==*/0};
bias_ = AddInput(bias);
} else {
// per tensor quantization.
auto bias_scale = GetScale(input_) * GetScale(filter_);
TensorData bias{TensorType_INT32, {bias_size}, 0, 0, bias_scale};
bias_ = AddInput(bias);
}
output_ = AddOutput(output);
if (type == BuiltinOperator_DEPTHWISE_CONV_2D) {
int input_depth = GetShape(input_)[3];
int output_depth = GetShape(filter_)[3];
int depth_mul = output_depth / input_depth;
SetBuiltinOp(
BuiltinOperator_DEPTHWISE_CONV_2D,
BuiltinOptions_DepthwiseConv2DOptions,
CreateDepthwiseConv2DOptions(
builder_, padding_type, stride_length, stride_length, depth_mul,
fused_activation_function, dilation_factor, dilation_factor)
.Union());
} else {
SetBuiltinOp(BuiltinOperator_CONV_2D, BuiltinOptions_Conv2DOptions,
CreateConv2DOptions(builder_, padding_type, stride_length,
stride_length, fused_activation_function,
dilation_factor, dilation_factor)
.Union());
}
BuildInterpreter({GetShape(input_), GetShape(filter_), GetShape(bias_)});
// Filter needs to be a constant.
// We don't use AddConstInput to allow setting filter values later.
auto* filter_tensor = interpreter_->tensor(filter_);
filter_tensor->allocation_type = kTfLiteMmapRo;
}
void SetInput(std::initializer_list<float> data) {
QuantizeAndPopulate<uint8_t>(input_, data);
}
void SetFilter(std::initializer_list<float> data) {
QuantizeAndPopulate<uint8_t>(filter_, data);
}
void SetBias(std::initializer_list<float> data) {
QuantizeAndPopulate<int>(bias_, data);
}
template <typename T>
std::vector<float> GetDequantizedOutput() {
return Dequantize<T>(ExtractVector<T>(output_), GetScale(output_),
GetZeroPoint(output_));
}
void SetInt8Input(std::initializer_list<float> data) {
QuantizeAndPopulate<int8_t>(input_, data);
}
void SetInt8Input(const std::vector<float>& data) {
QuantizeAndPopulate<int8_t>(input_, data);
}
void SetPerChannelQuantizedFilter(std::initializer_list<float> data) {
PerChannelSymmetricQuantizeAndPopulate(filter_, data);
}
void SetPerChannelQuantizedFilter(const std::vector<float>& data) {
PerChannelSymmetricQuantizeAndPopulate(filter_, data);
}
void SetPerChannelQuantizedBias(std::initializer_list<float> data) {
PerChannelQuantizeBias(bias_, data);
}
void SetPerChannelQuantizedBias(const std::vector<float>& data) {
PerChannelQuantizeBias(bias_, data);
}
protected:
int input_;
int filter_;
int bias_;
int output_;
};
// CONVOLUTION TESTS
TEST(QuantizedConvolutionOpModel, SimpleConvTestNoActivation) {
QuantizedConvolutionOpModel m(
BuiltinOperator_CONV_2D, {TensorType_UINT8, {2, 2, 4, 1}, -63.5, 64},
{TensorType_UINT8, {3, 2, 2, 1}, -63.5, 64},
{TensorType_UINT8, {}, -127, 128}, Padding_VALID, /**dilation_factor**/ 1,
/**stride**/ 2);
m.SetInput({
// First batch
1, 1, 1, 1, // row = 1
2, 2, 2, 2, // row = 2
// Second batch
1, 2, 3, 4, // row = 1
1, 2, 3, 4, // row = 2
});
m.SetFilter({
1, 2, 3, 4, // first 2x2 filter
-1, 1, -1, 1, // second 2x2 filter
-1, -1, 1, 1, // third 2x2 filter
});
m.SetBias({1, 2, 3});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(
{
18, 2, 5, // first batch, left
18, 2, 5, // first batch, right
17, 4, 3, // second batch, left
37, 4, 3, // second batch, right
},
1e-5)));
}
TEST(QuantizedConvolutionOpModel, SimpleConvTestReLU6Activation) {
QuantizedConvolutionOpModel m(
BuiltinOperator_CONV_2D, {TensorType_UINT8, {2, 2, 4, 1}, -63.5, 64},
{TensorType_UINT8, {3, 2, 2, 1}, -63.5, 64},
{TensorType_UINT8, {}, -127, 128}, Padding_VALID, /**dilation_factor**/ 1,
/**stride**/ 2, ActivationFunctionType_RELU6);
m.SetInput({
// First batch
1, 1, 1, 1, // row = 1
2, 2, 2, 2, // row = 2
// Second batch
1, 2, 3, 4, // row = 1
1, 2, 3, 4, // row = 2
});
m.SetFilter({
1, 2, 3, 4, // first 2x2 filter
-1, 1, -1, 1, // second 2x2 filter
-1, -1, 1, 1, // third 2x2 filter
});
m.SetBias({1, 2, 3});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(
{
6, 2, 5, // first batch, left
6, 2, 5, // first batch, right
6, 4, 3, // second batch, left
6, 4, 3, // second batch, right
},
1e-5)));
}
// Same as above, but the output min/max matches the RELU bounds.
// Therefore, a Requantize node will not get added after Supernode.
TEST(QuantizedConvolutionOpModel,
SimpleConvTestReLU6Activation_NoRequantizeRequired) {
QuantizedConvolutionOpModel m(
BuiltinOperator_CONV_2D, {TensorType_UINT8, {2, 2, 4, 1}, -63.5, 64},
{TensorType_UINT8, {3, 2, 2, 1}, -63.5, 64}, {TensorType_UINT8, {}, 0, 6},
Padding_VALID, /**dilation_factor**/ 1,
/**stride**/ 2, ActivationFunctionType_RELU6);
m.SetInput({
// First batch
1, 1, 1, 1, // row = 1
2, 2, 2, 2, // row = 2
// Second batch
1, 2, 3, 4, // row = 1
1, 2, 3, 4, // row = 2
});
m.SetFilter({
1, 2, 3, 4, // first 2x2 filter
-1, 1, -1, 1, // second 2x2 filter
-1, -1, 1, 1, // third 2x2 filter
});
m.SetBias({1, 2, 3});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(
{
6, 2, 5, // first batch, left
6, 2, 5, // first batch, right
6, 4, 3, // second batch, left
6, 4, 3, // second batch, right
},
2e-2)));
}
TEST(QuantizedConvolutionOpModel, SimplePerTensor_Int8) {
QuantizedConvolutionOpModel m(
BuiltinOperator_CONV_2D,
{TensorType_INT8, {1, 2, 3, 2}, -63.5, 64, 0.5, -1},
{TensorType_INT8,
// [2 * 2 * 2 * 2] as [output_channel, y, x, input_channel]
{2, 2, 2, 2},
0,
0,
0,
0,
/*per_channel_quantization=*/true,
/*per_channel_quantization_scales=*/{1},
/*per_channel_quantization_offsets=*/{0},
/*channel_index=*/0},
{TensorType_INT8, {}, -63.5, 64, 0.5, -1}, Padding_VALID);
m.SetInt8Input({
// [1 * 2 * 3 * 2] as [batch, y, x, input_channel]
3, 2, // batch = 0, y = 0, x = 0
1, -1, // batch = 0, y = 0, x = 1
-2, -3, // batch = 0, y = 0, x = 2
4, 3, // batch = 0, y = 1, x = 0
2, -2, // batch = 0, y = 1, x = 1
-3, -4, // batch = 0, y = 1, x = 2
});
m.SetPerChannelQuantizedFilter(
// [2 * 2 * 2 * 2] as [output_channel, y, x,input_channel]
{
1, 2, // out channel = 0, y = 0, x = 0
3, 4, // out channel = 0, y = 0, x = 1
3, 4, // out channel = 0, y = 1, x = 0
5, 6, // out channel = 0, y = 1, x = 1
7, 8, // out channel = 1, y = 0, x = 0
5, 6, // out channel = 1, y = 0, x = 1
3, 4, // out channel = 1, y = 1, x = 0
1, 2, // out channel = 1, y = 1, x = 1
});
m.SetPerChannelQuantizedBias({3, -2});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear({31, 56, -57, -44}, 1e-5)));
}
TEST(QuantizedConvolutionOpModel, SimplePerChannel_Int8) {
QuantizedConvolutionOpModel m(
BuiltinOperator_CONV_2D,
{TensorType_INT8, {1, 2, 3, 2}, -63.5, 64, 0.5, -1},
{TensorType_INT8,
// [2 * 2 * 2 * 2] as [output_channel, y, x, input_channel]
{2, 2, 2, 2},
0,
0,
0,
0,
/*per_channel_quantization=*/true,
/*per_channel_quantization_scales=*/{1, 2},
/*per_channel_quantization_offsets=*/{0, 0},
/*channel_index=*/0},
{TensorType_INT8, {}, -63.5, 64, 0.5, -1}, Padding_VALID);
m.SetInt8Input({
// [1 * 2 * 3 * 2] as [batch, y, x, input_channel]
3, 2, // batch = 0, y = 0, x = 0
1, -1, // batch = 0, y = 0, x = 1
-2, -3, // batch = 0, y = 0, x = 2
4, 3, // batch = 0, y = 1, x = 0
2, -2, // batch = 0, y = 1, x = 1
-3, -4, // batch = 0, y = 1, x = 2
});
m.SetPerChannelQuantizedFilter(
// [2 * 2 * 2 * 2] as [output_channel, y, x, input_channel]
{
1, 2, // out channel = 0, y = 0, x = 0
3, 4, // out channel = 0, y = 0, x = 1
3, 4, // out channel = 0, y = 1, x = 0
5, 6, // out channel = 0, y = 1, x = 1
7, 8, // out channel = 1, y = 0, x = 0
5, 6, // out channel = 1, y = 0, x = 1
3, 4, // out channel = 1, y = 1, x = 0
1, 2, // out channel = 1, y = 1, x = 1
});
m.SetPerChannelQuantizedBias({3, -2});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear({31, 64, -57, -46}, 0.6f)));
}
// DEPTHWISE CONVOLUTION TESTS
TEST(QuantizedConvolutionOpModel, SimpleDilatedDepthwiseConvTestPaddingValid) {
const int depth = 1;
const int image_width = 9;
const int image_height = 9;
const int image_batch_count = 1;
const int filter_size = 3;
const int filter_count = 1;
const int dilation_factor = 3;
QuantizedConvolutionOpModel m(
BuiltinOperator_DEPTHWISE_CONV_2D,
{TensorType_UINT8,
{image_batch_count, image_height, image_width, depth},
0,
255},
{TensorType_UINT8,
{depth, filter_size, filter_size, filter_count},
0,
255},
{TensorType_UINT8, {}, 0, 255}, Padding_VALID, dilation_factor);
// The image matrix is:
// | 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 | 1 | 1 | 1 | 0 | 0 | 0 |
// | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 |
// | 0 | 0 | 0 | 1 | 1 | 1 | 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 |
// clang-format off
m.SetInput({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, 1, 1, 1, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 1, 1, 1, 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});
// clang-format on
// The filter matrix is:
// | 1 | 2 | 3 |
// | 4 | 5 | 6 |
// | 7 | 8 | 9 |
m.SetFilter({1, 2, 3, 4, 5, 6, 7, 8, 9});
// No bias for this test.
m.SetBias({0});
m.ApplyDelegateAndInvoke();
// Since the dilation rate is 3 this will reduce the size of the output from
// 10x10 to 3x3 of all 5s. Specifically:
// | 5 | 5 | 5 |
// | 5 | 5 | 5 |
// | 5 | 5 | 5 |
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray({5, 5, 5, 5, 5, 5, 5, 5, 5}));
}
TEST(QuantizedConvolutionOpModel, DepthwiseConv5x5) {
QuantizedConvolutionOpModel m(BuiltinOperator_DEPTHWISE_CONV_2D,
{TensorType_UINT8, {1, 6, 6, 2}, -63.5, 64},
{TensorType_UINT8, {1, 5, 5, 2}, -63.5, 64},
{TensorType_UINT8, {}, -127, 128},
Padding_VALID);
// clang-format off
m.SetInput({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, 1, 1, 1, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0});
// clang-format on
m.SetFilter({1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2,
3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4,
5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5});
m.SetBias({1, 2});
// Reference output.
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<uint8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output, 1e-5)));
}
// Depthwise Conv with multiplier > 1 but input depth==1 should resolve into a
// Conv op.
TEST(QuantizedConvolutionOpModel, DepthwiseConvWithMultiplier_InputDepth1) {
QuantizedConvolutionOpModel m(BuiltinOperator_DEPTHWISE_CONV_2D,
{TensorType_UINT8, {1, 6, 6, 1}, -63.5, 64},
{TensorType_UINT8, {1, 5, 5, 3}, -63.5, 64},
{TensorType_UINT8, {}, -127, 128},
Padding_VALID);
// clang-format off
m.SetInput({0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0});
m.SetFilter({1, 2, 3, 4, 5,
1, 2, 3, 4, 5,
1, 2, 3, 4, 5,
1, 2, 3, 4, 5,
1, 2, 3, 4, 5,
6, 7, 8, 9, 10,
6, 7, 8, 9, 10,
6, 7, 8, 9, 10,
6, 7, 8, 9, 10,
6, 7, 8, 9, 10,
1, 2, 3, 4, 5,
1, 2, 3, 4, 5,
1, 2, 3, 4, 5,
1, 2, 3, 4, 5,
1, 2, 3, 4, 5});
// clang-format on
m.SetBias({1, 2, 3});
// Reference output.
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<uint8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output, 1e-5)));
}
// Depthwise Conv with multiplier > 1 but input depth==1 should resolve into a
// Conv op.
TEST(QuantizedConvolutionOpModel,
DepthwiseConvWithMultiplier_InputDepth1_RELU) {
QuantizedConvolutionOpModel m(BuiltinOperator_DEPTHWISE_CONV_2D,
{TensorType_UINT8, {1, 6, 6, 1}, -63.5, 64},
{TensorType_UINT8, {1, 5, 5, 3}, -63.5, 64},
{TensorType_UINT8, {}, -127, 128},
Padding_VALID, /**dilation_factor**/ 1,
/**stride**/ 2, ActivationFunctionType_RELU6);
// clang-format off
m.SetInput({0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0});
m.SetFilter({1, 2, 3, 4, 5,
1, 2, 3, 4, 5,
1, 2, 3, 4, 5,
1, 2, 3, 4, 5,
1, 2, 3, 4, 5,
6, 7, 8, 9, 10,
6, 7, 8, 9, 10,
6, 7, 8, 9, 10,
6, 7, 8, 9, 10,
6, 7, 8, 9, 10,
1, 2, 3, 4, 5,
1, 2, 3, 4, 5,
1, 2, 3, 4, 5,
1, 2, 3, 4, 5,
1, 2, 3, 4, 5});
// clang-format on
m.SetBias({1, 2, 3});
// Reference output.
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<uint8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output, 1e-5)));
}
TEST(QuantizedConvolutionOpModel, DepthwiseConvSimplePerTensor_Int8) {
QuantizedConvolutionOpModel m(
BuiltinOperator_DEPTHWISE_CONV_2D,
{TensorType_INT8, {1, 2, 3, 1}, -63.5, 64, 0.5, -1},
{TensorType_INT8,
// [1 * 2 * 2 * 4] as [input_channel, y, x, output_channel]
{1, 2, 2, 4},
0,
0,
0,
0,
/*per_channel_quantization=*/true,
/*per_channel_quantization_scales=*/{1},
/*per_channel_quantization_offsets=*/{0},
/*channel_index=*/3},
{TensorType_INT8, {}, -63.5, 64, 0.5, -1}, Padding_VALID);
m.SetInt8Input({
// [1 * 2 * 3 * 1] as [batch, y, x, input_channel]
3, // batch = 0, y = 0, x = 0
1, // batch = 0, y = 0, x = 1
-2, // batch = 0, y = 0, x = 2
4, // batch = 0, y = 1, x = 0
2, // batch = 0, y = 1, x = 1
-3, // batch = 0, y = 1, x = 2
});
m.SetPerChannelQuantizedFilter({
// [1 * 2 * 2 * 4] as [input_channel, y, x, output_channel]
// depth multiplier = 2
1, 2, 3, 4, // y = 0, x = 0
3, 4, 5, 6, // y = 0, x = 1
7, 8, 5, 6, // y = 1, x = 0
3, 4, 1, 2, // y = 1, x = 1
});
m.SetPerChannelQuantizedBias({3, -2, 4, 6});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(
m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear({43, 48, 40, 52, 3, -4, 4, 4}, 0.6f)));
}
TEST(QuantizedConvolutionOpModel, DepthwiseConvSimplePerTensor_Int8_RELU1) {
QuantizedConvolutionOpModel m(
BuiltinOperator_DEPTHWISE_CONV_2D,
{TensorType_INT8, {1, 2, 3, 1}, -63.5, 64, 0.5, -1},
{TensorType_INT8,
// [1 * 2 * 2 * 4] as [input_channel, y, x, output_channel]
{1, 2, 2, 4},
0,
0,
0,
0,
/*per_channel_quantization=*/true,
/*per_channel_quantization_scales=*/{0.1, 2, 3, 0.4},
/*per_channel_quantization_offsets=*/{0, 0, 0, 0},
/*channel_index=*/3},
{TensorType_INT8, {}, -63.5, 64, 0.5, -1}, Padding_VALID,
/**dilation_factor**/ 1,
/**stride**/ 1, ActivationFunctionType_RELU_N1_TO_1);
m.SetInt8Input({
// [1 * 2 * 3 * 1] as [batch, y, x, input_channel]
3, // batch = 0, y = 0, x = 0
1, // batch = 0, y = 0, x = 1
-2, // batch = 0, y = 0, x = 2
4, // batch = 0, y = 1, x = 0
2, // batch = 0, y = 1, x = 1
-4, // batch = 0, y = 1, x = 2
});
m.SetPerChannelQuantizedFilter({
// [1 * 2 * 2 * 4] as [input_channel, y, x, output_channel]
// depth multiplier = 2
1, 2, 3, 4, // y = 0, x = 0
3, 4, 5, 6, // y = 0, x = 1
7, 8, 5, 6, // y = 1, x = 0
3, 4, 1, 2, // y = 1, x = 1
});
m.SetPerChannelQuantizedBias({3, -2, 4, 6});
// Reference output.
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<int8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output, 1e-2)));
}
TEST(QuantizedConvolutionOpModel, DepthwiseConvSimplePerAxis_Int8) {
QuantizedConvolutionOpModel m(
BuiltinOperator_DEPTHWISE_CONV_2D,
{TensorType_INT8, {1, 2, 3, 1}, -63.5, 64, 0.5, -1},
{TensorType_INT8,
// [1 * 2 * 2 * 4] as [input_channel, y, x, output_channel]
{1, 2, 2, 4},
0,
0,
0,
0,
/*per_channel_quantization=*/true,
/*per_channel_quantization_scales=*/{0.1, 2, 3, 0.4},
/*per_channel_quantization_offsets=*/{0, 0, 0, 0},
/*channel_index=*/3},
{TensorType_INT8, {}, -63.5, 64, 0.5, -1}, Padding_VALID);
m.SetInt8Input({
// [1 * 2 * 3 * 1] as [batch, y, x, input_channel]
3, // batch = 0, y = 0, x = 0
1, // batch = 0, y = 0, x = 1
-2, // batch = 0, y = 0, x = 2
4, // batch = 0, y = 1, x = 0
2, // batch = 0, y = 1, x = 1
-4, // batch = 0, y = 1, x = 2
});
m.SetPerChannelQuantizedFilter({
// [1 * 2 * 2 * 4] as [input_channel, y, x, output_channel]
// depth multiplier = 2
1, 2, 3, 4, // y = 0, x = 0
3, 4, 5, 6, // y = 0, x = 1
7, 8, 5, 6, // y = 1, x = 0
3, 4, 1, 2, // y = 1, x = 1
});
m.SetPerChannelQuantizedBias({3, -2, 4, 6});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(
m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear({43, 48, 42, 52, 0, -8, 6, 2}, 0.6f)));
}
TEST(QuantizedConvolutionOpModel, DepthwiseConvPerChannel_3x3Filter) {
QuantizedConvolutionOpModel m(
BuiltinOperator_DEPTHWISE_CONV_2D,
{TensorType_INT8, {1, 3, 3, 8}, -63.5, 64, 0.5, -1},
{TensorType_INT8,
// [1 * 3 * 3 * 8] as [input_channel, y, x, output_channel]
{1, 3, 3, 8},
0,
0,
0,
0,
/*per_channel_quantization=*/true,
/*per_channel_quantization_scales=*/
{0.1, 0.2, 0.3, 0.4, 0.4, 0.3, 0.2, 0.1},
/*per_channel_quantization_offsets=*/{0, 0, 0, 0, 0, 0, 0, 0},
/*channel_index=*/3},
{TensorType_INT8, {}, -63.5, 64, 0.5, -1}, Padding_VALID);
m.SetInt8Input({// array of 9 x 8 => [1, 3, 3, 8]
1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0});
m.SetPerChannelQuantizedFilter(
{// array of 9 x 8 => [1, 3, 3, 8]
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8});
m.SetPerChannelQuantizedBias({0, 0, 0, 0, 0, 0, 0, 0});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(
m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear({9, 18, 0, 0, 47, 54, 0, 0}, 0.6f)));
}
TEST(QuantizedConvolutionOpModel,
DepthwiseConvPerChannel_3x3FilterPaddingSame) {
QuantizedConvolutionOpModel m(
BuiltinOperator_DEPTHWISE_CONV_2D,
{TensorType_INT8, {1, 3, 3, 8}, -63.5, 64, 0.5, -1},
{TensorType_INT8,
// [1 * 3 * 3 * 8] as [input_channel, y, x, output_channel]
{1, 3, 3, 8},
0,
0,
0,
0,
/*per_channel_quantization=*/true,
/*per_channel_quantization_scales=*/
{0.1, 0.2, 0.3, 0.4, 0.4, 0.3, 0.2, 0.1},
/*per_channel_quantization_offsets=*/{0, 0, 0, 0, 0, 0, 0, 0},
/*channel_index=*/3},
{TensorType_INT8, {}, -63.5, 64, 0.5, -1}, Padding_SAME);
m.SetInt8Input({// array of 9 x 8 => [1, 3, 3, 8]
1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0});
m.SetPerChannelQuantizedFilter(
{// array of 9 x 8 => [1, 3, 3, 8]
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8});
m.SetPerChannelQuantizedBias({0, 0, 0, 0, 0, 0, 0, 0});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear(
{
// array of 9 x 8 => [1, 3, 3, 8]
4, 8, 0, 0, 21, 24, 0, 0, 6, 12, 0, 0, 31.5, 36, 0, 0,
4, 8, 0, 0, 21, 24, 0, 0, 6, 12, 0, 0, 31.5, 36, 0, 0,
9, 18, 0, 0, 47, 54, 0, 0, 6, 12, 0, 0, 31.5, 36, 0, 0,
4, 8, 0, 0, 21, 24, 0, 0, 6, 12, 0, 0, 31.5, 36, 0, 0,
4, 8, 0, 0, 21, 24, 0, 0,
},
0.6f)));
}
TEST(QuantizedConvolutionOpModel,
DepthwiseConvPerChannel_5x5Filt2x2Stride64Chan) {
std::vector<float> per_channel_quantization_scales = {
0.00053629, 0.00052256, 0.00051463, 0.00050993, 0.00050885, 0.00052403,
0.00053925, 0.00053854, 0.00053962, 0.00048332, 0.00053551, 0.00052817,
0.00052771, 0.00051854, 0.00053823, 0.000531, 0.000521, 0.00053908,
0.00053849, 0.0005063, 0.00052631, 0.00050862, 0.00050484, 0.00053353,
0.0005352, 0.00051084, 0.00052429, 0.00052653, 0.00051875, 0.0005391,
0.00050941, 0.00053934, 0.00049698, 0.00050956, 0.00053204, 0.00051116,
0.00052303, 0.00053624, 0.00053452, 0.00050418, 0.00048261, 0.00053418,
0.00053058, 0.0005359, 0.0005324, 0.00053648, 0.00053957, 0.00052388,
0.00053638, 0.00052164, 0.00052303, 0.00053624, 0.00053452, 0.00050418,
0.00048261, 0.00053418, 0.00053058, 0.0005359, 0.0005324, 0.00053648,
0.00053957, 0.00052388, 0.00053638, 0.00052164};
std::vector<int64_t> per_channel_quantization_offsets(64, 0);
QuantizedConvolutionOpModel m(
BuiltinOperator_DEPTHWISE_CONV_2D,
{TensorType_INT8, {1, 5, 5, 64}, 0, 0, 1.8942945003509521, -6},
{TensorType_INT8,
{1, 5, 5, 64},
0,
0,
0,
0,
/*per_channel_quantization=*/true,
/*per_channel_quantization_scales=*/per_channel_quantization_scales,
/*per_channel_quantization_offsets=*/per_channel_quantization_offsets,
/*channel_index=*/3},
{TensorType_INT8, {}, 0, 0, 0.2960677146911621, 7}, Padding_VALID,
/* dilation_factor = */ 1,
/* stride_length = */ 2);
std::vector<float> inputs;
std::vector<float> filter;
for (auto i = 0; i < 5 * 5 * 64; i++) {
inputs.push_back(UniformRandomFloat(-248, 234));
filter.push_back(UniformRandomFloat(-0.06, 0.06));
}
m.SetInt8Input(inputs);
m.SetPerChannelQuantizedFilter(filter);
std::vector<float> bias(64);
m.SetPerChannelQuantizedBias(bias);
m.Invoke();
auto interpreter_result = m.GetDequantizedOutput<int8_t>();
m.ApplyDelegateAndInvoke();
auto delegate_result = m.GetDequantizedOutput<int8_t>();
EXPECT_THAT(delegate_result,
ElementsAreArray(ArrayFloatNear(interpreter_result, 0.6f)));
}
} // namespace tflite
@@ -0,0 +1,87 @@
/* Copyright 2020 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_TESTS_HEXAGON_DELEGATE_OP_MODEL_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_TESTS_HEXAGON_DELEGATE_OP_MODEL_H_
#include <algorithm>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/core/model.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/kernels/internal/reference/reference_ops.h"
#include "tensorflow/lite/kernels/internal/tensor.h"
#include "tensorflow/lite/kernels/internal/types.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
class SingleOpModelWithHexagon : public SingleOpModel {
public:
SingleOpModelWithHexagon() : delegate_(nullptr, [](TfLiteDelegate*) {}) {
SetBypassDefaultDelegates();
}
void ApplyDelegateAndInvoke() {
static const char kDelegateName[] = "TfLiteHexagonDelegate";
// Make sure we set the environment.
setenv(
"ADSP_LIBRARY_PATH",
"/data/local/tmp/hexagon_delegate_test;/system/lib/rfsa/adsp;/system/"
"vendor/lib/rfsa/adsp;/dsp",
1 /*overwrite*/);
// For tests, we use one-op-models.
params_.min_nodes_per_partition = 1;
auto* delegate_ptr = TfLiteHexagonDelegateCreate(&params_);
ASSERT_TRUE(delegate_ptr != nullptr);
delegate_ = Interpreter::TfLiteDelegatePtr(
delegate_ptr, [](TfLiteDelegate* delegate) {
TfLiteHexagonDelegateDelete(delegate);
// Turn off the fast rpc and cleanup.
// Any communication with the DSP will fail unless new
// HexagonDelegateInit called.
TfLiteHexagonTearDown();
});
TfLiteHexagonInit();
// Make sure we have valid interpreter.
ASSERT_TRUE(interpreter_ != nullptr);
// Add delegate.
EXPECT_TRUE(interpreter_->ModifyGraphWithDelegate(delegate_.get()) !=
kTfLiteError);
// Make sure graph has one Op which is the delegate node.
ASSERT_EQ(1, interpreter_->execution_plan().size());
const int node = interpreter_->execution_plan()[0];
const auto* node_and_reg = interpreter_->node_and_registration(node);
ASSERT_TRUE(node_and_reg != nullptr);
ASSERT_TRUE(node_and_reg->second.custom_name != nullptr);
ASSERT_STREQ(kDelegateName, node_and_reg->second.custom_name);
Invoke();
}
protected:
using SingleOpModel::builder_;
private:
Interpreter::TfLiteDelegatePtr delegate_;
TfLiteHexagonDelegateOptions params_ = {0};
};
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_TESTS_HEXAGON_DELEGATE_OP_MODEL_H_
@@ -0,0 +1,127 @@
/* Copyright 2020 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 <initializer_list>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
class L2NormOpModel : public SingleOpModelWithHexagon {
public:
L2NormOpModel(const std::initializer_list<int> input_shape,
const TensorType tensor_type) {
TensorData data = TensorData{tensor_type};
data.min = -2.0;
data.max = 2.0;
data.scale = 2.0;
data.zero_point = 128;
input_ = AddInput(data);
data.min = -1.0;
data.max = 127.0 / 128.0;
output_ = AddOutput(data);
SetBuiltinOp(
BuiltinOperator_L2_NORMALIZATION, BuiltinOptions_L2NormOptions,
CreateL2NormOptions(builder_, ActivationFunctionType_NONE).Union());
BuildInterpreter({input_shape});
}
void SetInput(std::initializer_list<float> data) {
PopulateTensor(input_, data);
}
template <typename T>
std::vector<T> GetOutput() {
return ExtractVector<T>(output_);
}
template <typename T>
std::vector<float> GetDequantizedOutput() {
return Dequantize<T>(ExtractVector<T>(output_), GetScale(output_),
GetZeroPoint(output_));
}
int input() const { return input_; }
private:
int input_;
int output_;
};
TEST(L2NormOpTest, ZerosVectorUint8Test) {
L2NormOpModel m({1, 1, 1, 6}, TensorType_UINT8);
m.QuantizeAndPopulate<uint8_t>(m.input(), {0, 0, 0, 0, 0, 0});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear({0, 0, 0, 0, 0, 0}, 0.1)));
}
TEST(L2NormOpTest, ZerosVectorInt8Test) {
L2NormOpModel m({1, 1, 1, 6}, TensorType_INT8);
m.QuantizeAndPopulate<int8_t>(m.input(), {0, 0, 0, 0, 0, 0});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear({0, 0, 0, 0, 0, 0}, 0.1)));
}
TEST(L2NormOpTest, MultipleBatchUint8Test) {
L2NormOpModel m({3, 1, 1, 6}, TensorType_UINT8);
m.QuantizeAndPopulate<uint8_t>(m.input(),
{
-1.1, 0.6, 0.7, 1.2, -0.7, 0.1, // batch 1
-1.1, 0.6, 0.7, 1.2, -0.7, 0.1, // batch 2
-1.1, 0.6, 0.7, 1.2, -0.7, 0.1, // batch 3
});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(
{
-0.55, 0.3, 0.35, 0.6, -0.35, 0.05, // batch 1
-0.55, 0.3, 0.35, 0.6, -0.35, 0.05, // batch 2
-0.55, 0.3, 0.35, 0.6, -0.35, 0.05, // batch 3
},
0.1)));
}
TEST(L2NormOpTest, MultipleBatchInt8Test) {
L2NormOpModel m({3, 1, 1, 6}, TensorType_INT8);
m.QuantizeAndPopulate<int8_t>(m.input(),
{
-1.1, 0.6, 0.7, 1.2, -0.7, 0.1, // batch 1
-1.1, 0.6, 0.7, 1.2, -0.7, 0.1, // batch 2
-1.1, 0.6, 0.7, 1.2, -0.7, 0.1, // batch 3
});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear(
{
-0.55, 0.3, 0.35, 0.6, -0.35, 0.05, // batch 1
-0.55, 0.3, 0.35, 0.6, -0.35, 0.05, // batch 2
-0.55, 0.3, 0.35, 0.6, -0.35, 0.05, // batch 3
},
0.1)));
}
} // namespace tflite
@@ -0,0 +1,436 @@
/* Copyright 2020 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
class FullyConnectedOpModel : public SingleOpModelWithHexagon {
public:
FullyConnectedOpModel(
int units, int batches, const TensorData& input, const TensorData& output,
bool optional_bias, bool const_weights,
ActivationFunctionType activation_function = ActivationFunctionType_NONE)
: batches_(batches), units_(units) {
int total_input_size = 1;
for (size_t i = 0; i < input.shape.size(); ++i) {
total_input_size *= input.shape[i];
}
input_size_ = total_input_size / batches_;
input_ = AddInput(input);
weights_ =
AddInput({input.type, {units_, input_size_}, input.min, input.max});
if (optional_bias) {
bias_ = AddNullInput();
} else {
auto bias_scale = GetScale(input_) * GetScale(weights_);
TensorData bias{TensorType_INT32, {units_}, 0, 0, bias_scale};
bias_ = AddInput(bias);
}
output_ = AddOutput(output);
SetBuiltinOp(
BuiltinOperator_FULLY_CONNECTED, BuiltinOptions_FullyConnectedOptions,
CreateFullyConnectedOptions(builder_, activation_function,
FullyConnectedOptionsWeightsFormat_DEFAULT,
/*keep_num_dims=*/false)
.Union());
BuildInterpreter({GetShape(input_), GetShape(weights_)});
// Weights & bias tensors need to be constant.
// We don't use AddConstInput to allow setting filter values later.
if (const_weights) {
auto* weights_tensor = interpreter_->tensor(weights_);
weights_tensor->allocation_type = kTfLiteMmapRo;
}
if (!optional_bias) {
auto* bias_tensor = interpreter_->tensor(bias_);
bias_tensor->allocation_type = kTfLiteMmapRo;
}
}
void SetBias(const std::vector<float>& data) {
QuantizeAndPopulate<int>(bias_, data);
}
template <typename T>
void SetWeights(const std::vector<float>& data) {
QuantizeAndPopulate<T>(weights_, data);
}
template <typename T>
void SetInput(const std::vector<float>& data) {
QuantizeAndPopulate<T>(input_, data);
}
template <typename T>
std::vector<T> GetOutput() {
return ExtractVector<T>(output_);
}
template <typename T>
std::vector<float> GetDequantizedOutput() {
return Dequantize<T>(ExtractVector<T>(output_), GetScale(output_),
GetZeroPoint(output_));
}
protected:
int input_;
int weights_;
int bias_;
int output_;
int batches_;
int units_;
int input_size_;
};
class PerChannelFullyConnectedOpModel : public SingleOpModelWithHexagon {
public:
PerChannelFullyConnectedOpModel(int units, int batches,
const TensorData& input,
const TensorData& output)
: batches_(batches), units_(units) {
int total_input_size = 1;
for (size_t i = 0; i < input.shape.size(); ++i) {
total_input_size *= input.shape[i];
}
input_size_ = total_input_size / batches_;
input_ = AddInput(input);
weights_ = AddInput({input.type,
{units_, input_size_},
0,
0,
0,
0,
true,
{1.0f, 2.0f, 3.0f},
{0, 0, 0},
0});
bias_ = AddInput({TensorType_INT32,
{units_},
0,
0,
0,
0,
true,
{1.0f, 2.0f, 3.0f},
{0, 0, 0},
0});
output_ = AddOutput(output);
SetBuiltinOp(
BuiltinOperator_FULLY_CONNECTED, BuiltinOptions_FullyConnectedOptions,
CreateFullyConnectedOptions(builder_, ActivationFunctionType_NONE,
FullyConnectedOptionsWeightsFormat_DEFAULT,
/*keep_num_dims=*/false)
.Union());
BuildInterpreter({GetShape(input_), GetShape(weights_)});
auto* weights_tensor = interpreter_->tensor(weights_);
weights_tensor->allocation_type = kTfLiteMmapRo;
auto* bias_tensor = interpreter_->tensor(bias_);
bias_tensor->allocation_type = kTfLiteMmapRo;
}
void SetPerChannelQuantizedFilter(const std::vector<float>& data) {
PerChannelSymmetricQuantizeAndPopulate(weights_, data);
}
void SetPerChannelQuantizedBias(const std::vector<float>& data) {
PerChannelQuantizeBias(bias_, data);
}
void SetInt8Input(const std::vector<float>& data) {
QuantizeAndPopulate<int8_t>(input_, data);
}
bool ApplyDelegateAndExpectRejection() {
static const char kDelegateName[] = "TfLiteHexagonDelegate";
setenv(
"ADSP_LIBRARY_PATH",
"/data/local/tmp/hexagon_delegate_test;/system/lib/rfsa/adsp;/system/"
"vendor/lib/rfsa/adsp;/dsp",
/*overwrite=*/1);
TfLiteHexagonDelegateOptions params = {0};
params.min_nodes_per_partition = 1;
auto* delegate_ptr = TfLiteHexagonDelegateCreate(&params);
if (!delegate_ptr) return true;
Interpreter::TfLiteDelegatePtr delegate(
delegate_ptr, [](TfLiteDelegate* delegate) {
TfLiteHexagonDelegateDelete(delegate);
TfLiteHexagonTearDown();
});
TfLiteHexagonInit();
if (interpreter_->ModifyGraphWithDelegate(delegate.get()) == kTfLiteError) {
return true;
}
for (int node : interpreter_->execution_plan()) {
const auto* node_and_reg = interpreter_->node_and_registration(node);
if (node_and_reg && node_and_reg->second.custom_name &&
strcmp(kDelegateName, node_and_reg->second.custom_name) == 0) {
return false;
}
}
return true;
}
protected:
int input_;
int weights_;
int bias_;
int output_;
int batches_;
int units_;
int input_size_;
};
TEST(QuantizedFullyConnectedOpTest, PerChannelQuantizedRejected) {
PerChannelFullyConnectedOpModel m(
/*units=*/3, /*batches=*/2,
/*input=*/{TensorType_INT8, {2, 2}, -63.5, 64},
/*output=*/{TensorType_INT8, {}, -127, 128});
m.SetInt8Input({1, 2, 3, 4});
m.SetPerChannelQuantizedFilter({1, 2, 3, 4, 5, 6});
m.SetPerChannelQuantizedBias({1, 2, 3});
EXPECT_TRUE(m.ApplyDelegateAndExpectRejection());
}
class QuantizedFullyConnectedOpTest
: public ::testing::TestWithParam<ActivationFunctionType> {};
TEST_P(QuantizedFullyConnectedOpTest, TestQuantizedInt8) {
FullyConnectedOpModel m(/*units=*/3, /*batches*/ 2,
/*input=*/{TensorType_INT8, {2, 10}, -63.5, 64},
/*output=*/{TensorType_INT8, {}, -127, 128},
/*optional_bias*/ false, /*const_weight*/ false,
GetParam());
m.SetWeights<int8_t>({
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 0
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 1
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 2
});
m.SetBias({1, 2, 3});
m.SetInput<int8_t>({
1, 2, 3, 4, 5, 6, 7, 8, -9, -10, // b = 0
1, 2, 3, 4, 5, 6, 7, -8, 9, -10, // b = 1
});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<int8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output)));
}
TEST_P(QuantizedFullyConnectedOpTest, TestQuantizedUint8) {
FullyConnectedOpModel m(
/*units=*/3, /*batches*/ 2,
/*input=*/{TensorType_UINT8, {2, 10}, -63.5, 64},
/*output=*/{TensorType_UINT8, {}, -127, 128}, /*optional_bias*/ false,
/*const_weight*/ false, GetParam());
m.SetWeights<uint8_t>({
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 0
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 1
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 2
});
m.SetBias({1, 2, 3});
m.SetInput<uint8_t>({
1, 2, 3, 4, 5, 6, 7, 8, -9, -10, // b = 0
1, 2, 3, 4, 5, 6, 7, -8, 9, -10, // b = 1
});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<uint8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output)));
}
TEST_P(QuantizedFullyConnectedOpTest, TestQuantizedUint8_NoBias) {
FullyConnectedOpModel m(
/*units=*/3, /*batches*/ 2,
/*input=*/{TensorType_UINT8, {2, 10}, -63.5, 64},
/*output=*/{TensorType_UINT8, {}, -127, 128}, /*optional_bias*/ true,
/*const_weight*/ false, GetParam());
m.SetWeights<uint8_t>({
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 0
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 1
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 2
});
m.SetInput<uint8_t>({
1, 2, 3, 4, 5, 6, 7, 8, -9, -10, // b = 0
1, 2, 3, 4, 5, 6, 7, -8, 9, -10, // b = 1
});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<uint8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output)));
}
TEST_P(QuantizedFullyConnectedOpTest, TestQuantizedInt8_NoBias) {
FullyConnectedOpModel m(/*units=*/3, /*batches*/ 2,
/*input=*/{TensorType_INT8, {2, 10}, -63.5, 64},
/*output=*/{TensorType_INT8, {}, -127, 128},
/*optional_bias*/ true, /*const_weight*/ false,
GetParam());
m.SetWeights<int8_t>({
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 0
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 1
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 2
});
m.SetInput<int8_t>({
1, 2, 3, 4, 5, 6, 7, 8, -9, -10, // b = 0
1, 2, 3, 4, 5, 6, 7, -8, 9, -10, // b = 1
});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<int8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output)));
}
TEST_P(QuantizedFullyConnectedOpTest, TestQuantizedInt8_NonConstWeights) {
FullyConnectedOpModel m(/*units=*/3, /*batches*/ 2,
/*input=*/{TensorType_INT8, {2, 10}, -63.5, 64},
/*output=*/{TensorType_INT8, {}, -127, 128},
/*optional_bias=*/false, /*const_weights=*/false,
GetParam());
m.SetWeights<int8_t>({
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 0
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 1
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 2
});
m.SetBias({1, 2, 3});
m.SetInput<int8_t>({
1, 2, 3, 4, 5, 6, 7, 8, -9, -10, // b = 0
1, 2, 3, 4, 5, 6, 7, -8, 9, -10, // b = 1
});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<int8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output)));
}
TEST_P(QuantizedFullyConnectedOpTest, TestQuantizedUint8_NonConstWeights) {
FullyConnectedOpModel m(
/*units=*/3, /*batches*/ 2,
/*input=*/{TensorType_UINT8, {2, 10}, -63.5, 64},
/*output=*/{TensorType_UINT8, {}, -127, 128}, /*optional_bias=*/false,
/*const_weights=*/false, GetParam());
m.SetWeights<uint8_t>({
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 0
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 1
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 2
});
m.SetBias({1, 2, 3});
m.SetInput<uint8_t>({
1, 2, 3, 4, 5, 6, 7, 8, -9, -10, // b = 0
1, 2, 3, 4, 5, 6, 7, -8, 9, -10, // b = 1
});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<uint8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output)));
}
INSTANTIATE_TEST_SUITE_P(QuantizedFullyConnectedOpTest,
QuantizedFullyConnectedOpTest,
testing::Values(ActivationFunctionType_NONE,
ActivationFunctionType_RELU));
TEST(QuantizedFullyConnected, TestQuantizedUint8_NonConstWeights_Relu6) {
// We rely on output min/max set to values that guarantees the activation
// function results.
// So setting output min/max (0, 6) should be equivalent to relu6
FullyConnectedOpModel m(
/*units=*/3, /*batches*/ 2,
/*input=*/{TensorType_UINT8, {2, 10}, -63.5, 64},
/*output=*/{TensorType_UINT8, {}, 0, 6}, /*optional_bias=*/false,
/*const_weights=*/false, ActivationFunctionType_RELU6);
m.SetWeights<uint8_t>({
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 0
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 1
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // u = 2
});
m.SetBias({1, 2, 3});
m.SetInput<uint8_t>({
1, 2, 3, 4, 5, 6, 7, 8, -9, -10, // b = 0
1, 2, 3, 4, 5, 6, 7, -8, 9, -10, // b = 1
});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<uint8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output)));
}
} // namespace tflite
@@ -0,0 +1,178 @@
/* Copyright 2020 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 <initializer_list>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
template <typename data_type>
class MinMaxOpModel : public SingleOpModelWithHexagon {
public:
MinMaxOpModel(tflite::BuiltinOperator op, const TensorData& input1,
const TensorData& input2, const TensorData& output) {
input1_ = AddInput(input1);
input2_ = AddInput(input2);
output_ = AddOutput(output);
SetBuiltinOp(op, BuiltinOptions_MaximumMinimumOptions,
CreateMaximumMinimumOptions(builder_).Union());
BuildInterpreter({GetShape(input1_), GetShape(input2_)});
}
MinMaxOpModel(tflite::BuiltinOperator op, const TensorData& input1,
std::initializer_list<data_type> input1_values,
const TensorData& input2,
std::initializer_list<data_type> input2_values,
const TensorData& output, bool input1_const) {
input1_ = AddInput(input1);
input2_ = AddInput(input2);
output_ = AddOutput(output);
SetBuiltinOp(op, BuiltinOptions_MaximumMinimumOptions,
CreateMaximumMinimumOptions(builder_).Union());
BuildInterpreter({GetShape(input1_), GetShape(input2_)});
// A workaround to mark the tensors as constant.
if (input1_const) {
auto* input1_tensor = interpreter_->tensor(input1_);
input1_tensor->allocation_type = kTfLiteMmapRo;
} else {
auto* input2_tensor = interpreter_->tensor(input2_);
input2_tensor->allocation_type = kTfLiteMmapRo;
}
}
void SetInput1(std::vector<data_type> data) { PopulateTensor(input1_, data); }
void SetInput2(std::vector<data_type> data) { PopulateTensor(input2_, data); }
std::vector<data_type> GetOutput() {
return ExtractVector<data_type>(output_);
}
template <typename T>
std::vector<float> GetDequantizedOutput() {
return Dequantize<T>(ExtractVector<T>(output_), GetScale(output_),
GetZeroPoint(output_));
}
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
protected:
int input1_;
int input2_;
int output_;
};
template <typename data_type>
void TestModel(tflite::BuiltinOperator op, const TensorData& input1,
const TensorData& input2, const TensorData& output,
std::initializer_list<data_type> input1_values,
std::initializer_list<data_type> input2_values) {
std::unique_ptr<MinMaxOpModel<data_type>> m;
m = std::make_unique<MinMaxOpModel<data_type>>(op, input1, input2, output);
m->SetInput1(input1_values);
m->SetInput2(input2_values);
ASSERT_EQ(m->Invoke(), kTfLiteOk);
const auto reference_output = m->GetOutput();
const auto reference_output_shape = m->GetOutputShape();
m->ApplyDelegateAndInvoke();
EXPECT_THAT(m->GetOutputShape(), ElementsAreArray(reference_output_shape));
EXPECT_THAT(m->GetOutput(), ElementsAreArray(reference_output));
}
template <typename data_type>
void TestModelConstInput(tflite::BuiltinOperator op, const TensorData& input1,
const TensorData& input2, const TensorData& output,
std::initializer_list<data_type> input1_values,
std::initializer_list<data_type> input2_values,
bool input1_const) {
std::unique_ptr<MinMaxOpModel<data_type>> m;
m = std::make_unique<MinMaxOpModel<data_type>>(
op, input1, input1_values, input2, input2_values, output, input1_const);
m->SetInput1(input1_values);
m->SetInput2(input2_values);
ASSERT_EQ(m->Invoke(), kTfLiteOk);
const auto reference_output = m->GetOutput();
const auto reference_output_shape = m->GetOutputShape();
m->ApplyDelegateAndInvoke();
EXPECT_THAT(m->GetOutputShape(), ElementsAreArray(reference_output_shape));
EXPECT_THAT(m->GetOutput(), ElementsAreArray(reference_output));
}
TEST(MinMaxOpTest, Maximum_Uint8Test) {
std::initializer_list<uint8_t> data1 = {1, 0, 2, 11, 2, 23};
std::initializer_list<uint8_t> data2 = {0, 0, 1, 12, 255, 1};
TestModel<uint8_t>(BuiltinOperator_MAXIMUM,
{TensorType_UINT8, {1, 3, 1, 2}, -1, 255},
{TensorType_UINT8, {1, 3, 1, 2}, -1, 255},
{TensorType_UINT8, {1, 3, 1, 2}, -1, 255}, data1, data2);
}
TEST(MinMaxOpTest, Maximum_Uint8Test_Const) {
std::initializer_list<uint8_t> data1 = {1, 0, 2, 11, 2, 23};
std::initializer_list<uint8_t> data2 = {0, 0, 1, 12, 255, 1};
TestModelConstInput<uint8_t>(
BuiltinOperator_MAXIMUM, {TensorType_UINT8, {1, 3, 1, 2}, -1, 255},
{TensorType_UINT8, {1, 3, 1, 2}, -1, 255},
{TensorType_UINT8, {1, 3, 1, 2}, -1, 255}, data1, data2, false);
}
TEST(MinMaxOpTest, Minimum_Uint8Test) {
std::initializer_list<uint8_t> data1 = {1, 0, 2, 11, 2, 23};
std::initializer_list<uint8_t> data2 = {0, 0, 1, 12, 255, 1};
TestModel<uint8_t>(BuiltinOperator_MINIMUM,
{TensorType_UINT8, {1, 3, 1, 2}, -1, 255},
{TensorType_UINT8, {1, 3, 1, 2}, -1, 255},
{TensorType_UINT8, {1, 3, 1, 2}, -1, 255}, data1, data2);
}
TEST(MinMaxOpTest, Minimum_Uint8Test_Const) {
std::initializer_list<uint8_t> data1 = {1, 0, 2, 11, 2, 23};
std::initializer_list<uint8_t> data2 = {0, 0, 1, 12, 20, 1};
TestModelConstInput<uint8_t>(
BuiltinOperator_MINIMUM, {TensorType_UINT8, {1, 3, 1, 2}, -1, 25},
{TensorType_UINT8, {1, 3, 1, 2}, -1, 25},
{TensorType_UINT8, {1, 3, 1, 2}, -1, 25}, data1, data2, false);
}
TEST(MinMaxOpTest, Maximum_Int8Test) {
std::initializer_list<int8_t> data1 = {1, 0, 2, 11, 2, 23};
std::initializer_list<int8_t> data2 = {0, 0, 1, 12, 123, 1};
TestModel<int8_t>(BuiltinOperator_MAXIMUM,
{TensorType_INT8, {1, 3, 1, 2}, -1, 125},
{TensorType_INT8, {1, 3, 1, 2}, -1, 125},
{TensorType_INT8, {1, 3, 1, 2}, -1, 125}, data1, data2);
}
TEST(MinMaxOpTest, Minimum_Int8Test) {
std::initializer_list<int8_t> data1 = {1, 0, 2, 11, 2, 23};
std::initializer_list<int8_t> data2 = {0, 0, 1, 12, 12, 1};
TestModel<int8_t>(BuiltinOperator_MINIMUM,
{TensorType_INT8, {1, 3, 1, 2}, -1, 25},
{TensorType_INT8, {1, 3, 1, 2}, -1, 25},
{TensorType_INT8, {1, 3, 1, 2}, -1, 25}, data1, data2);
}
} // namespace tflite
@@ -0,0 +1,132 @@
/* Copyright 2020 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 <initializer_list>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
template <typename T>
class MirrorPadOpModel : public SingleOpModelWithHexagon {
public:
MirrorPadOpModel(const TensorData& input,
std::initializer_list<int> paddings_shape,
std::initializer_list<int> paddings,
const TensorData& output, const tflite::MirrorPadMode mode) {
input_id_ = AddInput(input);
padding_matrix_id_ =
AddConstInput(TensorType_INT32, paddings, paddings_shape);
output_id_ = AddOutput(output);
SetBuiltinOp(BuiltinOperator_MIRROR_PAD, BuiltinOptions_MirrorPadOptions,
CreateMirrorPadOptions(builder_, mode).Union());
BuildInterpreter({GetShape(input_id_), GetShape(padding_matrix_id_)});
}
int input_tensor_id() { return input_id_; }
std::vector<T> GetOutput() { return ExtractVector<T>(output_id_); }
protected:
int input_id_;
int padding_matrix_id_;
int output_id_;
};
TEST(MirrorPadTest, EmptyPad_UInt8) {
MirrorPadOpModel<uint8_t> model(
{TensorType_UINT8, {2, 3}, -1.0, 1.0}, {2, 2}, {0, 0, 0, 0},
{TensorType_UINT8, {}, -1.0, 1.0}, tflite::MirrorPadMode_REFLECT);
model.PopulateTensor<uint8_t>(model.input_tensor_id(), {1, 2, 3, 4, 5, 6});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutput(), ElementsAreArray({1, 2, 3, 4, 5, 6}));
}
TEST(MirrorPadTest, PadBothSides_Symmetric_Int8) {
MirrorPadOpModel<int8_t> model({TensorType_INT8, {2, 3}, -1.0, 1.0}, {2, 2},
{1, 1, 1, 1}, {TensorType_INT8, {}, -1.0, 1.0},
tflite::MirrorPadMode_SYMMETRIC);
model.PopulateTensor<int8_t>(model.input_tensor_id(), {1, 2, 3, 4, 5, 6});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutput(),
ElementsAreArray({1, 1, 2, 3, 3, 1, 1, 2, 3, 3,
4, 4, 5, 6, 6, 4, 4, 5, 6, 6}));
}
TEST(MirrorPadTest, PadBothSides_Reflect_UInt8) {
MirrorPadOpModel<uint8_t> model(
{TensorType_UINT8, {2, 3}, -1.0, 1.0}, {2, 2}, {1, 1, 1, 1},
{TensorType_UINT8, {}, -1.0, 1.0}, tflite::MirrorPadMode_REFLECT);
model.PopulateTensor<uint8_t>(model.input_tensor_id(), {1, 2, 3, 4, 5, 6});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutput(),
ElementsAreArray({5, 4, 5, 6, 5, 2, 1, 2, 3, 2,
5, 4, 5, 6, 5, 2, 1, 2, 3, 2}));
}
TEST(MirrorPadTest, PadOneSide_left_Reflect_Int8) {
MirrorPadOpModel<int8_t> model({TensorType_INT8, {2, 3}, -1.0, 1.0}, {2, 2},
{1, 0, 1, 0}, {TensorType_INT8, {}, -1.0, 1.0},
tflite::MirrorPadMode_REFLECT);
model.PopulateTensor<int8_t>(model.input_tensor_id(), {1, 2, 3, 4, 5, 6});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutput(),
ElementsAreArray({5, 4, 5, 6, 2, 1, 2, 3, 5, 4, 5, 6}));
}
TEST(MirrorPadTest, PadOneSide_right_Symmetric_UInt8) {
MirrorPadOpModel<uint8_t> model(
{TensorType_UINT8, {2, 3}, -1.0, 1.0}, {2, 2}, {0, 1, 0, 1},
{TensorType_UINT8, {}, -1.0, 1.0}, tflite::MirrorPadMode_SYMMETRIC);
model.PopulateTensor<uint8_t>(model.input_tensor_id(), {1, 2, 3, 4, 5, 6});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutput(),
ElementsAreArray({1, 2, 3, 3, 4, 5, 6, 6, 4, 5, 6, 6}));
}
TEST(MirrorPadTest, Pad_1D_Reflect_Int8) {
MirrorPadOpModel<int8_t> model({TensorType_INT8, {3}, -1.0, 1.0}, {1, 2},
{0, 2}, {TensorType_INT8, {}, -1.0, 1.0},
tflite::MirrorPadMode_REFLECT);
model.PopulateTensor<int8_t>(model.input_tensor_id(), {1, 2, 3});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutput(), ElementsAreArray({1, 2, 3, 2, 1}));
}
TEST(MirrorPadTest, Pad_1D_Symmetric_UInt8) {
MirrorPadOpModel<uint8_t> model({TensorType_UINT8, {3}, -1.0, 1.0}, {1, 2},
{0, 2}, {TensorType_UINT8, {}, -1.0, 1.0},
tflite::MirrorPadMode_SYMMETRIC);
model.PopulateTensor<uint8_t>(model.input_tensor_id(), {1, 2, 3});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutput(), ElementsAreArray({1, 2, 3, 3, 2}));
}
TEST(MirrorPadTest, PadBothSides_Reflect_Whole_UInt8) {
MirrorPadOpModel<uint8_t> model(
{TensorType_UINT8, {2, 3}, -1.0, 1.0}, {2, 2}, {1, 1, 2, 2},
{TensorType_UINT8, {}, -1.0, 1.0}, tflite::MirrorPadMode_REFLECT);
model.PopulateTensor<uint8_t>(model.input_tensor_id(), {1, 2, 3, 4, 5, 6});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutput(),
ElementsAreArray({6, 5, 4, 5, 6, 5, 4, 3, 2, 1, 2, 3, 2, 1,
6, 5, 4, 5, 6, 5, 4, 3, 2, 1, 2, 3, 2, 1}));
}
} // namespace tflite
@@ -0,0 +1,124 @@
/* Copyright 2020 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
class MulOpModel : public SingleOpModelWithHexagon {
public:
explicit MulOpModel(const TensorData& input1, const TensorData& input2,
const TensorData& output,
ActivationFunctionType activation_func) {
input1_ = AddInput(input1);
input2_ = AddInput(input2);
output_ = AddOutput(output);
SetBuiltinOp(BuiltinOperator_MUL, BuiltinOptions_MulOptions,
CreateMulOptions(builder_, activation_func).Union());
BuildInterpreter({GetShape(input1_), GetShape(input2_)});
}
template <typename T>
void SetInput1(const std::vector<float>& data) {
QuantizeAndPopulate<T>(input1_, data);
}
template <typename T>
void SetInput2(const std::vector<float>& data) {
QuantizeAndPopulate<T>(input2_, data);
}
template <typename T>
std::vector<float> GetDequantizedOutput() {
return Dequantize<T>(ExtractVector<T>(output_), GetScale(output_),
GetZeroPoint(output_));
}
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
protected:
int input1_;
int input2_;
int output_;
};
template <TensorType tensor_type, typename integer_dtype>
void TestMulOutputImpl(ActivationFunctionType activation_func) {
MulOpModel model(
/*input1=*/{tensor_type, {2, 3}, -0.44f, 8.0f},
/*input2=*/{tensor_type, {1, 3}, 0, 0.999f},
/*output=*/{tensor_type, {2, 3}, -1.0f, 1.0f}, activation_func);
model.SetInput1<integer_dtype>({1, 2, 3, 4, 5, 6});
model.SetInput2<integer_dtype>({0.1f, 0.2f, 0.3f});
// Reference output.
ASSERT_EQ(model.Invoke(), kTfLiteOk);
auto reference_out = model.GetDequantizedOutput<integer_dtype>();
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 3}));
EXPECT_THAT(model.GetDequantizedOutput<integer_dtype>(),
ElementsAreArray(ArrayFloatNear(reference_out, 0.03)));
}
template <TensorType tensor_type, typename integer_dtype>
void TestLargeInputRangeImpl(ActivationFunctionType activation_func) {
MulOpModel model(
/*input1=*/{tensor_type, {1, 2, 2, 3}, -0.44f, 55.7f},
/*input2=*/{tensor_type, {1, 1, 2, 3}, 0, 0.999f},
/*output=*/{tensor_type, {1, 2, 2, 3}, -1.0f, 1.0f}, activation_func);
model.SetInput1<integer_dtype>({1, 2, 3, 4, 5, 6, 20, 30, 40, 50, 52, 55});
model.SetInput2<integer_dtype>({0.8f, 0.9f, 0.99f, 0.8f, 0.9f, 0.99f});
// Reference output.
ASSERT_EQ(model.Invoke(), kTfLiteOk);
auto reference_out = model.GetDequantizedOutput<integer_dtype>();
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 2, 3}));
EXPECT_THAT(model.GetDequantizedOutput<integer_dtype>(),
ElementsAreArray(ArrayFloatNear(reference_out, 0.03)));
}
class MulOpModelTest : public testing::TestWithParam<ActivationFunctionType> {};
TEST_P(MulOpModelTest, MulOutput_UInt8) {
TestMulOutputImpl<TensorType_UINT8, uint8_t>(GetParam());
}
TEST_P(MulOpModelTest, MulOutput_Int8) {
TestMulOutputImpl<TensorType_INT8, int8_t>(GetParam());
}
TEST_P(MulOpModelTest, LargeInputRange_UInt8) {
TestLargeInputRangeImpl<TensorType_UINT8, uint8_t>(GetParam());
}
TEST_P(MulOpModelTest, LargeInputRange_Int8) {
TestLargeInputRangeImpl<TensorType_INT8, int8_t>(GetParam());
}
INSTANTIATE_TEST_SUITE_P(MulOpModelTest, MulOpModelTest,
testing::Values(ActivationFunctionType_NONE,
ActivationFunctionType_RELU,
ActivationFunctionType_RELU_N1_TO_1,
ActivationFunctionType_RELU6));
} // namespace tflite
@@ -0,0 +1,74 @@
/* Copyright 2020 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 <initializer_list>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
class NegOpModel : public SingleOpModelWithHexagon {
public:
NegOpModel(const TensorData& input, const TensorData& output) {
input_ = AddInput(input);
output_ = AddOutput(output);
SetBuiltinOp(BuiltinOperator_NEG, BuiltinOptions_NegOptions,
CreateNegOptions(builder_).Union());
BuildInterpreter({GetShape(input_)});
}
template <typename integer_type>
void SetQuantizedInput(std::initializer_list<float> data) {
QuantizeAndPopulate<integer_type>(input_, data);
}
template <typename integer_type>
std::vector<float> GetDequantizedOutput() {
return Dequantize<integer_type>(ExtractVector<integer_type>(output_),
GetScale(output_), GetZeroPoint(output_));
}
protected:
int input_;
int output_;
};
TEST(NegOpModel, NegTest_UInt8) {
NegOpModel m({TensorType_UINT8, {2, 3}, -4, 4},
{TensorType_UINT8, {2, 3}, -4, 4});
m.SetQuantizedInput<uint8_t>({-2.0f, -1.0f, 0.f, 1.0f, 2.0f, 3.0f});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(
m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear({2.0f, 1.0f, 0.f, -1.0f, -2.0f, -3.0f},
/*max_abs_err=*/0.05)));
}
TEST(NegOpModel, NegTest_Int8) {
NegOpModel m({TensorType_INT8, {2, 3}, -4, 4},
{TensorType_INT8, {2, 3}, -4, 4});
m.SetQuantizedInput<int8_t>({-2.0f, -1.0f, 0.f, 1.0f, 2.0f, 3.0f});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(
m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear({2.0f, 1.0f, 0.f, -1.0f, -2.0f, -3.0f},
/*max_abs_err=*/0.05)));
}
} // namespace tflite
@@ -0,0 +1,131 @@
/* Copyright 2020 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 <initializer_list>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
class PackOpModel : public SingleOpModelWithHexagon {
public:
PackOpModel(const TensorData& input_template, int axis, int values_count) {
std::vector<std::vector<int>> all_input_shapes;
for (int i = 0; i < values_count; ++i) {
all_input_shapes.push_back(input_template.shape);
AddInput(input_template);
}
output_ = AddOutput({input_template.type, /*shape=*/{}, input_template.min,
input_template.max});
SetBuiltinOp(BuiltinOperator_PACK, BuiltinOptions_PackOptions,
CreatePackOptions(builder_, values_count, axis).Union());
BuildInterpreter(all_input_shapes);
}
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
template <typename integer_type>
void SetInput(int index, std::initializer_list<float> data) {
QuantizeAndPopulate<integer_type>(index, data);
}
template <typename integer_type>
std::vector<float> GetDequantizedOutput() {
return Dequantize<integer_type>(ExtractVector<integer_type>(output_),
GetScale(output_), GetZeroPoint(output_));
}
private:
int output_;
};
template <typename InputType>
struct PackOpTest : public ::testing::Test {
using TypeToTest = InputType;
TensorType TENSOR_TYPE =
(std::is_same<InputType, int16_t>::value
? TensorType_INT16
: (std::is_same<InputType, uint8_t>::value ? TensorType_UINT8
: TensorType_INT8));
};
using TestTypes = testing::Types<int8_t, uint8_t>;
TYPED_TEST_CASE(PackOpTest, TestTypes);
TYPED_TEST(PackOpTest, ThreeInputs) {
PackOpModel model({TestFixture::TENSOR_TYPE, {2}, -10, 10}, 0, 3);
model.SetInput<typename TestFixture::TypeToTest>(0, {1, 4});
model.SetInput<typename TestFixture::TypeToTest>(1, {2, 5});
model.SetInput<typename TestFixture::TypeToTest>(2, {3, 6});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
auto ref_output_shape = model.GetOutputShape();
auto ref_output =
model.GetDequantizedOutput<typename TestFixture::TypeToTest>();
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray(ref_output_shape));
EXPECT_THAT(model.GetDequantizedOutput<typename TestFixture::TypeToTest>(),
ElementsAreArray(ArrayFloatNear(ref_output)));
}
TYPED_TEST(PackOpTest, ThreeInputsDifferentAxis) {
PackOpModel model({TestFixture::TENSOR_TYPE, {2}, -10, 10}, 1, 3);
model.SetInput<typename TestFixture::TypeToTest>(0, {1, 4});
model.SetInput<typename TestFixture::TypeToTest>(1, {2, 5});
model.SetInput<typename TestFixture::TypeToTest>(2, {3, 6});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
auto ref_output_shape = model.GetOutputShape();
auto ref_output =
model.GetDequantizedOutput<typename TestFixture::TypeToTest>();
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray(ref_output_shape));
EXPECT_THAT(model.GetDequantizedOutput<typename TestFixture::TypeToTest>(),
ElementsAreArray(ArrayFloatNear(ref_output)));
}
TYPED_TEST(PackOpTest, ThreeInputsNegativeAxis) {
PackOpModel model({TestFixture::TENSOR_TYPE, {2}, -10, 10}, -1, 3);
model.SetInput<typename TestFixture::TypeToTest>(0, {1, 4});
model.SetInput<typename TestFixture::TypeToTest>(1, {2, 5});
model.SetInput<typename TestFixture::TypeToTest>(2, {3, 6});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
auto ref_output_shape = model.GetOutputShape();
auto ref_output =
model.GetDequantizedOutput<typename TestFixture::TypeToTest>();
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray(ref_output_shape));
EXPECT_THAT(model.GetDequantizedOutput<typename TestFixture::TypeToTest>(),
ElementsAreArray(ArrayFloatNear(ref_output)));
}
TYPED_TEST(PackOpTest, MultilDimensions) {
PackOpModel model({TestFixture::TENSOR_TYPE, {2, 3}, -10, 20}, 1, 2);
model.SetInput<typename TestFixture::TypeToTest>(0, {1, 2, 3, 4, 5, 6});
model.SetInput<typename TestFixture::TypeToTest>(1, {7, 8, 9, 10, 11, 12});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
auto ref_output_shape = model.GetOutputShape();
auto ref_output =
model.GetDequantizedOutput<typename TestFixture::TypeToTest>();
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray(ref_output_shape));
EXPECT_THAT(model.GetDequantizedOutput<typename TestFixture::TypeToTest>(),
ElementsAreArray(ArrayFloatNear(ref_output)));
}
} // namespace tflite
@@ -0,0 +1,111 @@
/* Copyright 2020 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 <initializer_list>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
class PadOpConstModel : public SingleOpModelWithHexagon {
public:
PadOpConstModel(const TensorData& input,
std::initializer_list<int> paddings_shape,
std::initializer_list<int> paddings,
const TensorData& output) {
this->input_ = AddInput(input);
paddings_ = AddConstInput(TensorType_INT32, paddings, paddings_shape);
output_ = AddOutput(output);
SetBuiltinOp(BuiltinOperator_PAD, BuiltinOptions_PadOptions,
CreatePadOptions(builder_).Union());
BuildInterpreter({input.shape});
}
template <typename integer_type>
void SetQuantizedInput(std::initializer_list<float> data) {
QuantizeAndPopulate<integer_type>(input_, data);
}
void SetPaddings(std::initializer_list<int> paddings) {
PopulateTensor<int>(paddings_, paddings);
}
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
template <typename integer_type>
std::vector<float> GetDequantizedOutput() {
return Dequantize<integer_type>(ExtractVector<integer_type>(output_),
GetScale(output_), GetZeroPoint(output_));
}
protected:
int input_;
int output_;
int paddings_;
};
template <typename integer_type, TensorType tensor_dtype>
void SimpleConstTestImpl() {
const float quantization_tolerance = 2 / 255.0;
// Padding is represented as four 2-D lists representing above padding and
// below padding (i.e. {{0, 0}, {1, 1}, {1, 1}, {0, 0}}).
PadOpConstModel m({tensor_dtype, {1, 2, 2, 1}, -1.0, 1.0}, {4, 2},
{0, 0, 1, 1, 1, 1, 0, 0}, {tensor_dtype, {}, -1.0, 1.0});
m.SetQuantizedInput<integer_type>({-0.8, 0.2, 0.9, 0.7});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<integer_type>(),
ElementsAreArray(ArrayFloatNear(
{0, 0, 0, 0, 0, -0.8, 0.2, 0, 0, 0.9, 0.7, 0, 0, 0, 0, 0},
quantization_tolerance)));
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
}
template <typename integer_type, TensorType tensor_dtype>
void AdvancedConstTestImpl() {
const float quantization_tolerance = 2 / 255.0;
PadOpConstModel m({tensor_dtype, {1, 2, 3, 1}, -1.0, 1.0}, {4, 2},
{0, 0, 0, 2, 1, 3, 0, 0}, {tensor_dtype, {}, -1.0, 1.0});
m.SetQuantizedInput<integer_type>({-0.8, 0.2, 0.9, 0.7, 0.1, -0.3});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<integer_type>(),
ElementsAreArray(ArrayFloatNear(
{0, -0.8, 0.2, 0.9, 0, 0, 0, 0, 0.7, 0.1, -0.3, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
quantization_tolerance)));
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 7, 1}));
}
TEST(PadOpConstModel, SimpleConstTest_UInt8) {
SimpleConstTestImpl<uint8_t, TensorType_UINT8>();
}
TEST(PadOpConstModel, SimpleConstTest_Int8) {
SimpleConstTestImpl<int8_t, TensorType_INT8>();
}
TEST(PadOpConstModel, AdvancedConstTest_UInt8) {
AdvancedConstTestImpl<uint8_t, TensorType_UINT8>();
}
TEST(PadOpConstModel, AdvancedConstTest_Int8) {
AdvancedConstTestImpl<int8_t, TensorType_INT8>();
}
} // namespace tflite
@@ -0,0 +1,190 @@
/* Copyright 2020 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 <random>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
class PoolingOpModel : public SingleOpModelWithHexagon {
public:
explicit PoolingOpModel(BuiltinOperator type, const TensorData& input,
int filter_width, int filter_height,
const TensorData& output,
tflite::Padding padding = Padding_VALID) {
input_ = AddInput(input);
output_ = AddOutput(output);
SetBuiltinOp(type, BuiltinOptions_Pool2DOptions,
CreatePool2DOptions(builder_, padding, /*stride_w=*/2,
/*stride_h=*/2, filter_width,
filter_height, ActivationFunctionType_NONE)
.Union());
BuildInterpreter({GetShape(input_)});
}
template <typename T>
void SetInput(const std::vector<float>& data) {
QuantizeAndPopulate<T>(input_, data);
}
template <typename T>
std::vector<float> GetDequantizedOutput() {
return Dequantize<T>(ExtractVector<T>(output_), GetScale(output_),
GetZeroPoint(output_));
}
private:
int input_;
int output_;
};
TEST(QuantizedPoolingOpTest, AveragePool) {
PoolingOpModel m(BuiltinOperator_AVERAGE_POOL_2D,
/*input=*/{TensorType_UINT8, {1, 16, 8, 1}, 0, 10},
/*filter_width=*/8, /*filter_height=*/8,
/*output=*/{TensorType_UINT8, {}, 0, 10});
m.SetInput<uint8_t>({
0, 6, 2, 4, 0, 6, 2, 4, //
3, 2, 10, 7, 3, 2, 10, 7, //
0, 6, 2, 4, 0, 6, 2, 4, //
3, 2, 10, 7, 3, 2, 10, 7, //
0, 6, 2, 4, 0, 6, 2, 4, //
3, 2, 10, 7, 3, 2, 10, 7, //
3, 2, 10, 7, 3, 2, 10, 7, //
3, 2, 10, 7, 3, 2, 10, 7, //
0, 6, 2, 4, 0, 6, 2, 4, //
3, 2, 10, 7, 3, 2, 10, 7, //
3, 2, 10, 7, 3, 2, 10, 7, //
3, 2, 10, 7, 3, 2, 10, 7, //
0, 6, 2, 4, 0, 6, 2, 4, //
0, 6, 2, 4, 0, 6, 2, 4, //
0, 6, 2, 4, 0, 6, 2, 4, //
3, 2, 10, 7, 3, 2, 10, 7, //
});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(
{4.58824, 4.58824, 4.90196, 4.58824, 4.27451})));
}
TEST(QuantizedPoolingOpTest, AveragePool_Int8) {
PoolingOpModel m(BuiltinOperator_AVERAGE_POOL_2D,
/*input=*/{TensorType_INT8, {1, 16, 8, 1}, 0, 10},
/*filter_width=*/8, /*filter_height=*/8,
/*output=*/{TensorType_INT8, {}, 0, 10});
m.SetInput<int8_t>({
0, 6, 2, 4, 0, 6, 2, 4, //
3, 2, 10, 7, 3, 2, 10, 7, //
0, 6, 2, 4, 0, 6, 2, 4, //
3, 2, 10, 7, 3, 2, 10, 7, //
0, 6, 2, 4, 0, 6, 2, 4, //
3, 2, 10, 7, 3, 2, 10, 7, //
3, 2, 10, 7, 3, 2, 10, 7, //
3, 2, 10, 7, 3, 2, 10, 7, //
0, 6, 2, 4, 0, 6, 2, 4, //
3, 2, 10, 7, 3, 2, 10, 7, //
3, 2, 10, 7, 3, 2, 10, 7, //
3, 2, 10, 7, 3, 2, 10, 7, //
0, 6, 2, 4, 0, 6, 2, 4, //
0, 6, 2, 4, 0, 6, 2, 4, //
0, 6, 2, 4, 0, 6, 2, 4, //
3, 2, 10, 7, 3, 2, 10, 7, //
});
// Reference data.
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<int8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output)));
}
TEST(QuantizedUInt8PoolingOpTest, MaxPool) {
// Choose the input ranges carefully so that the dequantized output matches
// the results of the float model above.
// Input Range[0, 15.9375] --> [Scale{0.0625}, zero_point{0}]
PoolingOpModel m(BuiltinOperator_MAX_POOL_2D,
/*input=*/{TensorType_UINT8, {1, 2, 4, 1}, 0, 15.9375},
/*filter_width=*/2, /*filter_height=*/2,
/*output=*/{TensorType_UINT8, {}, 0, 15.9375}, Padding_SAME);
m.SetInput<uint8_t>({
0, 6, 2, 4, //
3, 2, 10, 7, //
});
// Reference data.
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<uint8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output)));
}
void GenerateUniformRandomVector(int size, float min, float max,
std::minstd_rand* random_engine,
std::vector<float>* result) {
// Never use std::uniform_*_distribution in tests, it's
// implementation-defined. Likewise, don't use std::default_random_engine,
// implementation-defined. Implementation-defined is bad because it means that
// any toolchain update or new platform may run into test failures.
// std::minstd_rand is a standard instantiation of
// std::linear_congruential_engine, the cheapest generator in c++11 stdlib,
// it's good enough here.
result->resize(size);
for (int i = 0; i < size; i++) {
// We don't care whether the `max` value may ever be produced exactly.
// It may actually be thanks to rounding, as std::minstd_rand::modulus
// is 2^31 - 1 is greater than the inverse float epsilon.
float random_value_scaled_0_1 =
(*random_engine)() *
(1.0f / static_cast<float>(std::minstd_rand::modulus));
(*result)[i] = min + (max - min) * random_value_scaled_0_1;
}
}
TEST(QuantizedUInt8PoolingOpTest, MaxPool_Valid_Large_Filter) {
const int ksize = 15;
PoolingOpModel m(BuiltinOperator_MAX_POOL_2D,
/*input=*/{TensorType_UINT8, {1, ksize, ksize, 512}, 0, 30},
/*filter_width=*/ksize, /*filter_height=*/ksize,
/*output=*/{TensorType_UINT8, {}, 0, 30}, Padding_VALID);
std::minstd_rand random_engine;
std::vector<float> input;
GenerateUniformRandomVector(ksize * ksize * 512, 0, 30, &random_engine,
&input);
m.SetInput<uint8_t>(input);
// Reference data.
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<uint8_t>();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output)));
}
} // namespace tflite
@@ -0,0 +1,173 @@
/* Copyright 2020 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
class QuantizeOpModel : public SingleOpModelWithHexagon {
public:
explicit QuantizeOpModel(const TensorData& input, const TensorData& output) {
input_ = AddInput(input);
output_ = AddOutput(output);
SetBuiltinOp(BuiltinOperator_QUANTIZE, BuiltinOptions_QuantizeOptions,
CreateQuantizeOptions(builder_).Union());
BuildInterpreter({GetShape(input_)});
}
template <typename T>
void SetInput(const std::vector<float>& data) {
QuantizeAndPopulate<T>(input_, data);
}
template <typename T>
std::vector<T> GetOutput() {
return ExtractVector<T>(output_);
}
protected:
BuiltinOperator op_code_;
int input_;
int output_;
};
// Input scale 0.500000, output scale 0.500000, input zeropoint 127, output
// zeropoint 127
TEST(QuantizeOpTest, UInt8UInt8SameScale) {
QuantizeOpModel m({TensorType_UINT8, {1, 1, 2, 5}, -63.5, 64},
{TensorType_UINT8, {1, 1, 2, 5}, -63.5, 64});
// Input will quantized to {129,131,133,135,137,139,141,143,145,147}.
m.SetInput<uint8_t>({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(
m.GetOutput<uint8_t>(),
ElementsAreArray({129, 131, 133, 135, 137, 139, 141, 143, 145, 147}));
}
// Input scale 0.500000, output scale 1.000000, input zeropoint 127, output
// zeropoint 127
TEST(QuantizeOpTest, Uint8Uint8LargerScale) {
QuantizeOpModel m({TensorType_UINT8, {1, 1, 2, 5}, -63.5, 64},
{TensorType_UINT8, {1, 1, 2, 5}, -127, 128});
// Input will quantized to {129,131,133,135,137,139,141,143,145,147}.
m.SetInput<uint8_t>({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(
m.GetOutput<uint8_t>(),
ElementsAreArray({128, 129, 130, 131, 132, 133, 134, 135, 136, 137}));
}
// Input scale 1.000000, output scale 0.500000, input zeropoint 127, output
// zeropoint 127
TEST(QuantizeOpTest, Uint8Uint8SmallerScale) {
QuantizeOpModel m({TensorType_UINT8, {1, 1, 2, 5}, -127, 128},
{TensorType_UINT8, {1, 1, 2, 5}, -63.5, 64});
// Input will quantized to {128, 129, 130, 131, 132, 133, 134, 135, 136, 137}.
m.SetInput<uint8_t>({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(
m.GetOutput<uint8_t>(),
ElementsAreArray({129, 131, 133, 135, 137, 139, 141, 143, 145, 147}));
}
// Input scale 1.000000, output scale 0.500000, input zeropoint -1, output
// zeropoint 127
TEST(QuantizeOpTest, Int8Uint8SmallerScale) {
QuantizeOpModel m({TensorType_INT8, {1, 1, 2, 5}, -127, 128},
{TensorType_UINT8, {1, 1, 2, 5}, -63.5, 64});
// Input will quantized to {0,1,2,3,4,5,6,7,8,9}.
m.SetInput<int8_t>({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(
m.GetOutput<uint8_t>(),
ElementsAreArray({129, 131, 133, 135, 137, 139, 141, 143, 145, 147}));
}
// Input scale 1.000000, output scale 2.000000, input zeropoint -1, output
// zeropoint 127
TEST(QuantizeOpTest, Int8Uint8LargerScale) {
QuantizeOpModel m({TensorType_INT8, {1, 1, 2, 5}, -127, 128},
{TensorType_UINT8, {1, 1, 2, 5}, -254, 256});
// Input will quantized to {0,1,2,3,4,5,6,7,8,9}.
m.SetInput<int8_t>({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(
m.GetOutput<uint8_t>(),
ElementsAreArray({128, 128, 129, 129, 130, 130, 131, 131, 132, 132}));
}
// input scale 0.500000, output scale 0.500000, input zeropoint 127, output
// zeropoint -1
TEST(QuantizeOpTest, UInt8Int8SameScale128Diff) {
QuantizeOpModel m({TensorType_UINT8, {1, 1, 2, 5}, -127, 128},
{TensorType_INT8, {1, 1, 2, 5}, -127, 128});
// Input will quantized to {128, 129, 130, 131, 132, 133, 134, 135, 136, 137}.
m.SetInput<uint8_t>({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutput<int8_t>(),
ElementsAreArray({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}));
}
// Input scale 0.500000, output scale 0.500000, input zeropoint -1, output
// zeropoint -1
TEST(QuantizeOpTest, Int8Int8SameScale) {
QuantizeOpModel m({TensorType_INT8, {1, 1, 2, 5}, -63.5, 64},
{TensorType_INT8, {1, 1, 2, 5}, -63.5, 64});
// Input will quantized to {1,3,5,7,9,11,13,15,17,19}.
m.SetInput<int8_t>({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutput<int8_t>(),
ElementsAreArray({1, 3, 5, 7, 9, 11, 13, 15, 17, 19}));
}
// Input scale 0.500000, output scale 1.000000, input zeropoint -1, output
// zeropoint -1
TEST(QuantizeOpTest, Int8Int8LargerScale) {
QuantizeOpModel m({TensorType_INT8, {1, 1, 2, 5}, -63.5, 64},
{TensorType_INT8, {1, 1, 2, 5}, -127, 128});
// Input will quantized to {1,3,5,7,9,11,13,15,17,19}.
m.SetInput<int8_t>({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutput<int8_t>(),
ElementsAreArray({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}));
}
// Input scale 1.000000, output scale 0.500000, input zeropoint -1, output
// zeropoint -1
TEST(QuantizeOpTest, Int8Int8SmallerScale) {
QuantizeOpModel m({TensorType_INT8, {1, 1, 2, 5}, -127, 128},
{TensorType_INT8, {1, 1, 2, 5}, -63.5, 64});
// Input will quantized to {0,1,2,3,4,5,6,7,8,9}.
m.SetInput<int8_t>({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutput<int8_t>(),
ElementsAreArray({1, 3, 5, 7, 9, 11, 13, 15, 17, 19}));
}
} // namespace tflite
@@ -0,0 +1,157 @@
/* Copyright 2020 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 <initializer_list>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
// TODO(b/148390890): Reduce Sum tests are disabled, enable after fix is
// available and op is enabled.
class ReduceOpModel : public SingleOpModelWithHexagon {
public:
ReduceOpModel(BuiltinOperator type, const TensorData& input,
const TensorData& output, std::initializer_list<int> axis_shape,
std::initializer_list<int> axis, bool keep_dims) {
input_ = AddInput(input);
axis_ = AddConstInput(TensorType_INT32, axis, axis_shape);
output_ = AddOutput(output);
SetBuiltinOp(type, BuiltinOptions_ReducerOptions,
CreateReducerOptions(builder_, keep_dims).Union());
BuildInterpreter({GetShape(input_)});
}
int Input() { return input_; }
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
template <typename T>
std::vector<float> GetDequantizedOutput() {
return Dequantize<T>(ExtractVector<T>(output_), GetScale(output_),
GetZeroPoint(output_));
}
private:
int input_;
int axis_;
int output_;
};
template <TensorType Tensor_Type, typename input_type>
void TestMeanImpl(bool full_input_dims = true) {
float kQuantizedTolerance = 2.0 / 255;
std::vector<float> data = {0.4, 0.2, 0.3, 0.4, 0.5, 0.6};
ReduceOpModel m(BuiltinOperator_MEAN,
{Tensor_Type,
// if !full_input_dims, input dimension will be less than 4.
(full_input_dims ? std::vector<int>({1, 1, 3, 2})
: std::vector<int>({1, 3, 2})),
-1.0, 1.0},
{Tensor_Type, {2}, -1.0, 1.0}, {1}, {full_input_dims ? 2 : 1},
false);
m.QuantizeAndPopulate<input_type>(m.Input(), data);
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<input_type>();
m.ApplyDelegateAndInvoke();
if (full_input_dims) {
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 1, 2}));
} else {
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2}));
}
EXPECT_THAT(
m.GetDequantizedOutput<input_type>(),
ElementsAreArray(ArrayFloatNear(reference_output, kQuantizedTolerance)));
}
TEST(ReduceOpModel, MeanNotKeepDims_Uint8) {
TestMeanImpl<TensorType_UINT8, uint8_t>(true);
TestMeanImpl<TensorType_UINT8, uint8_t>(false);
}
TEST(ReduceOpModel, MeanNotKeepDims_Int8) {
TestMeanImpl<TensorType_INT8, int8_t>(true);
TestMeanImpl<TensorType_INT8, int8_t>(false);
}
template <TensorType Tensor_Type, typename input_type>
void TestMeanKeppDimsImpl(bool full_input_dims = true) {
float kQuantizedTolerance = 2.0 / 255;
std::vector<float> data = {0.4, 0.2, 0.3, 0.4, 0.5, 0.6};
ReduceOpModel m(BuiltinOperator_MEAN,
{Tensor_Type,
(full_input_dims ? std::vector<int>({1, 1, 3, 2})
: std::vector<int>({1, 3, 2})),
-1.0, 1.0},
{Tensor_Type, {3}, -1.0, 1.0}, {1}, {full_input_dims ? 3 : 2},
true);
m.QuantizeAndPopulate<input_type>(m.Input(), data);
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<input_type>();
m.ApplyDelegateAndInvoke();
if (full_input_dims) {
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 1, 3, 1}));
} else {
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 3, 1}));
}
EXPECT_THAT(
m.GetDequantizedOutput<input_type>(),
ElementsAreArray(ArrayFloatNear(reference_output, kQuantizedTolerance)));
}
TEST(ReduceOpModel, MeanKeepDims_Int8) {
TestMeanKeppDimsImpl<TensorType_INT8, int8_t>(true);
TestMeanKeppDimsImpl<TensorType_INT8, int8_t>(false);
}
TEST(ReduceOpModel, MeanKeepDims_Uint8) {
TestMeanKeppDimsImpl<TensorType_UINT8, uint8_t>(true);
TestMeanKeppDimsImpl<TensorType_UINT8, uint8_t>(false);
}
TEST(ReduceOpModel, DISABLED_SumNotKeepDims) {
float kQuantizedTolerance = 2.0 / 255;
std::vector<float> data = {0.4, 0.2, 0.3, 0.4, 0.5, 0.6};
ReduceOpModel m(BuiltinOperator_SUM,
{TensorType_UINT8, {1, 1, 3, 2}, -1.0, 1.0},
{TensorType_UINT8, {2}, -1.0, 1.0}, {1}, {2}, false);
m.QuantizeAndPopulate<uint8_t>(m.Input(), data);
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 1, 2}));
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(
ArrayFloatNear({-0.823529, -0.815686}, kQuantizedTolerance)));
}
TEST(ReduceOpModel, DISABLED_SumKeepDims) {
float kQuantizedTolerance = 2.0 / 255;
std::vector<float> data = {0.4, 0.2, 0.3, 0.4, 0.5, 0.6};
ReduceOpModel m(BuiltinOperator_SUM,
{TensorType_UINT8, {1, 1, 3, 2}, -1.0, 1.0},
{TensorType_UINT8, {3}, -1.0, 1.0}, {1}, {3}, true);
m.QuantizeAndPopulate<uint8_t>(m.Input(), data);
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 1, 3, 1}));
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear({-0.407843, -0.313726, 0.0941177},
kQuantizedTolerance)));
}
} // namespace tflite
@@ -0,0 +1,59 @@
/* Copyright 2020 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/reshape_test_common.h"
namespace tflite {
using ::testing::ElementsAreArray;
template <typename T>
class ReshapeOpTest : public ::testing::Test {};
using DataTypes = ::testing::Types<uint8_t, int8_t>;
TYPED_TEST_SUITE(ReshapeOpTest, DataTypes);
TYPED_TEST(ReshapeOpTest, RegularShapes) {
std::vector<ShapeSpecificationType> shape_types = {
ShapeSpecificationType::kAsReshapeOption,
ShapeSpecificationType::kAsConstantTensor};
for (ShapeSpecificationType shape_type : shape_types) {
ReshapeOpModel<TypeParam, SingleOpModelWithHexagon> m(
{1, 2, 4, 1}, {3}, {2, 2, 2}, shape_type);
m.SetInput({1, 2, 3, 4, 5, 6, 7, 8});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutput(), ElementsAreArray({1, 2, 3, 4, 5, 6, 7, 8}));
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 2, 2}));
}
}
TYPED_TEST(ReshapeOpTest, WithStretchDimension) {
std::vector<ShapeSpecificationType> shape_types = {
ShapeSpecificationType::kAsReshapeOption,
ShapeSpecificationType::kAsConstantTensor};
for (ShapeSpecificationType shape_type : shape_types) {
ReshapeOpModel<TypeParam, SingleOpModelWithHexagon> m(
{1, 2, 4, 1}, {3}, {2, 1, -1}, shape_type);
m.SetInput({1, 2, 3, 4, 5, 6, 7, 8});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutput(), ElementsAreArray({1, 2, 3, 4, 5, 6, 7, 8}));
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 4}));
}
}
} // namespace tflite
@@ -0,0 +1,338 @@
/* Copyright 2020 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 <initializer_list>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
class ResizeOpModel : public SingleOpModelWithHexagon {
public:
explicit ResizeOpModel(BuiltinOperator op_type, const TensorData& input,
std::initializer_list<int> size_data,
const TensorData& output, bool align_corners = false,
bool half_pixel_centers = false) {
input_ = AddInput(input);
size_ = AddConstInput(TensorType_INT32, size_data, {2});
output_ = AddOutput(output);
if (op_type == BuiltinOperator_RESIZE_NEAREST_NEIGHBOR) {
SetBuiltinOp(BuiltinOperator_RESIZE_NEAREST_NEIGHBOR,
BuiltinOptions_ResizeNearestNeighborOptions,
CreateResizeNearestNeighborOptions(
builder_, /*align_corners*/ align_corners,
/*half_pixel_centers*/ half_pixel_centers)
.Union());
} else {
SetBuiltinOp(op_type, BuiltinOptions_ResizeBilinearOptions,
CreateResizeBilinearOptions(
builder_, /**align_corners**/ align_corners,
/**half_pixel_centers**/ half_pixel_centers)
.Union());
}
BuildInterpreter({GetShape(input_)});
}
template <typename T>
void SetInput(std::initializer_list<T> data) {
PopulateTensor(input_, data);
}
template <typename T>
std::vector<T> GetOutput() {
return ExtractVector<T>(output_);
}
template <typename T>
void SetQuantizedInput(std::initializer_list<float> data) {
QuantizeAndPopulate<T>(input_, data);
}
template <typename T>
std::vector<float> GetDequantizedOutput() {
return Dequantize<T>(ExtractVector<T>(output_), GetScale(output_),
GetZeroPoint(output_));
}
int input() { return input_; }
private:
int input_;
int size_;
int output_;
};
TEST(ResizeOpModel, HorizontalResizeBiliear_UInt8) {
ResizeOpModel m(BuiltinOperator_RESIZE_BILINEAR,
{TensorType_UINT8, {1, 1, 2, 1}, -2.0, 10}, {1, 3},
{TensorType_UINT8, {}, -2.0, 10});
m.SetQuantizedInput<uint8_t>({3, 6});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear({3, 5, 6}, /*max_abs_err=*/1)));
}
TEST(ResizeOpModel, HorizontalResizeNearestNeighbor_Int8) {
ResizeOpModel m(BuiltinOperator_RESIZE_NEAREST_NEIGHBOR,
{TensorType_INT8, {1, 1, 2, 1}, -2.0, 10}, {1, 3},
{TensorType_INT8, {}, -2.0, 10});
m.SetQuantizedInput<int8_t>({3, 6});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear({3.01176, 3.01176, 6.02353},
/*max_abs_err=*/1)));
}
TEST(ResizeOpModel, VerticalResizeBiliear_Int8) {
ResizeOpModel m(BuiltinOperator_RESIZE_BILINEAR,
{TensorType_INT8, {1, 2, 1, 1}, -2.0, 20}, {3, 1},
{TensorType_INT8, {}, -2.0, 20});
m.SetQuantizedInput<int8_t>({3, 9});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear({3, 7, 9}, /*max_abs_err=*/1)));
}
TEST(ResizeOpModel, VerticalResizeNearestNeighbor_UInt8) {
ResizeOpModel m(BuiltinOperator_RESIZE_NEAREST_NEIGHBOR,
{TensorType_UINT8, {1, 2, 1, 1}, -2.0, 20}, {3, 1},
{TensorType_UINT8, {}, -2.0, 20});
m.SetQuantizedInput<uint8_t>({3, 9});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear({3.01961, 3.01961, 8.97255},
/*max_abs_err=*/1)));
}
TEST(ResizeOpModel, ThreeDimensionalResizeBiliear_UInt8) {
ResizeOpModel m(BuiltinOperator_RESIZE_BILINEAR,
{TensorType_UINT8, {1, 2, 2, 2}, -2, 30}, {3, 3},
{TensorType_UINT8, {}, -2.0, 30.0});
m.SetQuantizedInput<uint8_t>({
3, 4, 6, 10, //
10, 12, 14, 16, //
});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(
{
3,
4,
5,
8,
6,
10, //
7,
9,
10,
12,
11,
14, //
10,
12,
12,
14,
14,
16, //
},
/*max_abs_err=*/1)));
}
TEST(ResizeOpModel, ThreeDimensionalResizeNearestNeighbor_Int8) {
ResizeOpModel m(BuiltinOperator_RESIZE_NEAREST_NEIGHBOR,
{TensorType_INT8, {1, 2, 2, 2}, -2, 30}, {3, 3},
{TensorType_INT8, {}, -2.0, 30.0});
m.SetQuantizedInput<int8_t>({
3, 4, 6, 10, //
10, 12, 14, 16, //
});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(), ElementsAreArray(ArrayFloatNear(
{
3.01177,
4.01569,
3.01177,
4.01569,
6.02353,
10.0392, //
3.01177,
4.01569,
3.01177,
4.01569,
6.02353,
10.0392, //
10.0392,
12.0471,
10.0392,
12.0471,
14.0549,
16.0627, //
},
/*max_abs_err=*/1)));
}
TEST(ResizeOpModel, TwoDimensionalResizeBilinearWithTwoBatches_Int8) {
ResizeOpModel m(BuiltinOperator_RESIZE_BILINEAR,
{TensorType_INT8, {2, 2, 2, 1}, -2, 30}, {3, 3},
{TensorType_INT8, {}, -2.0, 30.0});
m.SetQuantizedInput<int8_t>({
3, 6, //
9, 12, //
4, 10, //
12, 16 //
});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(), ElementsAreArray(ArrayFloatNear(
{
3,
5,
6, //
7,
9,
10, //
9,
11,
12, //
4,
8,
10, //
9,
12,
14, //
12,
14,
16, //
},
/*max_abs_err=*/1)));
}
TEST(ResizeOpModel, TwoDimensionalResizeNNWithTwoBatches_UInt8) {
ResizeOpModel m(BuiltinOperator_RESIZE_NEAREST_NEIGHBOR,
{TensorType_UINT8, {2, 2, 2, 1}, -2, 30}, {3, 3},
{TensorType_UINT8, {}, -2.0, 30.0});
m.SetQuantizedInput<uint8_t>({
3, 6, //
9, 12, //
4, 10, //
12, 16 //
});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(
{
3.01177,
3.01177,
6.02353, //
3.01177,
3.01177,
6.02353, //
9.03529,
9.03529,
12.0471, //
4.01569,
4.01569,
10.0392, //
4.01569,
4.01569,
10.0392, //
12.0471,
12.0471,
16.0627, //
},
/*max_abs_err=*/1)));
}
TEST(ResizeOpModel, TwoDimResizeBilinearWithTwoBatches_HalfPixelCenters_UInt8) {
ResizeOpModel m(BuiltinOperator_RESIZE_BILINEAR,
{TensorType_UINT8, {2, 2, 2, 1}, -2.0, 20}, {3, 3},
{TensorType_UINT8, {}, -2.0, 20}, /**align_corners**/ false,
/**half_pixel_centers**/ true);
m.SetQuantizedInput<uint8_t>({
3, 6, //
9, 12, //
4, 10, //
12, 16 //
});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear({2, 4, 6, //
6, 7, 9, //
9, 10, 12, //
4, 7, 10, //
8, 10, 13, //
12, 14, 16},
/*max_abs_err=*/2)));
}
TEST(ResizeOpModel, TwoDimResizeBilinearWithTwoBatches_AlignCorners_UInt8) {
ResizeOpModel m(BuiltinOperator_RESIZE_BILINEAR,
{TensorType_UINT8, {2, 2, 2, 1}, -2.0, 20}, {3, 3},
{TensorType_UINT8, {}, -2.0, 20}, /**align_corners**/ true,
/**half_pixel_centers**/ false);
m.SetQuantizedInput<uint8_t>({
3, 6, //
9, 12, //
4, 10, //
12, 16 //
});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear({3, 5, 6, //
7, 9, 10, //
9, 11, 12, //
4, 8, 10, //
9, 12, 13, //
12, 15, 16},
/*max_abs_err=*/2)));
}
TEST(ResizeOpModel, ThreeDimensionalResizeNN_AlignCorners_UInt8) {
ResizeOpModel m(BuiltinOperator_RESIZE_NEAREST_NEIGHBOR,
{TensorType_UINT8, {1, 2, 2, 2}, -2.0, 20}, {3, 3},
{TensorType_UINT8, {}, -2.0, 20}, /**align_corners**/ true);
m.SetQuantizedInput<uint8_t>({
3, 4, 6, 10, //
10, 12, 14, 16, //
});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear({3, 4, 6, 10, 6, 10, //
10, 12, 14, 16, 14, 16, //
10, 12, 14, 16, 14, 16},
/*max_abs_err=*/1)));
}
TEST(ResizeOpModel, ThreeDimensionalResizeNN_HalfPixelCenters_UInt8) {
ResizeOpModel m(BuiltinOperator_RESIZE_NEAREST_NEIGHBOR,
{TensorType_UINT8, {1, 2, 2, 2}, -2.0, 20}, {3, 3},
{TensorType_UINT8, {}, -2.0, 20}, /**align_corners**/ false,
/**half_pixel_centers**/ true);
m.SetQuantizedInput<uint8_t>({
3, 4, 6, 10, //
10, 12, 14, 16, //
});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear({3, 4, 6, 10, 6, 10, //
10, 12, 14, 16, 14, 16, //
10, 12, 14, 16, 14, 16},
/*max_abs_err=*/1)));
}
} // namespace tflite
@@ -0,0 +1,118 @@
/* Copyright 2020 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 <initializer_list>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
class RsqrtOpModel : public SingleOpModelWithHexagon {
public:
RsqrtOpModel(const TensorData& input, const TensorData& output) {
input_ = AddInput(input);
output_ = AddOutput(output);
SetBuiltinOp(BuiltinOperator_RSQRT, BuiltinOptions_NONE, 0);
BuildInterpreter({GetShape(input_)});
}
void SetInput(std::initializer_list<float> data) {
PopulateTensor(input_, data);
}
template <typename T>
std::vector<float> GetDequantizedOutput() {
return Dequantize<T>(ExtractVector<T>(output_), GetScale(output_),
GetZeroPoint(output_));
}
int input() const { return input_; }
private:
int input_;
int output_;
};
TEST(RsqrtOpTest, Int8) {
std::vector<float> data = {15., 46., 78., 142., 1., 17., 49., 113.};
std::vector<float> rsqrt_data(data.size());
for (int i = 0; i < rsqrt_data.size(); i++) {
rsqrt_data[i] = 1.f / std::sqrt(data[i]);
}
const float kInputScale = 142.0 / 255.0;
const float kOutputScale = 1.0 / 255.0;
int32_t zero_point = -128;
RsqrtOpModel m({TensorType_INT8,
{1, 8},
0,
142.0,
kInputScale,
zero_point,
true,
{kInputScale},
{zero_point}},
{TensorType_INT8,
{1, 8},
0,
1.0,
kOutputScale,
zero_point,
true,
{kOutputScale},
{zero_point}});
m.QuantizeAndPopulate<int8_t>(m.input(), data);
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear(rsqrt_data, kInputScale)));
}
TEST(RsqrtOpTest, Int8_2D) {
std::vector<float> data = {15., 46., 78., 142., 1., 17., 49., 113.};
std::vector<float> rsqrt_data(data.size());
for (int i = 0; i < rsqrt_data.size(); i++) {
rsqrt_data[i] = 1.f / std::sqrt(data[i]);
}
const float kInputScale = 142.0 / 255.0;
const float kOutputScale = 1.0 / 255.0;
int32_t zero_point = -128;
RsqrtOpModel m({TensorType_INT8,
{2, 4},
0,
142.0,
kInputScale,
zero_point,
true,
{kInputScale},
{zero_point}},
{TensorType_INT8,
{2, 4},
0,
1.0,
kOutputScale,
zero_point,
true,
{kOutputScale},
{zero_point}});
m.QuantizeAndPopulate<int8_t>(m.input(), data);
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear(rsqrt_data, kInputScale)));
}
} // namespace tflite
@@ -0,0 +1,59 @@
#!/bin/bash
# Copyright 2020 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.
# ==============================================================================
set -e
on_device_dir="/data/local/tmp/hexagon_delegate_test/"
hexagon_libs_path=third_party/hexagon_nn_sdk/src/libs/
if [ "$1" != "" ]; then
hexagon_libs_path=$1
fi
hexagon_libs_path="${hexagon_libs_path}/libhexagon_nn_skel*"
adb shell rm -rf "${on_device_dir}"
adb shell mkdir "${on_device_dir}"
bazel --bazelrc=/dev/null build -c opt --config=android_arm64 //tensorflow/lite/delegates/hexagon/builders/tests:all
bazel --bazelrc=/dev/null build -c opt --config=android_arm64 //tensorflow/lite/delegates/hexagon/hexagon_nn:libhexagon_interface.so
adb push bazel-bin/tensorflow/lite/delegates/hexagon/hexagon_nn/libhexagon_interface.so "${on_device_dir}"
adb push ${hexagon_libs_path} "${on_device_dir}"
for test_binary in bazel-bin/tensorflow/lite/delegates/hexagon/builders/tests/hexagon_*_test; do
echo "Copying $test_binary"
adb push $test_binary "${on_device_dir}"
IFS='/'
read -ra split_path <<< "$test_binary"
binary_name=${split_path[-1]}
run_command="/data/local/tmp/hexagon_delegate_test/${binary_name}"
echo "Running ${run_command}"
result=$(adb shell 'LD_LIBRARY_PATH=/data/local/tmp/hexagon_delegate_test:${LD_LIBRARY_PATH} '"${run_command}")
echo 'Output: '
echo "${result}"
IFS=$'\n'
result=($result)
echo "${result[-1]}"
if [[ "${result[-1]}" == *"FAILED"* ]]; then
echo "TEST FAILED"
exit
fi
# Reset delimiter
IFS=' '
done
echo 'ALL TESTS PASSED -- Yay!!'
@@ -0,0 +1,169 @@
/* Copyright 2020 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 <initializer_list>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
template <typename index_type>
class SliceOpModel : public SingleOpModelWithHexagon {
public:
SliceOpModel(const TensorData& input, const TensorData& output,
const TensorData& begin, const TensorData& size,
std::initializer_list<index_type> begin_data,
std::initializer_list<index_type> size_data) {
input_ = AddInput(input);
begin_ = AddConstInput(begin, begin_data);
size_ = AddConstInput(size, size_data);
output_ = AddOutput(output);
SetBuiltinOp(BuiltinOperator_SLICE, BuiltinOptions_SliceOptions,
CreateSliceOptions(builder_).Union());
BuildInterpreter({GetShape(input_), GetShape(begin_), GetShape(size_)});
}
template <typename T>
void SetInput(std::initializer_list<float> data) {
QuantizeAndPopulate<T>(input_, data);
}
template <typename T>
std::vector<float> GetDequantizedOutput() {
return Dequantize<T>(ExtractVector<T>(output_), GetScale(output_),
GetZeroPoint(output_));
}
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
private:
int input_;
int begin_;
int size_;
int output_;
};
TEST(SliceOpTest, Input_1D_Uint8) {
SliceOpModel<int> m(/*input=*/{TensorType_UINT8, {4}, -10, 10},
/*output=*/{TensorType_UINT8, {2}, -10, 10},
{TensorType_INT32, {1}}, {TensorType_INT32, {1}}, {1},
{2});
m.SetInput<uint8_t>({1, 2, 3, 4});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2}));
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear({2, 3}, 0.1)));
}
TEST(SliceOpTest, Input_2D_Uint8) {
SliceOpModel<int> m(
/*input=*/{TensorType_UINT8, {2, 3}, -10, 10},
/*output=*/{TensorType_UINT8, {1, 2}, -10, 10}, {TensorType_INT32, {2}},
{TensorType_INT32, {2}}, {1, 0}, {1, 2});
m.SetInput<uint8_t>({1, 2, 3, 4, 5, 6});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<uint8_t>();
auto reference_output_shape = m.GetOutputShape();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray(reference_output_shape));
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output, 0.1)));
}
TEST(SliceOpTest, SizeInt64_Uint8) {
SliceOpModel<int64_t> m(/*input=*/{TensorType_UINT8, {4, 1, 1, 1}, -10, 10},
/*output=*/{TensorType_UINT8, {3, 1, 1, 1}, -10, 10},
{TensorType_INT64, {4}}, {TensorType_INT64, {4}},
{1, 0, 0, 0}, {3, 1, 1, 1});
m.SetInput<uint8_t>({1, 2, 3, 4});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<uint8_t>();
auto reference_output_shape = m.GetOutputShape();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray(reference_output_shape));
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output, 0.1)));
}
TEST(SliceOpTest, SizeMinus1) {
SliceOpModel<int64_t> m(
/*input=*/{TensorType_UINT8, {3, 2, 3, 1}, -10, 10},
/*output=*/{TensorType_UINT8, {2, 1, 3, 1}, -10, 10},
{TensorType_INT64, {4}}, {TensorType_INT64, {4}}, {1, 0, 0, 0},
{2, 1, -1, 1});
m.SetInput<uint8_t>({1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<uint8_t>();
auto reference_output_shape = m.GetOutputShape();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray(reference_output_shape));
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output, 0.1)));
}
TEST(SliceOpTest, BeginNonZeroSizeMinus1Axis1) {
SliceOpModel<int64_t> m(
/*input=*/{TensorType_UINT8, {3, 3, 2, 1}, -10, 10},
/*output=*/{TensorType_UINT8, {2, 2, 1, 1}, -10, 10},
{TensorType_INT64, {4}}, {TensorType_INT64, {4}}, {1, 1, 0, 0},
{2, -1, 1, 1});
m.SetInput<uint8_t>({1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<uint8_t>();
auto reference_output_shape = m.GetOutputShape();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray(reference_output_shape));
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output, 0.1)));
}
TEST(SliceOpTest, BeginNonZeroSizeMinus1Axis2) {
SliceOpModel<int64_t> m(
/*input=*/{TensorType_UINT8, {3, 2, 3, 1}, -10, 10},
/*output=*/{TensorType_UINT8, {2, 1, 2, 1}, -10, 10},
{TensorType_INT64, {4}}, {TensorType_INT64, {4}}, {1, 0, 1, 0},
{2, 1, -1, 1});
m.SetInput<uint8_t>({1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<uint8_t>();
auto reference_output_shape = m.GetOutputShape();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray(reference_output_shape));
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output, 0.1)));
}
TEST(SliceOpTest, BeginNonZeroSizeMinus1Axis2_Int8) {
SliceOpModel<int64_t> m(
/*input=*/{TensorType_INT8, {3, 2, 3, 1}, -10, 10},
/*output=*/{TensorType_INT8, {2, 1, 2, 1}, -10, 10},
{TensorType_INT64, {4}}, {TensorType_INT64, {4}}, {1, 0, 1, 0},
{2, 1, -1, 1});
m.SetInput<int8_t>({1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6});
ASSERT_EQ(m.Invoke(), kTfLiteOk);
auto reference_output = m.GetDequantizedOutput<int8_t>();
auto reference_output_shape = m.GetOutputShape();
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray(reference_output_shape));
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear(reference_output, 0.1)));
}
} // namespace tflite
@@ -0,0 +1,131 @@
/* Copyright 2020 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
const float kTolerance = 2 * (1. / 256);
class SoftmaxOpModel : public SingleOpModelWithHexagon {
public:
SoftmaxOpModel(float softmax_beta, const TensorData& input) {
input_ = AddInput(input);
if (input.type == TensorType_UINT8) {
output_ = AddOutput({input.type, {}, 0, 0, 1. / 256});
} else if (input.type == TensorType_INT8) {
output_ = AddOutput({TensorType_INT8, {}, 0, 0, 1. / 256, -128});
}
SetBuiltinOp(BuiltinOperator_SOFTMAX, BuiltinOptions_SoftmaxOptions,
CreateSoftmaxOptions(builder_, softmax_beta).Union());
BuildInterpreter({GetShape(input_)});
}
template <typename T>
void SetInput(const std::vector<float>& data) {
QuantizeAndPopulate<T>(input_, data);
}
template <typename T>
std::vector<float> GetDequantizedOutput() {
return Dequantize<T>(ExtractVector<T>(output_), GetScale(output_),
GetZeroPoint(output_));
}
protected:
int input_;
int output_;
};
TEST(SoftmaxOpModel, Softmax4DUint8) {
SoftmaxOpModel m(0.1,
/*input=*/{TensorType_UINT8, {1, 2, 1, 4}, -10, 10});
m.SetInput<uint8_t>({
0, -6, 2, 4, // depth = 0
3, -2, 10, 1, // depth = 1
});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(
{
.23463, .12877, .28658, .35003, //
.22528, .13664, .45365, .18443, //
},
kTolerance)));
}
TEST(SoftmaxOpModel, Softmax4DUint8_MultipleBatch) {
SoftmaxOpModel m(0.1,
/*input=*/{TensorType_UINT8, {4, 1, 1, 2}, -10, 10});
m.SetInput<uint8_t>({
0, -6, //
2, 4, //
3, -2, //
10, 1, //
});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
ElementsAreArray(ArrayFloatNear(
{
0.645656, 0.354344, //
0.450166, 0.549834, //
0.622459, 0.377541, //
0.710949, 0.28905, //
},
kTolerance)));
}
TEST(SoftmaxOpModel, Softmax4DInt8) {
SoftmaxOpModel m(0.1,
/*input=*/{TensorType_INT8, {1, 2, 1, 4}, -10, 10});
m.SetInput<int8_t>({
0, -6, 2, 4, // depth = 0
3, -2, 10, 1, // depth = 1
});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear(
{
.23463, .12877, .28658, .35003, //
.22528, .13664, .45365, .18443, //
},
kTolerance)));
}
TEST(SoftmaxOpModel, Softmax4DInt8_MultipleBatch) {
SoftmaxOpModel m(0.1,
/*input=*/{TensorType_INT8, {4, 1, 1, 2}, -10, 10});
m.SetInput<int8_t>({
0, -6, //
2, 4, //
3, -2, //
10, 1, //
});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(), ElementsAreArray(ArrayFloatNear(
{
0.645656, 0.354344, //
0.450166, 0.549834, //
0.622459, 0.377541, //
0.710949, 0.28905, //
},
kTolerance)));
}
} // namespace tflite
@@ -0,0 +1,97 @@
/* Copyright 2020 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
class SpaceToDepthOpModel : public SingleOpModelWithHexagon {
public:
SpaceToDepthOpModel(const TensorData& tensor_data, int block_size,
BuiltinOperator type) {
input_ = AddInput(tensor_data);
output_ = AddOutput(tensor_data);
if (type == BuiltinOperator_SPACE_TO_DEPTH) {
SetBuiltinOp(BuiltinOperator_SPACE_TO_DEPTH,
BuiltinOptions_SpaceToDepthOptions,
CreateSpaceToDepthOptions(builder_, block_size).Union());
} else {
SetBuiltinOp(BuiltinOperator_DEPTH_TO_SPACE,
BuiltinOptions_DepthToSpaceOptions,
CreateDepthToSpaceOptions(builder_, block_size).Union());
}
BuildInterpreter({GetShape(input_)});
}
template <typename integer_type>
void SetInput(const std::vector<integer_type>& data) {
PopulateTensor<integer_type>(input_, data);
}
template <typename integer_type>
std::vector<integer_type> GetOutput() {
return ExtractVector<integer_type>(output_);
}
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
private:
int input_;
int output_;
};
TEST(SpaceToDepthOpModel, SpaceToDepth_UInt8) {
SpaceToDepthOpModel m({TensorType_UINT8, {1, 2, 2, 1}, -5, 5}, 2,
BuiltinOperator_SPACE_TO_DEPTH);
m.SetInput<uint8_t>({1, 2, 3, 4});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutput<uint8_t>(), ElementsAreArray({1, 2, 3, 4}));
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 1, 1, 4}));
}
TEST(SpaceToDepthOpModel, SpaceToDepth_Int8) {
SpaceToDepthOpModel m({TensorType_INT8, {1, 2, 2, 1}, -5, 5}, 2,
BuiltinOperator_SPACE_TO_DEPTH);
m.SetInput<int8_t>({1, 2, 3, 4});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutput<int8_t>(), ElementsAreArray({1, 2, 3, 4}));
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 1, 1, 4}));
}
TEST(SpaceToDepthOpModel, DepthToSpace_UInt8) {
SpaceToDepthOpModel m({TensorType_UINT8, {1, 1, 2, 4}, -8, 8}, 2,
BuiltinOperator_DEPTH_TO_SPACE);
m.SetInput<uint8_t>({1, 2, 3, 4, 5, 6, 7, 8});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutput<uint8_t>(),
ElementsAreArray({1, 2, 5, 6, 3, 4, 7, 8}));
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2, 4, 1}));
}
TEST(SpaceToDepthOpModel, DepthToSpace_Int8) {
SpaceToDepthOpModel m({TensorType_INT8, {1, 1, 2, 4}, -8, 8}, 2,
BuiltinOperator_DEPTH_TO_SPACE);
m.SetInput<int8_t>({1, 2, 3, 4, 5, 6, 7, 8});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutput<int8_t>(),
ElementsAreArray({1, 2, 5, 6, 3, 4, 7, 8}));
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2, 4, 1}));
}
} // namespace tflite
@@ -0,0 +1,176 @@
/* Copyright 2020 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 <algorithm>
#include <initializer_list>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
class SplitOpModel : public SingleOpModelWithHexagon {
public:
explicit SplitOpModel(const TensorData& input, const TensorData& output,
int num_splits, int axis) {
axis_ = AddConstInput(TensorType_INT32, {axis}, {1});
input_ = AddInput(input);
for (int i = 0; i < num_splits; ++i) {
outputs_.push_back(AddOutput(output));
}
SetBuiltinOp(BuiltinOperator_SPLIT, BuiltinOptions_SplitOptions,
CreateSplitOptions(builder_, num_splits).Union());
BuildInterpreter({{}, GetShape(input_)});
}
template <typename T>
void SetInput(const std::vector<float>& data) {
QuantizeAndPopulate<T>(input_, data);
}
template <typename T>
std::vector<float> GetDequantizedOutput(int idx) {
return Dequantize<T>(ExtractVector<T>(outputs_[idx]),
GetScale(outputs_[idx]), GetZeroPoint(outputs_[idx]));
}
std::vector<int> GetOutputShape(int i) { return GetTensorShape(outputs_[i]); }
private:
int input_;
int axis_;
std::vector<int> outputs_;
};
template <typename integer_type, TensorType tensor_dtype>
void CheckSplitBehavior(
int axis, int num_splits, std::initializer_list<int> input_shape,
std::initializer_list<int> output_shape,
const std::initializer_list<float>& input_data,
const std::vector<std::initializer_list<float>>& output_data) {
auto debug = [&](int i) {
std::stringstream ss;
ss << "for output tensor " << i << " axis=" << axis
<< " and num_splits=" << num_splits;
return ss.str();
};
const float kMin = std::min({0.0f, std::min(input_data)});
const float kMax = std::max(input_data);
SplitOpModel const_m({tensor_dtype, input_shape, kMin, kMax},
{tensor_dtype, output_shape, kMin, kMax}, num_splits,
axis);
const_m.SetInput<integer_type>(input_data);
const_m.ApplyDelegateAndInvoke();
for (int i = 0; i < num_splits; ++i) {
EXPECT_THAT(
const_m.GetDequantizedOutput<integer_type>(i),
ElementsAreArray(ArrayFloatNear(output_data[i], /*max_abs_err=*/0.1)))
<< debug(i);
EXPECT_THAT(const_m.GetOutputShape(i), ElementsAreArray(output_shape))
<< debug(i);
}
}
template <typename integer_type, TensorType tensor_dtype>
void CheckFourDimSplitImpl() {
CheckSplitBehavior<integer_type, tensor_dtype>(
/*axis=*/0, /*num_splits=*/2, {2, 2, 2, 2}, {1, 2, 2, 2},
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
{
{1, 2, 3, 4, 5, 6, 7, 8},
{9, 10, 11, 12, 13, 14, 15, 16},
});
CheckSplitBehavior<integer_type, tensor_dtype>(
/*axis=*/1, /*num_splits=*/2, {2, 2, 2, 2}, {2, 1, 2, 2},
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
{
{1, 2, 3, 4, 9, 10, 11, 12},
{5, 6, 7, 8, 13, 14, 15, 16},
});
CheckSplitBehavior<integer_type, tensor_dtype>(
/*axis=*/2, /*num_splits=*/2, {2, 2, 2, 2}, {2, 2, 1, 2},
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
{
{1, 2, 5, 6, 9, 10, 13, 14},
{3, 4, 7, 8, 11, 12, 15, 16},
});
CheckSplitBehavior<integer_type, tensor_dtype>(
/*axis=*/3, /*num_splits=*/2, {2, 2, 2, 2}, {2, 2, 2, 1},
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
{
{1, 3, 5, 7, 9, 11, 13, 15},
{2, 4, 6, 8, 10, 12, 14, 16},
});
}
TEST(SplitOpModel, CheckFourDimSplitImpl_UInt8) {
CheckSplitBehavior<uint8_t, TensorType_UINT8>(
/*axis=*/0, /*num_splits=*/8, {8}, {1}, {1, 2, 3, 4, 5, 6, 7, 8},
{{1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}});
}
TEST(SplitOpModel, CheckFourDimSplitImpl_Int8) {
CheckSplitBehavior<int8_t, TensorType_INT8>(
/*axis=*/0, /*num_splits=*/8, {8}, {1}, {1, 2, 3, 4, 5, 6, 7, 8},
{{1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}});
}
TEST(SplitOpModel, CheckOneDimensionalSplit_UInt8) {
CheckSplitBehavior<uint8_t, TensorType_UINT8>(
/*axis=*/0, /*num_splits=*/8, {8}, {1}, {1, 2, 3, 4, 5, 6, 7, 8},
{{1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}});
}
TEST(SplitOpModel, CheckOneDimensionalSplit_Int8) {
CheckSplitBehavior<int8_t, TensorType_INT8>(
/*axis=*/0, /*num_splits=*/8, {8}, {1}, {1, 2, 3, 4, 5, 6, 7, 8},
{{1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}});
}
TEST(SplitOpModel, CheckNegativeOneAxisSplit_UInt8) {
CheckSplitBehavior<uint8_t, TensorType_UINT8>(
/*axis=*/-1, /*num_splits=*/2, {2, 2, 2}, {2, 2, 1},
{1, 2, 3, 4, 5, 6, 7, 8},
{
{1, 3, 5, 7},
{2, 4, 6, 8},
});
}
TEST(SplitOpModel, CheckNegativeAxisSplit_UInt8) {
CheckSplitBehavior<uint8_t, TensorType_UINT8>(
/*axis=*/-4, /*num_splits=*/2, {2, 2, 2, 2}, {1, 2, 2, 2},
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
{
{1, 2, 3, 4, 5, 6, 7, 8},
{9, 10, 11, 12, 13, 14, 15, 16},
});
}
TEST(SplitOpModel, CheckNegativeAxisSplit_Int8) {
CheckSplitBehavior<int8_t, TensorType_INT8>(
/*axis=*/-4, /*num_splits=*/2, {2, 2, 2, 2}, {1, 2, 2, 2},
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
{
{1, 2, 3, 4, 5, 6, 7, 8},
{9, 10, 11, 12, 13, 14, 15, 16},
});
}
} // namespace tflite
@@ -0,0 +1,109 @@
/* Copyright 2020 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using ::testing::ElementsAreArray;
class SquaredDifferenceOpModel : public SingleOpModelWithHexagon {
public:
SquaredDifferenceOpModel(const TensorData& input1, const TensorData& input2,
const TensorData& output) {
input1_ = AddInput(input1);
input2_ = AddInput(input2);
output_ = AddOutput(output);
SetBuiltinOp(BuiltinOperator_SQUARED_DIFFERENCE,
BuiltinOptions_SquaredDifferenceOptions,
CreateSquaredDifferenceOptions(builder_).Union());
BuildInterpreter({GetShape(input1_), GetShape(input2_)});
}
int input1() { return input1_; }
int input2() { return input2_; }
template <typename integer_dtype>
std::vector<float> GetDequantizedOutput() {
return Dequantize<int8_t>(ExtractVector<int8_t>(output_), GetScale(output_),
GetZeroPoint(output_));
}
protected:
int input1_;
int input2_;
int output_;
};
float GetTolerance(int min, int max) {
float kQuantizedStep = (max - min) / 255.0;
return kQuantizedStep;
}
TEST(QuantizedSquaredDifferenceOpTest, Quantized_SameShape) {
float kQuantizedTolerance = GetTolerance(0, 1);
SquaredDifferenceOpModel m({TensorType_INT8, {1, 2, 2, 1}, -1.2, 0.8},
{TensorType_INT8, {1, 2, 2, 1}, -1.5, 0.5},
{TensorType_INT8, {}, 0.0, 0.5});
m.QuantizeAndPopulate<int8_t>(m.input1(), {-0.2, 0.2, -1.2, 0.8});
m.QuantizeAndPopulate<int8_t>(m.input2(), {0.5, 0.2, -1.5, 0.5});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear({0.49, 0.0, 0.09, 0.09},
kQuantizedTolerance)));
}
TEST(QuantizedSquaredDifferenceOpTest, Quantized_VariousInputShapes) {
// NOTE: the min/max are 0 and 9. We use larger threshold for accuracy
// issue in Hexagon.
float kQuantizedTolerance = GetTolerance(0, 10);
std::vector<std::vector<int>> test_shapes = {
{6}, {2, 3}, {2, 1, 3}, {1, 3, 1, 2}};
for (int i = 0; i < test_shapes.size(); ++i) {
SquaredDifferenceOpModel m({TensorType_INT8, test_shapes[i], -2.0, 1.7},
{TensorType_INT8, test_shapes[i], -1.0, 1.0},
{TensorType_INT8, {}, 0.0, 9.0});
m.QuantizeAndPopulate<int8_t>(m.input1(), {-2.0, 0.2, 0.3, 0.8, 1.1, -2.0});
m.QuantizeAndPopulate<int8_t>(m.input2(), {1.0, 0.2, 0.6, 0.4, -1.0, -0.0});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear(
{9.0, 0.0, 0.09, 0.16, 4.41, 4.0}, kQuantizedTolerance)))
<< "With shape number " << i;
}
}
TEST(QuantizedSquaredDifferenceOpTest, Quantized_WithBroadcast) {
std::vector<std::vector<int>> test_shapes = {
{6}, {2, 3}, {2, 1, 3}, {1, 3, 1, 2}};
float kQuantizedTolerance = GetTolerance(0, 1);
for (int i = 0; i < test_shapes.size(); ++i) {
SquaredDifferenceOpModel m({TensorType_INT8, test_shapes[i], -0.2, 1.1},
{TensorType_INT8, {}, 0.0, 0.1},
{TensorType_INT8, {}, 0.0, 1.0});
m.QuantizeAndPopulate<int8_t>(m.input1(), {-0.2, 0.2, 0.5, 0.8, 0.11, 1.1});
m.QuantizeAndPopulate<int8_t>(m.input2(), {0.1});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(
m.GetDequantizedOutput<int8_t>(),
ElementsAreArray(ArrayFloatNear({0.09, 0.01, 0.16, 0.49, 0.0001, 1.0},
kQuantizedTolerance)))
<< "With shape number " << i;
}
}
} // namespace tflite
@@ -0,0 +1,240 @@
/* Copyright 2020 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 <initializer_list>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
template <typename input_type>
class StridedSliceOpModel : public SingleOpModelWithHexagon {
public:
StridedSliceOpModel(const TensorData& input, const TensorData& output,
const TensorData& begin,
std::initializer_list<int> begin_data,
const TensorData& end,
std::initializer_list<int> end_data,
const TensorData& strides,
std::initializer_list<int> strides_data, int begin_mask,
int end_mask, int shrink_axis_mask) {
input_ = AddInput(input);
begin_ = AddConstInput(begin, begin_data);
end_ = AddConstInput(end, end_data);
strides_ = AddConstInput(strides, strides_data);
output_ = AddOutput(output);
SetBuiltinOp(BuiltinOperator_STRIDED_SLICE,
BuiltinOptions_StridedSliceOptions,
CreateStridedSliceOptions(
builder_, begin_mask, end_mask, /*ellipsis_mask*/ 0,
/*new_axis_mask*/ 0, shrink_axis_mask)
.Union());
BuildInterpreter({GetShape(input_), GetShape(begin_), GetShape(end_),
GetShape(strides_)});
}
void SetInput(std::initializer_list<input_type> data) {
PopulateTensor<input_type>(input_, data);
}
std::vector<input_type> GetOutput() {
return ExtractVector<input_type>(output_);
}
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
private:
int input_;
int begin_;
int end_;
int strides_;
int output_;
};
TEST(StridedSliceOpModel, In1D_UInt8) {
StridedSliceOpModel<uint8_t> m(
/*input=*/{TensorType_UINT8, {4}, -10, 10},
/*output=*/{TensorType_UINT8, {2}, -10, 10},
/*begin*/ {TensorType_INT32, {1}},
/*begin_data*/ {1},
/*end*/ {TensorType_INT32, {1}},
/*end_data*/ {3},
/*strides*/ {TensorType_INT32, {1}},
/*strides_data*/ {1},
/*begin_mask*/ 0, /*end_mask*/ 0, /*shrink_axis_mask*/ 0);
m.SetInput({1, 2, 3, 4});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2}));
EXPECT_THAT(m.GetOutput(), ElementsAreArray({2, 3}));
}
TEST(StridedSliceOpModel, In1D_NegativeBegin_Int8) {
StridedSliceOpModel<int8_t> m(
/*input=*/{TensorType_INT8, {4}, -10, 10},
/*output=*/{TensorType_INT8, {2}, -10, 10},
/*begin*/ {TensorType_INT32, {1}},
/*begin_data*/ {-3},
/*end*/ {TensorType_INT32, {1}},
/*end_data*/ {3},
/*strides*/ {TensorType_INT32, {1}},
/*strides_data*/ {1},
/*begin_mask*/ 0, /*end_mask*/ 0, /*shrink_axis_mask*/ 0);
m.SetInput({1, 2, 3, 4});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2}));
EXPECT_THAT(m.GetOutput(), ElementsAreArray({2, 3}));
}
TEST(StridedSliceOpModel, In1D_NegativeEnd_Int8) {
StridedSliceOpModel<int8_t> m(
/*input=*/{TensorType_INT8, {4}, -10, 10},
/*output=*/{TensorType_INT8, {1}, -10, 10},
/*begin*/ {TensorType_INT32, {1}},
/*begin_data*/ {1},
/*end*/ {TensorType_INT32, {1}},
/*end_data*/ {-2},
/*strides*/ {TensorType_INT32, {1}},
/*strides_data*/ {1},
/*begin_mask*/ 0, /*end_mask*/ 0, /*shrink_axis_mask*/ 0);
m.SetInput({1, 2, 3, 4});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1}));
EXPECT_THAT(m.GetOutput(), ElementsAreArray({2}));
}
TEST(StridedSliceOpModel, In2D_MultipleStrides_UInt8) {
StridedSliceOpModel<uint8_t> m(
/*input=*/{TensorType_UINT8, {2, 3}, -10, 10},
/*output=*/{TensorType_UINT8, {1, 3}, -10, 10},
/*begin*/ {TensorType_INT32, {2}},
/*begin_data*/ {1, -1},
/*end*/ {TensorType_INT32, {2}},
/*end_data*/ {2, -4},
/*strides*/ {TensorType_INT32, {2}},
/*strides_data*/ {2, -1},
/*begin_mask*/ 0, /*end_mask*/ 0, /*shrink_axis_mask*/ 0);
m.SetInput({1, 2, 3, 4, 5, 6});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 3}));
EXPECT_THAT(m.GetOutput(), ElementsAreArray({6, 5, 4}));
}
TEST(StridedSliceOpModel, In2D_EndMask_Int8) {
StridedSliceOpModel<int8_t> m(
/*input=*/{TensorType_INT8, {2, 3}, -127, 128},
/*output=*/{TensorType_INT8, {1, 3}, -127, 128},
/*begin*/ {TensorType_INT32, {2}},
/*begin_data*/ {1, 0},
/*end*/ {TensorType_INT32, {2}},
/*end_data*/ {2, 2},
/*strides*/ {TensorType_INT32, {2}},
/*strides_data*/ {1, 1},
/*begin_mask*/ 0, /*end_mask*/ 2, /*shrink_axis_mask*/ 0);
m.SetInput({1, 2, 3, 4, 5, 6});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 3}));
EXPECT_THAT(m.GetOutput(), ElementsAreArray({4, 5, 6}));
}
TEST(StridedSliceOpModel, In2D_NegStrideBeginMask_UInt8) {
StridedSliceOpModel<uint8_t> m(
/*input=*/{TensorType_UINT8, {2, 3}, -10, 10},
/*output=*/{TensorType_UINT8, {1, 3}, -10, 10},
/*begin*/ {TensorType_INT32, {2}},
/*begin_data*/ {1, -2},
/*end*/ {TensorType_INT32, {2}},
/*end_data*/ {2, -4},
/*strides*/ {TensorType_INT32, {2}},
/*strides_data*/ {1, -1},
/*begin_mask*/ 2, /*end_mask*/ 0, /*shrink_axis_mask*/ 0);
m.SetInput({1, 2, 3, 4, 5, 6});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 3}));
EXPECT_THAT(m.GetOutput(), ElementsAreArray({6, 5, 4}));
}
TEST(StridedSliceOpModel, In2D_ShrinkAxis2_BeginEndAxis1_NegativeSlice_Int8) {
StridedSliceOpModel<int8_t> m(
/*input=*/{TensorType_INT8, {4, 1}, -10, 10},
/*output=*/{TensorType_INT8, {4}, -10, 10},
/*begin*/ {TensorType_INT32, {2}},
/*begin_data*/ {0, -1},
/*end*/ {TensorType_INT32, {2}},
/*end_data*/ {0, 0},
/*strides*/ {TensorType_INT32, {2}},
/*strides_data*/ {1, 1},
/*begin_mask*/ 1, /*end_mask*/ 1, /*shrink_axis_mask*/ 2);
m.SetInput({0, 1, 2, 3});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({4}));
EXPECT_THAT(m.GetOutput(), ElementsAreArray({0, 1, 2, 3}));
}
TEST(StridedSliceOpModel, In2D_ShrinkAxisMask3_Int8) {
StridedSliceOpModel<int8_t> m(
/*input=*/{TensorType_INT8, {2, 3}, -10, 10},
/*output=*/{TensorType_INT8, {}, -10, 10},
/*begin*/ {TensorType_INT32, {2}},
/*begin_data*/ {0, 0},
/*end*/ {TensorType_INT32, {2}},
/*end_data*/ {1, 1},
/*strides*/ {TensorType_INT32, {2}},
/*strides_data*/ {1, 1},
/*begin_mask*/ 0, /*end_mask*/ 0, /*shrink_axis_mask*/ 3);
m.SetInput({1, 2, 3, 4, 5, 6});
m.ApplyDelegateAndInvoke();
EXPECT_TRUE(m.GetOutputShape().empty());
EXPECT_THAT(m.GetOutput(), ElementsAreArray({1}));
}
TEST(StridedSliceOpModel, In3D_Identity_UInt8) {
StridedSliceOpModel<uint8_t> m(
/*input=*/{TensorType_UINT8, {2, 3, 2}, -15, 15},
/*output=*/{TensorType_UINT8, {2, 3, 2}, -15, 15},
/*begin*/ {TensorType_INT32, {3}},
/*begin_data*/ {0, 0, 0},
/*end*/ {TensorType_INT32, {3}},
/*end_data*/ {2, 3, 2},
/*strides*/ {TensorType_INT32, {3}},
/*strides_data*/ {1, 1, 1},
/*begin_mask*/ 0, /*end_mask*/ 0, /*shrink_axis_mask*/ 0);
m.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12});
m.ApplyDelegateAndInvoke();
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(StridedSliceOpModel, In3D_IdentityShrinkAxis4_Int8) {
StridedSliceOpModel<int8_t> m(
/*input=*/{TensorType_INT8, {2, 3, 2}, -15, 15},
/*output=*/{TensorType_INT8, {2, 3, 2}, -15, 15},
/*begin*/ {TensorType_INT32, {3}},
/*begin_data*/ {0, 0, 0},
/*end*/ {TensorType_INT32, {3}},
/*end_data*/ {2, 3, 1},
/*strides*/ {TensorType_INT32, {3}},
/*strides_data*/ {1, 1, 1},
/*begin_mask*/ 0, /*end_mask*/ 0, /*shrink_axis_mask*/ 4);
m.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12});
m.ApplyDelegateAndInvoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 3}));
EXPECT_THAT(m.GetOutput(), ElementsAreArray({1, 3, 5, 7, 9, 11}));
}
} // namespace tflite
@@ -0,0 +1,43 @@
"""Rules for generating unit-tests using hexagon delegates."""
load("@rules_cc//cc:cc_test.bzl", "cc_test")
load("//tensorflow/lite:special_rules.bzl", "tflite_hexagon_mobile_test") #'@unused'
def hexagon_op_tests(
srcs = [],
deps = []):
"""Create both monolithic and individual unit test targets for each test file in 'srcs'.
Args:
srcs: list of test files, separate target will be created for each item in the list.
deps: Dependencies will be added to all test targets.
"""
for src in srcs:
parts = src.split(".cc")
cc_test(
name = "hexagon_" + parts[0],
srcs = [src],
deps = deps,
linkstatic = 1,
tags = [
"no_oss",
"nobuilder",
"notap",
],
)
all_ops_test_name = "hexagon_op_tests_all"
cc_test(
name = all_ops_test_name,
srcs = srcs,
deps = deps,
linkstatic = 1,
tags = [
"no_oss",
"nobuilder",
"notap",
],
)
tflite_hexagon_mobile_test(all_ops_test_name)
@@ -0,0 +1,311 @@
/* Copyright 2020 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 <initializer_list>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
template <typename InputType>
class QuantizedTransposeConvOpModel : public SingleOpModelWithHexagon {
public:
QuantizedTransposeConvOpModel(std::initializer_list<int> output_shape_data,
const TensorData& filter,
std::initializer_list<InputType> filter_data,
const TensorData& input,
const TensorData& output, Padding padding,
int stride_w, int stride_h,
bool add_bias = false) {
// Just to be confusing, transpose_conv has an _input_ named "output_shape"
// that sets the shape of the output tensor of the op :). It must always be
// an int32 1D four element tensor.
output_shape_ = AddConstInput(TensorType_INT32, output_shape_data, {4});
filter_ = AddConstInput(filter, filter_data);
input_ = AddInput(input);
if (add_bias) {
int bias_size = GetShape(filter_)[0];
if (input.type == TensorType_INT8) {
// per channel quantization.
std::vector<float> bias_scale(
filter.per_channel_quantization_scales.size());
std::vector<int64_t> bias_zero_points(
filter.per_channel_quantization_scales.size());
for (size_t i = 0; i < filter.per_channel_quantization_scales.size();
++i) {
bias_scale[i] =
input.scale * filter.per_channel_quantization_scales[i];
bias_zero_points[i] = 0;
}
TensorData bias{TensorType_INT32,
{bias_size},
/*min=*/0,
/*max=*/0,
/*scale=*/0,
/*zero_point=*/0,
true,
/*per_channel_quantization_scales=*/bias_scale,
/*per_channel_quantization_offsets=*/bias_zero_points,
/*channel_index==*/0};
bias_ = AddInput(bias);
} else {
// per tensor quantization.
auto bias_scale = GetScale(input_) * GetScale(filter_);
TensorData bias{TensorType_INT32, {bias_size}, 0, 0, bias_scale};
bias_ = AddInput(bias);
}
}
output_ = AddOutput(output);
SetBuiltinOp(
BuiltinOperator_TRANSPOSE_CONV, BuiltinOptions_TransposeConvOptions,
CreateTransposeConvOptions(builder_, padding, stride_w, stride_h)
.Union());
BuildInterpreter(
{GetShape(output_shape_), GetShape(filter_), GetShape(input_)});
}
void SetInput(std::initializer_list<float> data) {
QuantizeAndPopulate<InputType>(input_, data);
}
void SetBias(std::initializer_list<float> bias) {
if (std::is_same<InputType, uint8_t>::value) {
QuantizeAndPopulate<int32_t>(bias_, bias);
} else if (std::is_same<InputType, int8_t>::value) {
PerChannelQuantizeBias(bias_, bias);
}
// Set allocation type to MmapRo to simulate a 'constant' tensor.
auto* bias_tensor = interpreter_->tensor(bias_);
bias_tensor->allocation_type = kTfLiteMmapRo;
}
std::vector<float> GetDequantizedOutput() {
return Dequantize<InputType>(ExtractVector<InputType>(output_),
GetScale(output_), GetZeroPoint(output_));
}
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
protected:
int output_shape_;
int filter_;
int input_;
int bias_;
int output_;
};
TEST(QuantizedTransposeConvOpModel, SimpleTestQuantized) {
// Float would be {1, 2, 3, 4, 5, 6, 7, 8, 9}
std::initializer_list<uint8_t> filter_data = {129, 131, 133, 135, 137,
139, 141, 143, 145};
QuantizedTransposeConvOpModel<uint8_t> model(
{1, 4, 4, 1}, {TensorType_UINT8, {1, 3, 3, 1}, -63.5, 64}, filter_data,
{TensorType_UINT8, {1, 4, 4, 1}, -63.5, 64},
{TensorType_UINT8, {}, -508, 512}, Padding_SAME, 1, 1);
model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(
model.GetDequantizedOutput(),
ElementsAreArray(ArrayFloatNear({28, 64, 84, 76, 100, 192, 236, 200, 208,
372, 416, 332, 264, 448, 484, 364},
1e-5)));
// GetOutputShape() should always be same as model.SetOutputShape(...);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
}
TEST(QuantizedTransposeConvOpModel, PaddingValidTestQuantized) {
// Float would be {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
// 18}
std::initializer_list<uint8_t> filter_data = {129, 131, 133, 135, 137, 139,
141, 143, 145, 147, 149, 151,
153, 155, 157, 159, 161, 163};
QuantizedTransposeConvOpModel<uint8_t> model(
{1, 6, 6, 1}, {TensorType_UINT8, {1, 3, 3, 2}, -63.5, 64}, filter_data,
{TensorType_UINT8, {1, 4, 4, 2}, -63.5, 64},
{TensorType_UINT8, {}, -4064, 4096}, Padding_VALID, 1, 1);
model.SetInput({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, 28, 29, 30, 31, 32});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetDequantizedOutput(),
ElementsAreArray(ArrayFloatNear(
{0, 32, 64, 96, 128, 96, 64, 192, 416,
576, 544, 352, 224, 672, 1344, 1696, 1440, 864,
608, 1504, 2720, 3072, 2432, 1440, 864, 1984, 3360,
3648, 2752, 1536, 704, 1536, 2528, 2720, 2016, 1088},
1e-5)));
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 6, 6, 1}));
}
TEST(QuantizedTransposeConvOpModel, TwoFiltersTestQuantized) {
// Float would be {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
// 18}
std::initializer_list<uint8_t> filter_data = {129, 131, 133, 135, 137, 139,
141, 143, 145, 147, 149, 151,
153, 155, 157, 159, 161, 163};
QuantizedTransposeConvOpModel<uint8_t> model(
{1, 4, 4, 1}, {TensorType_UINT8, {1, 3, 3, 2}, -63.5, 64}, filter_data,
{TensorType_UINT8, {1, 4, 4, 2}, -63.5, 64},
{TensorType_UINT8, {}, -4064, 4096}, Padding_SAME, 1, 1);
model.SetInput({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, 28, 29, 30, 31, 32});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetDequantizedOutput(),
ElementsAreArray(ArrayFloatNear(
{192, 416, 576, 544, 672, 1344, 1696, 1440, 1504, 2720, 3072,
2432, 1984, 3360, 3648, 2752},
1e-5)));
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
}
TEST(QuantizedTransposeConvOpModel,
SimpleTestQuantizedPerChannelSingleChannel) {
const std::initializer_list<int8_t> filter_data = {14, 28, 42, 56, 71,
85, 99, 113, 127};
QuantizedTransposeConvOpModel<int8_t> model(
{1, 4, 4, 1},
{TensorType_INT8, {1, 3, 3, 1}, 0, 0, 0, 0, true, {9.0 / 127}, {0}, 0},
filter_data, {TensorType_INT8, {1, 4, 4, 1}, 0, 0, 16.0 / 255, -128},
{TensorType_INT8, {}, 0, 0, 2, -128}, Padding_SAME, 1, 1);
model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(
model.GetDequantizedOutput(),
ElementsAreArray(ArrayFloatNear({28, 62, 82, 76, 98, 192, 236, 198, 206,
372, 416, 330, 262, 446, 486, 366},
1e-5)));
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
}
TEST(QuantizedTransposeConvOpModel, TestQuantizedPerChannelMultiChannel) {
const std::initializer_list<int8_t> filter_data = {
7, 22, 37, 52, 67, 82, 97, 112, 127,
14, 28, 42, 56, 71, 85, 99, 113, 127};
QuantizedTransposeConvOpModel<int8_t> model(
{1, 5, 5, 2},
{TensorType_INT8,
{2, 3, 3, 1},
0,
0,
0,
0,
true,
{17.0 / 127, 18.0 / 127},
{0, 0},
0},
filter_data, {TensorType_INT8, {1, 2, 2, 1}, 0, 0, 4.0 / 255, -128},
{TensorType_INT8, {}, 0, 0, 1, -128}, Padding_VALID, 2, 2);
model.SetInput({1, 2, 3, 4});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(
model.GetDequantizedOutput(),
ElementsAreArray(ArrayFloatNear(
{1, 2, 3, 4, 7, 10, 6, 8, 10, 12, 7, 8, 9, 10, 25, 28, 18,
20, 22, 24, 16, 20, 24, 28, 62, 72, 42, 48, 54, 60, 21, 24, 27, 30,
61, 68, 36, 40, 44, 48, 39, 42, 45, 48, 103, 110, 60, 64, 68, 72},
1e-5)));
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 5, 5, 2}));
}
TEST(QuantizedTransposeConvOpModel, SimpleBiasQuantized) {
const std::initializer_list<uint8_t> filter_data = {129, 131, 133, 135, 137,
139, 141, 143, 145};
QuantizedTransposeConvOpModel<uint8_t> model(
{1, 4, 4, 1}, {TensorType_UINT8, {1, 3, 3, 1}, -63.5, 64}, filter_data,
{TensorType_UINT8, {1, 4, 4, 1}, -63.5, 64},
{TensorType_UINT8, {}, -508, 512}, Padding_SAME, 1, 1,
/*add_bias=*/true);
model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
model.SetBias({1});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(
model.GetDequantizedOutput(),
ElementsAreArray(ArrayFloatNear({32, 64, 84, 76, 100, 192, 240, 200, 208,
372, 420, 332, 264, 448, 488, 368},
1e-5)));
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
}
TEST(QuantizedTransposeConvOpModel, PerChannelQuantizedBiasSingleChannel) {
const std::initializer_list<int8_t> filter_data = {14, 28, 42, 56, 70,
84, 98, 112, 126};
QuantizedTransposeConvOpModel<int8_t> model(
{1, 4, 4, 1},
{TensorType_INT8, {1, 3, 3, 1}, 0, 0, 0, 0, true, {9.0 / 127}, {0}, 0},
filter_data, {TensorType_INT8, {1, 4, 4, 1}, 0, 0, 16.0 / 255, -128},
{TensorType_INT8, {}, 0, 0, 2, -128}, Padding_SAME, 1, 1,
/*add_bias=*/true);
model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
model.SetBias({1});
model.ApplyDelegateAndInvoke();
EXPECT_THAT(
model.GetDequantizedOutput(),
ElementsAreArray(ArrayFloatNear({30, 62, 84, 76, 100, 192, 236, 198, 206,
370, 414, 328, 262, 442, 482, 362},
1e-5)));
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
}
TEST(QuantizedTransposeConvOpModel, PerChannelQuantizedBiasMultiChannel) {
const std::initializer_list<int8_t> filter_data = {
7, 22, 37, 52, 67, 82, 97, 112, 127,
14, 28, 42, 56, 71, 85, 99, 113, 127};
QuantizedTransposeConvOpModel<int8_t> model(
{1, 5, 5, 2},
{TensorType_INT8,
{2, 3, 3, 1},
0,
0,
0,
0,
true,
{17.0 / 127, 18.0 / 127},
{0, 0},
0},
filter_data, {TensorType_INT8, {1, 2, 2, 1}, 0, 0, 4.0 / 255, -128},
{TensorType_INT8, {}, 0, 0, 1, -128}, Padding_VALID, 2, 2,
/*add_bias=*/true);
model.SetInput({1, 2, 3, 4});
model.SetBias({1});
// Expected output from CPU.
ASSERT_EQ(model.Invoke(), kTfLiteOk);
auto expected_output = model.GetDequantizedOutput();
// Check delegate output.
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetDequantizedOutput(),
ElementsAreArray(ArrayFloatNear(expected_output, 1e-5)));
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 5, 5, 2}));
}
} // namespace tflite
@@ -0,0 +1,177 @@
/* Copyright 2020 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 <initializer_list>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/hexagon/builders/tests/hexagon_delegate_op_model.h"
#include "tensorflow/lite/kernels/internal/runtime_shape.h"
#include "tensorflow/lite/kernels/internal/tensor_ctypes.h"
#include "tensorflow/lite/kernels/internal/types.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
using testing::ElementsAreArray;
class TransposeOpModel : public SingleOpModelWithHexagon {
public:
TransposeOpModel(const TensorData& input,
std::initializer_list<int> perm_shape,
std::initializer_list<int> perm, bool const_perm,
const TensorData& output) {
input_ = AddInput(input);
if (const_perm) {
perm_ = AddConstInput(TensorType_INT32, perm, perm_shape);
} else {
perm_ = AddInput({TensorType_INT32, perm_shape});
}
output_ = AddOutput(output);
SetBuiltinOp(BuiltinOperator_TRANSPOSE, BuiltinOptions_TransposeOptions,
CreateTransposeOptions(builder_).Union());
BuildInterpreter({GetShape(input_)});
if (!const_perm) {
PopulateTensor<int>(perm_, perm);
}
}
template <typename integer_type>
void SetInput(const std::vector<integer_type>& data) {
PopulateTensor<integer_type>(input_, data);
}
template <typename integer_type>
std::vector<integer_type> GetOutput() {
return ExtractVector<integer_type>(output_);
}
protected:
int input_;
int perm_;
int output_;
};
template <typename integer_type>
void ComputeExpectedTransposeResult(
const std::vector<int>& shape, const std::vector<int>& perms,
std::vector<integer_type>* input,
std::vector<integer_type>* input_transposed) {
// Count elements and allocate output.
int count = 1;
for (auto factor : shape) count *= factor;
input_transposed->resize(count);
// Create the dummy data
(*input).resize(count);
for (int i = 0; i < count; i++) {
(*input)[i] = i;
}
// Make input and output shapes.
const RuntimeShape input_shape = ::tflite::GetTensorShape(shape);
RuntimeShape output_shape(perms.size());
for (int i = 0; i < perms.size(); i++) {
output_shape.SetDim(i, input_shape.Dims(perms[i]));
}
TransposeParams params;
params.perm_count = perms.size();
for (int i = 0; i < perms.size(); ++i) {
params.perm[i] = perms[i];
}
reference_ops::Transpose<integer_type>(params, input_shape, input->data(),
output_shape,
input_transposed->data());
}
TEST(TransposeOpTest, Test1D_UInt8) {
// Basic 1D identity.
std::vector<uint8_t> expected_output, input;
std::vector<int> input_shape = {3};
ComputeExpectedTransposeResult(input_shape, {0}, &input, &expected_output);
TransposeOpModel model({TensorType_UINT8, input_shape, -10, 10}, {1}, {0},
true, {TensorType_UINT8, {}, -10, 10});
model.SetInput<uint8_t>(input);
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutput<uint8_t>(), ElementsAreArray(expected_output));
}
TEST(TransposeOpTest, Test1D_Int8) {
// Basic 1D identity.
std::vector<int8_t> expected_output, input;
std::vector<int> input_shape = {3};
ComputeExpectedTransposeResult(input_shape, {0}, &input, &expected_output);
TransposeOpModel model({TensorType_INT8, input_shape, -10, 10}, {1}, {0},
true, {TensorType_INT8, {}, -10, 10});
model.SetInput<int8_t>(input);
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutput<int8_t>(), ElementsAreArray(expected_output));
}
TEST(TransposeOpTest, Test2D_UInt8) {
std::vector<uint8_t> expected_output, input;
std::vector<int> input_shape = {3, 2};
std::vector<int> perm = {1, 0};
ComputeExpectedTransposeResult(input_shape, perm, &input, &expected_output);
TransposeOpModel model({TensorType_UINT8, input_shape, -10, 10}, {2}, {1, 0},
true, {TensorType_UINT8, {}, -10, 10});
model.SetInput<uint8_t>(input);
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutput<uint8_t>(), ElementsAreArray(expected_output));
}
TEST(TransposeOpTest, Test2D_Int8) {
std::vector<int8_t> expected_output, input;
std::vector<int> input_shape = {3, 2};
std::vector<int> perm = {1, 0};
ComputeExpectedTransposeResult(input_shape, perm, &input, &expected_output);
TransposeOpModel model({TensorType_INT8, input_shape, -10, 10}, {2}, {1, 0},
true, {TensorType_INT8, {}, -10, 10});
model.SetInput<int8_t>(input);
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutput<int8_t>(), ElementsAreArray(expected_output));
}
TEST(TransposeOpTest, Test4D_UInt8) {
std::vector<uint8_t> expected_output, input;
std::vector<int> input_shape = {2, 2, 3, 1};
std::vector<int> perm = {3, 0, 1, 2};
ComputeExpectedTransposeResult(input_shape, perm, &input, &expected_output);
TransposeOpModel model({TensorType_UINT8, input_shape, -10, 10}, {4},
{3, 0, 1, 2}, true, {TensorType_UINT8, {}, -10, 10});
model.SetInput<uint8_t>(input);
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutput<uint8_t>(), ElementsAreArray(expected_output));
}
TEST(TransposeOpTest, Test4D_Int8) {
std::vector<int8_t> expected_output, input;
std::vector<int> input_shape = {2, 2, 3, 1};
std::vector<int> perm = {3, 0, 1, 2};
ComputeExpectedTransposeResult(input_shape, perm, &input, &expected_output);
TransposeOpModel model({TensorType_INT8, input_shape, -10, 10}, {4},
{3, 0, 1, 2}, true, {TensorType_INT8, {}, -10, 10});
model.SetInput<int8_t>(input);
model.ApplyDelegateAndInvoke();
EXPECT_THAT(model.GetOutput<int8_t>(), ElementsAreArray(expected_output));
}
} // namespace tflite