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
+182
View File
@@ -0,0 +1,182 @@
# Copyright 2019 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.
# ==============================================================================
load("@bazel_skylib//lib:selects.bzl", "selects")
load(
"@bazel_skylib//rules:common_settings.bzl",
"bool_flag",
)
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
load("//tensorflow/lite:build_def.bzl", "tflite_copts", "tflite_linkopts")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = [
"//visibility:public",
],
licenses = ["notice"],
)
# If --define EXPLICIT_DISABLE_HEXAGON=1 is passed, the Hexagon delegate will be disabled.
config_setting(
name = "explicit_disable_hexagon_define",
define_values = {
"EXPLICIT_DISABLE_HEXAGON": "1",
},
)
bool_flag(
name = "explicit_disable_hexagon",
build_setting_default = False,
)
config_setting(
name = "explicit_disable_hexagon_flag",
flag_values = {
":explicit_disable_hexagon": "True",
},
)
# Set if either the explicit_disable_hexagon define or the flag is set.
selects.config_setting_group(
name = "explicit_disable_hexagon_setting",
match_any = [
":explicit_disable_hexagon_define",
":explicit_disable_hexagon_flag",
],
)
cc_library(
name = "hexagon_implementation",
srcs = ["hexagon_implementation.cc"],
hdrs = [
"hexagon_implementation.h",
"hexagon_nn_interface.h",
],
tags = [
"manual",
"nobuilder",
],
deps = [
"//tensorflow/lite:minimal_logging",
"//tensorflow/lite/delegates/hexagon/hexagon_nn:hexagon_nn_header",
"//tensorflow/lite/kernels/internal:compatibility",
],
)
cc_library(
name = "hexagon_delegate_kernel",
srcs = [
"hexagon_delegate.h",
"hexagon_delegate_kernel.cc",
],
hdrs = ["hexagon_delegate_kernel.h"],
tags = [
"manual",
"nobuilder",
],
deps = [
":hexagon_implementation",
":utils",
"//tensorflow/lite:kernel_api",
"//tensorflow/lite/core/api",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/delegates/hexagon/builders:op_builder",
"//tensorflow/lite/delegates/hexagon/hexagon_nn:hexagon_nn_header",
"//tensorflow/lite/delegates/utils:simple_delegate",
"//tensorflow/lite/schema:schema_fbs",
"@hexagon_nn//:hexagon_nn_header",
"@hexagon_nn//:hexagon_nn_ops",
],
)
cc_library(
name = "enable_hexagon_delegate",
defines = select({
"//tensorflow:arm_any": ["TFLITE_ENABLE_HEXAGON"],
"//conditions:default": [],
}),
)
cc_library(
name = "hexagon_delegate",
srcs = select({
":explicit_disable_hexagon_setting": [],
"//tensorflow:windows": [],
"//conditions:default": ["hexagon_delegate.cc"],
}),
hdrs = select({
":explicit_disable_hexagon_setting": [],
"//tensorflow:windows": [],
"//conditions:default": ["hexagon_delegate.h"],
}),
tags = [
"manual",
"nobuilder",
],
deps = select({
":explicit_disable_hexagon_setting": [],
"//tensorflow:windows": [],
"//conditions:default": [
":hexagon_delegate_kernel",
":hexagon_implementation",
":utils",
"//tensorflow/lite:kernel_api",
"//tensorflow/lite:minimal_logging",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/delegates/utils:simple_delegate",
],
}) + select({
":explicit_disable_hexagon_setting": [],
"//tensorflow:ios": [],
"//tensorflow:ios_x86_64": [],
"//tensorflow:macos": [],
"//tensorflow:windows": [],
"//conditions:default": [":enable_hexagon_delegate"],
}),
)
cc_library(
name = "utils",
srcs = ["utils.cc"],
hdrs = ["utils.h"],
copts = tflite_copts(),
tags = [
"manual",
"nobuilder",
],
deps = [
"//tensorflow/lite:kernel_api",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/kernels:kernel_util",
],
)
cc_test(
name = "utils_test",
srcs = ["utils_test.cc"],
linkopts = tflite_linkopts() + ["-lm"],
deps = [
":utils",
"//tensorflow/lite/core/c:common",
"@com_google_googletest//:gtest_main",
],
)
exports_files([
"hexagon_delegate.h",
"version_script.lds",
])
+114
View File
@@ -0,0 +1,114 @@
# Hexagon Delegate
Delegate which uses Hexagon SDK to delegate the processing to QC DSP.
Note that we only support quantized models, since the DSP is efficient
with quantized versions. So all op support is for quantized versions.
For more detailed usage and examples check the [user guide.](https://www.tensorflow.org/lite/performance/hexagon_delegate)
Usage:
- Add dependency on hexagon_delegate rule.
- Code change example:
```
#include "tensorflow/lite/delegates/hexagon/hexagon_delegate.h"
// Assuming shared libraries are under "/data/local/tmp/"
// If files are packaged with native lib in android App then it
// will typically be equivalent to the path provided by
// "getContext().getApplicationInfo().nativeLibraryDir"
const char[] library_directory_path = "/data/local/tmp/";
TfLiteHexagonInitWithPath(library_directory_path); // Needed once at startup.
::tflite::TfLiteHexagonDelegateOptions params = {0};
// 'delegate_ptr' Need to outlive the interpreter. For example,
// If use case will need to resize input or anything that can trigger
// re-applying delegates then 'delegate_ptr' need to outlive the interpreter.
auto* delegate_ptr = ::tflite::TfLiteHexagonDelegateCreate(&params);
Interpreter::TfLiteDelegatePtr delegate(delegate_ptr,
[](TfLiteDelegate* delegate) {
::tflite::TfLiteHexagonDelegateDelete(delegate);
});
interpreter->ModifyGraphWithDelegate(delegate.get());
// IMPORTANT: AllocateTensors can be called only after ModifyGraphWithDelegate
TfLiteHexagonTearDown(); // Needed once at end of app/DSP usage.
```
* Shared libraries:
- 'libhexagon_interface.so' which holds the interface that the delegate uses.
It must be available if you linked the hexagon_delegate library to TFLite.
You can load it either from shell by overriding
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"path to the so",
or add it inside your apk in a way it is available.
- 'libhexagon_nn_skel(_v65/_v66).so' which holds the DSP code.
Use TfLiteHexagonInitWithPath(..) and provide the path to the directory
which holds the shared libraries for the Hexagon NN on device.
If you're using TfLiteHexagonInit() then
You will need to set environment variable "ADSP_LIBRARY_PATH" to
"path_to_the_lib";/system/lib/rfsa/adsp;/system/vendor/lib/rfsa/adsp;/dsp
Note that separator here is ';' not ':'
You can push all 3 files, and the library will pick the one needed based
on the runtime. Or if you are sure of what you will use on the device then
push only one of them.
## Supported Ops
Hexagon only supports ops that have inputs/outputs of <= 4 dimensions.
The following operations have been implemented, with a few constraints that
are verified in `IsNodeSupportedByHexagon`:
* Add (Support relu activations)
* ArgMax
* ArgMin
* AveragePool2D:
* Constraints:
- No Activation
* Concat
* Conv2D:
* Constraints:
- stride width/height <= 3
* DepthToSpace
* DepthwiseConv2D:
* Constraints:
- Filter height >= 2
- depth_multiplier == 1
- dilation only supported when stride == 1
- Otherwise, stride height/width <= 3
* FullyConnected
* Hardswish
* L2Normalization (without any activation)
* Logistic (aka Sigmoid)
* Maximum
* MaxPool2D (without any activation) (b/129276536)
* Mean
* Minimum
* MirrorPad
* Mul (Support relu activations)
* Neg
* Pack
* Pad: Only supports 0 padding (b/139277813)
* Quantize (8-bit inputs & outputs only)
* Relu
* Relu6
* Reshape
* Resize Bilinear:
* Constraints:
- Requested size <= 65 (b/143105433)
* Resize Nearest Neighbor
* Rsqrt
* Slice
* SoftMax
* SpaceToDepth
* Split
* SquaredDifference
* Strided Slice
* Sub (Support relu activations)
* Tanh
* Transpose
* TransposeConv2D:
* Constraints:
- stride height/width <= 3
- dilation height/width == 1
@@ -0,0 +1,111 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = [
"//visibility:public",
],
licenses = ["notice"],
)
cc_library(
name = "op_builder",
srcs = [
"activation_builder.cc",
"arg_min_max_builder.cc",
"arithmetic_builder.cc",
"batch_seq_builder.cc",
"cast_builder.cc",
"concat_builder.cc",
"conv_2d_builder.cc",
"conv_2d_helpers.cc",
"hardswish_builder.cc",
"l2_normalization_builder.cc",
"matmul_builder.cc",
"min_max_builder.cc",
"mirror_pad_builder.cc",
"neg_op_builder.cc",
"op_builder.cc",
"pack_builder.cc",
"pad_builder.cc",
"pool_2d_builder.cc",
"quantize_builder.cc",
"reduce_builder.cc",
"reshape_builder.cc",
"resize_bilinear_builder.cc",
"resize_nearest_neighbor_builder.cc",
"rsqrt_builder.cc",
"slice_builder.cc",
"softmax_builder.cc",
"space_to_depth_builder.cc",
"split_builder.cc",
"squared_difference.cc",
"strided_slice_builder.cc",
"transpose_builder.cc",
"transpose_conv_2d_builder.cc",
],
hdrs = [
"activation_builder.h",
"arg_min_max_builder.h",
"arithmetic_builder.h",
"batch_seq_builder.h",
"cast_builder.h",
"concat_builder.h",
"conv_2d_builder.h",
"hardswish_builder.h",
"l2_normalization_builder.h",
"matmul_builder.h",
"min_max_builder.h",
"mirror_pad_builder.h",
"neg_op_builder.h",
"op_builder.h",
"pack_builder.h",
"pad_builder.h",
"pool_2d_builder.h",
"quantize_builder.h",
"reduce_builder.h",
"reshape_builder.h",
"resize_bilinear_builder.h",
"resize_nearest_neighbor_builder.h",
"slice_builder.h",
"softmax_builder.h",
"space_to_depth_builder.h",
"split_builder.h",
"strided_slice_builder.h",
"transpose_builder.h",
"transpose_conv_2d_builder.h",
],
tags = [
"manual",
"nobuilder",
],
deps = [
":op_factory",
"//tensorflow/lite:kernel_api",
"//tensorflow/lite:util",
"//tensorflow/lite/c:c_api_types",
"//tensorflow/lite/c:common",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/delegates/hexagon:hexagon_implementation",
"//tensorflow/lite/delegates/hexagon/hexagon_nn:hexagon_nn_header",
"//tensorflow/lite/kernels:kernel_util",
"//tensorflow/lite/kernels:padding",
"//tensorflow/lite/kernels/internal:optimized_base",
"//tensorflow/lite/kernels/internal:tensor",
"//tensorflow/lite/kernels/internal:types",
"@farmhash_archive//:farmhash",
"@hexagon_nn//:hexagon_nn_ops",
],
)
cc_library(
name = "op_factory",
hdrs = ["op_factory.h"],
tags = [
"manual",
"nobuilder",
],
deps = [
"//tensorflow/lite/core/c:common",
],
)
@@ -0,0 +1,74 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/activation_builder.h"
#include <stdint.h>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus ActivationOpBuilder::PopulateSubGraph(
const TfLiteIntArray* inputs, const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Input data tensor.
int tensor_id = inputs->data[0];
const auto& input_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input_tensor));
if (op_node_.op_type == OP_QuantizedReluX_8) {
auto* relu_value_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&relu_value_),
sizeof(relu_value_));
AddInput(TensorID(relu_value_const->GetID(), 0));
}
// Hexagon outputs for this node.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, {1, 1, 1, 1});
AddOutput(sizeof(float), 4, {1, 1, 1, 1});
return kTfLiteOk;
}
TfLiteStatus ActivationOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
ActivationOpBuilder::~ActivationOpBuilder() {}
OpBuilder* CreateActivationBuilder(GraphBuilder* graph_builder, int op_type) {
return new ActivationOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,51 @@
/* Copyright 2019 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_ACTIVATION_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_ACTIVATION_BUILDER_H_
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class ActivationOpBuilder : public OpBuilder {
public:
explicit ActivationOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
explicit ActivationOpBuilder(GraphBuilder* graph_builder, int op_type,
int relu_value)
: OpBuilder(graph_builder, op_type), relu_value_(relu_value) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~ActivationOpBuilder() override;
private:
TensorID node_output_;
float relu_value_ = 6;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_ACTIVATION_BUILDER_H_
@@ -0,0 +1,90 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/arg_min_max_builder.h"
#include <cstddef>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus ArgMinMaxOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
if (inputs->size != 2) {
TF_LITE_KERNEL_LOG(context, "Expecting 2 inputs %d != 2\n", inputs->size);
return kTfLiteError;
}
// Input data tensor.
int input_tensor_id = inputs->data[0];
const auto& input_tensor = context->tensors[input_tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(input_tensor_id));
// Axis tensor.
const int axis_tensor_id = inputs->data[1];
const auto& axis = context->tensors[axis_tensor_id];
if (axis.allocation_type != kTfLiteMmapRo) {
TF_LITE_KERNEL_LOG(context,
"Axis tensor doesn't have correct allocation type: %s",
axis.name);
return kTfLiteError;
}
int axis_value = axis.data.i32[0];
if (axis_value < 0) {
axis_value += input_tensor.dims->size;
}
auto* input_axis_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&axis_value), sizeof(int));
AddInput(TensorID(input_axis_const->GetID(), 0));
// Compute Min/Max
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input_tensor));
// Output Node
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
size_t output_element_size = 0;
TF_LITE_ENSURE_STATUS(GetSizeOfType(
context, context->tensors[outputs->data[0]].type, &output_element_size));
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
node_output_ = AddOutput(output_element_size, 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
return kTfLiteOk;
}
TfLiteStatus ArgMinMaxOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
ArgMinMaxOpBuilder::~ArgMinMaxOpBuilder() {}
OpBuilder* CreateArgMinMaxOpBuilder(GraphBuilder* graph_builder, int op_type) {
return new ArgMinMaxOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,45 @@
/* Copyright 2019 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_ARG_MIN_MAX_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_ARG_MIN_MAX_BUILDER_H_
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class ArgMinMaxOpBuilder : public OpBuilder {
public:
explicit ArgMinMaxOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~ArgMinMaxOpBuilder() override;
private:
TensorID node_output_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_ARG_MIN_MAX_BUILDER_H_
@@ -0,0 +1,132 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/arithmetic_builder.h"
#include <stdint.h>
#include "hexagon/hexagon_nn_ops.h"
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus ArithmeticOpBuilder::PopulateSubGraph(
const TfLiteIntArray* inputs, const TfLiteIntArray* outputs,
TfLiteContext* context) {
TF_LITE_ENSURE(context, inputs != nullptr);
TF_LITE_ENSURE(context, outputs != nullptr);
TF_LITE_ENSURE(context, inputs->size >= 2);
TF_LITE_ENSURE(context, outputs->size >= 1);
// First input data tensor.
int tensor_id = inputs->data[0];
TF_LITE_ENSURE(context, tensor_id >= 0 && tensor_id < context->tensors_size);
const auto& input1_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
// Second input data tensor.
tensor_id = inputs->data[1];
TF_LITE_ENSURE(context, tensor_id >= 0 && tensor_id < context->tensors_size);
const auto& input2_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
// Inputs min/max
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input1_tensor));
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input2_tensor));
// Output details.
TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues(
context->tensors[outputs->data[0]], &output_min_, &output_max_));
auto* output_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&output_min_), sizeof(output_min_));
auto* output_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&output_max_), sizeof(output_max_));
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
if (op_node_.op_type == OP_QuantizedAdd_8p8to8 && output_max_ != 0) {
// Hexagon's QuantizedAdd supports output min/max as input.
AddInput(TensorID(output_min_const->GetID(), 0));
AddInput(TensorID(output_max_const->GetID(), 0));
}
if (op_node_.op_type == OP_QuantizedMul_8x8to32) {
const auto& math_out = AddOutput(sizeof(int), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
const auto& math_out_min = AddOutput(sizeof(float), 4, kScalarShape);
const auto& math_out_max = AddOutput(sizeof(float), 4, kScalarShape);
auto* requantize_op = graph_builder_->AddNode(GetTFLiteNodeID());
requantize_op->SetOpType(OP_Requantize_32to8);
requantize_op->AddInput(math_out);
requantize_op->AddInput(math_out_min);
requantize_op->AddInput(math_out_max);
requantize_op->AddInput(TensorID(output_min_const->GetID(), 0));
requantize_op->AddInput(TensorID(output_max_const->GetID(), 0));
node_output_ =
requantize_op->AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
requantize_op->AddOutput(sizeof(float), 4, kScalarShape);
requantize_op->AddOutput(sizeof(float), 4, kScalarShape);
} else {
auto result_out = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
auto result_min = AddOutput(sizeof(float), 4, kScalarShape);
auto result_max = AddOutput(sizeof(float), 4, kScalarShape);
auto* requantize_op = graph_builder_->AddNode(GetTFLiteNodeID());
requantize_op->SetOpType(OP_Requantize_8to8);
requantize_op->AddInput(result_out);
requantize_op->AddInput(result_min);
requantize_op->AddInput(result_max);
requantize_op->AddInput(TensorID(output_min_const->GetID(), 0));
requantize_op->AddInput(TensorID(output_max_const->GetID(), 0));
node_output_ =
requantize_op->AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
requantize_op->AddOutput(sizeof(float), 4, kScalarShape);
requantize_op->AddOutput(sizeof(float), 4, kScalarShape);
}
return kTfLiteOk;
}
TfLiteStatus ArithmeticOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
TF_LITE_ENSURE(context, outputs != nullptr);
TF_LITE_ENSURE(context, outputs->size >= 1);
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
ArithmeticOpBuilder::~ArithmeticOpBuilder() {}
OpBuilder* CreateArithmeticBuilder(GraphBuilder* graph_builder, int op_type) {
return new ArithmeticOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,48 @@
/* Copyright 2019 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_ARITHMETIC_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_ARITHMETIC_BUILDER_H_
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class ArithmeticOpBuilder : public OpBuilder {
public:
explicit ArithmeticOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~ArithmeticOpBuilder() override;
private:
TensorID node_output_;
float output_min_, output_max_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_ARITHMETIC_BUILDER_H_
@@ -0,0 +1,69 @@
/* 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 "tensorflow/lite/delegates/hexagon/builders/batch_seq_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus BatchSeqBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Add config input.
static const int config_shape[] = {1, 1, 1, 3};
// TODO(b/152562126): Allow custom setting for BQ (preferred batch multiple),
// and Options.
// BQ is preferred batch multiple
// Options is currently 0 or 1, 0 is default and batches
// will run in increasing order, this behavior can be disabled by setting 1.
// Refer to Hexagon NN docs for more details.
int config[] = {max_size_for_batch_, 1, 0};
auto* input_config = graph_builder_->AddConstNodeWithData(
config_shape, reinterpret_cast<char*>(&config), sizeof(int) * 3);
AddInput(TensorID(input_config->GetID(), 0));
// Add Input batch details.
const int input_batch_dims_shape[] = {1, 1, 1, input_batch_dims_->size};
auto* input_batch_dims_node = graph_builder_->AddConstNodeWithData(
input_batch_dims_shape, reinterpret_cast<char*>(input_batch_dims_->data),
sizeof(input_batch_dims_[0]) * input_batch_dims_->size);
AddInput(TensorID(input_batch_dims_node->GetID(), 0));
// Add Output batch details.
const int output_batch_dims_shape[] = {1, 1, 1, output_batch_dims_->size};
auto* output_batch_dims_node = graph_builder_->AddConstNodeWithData(
output_batch_dims_shape,
reinterpret_cast<char*>(output_batch_dims_->data),
sizeof(output_batch_dims_[0]) * output_batch_dims_->size);
AddInput(TensorID(output_batch_dims_node->GetID(), 0));
return kTfLiteOk;
}
OpBuilder* CreateBatchSeqBuilder(GraphBuilder* graph_builder, int op_type,
int max_size_for_batch,
TfLiteIntArray* input_batch_dimensions,
TfLiteIntArray* output_batch_dimensions) {
auto* builder = new BatchSeqBuilder(graph_builder, op_type);
builder->SetMaxSizeForBatch(max_size_for_batch);
builder->SetInputBatchDimensions(input_batch_dimensions);
builder->SetOutputBatchDimensions(output_batch_dimensions);
return builder;
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,69 @@
/* 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_BATCH_SEQ_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_BATCH_SEQ_BUILDER_H_
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class BatchSeqBuilder : public OpBuilder {
public:
explicit BatchSeqBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override {
// BatchSeqConfig doesn't have any outputs.
return kTfLiteOk;
}
void SetMaxSizeForBatch(int max_size_for_batch) {
max_size_for_batch_ = max_size_for_batch;
}
void SetInputBatchDimensions(TfLiteIntArray* input_batch_dimensions) {
input_batch_dims_ = input_batch_dimensions;
}
void SetOutputBatchDimensions(TfLiteIntArray* output_batch_dimensions) {
output_batch_dims_ = output_batch_dimensions;
}
private:
// Maximum size for the batch dimension in a single run.
// The graph can have input with larger batch, internally
// multiple runs will happen each won't have more than 'max_size_for_batch_'
// in batch dimension.
int max_size_for_batch_ = 1;
// Input dimension for each input in the graph.
// Input with fixed batch should have -1.
TfLiteIntArray* input_batch_dims_;
// Output dimension for each output in the graph.
// Output with fixed batch should have -1.
TfLiteIntArray* output_batch_dims_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_BATCH_SEQ_BUILDER_H_
@@ -0,0 +1,90 @@
/* 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 "tensorflow/lite/delegates/hexagon/builders/cast_builder.h"
#include <stdint.h>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus CastOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 tensor that is cast in-place.
if (inputs->size != 1 || outputs->size != 1) {
TF_LITE_KERNEL_LOG(context, "Cast supports a single tensor");
return kTfLiteError;
} else if (inputs->data[0] != outputs->data[0]) {
TF_LITE_KERNEL_LOG(context, "input & output should be same for Cast");
return kTfLiteError;
}
int tensor_id = inputs->data[0];
const auto& tensor = context->tensors[tensor_id];
int batch_size, height_size, width_size, depth_size;
GetDims(&batch_size, &height_size, &width_size, &depth_size, tensor.dims);
float min_value = 0;
float max_value = 0;
if (tensor.quantization.type ==
TfLiteQuantizationType::kTfLiteAffineQuantization) {
// Casting doesn't require min/max, so populate only if available.
TF_LITE_ENSURE_STATUS(
ComputeMinAndMaxQuantValues(tensor, &min_value, &max_value));
}
auto* min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&min_value), sizeof(min_value));
auto* max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&max_value), sizeof(max_value));
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
AddInput(TensorID(min_const->GetID(), 0));
AddInput(TensorID(max_const->GetID(), 0));
node_output_ = AddOutput(sizeof(uint8_t), 4,
{batch_size, height_size, width_size, depth_size});
AddOutput(sizeof(float), 4, {1, 1, 1, 1});
AddOutput(sizeof(float), 4, {1, 1, 1, 1});
return kTfLiteOk;
}
TfLiteStatus CastOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
// Cast tensor already exists in the graph, so we need to overwrite it with
// the new TensorID.
if (!graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second,
/*overwrite*/ true)) {
TF_LITE_KERNEL_LOG(context, "Could not register Cast output.");
return kTfLiteError;
}
return kTfLiteOk;
}
CastOpBuilder::~CastOpBuilder() {}
OpBuilder* CreateCastBuilder(GraphBuilder* graph_builder, int op_type) {
return new CastOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,54 @@
/* 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_CAST_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_CAST_BUILDER_H_
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
// This builder is used to cast int8 input or output tensors to & from uint8
// respectively. No TFLite op converts to this.
// NOTE: There are no explicit tests for this, but is required for all int8 unit
// tests.
class CastOpBuilder : public OpBuilder {
public:
explicit CastOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
// inputs & outputs should contain the *same* (one) TFLite tensor-id, since
// tensors are cast in-place. The tensor will point to a different Hexagon
// TensorID after this runs.
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~CastOpBuilder() override;
private:
TensorID node_output_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_CAST_BUILDER_H_
@@ -0,0 +1,144 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/concat_builder.h"
#include <stdint.h>
#include <limits>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus ConcatOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
const TfLiteConcatenationParams* concat_params =
reinterpret_cast<const TfLiteConcatenationParams*>(builtin_data_);
int concat_axis = concat_params->axis;
const int output_dim_size = context->tensors[outputs->data[0]].dims->size;
// Axis value is incremented if tensor dims are < 4 and/or axis < 0.
concat_axis =
concat_axis < 0 ? concat_axis + 4 : concat_axis + 4 - output_dim_size;
auto* axis_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&concat_axis), sizeof(concat_axis));
AddInput(TensorID(axis_const->GetID(), 0));
int tensor_id;
// Input data tensors.
// input_bound_minimum & input_bound_maximum track the minimum & maximum
// min/max bounds across all inputs.
float input_bound_minimum = std::numeric_limits<float>::max();
float input_bound_maximum = std::numeric_limits<float>::min();
input_minima_.reserve(inputs->size);
input_maxima_.reserve(inputs->size);
for (int i = 0; i < inputs->size; ++i) {
tensor_id = inputs->data[i];
float data_min, data_max;
const auto& data_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
TF_LITE_ENSURE_STATUS(
ComputeMinAndMaxQuantValues(data_tensor, &data_min, &data_max));
input_minima_.push_back(data_min);
input_maxima_.push_back(data_max);
if (data_min < input_bound_minimum) input_bound_minimum = data_min;
if (data_max > input_bound_maximum) input_bound_maximum = data_max;
}
// Minima tensors.
for (int i = 0; i < input_minima_.size(); ++i) {
auto* data_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&input_minima_[i]),
sizeof(input_minima_[i]));
AddInput(TensorID(data_min_const->GetID(), 0));
}
// Maxima tensors.
for (int i = 0; i < input_minima_.size(); ++i) {
auto* data_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&input_maxima_[i]),
sizeof(input_maxima_[i]));
AddInput(TensorID(data_max_const->GetID(), 0));
}
// Hexagon outputs for this node.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
// We requantize the output from concat to the range expected by TFLite.
// Otherwise, we see accuracy issues for cases where the inputs have different
// min/max bounds.
TensorID concat_out = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
const auto& concat_out_min = AddOutput(sizeof(float), 4, {1, 1, 1, 1});
const auto& concat_out_max = AddOutput(sizeof(float), 4, {1, 1, 1, 1});
// Output min/max for requantization.
TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues(
context->tensors[outputs->data[0]], &output_min_, &output_max_));
auto* output_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, (char*)&output_min_, sizeof(output_min_));
auto* output_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, (char*)&output_max_, sizeof(output_max_));
if (output_min_ == input_bound_minimum &&
output_max_ == input_bound_maximum) {
// If the input min/max (across all tensors) is same as the output min/max,
// Hexagon's Requantize causes errors in InceptionV3.
// TODO(b/150137234): Figure out why this is.
node_output_ = concat_out;
} else {
auto* requantize_op = graph_builder_->AddNode(GetTFLiteNodeID());
requantize_op->SetOpType(OP_Requantize_8to8);
requantize_op->AddInput(concat_out);
requantize_op->AddInput(concat_out_min);
requantize_op->AddInput(concat_out_max);
requantize_op->AddInput(TensorID(output_min_const->GetID(), 0));
requantize_op->AddInput(TensorID(output_max_const->GetID(), 0));
node_output_ =
requantize_op->AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
requantize_op->AddOutput(sizeof(float), 4, kScalarShape);
requantize_op->AddOutput(sizeof(float), 4, kScalarShape);
}
return kTfLiteOk;
}
TfLiteStatus ConcatOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
ConcatOpBuilder::~ConcatOpBuilder() {}
OpBuilder* CreateConcatBuilder(GraphBuilder* graph_builder, int op_type) {
return new ConcatOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,50 @@
/* Copyright 2019 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_CONCAT_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_CONCAT_BUILDER_H_
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class ConcatOpBuilder : public OpBuilder {
public:
explicit ConcatOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~ConcatOpBuilder() override;
private:
TensorID node_output_;
std::vector<float> input_minima_;
std::vector<float> input_maxima_;
float output_min_, output_max_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_CONCAT_BUILDER_H_
@@ -0,0 +1,542 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/conv_2d_builder.h"
#include <stdint.h>
#include <cmath>
#include <vector>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
namespace {
// Channel count to split depthwise convolution op.
// See Conv2dOpBuilder.should_split_dwconv_ for details.
constexpr int kDwConv5x5Filt2x2StrideChannelCount = 32;
// Dilated Depthwise Convolution performs SpaceToBatchND & BatchToSpaceND before
// and after the op respectively.
// This helper computes the paddings param for SpaceToBatchND and crops param
// for BatchToSpaceND.
//
// Inspired by tf.nn.with_space_to_batch & tf.required_space_to_batch_paddings.
void ComputeSpaceToBatchParams(int input_height, int input_width,
int weights_height, int weights_width,
const std::vector<int>& dilation_factors_h_w,
const TfLitePadding padding_type,
std::vector<int>* paddings,
std::vector<int>* crops) {
// Base paddings depend on padding applied to the Depthwise Conv op.
// 4-element array: {top, bottom, left, right}.
std::vector<int> base_paddings(4, 0);
if (padding_type == kTfLitePaddingSame) {
const int dilated_weights_h =
dilation_factors_h_w[0] * (weights_height - 1) + 1;
const int dilated_weights_w =
dilation_factors_h_w[1] * (weights_width - 1) + 1;
base_paddings[0] = (dilated_weights_h - 1) / 2;
base_paddings[1] = dilated_weights_h - 1 - (dilated_weights_h - 1) / 2;
base_paddings[2] = (dilated_weights_w - 1) / 2;
base_paddings[3] = dilated_weights_w - 1 - (dilated_weights_w - 1) / 2;
}
// paddings represents {pad_top, pad_bottom, pad_left, pad_right}.
paddings->resize(4, 0);
// crops represents {crop_top, crop_bottom, crop_left, crop_right}.
crops->resize(4, 0);
// Logic for computing paddings & crops follows.
// Taken from tf.required_space_to_batch_paddings, but without array
// operations since we only deal with 2 dimensions.
int pad_start_h = base_paddings[0];
int pad_start_w = base_paddings[2];
int orig_pad_end_h = base_paddings[1];
int orig_pad_end_w = base_paddings[3];
int full_input_h = input_height + pad_start_h + orig_pad_end_h;
int full_input_w = input_width + pad_start_w + orig_pad_end_w;
int pad_end_extra_h =
(dilation_factors_h_w[0] - full_input_h % dilation_factors_h_w[0]) %
dilation_factors_h_w[0];
int pad_end_extra_w =
(dilation_factors_h_w[1] - full_input_w % dilation_factors_h_w[1]) %
dilation_factors_h_w[1];
int pad_end_h = orig_pad_end_h + pad_end_extra_h;
int pad_end_w = orig_pad_end_w + pad_end_extra_w;
// Assign values.
(*paddings)[0] = pad_start_h;
(*paddings)[1] = pad_end_h;
(*paddings)[2] = pad_start_w;
(*paddings)[3] = pad_end_w;
(*crops)[0] = 0;
(*crops)[1] = pad_end_extra_h;
(*crops)[2] = 0;
(*crops)[3] = pad_end_extra_w;
}
// Computes output dimensions for the SpaceToBatchND op used in the dilated
// Depthwise Conv case.
// space_to_batch_paddings should be in format {top, bottom, left, right}.
// These are computed from the documentation for SpaceToBatchND_8's output.
void PopulateSpaceToBatchOutputDims(
int input_batch_size, int input_height_size, int input_width_size,
int input_depth_size, const std::vector<int>& dilation_factors_h_w,
const std::vector<int>& space_to_batch_paddings,
std::vector<int>* space_to_batch_output_dims) {
// Batches.
space_to_batch_output_dims->push_back(
input_batch_size * dilation_factors_h_w[0] * dilation_factors_h_w[1]);
// Height.
space_to_batch_output_dims->push_back((space_to_batch_paddings[0] +
input_height_size +
space_to_batch_paddings[1]) /
dilation_factors_h_w[0]);
// Width.
space_to_batch_output_dims->push_back((space_to_batch_paddings[2] +
input_width_size +
space_to_batch_paddings[3]) /
dilation_factors_h_w[1]);
// Depth.
space_to_batch_output_dims->push_back(input_depth_size);
}
} // namespace
void Conv2dOpBuilder::BuildDilatedDwConv(
const TfLiteIntArray* inputs, const TfLiteTensor& data_tensor,
const TfLiteTensor& output_data_tensor, OpBuilder* data_min_const,
OpBuilder* data_max_const, OpBuilder* conv_output_min_const,
OpBuilder* conv_output_max_const, OpBuilder* stride_node, int stride_height,
const TfLitePadding padding_type, TensorID* output_tensor,
TensorID* output_min_tensor, TensorID* output_max_tensor) {
static std::vector<int> dilation_factors_shape = {1, 1, 1, 2};
static std::vector<int> paddings_shape = {1, 1, 2, 2};
// Output dimensions.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, output_data_tensor.dims);
// For dilated Depthwise Conv, we convert this node into SpaceToBatchND, and
// then chain Supernode & BatchToSpaceND after it.
int input_batch_size, input_height_size, input_width_size, input_depth_size;
GetDims(&input_batch_size, &input_height_size, &input_width_size,
&input_depth_size, data_tensor.dims);
ComputeSpaceToBatchParams(input_height_size, input_width_size,
weight_shape_[0], weight_shape_[1],
dilation_factors_h_w_, padding_type,
&space_to_batch_paddings_, &batch_to_space_crops_);
auto* dilation_factors_const = graph_builder_->AddConstNodeWithData(
dilation_factors_shape.data(),
reinterpret_cast<char*>(dilation_factors_h_w_.data()),
dilation_factors_h_w_.size() * sizeof(stride_height));
auto* paddings_const = graph_builder_->AddConstNodeWithData(
paddings_shape.data(),
reinterpret_cast<char*>(space_to_batch_paddings_.data()),
space_to_batch_paddings_.size() * sizeof(stride_height));
auto* crops_const = graph_builder_->AddConstNodeWithData(
paddings_shape.data(),
reinterpret_cast<char*>(batch_to_space_crops_.data()),
batch_to_space_crops_.size() * sizeof(stride_height));
// 1. SpaceToBatch.
SetOpType(OP_SpaceToBatchND_8);
AddInput(graph_builder_->GetHexagonTensorId(inputs->data[0]));
AddInput(TensorID(dilation_factors_const->GetID(), 0));
AddInput(TensorID(paddings_const->GetID(), 0));
AddInput(TensorID(data_min_const->GetID(), 0));
AddInput(TensorID(data_max_const->GetID(), 0));
std::vector<int> space_to_batch_output_dims;
PopulateSpaceToBatchOutputDims(
input_batch_size, input_height_size, input_width_size, input_depth_size,
dilation_factors_h_w_, space_to_batch_paddings_,
&space_to_batch_output_dims);
TensorID space_to_batch_op_out =
AddOutput(sizeof(uint8_t), 4, space_to_batch_output_dims);
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
// 2. Depthwise Conv.
auto* conv_op = graph_builder_->AddNode(GetTFLiteNodeID());
conv_op->SetOpType(OP_DepthwiseSupernode_8x8p32to8);
conv_op->AddInput(space_to_batch_op_out);
conv_op->AddInput(graph_builder_->GetHexagonTensorId(inputs->data[1]));
conv_op->AddInput(TensorID(data_min_const->GetID(), 0));
conv_op->AddInput(TensorID(data_max_const->GetID(), 0));
conv_op->AddInput(TensorID(weights_min_node_->GetID(), 0));
conv_op->AddInput(TensorID(weights_max_node_->GetID(), 0));
conv_op->AddInput(TensorID(stride_node->GetID(), 0));
conv_op->AddInput(graph_builder_->GetHexagonTensorId(inputs->data[2]));
conv_op->AddInput(TensorID(bias_min_node_->GetID(), 0));
conv_op->AddInput(TensorID(bias_max_node_->GetID(), 0));
conv_op->AddInput(TensorID(conv_output_min_const->GetID(), 0));
conv_op->AddInput(TensorID(conv_output_max_const->GetID(), 0));
if (per_channel_quant_.channel_scales_node != nullptr) {
conv_op->AddInput(
TensorID(per_channel_quant_.channel_scales_node->GetID(), 0));
}
// The padding is handled by the SpaceToBatch/BatchToSpace ops surrounding
// this node. Hence, this op's padding remains VALID only.
// tf.nn.with_space_to_batch's docs state the following pattern:
// """
// batch_to_space_nd(
// op(space_to_batch_nd(input, adjusted_dilation_rate, adjusted_paddings),
// num_spatial_dims,
// "VALID")
// adjusted_dilation_rate,
// adjusted_crops)
// """
conv_op->SetPaddingType(NN_PAD_VALID);
// These dimensions are probably a little excessive, but they upper-bound
// the possible output from DepthwiseConv.
// TODO(b/139955809): Find better bounds?
TensorID conv_output = conv_op->AddOutput(
sizeof(uint8_t), 4,
{output_batch_size * dilation_factors_h_w_[0] * dilation_factors_h_w_[1],
output_height_size, output_width_size, output_depth_size});
conv_op->AddOutput(sizeof(float), 4, kScalarShape);
conv_op->AddOutput(sizeof(float), 4, kScalarShape);
// 3. BatchToSpace.
auto* batch_to_space_op = graph_builder_->AddNode(GetTFLiteNodeID());
batch_to_space_op->SetOpType(OP_BatchToSpaceND_8);
batch_to_space_op->AddInput(conv_output);
batch_to_space_op->AddInput(TensorID(dilation_factors_const->GetID(), 0));
batch_to_space_op->AddInput(TensorID(crops_const->GetID(), 0));
batch_to_space_op->AddInput(TensorID(conv_output_min_const->GetID(), 0));
batch_to_space_op->AddInput(TensorID(conv_output_max_const->GetID(), 0));
*output_tensor =
batch_to_space_op->AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
*output_min_tensor =
batch_to_space_op->AddOutput(sizeof(float), 4, kScalarShape);
*output_max_tensor =
batch_to_space_op->AddOutput(sizeof(float), 4, kScalarShape);
}
// Workaround for depthwise conv accuracy issues.
// See Conv2dOpBuilder.should_split_dwconv_ for details.
void Conv2dOpBuilder::BuildSplittedDwConv(
const TfLiteIntArray* inputs, const TfLiteTensor& data_tensor,
const TfLiteTensor& output_data_tensor, OpBuilder* data_min_const,
OpBuilder* data_max_const, OpBuilder* conv_output_min_const,
OpBuilder* conv_output_max_const, OpBuilder* stride_node,
const TfLitePadding padding_type, TensorID* output_tensor,
TensorID* output_min_tensor, TensorID* output_max_tensor) {
// Input dimensions.
int input_batch_size, input_height_size, input_width_size, input_depth_size;
GetDims(&input_batch_size, &input_height_size, &input_width_size,
&input_depth_size, data_tensor.dims);
// Output dimensions.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, output_data_tensor.dims);
auto* split = this;
split->SetOpType(OP_QuantizedSplit_8);
int32_t dim_channel = 3;
auto* dimension_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&dim_channel), sizeof(dim_channel));
split->AddInput(TensorID(dimension_const->GetID(), 0));
split->AddInput(graph_builder_->GetHexagonTensorId(inputs->data[0]));
split->AddInput(TensorID(data_min_const->GetID(), 0));
split->AddInput(TensorID(data_max_const->GetID(), 0));
std::vector<TensorID> data_nodes;
data_nodes.reserve(per_channel_quant_.splits);
for (auto i = 0; i < per_channel_quant_.splits; i++) {
data_nodes.emplace_back(
split->AddOutput(sizeof(uint8_t), 4,
{input_batch_size, input_height_size, input_width_size,
kDwConv5x5Filt2x2StrideChannelCount}));
}
auto data_min = split->AddOutput(sizeof(float), 4, kScalarShape);
auto data_max = split->AddOutput(sizeof(float), 4, kScalarShape);
std::vector<TensorID> dconv_outputs, dconv_min, dconv_max;
for (auto i = 0; i < per_channel_quant_.splits; i++) {
auto* dw_conv = graph_builder_->AddNode(GetTFLiteNodeID());
dw_conv->SetOpType(OP_DepthwiseSupernode_8x8p32to8);
if (padding_type == kTfLitePaddingSame) {
dw_conv->SetPaddingType(NN_PAD_SAME);
} else if (padding_type == kTfLitePaddingValid) {
dw_conv->SetPaddingType(NN_PAD_VALID);
}
dw_conv->AddInput(data_nodes[i]);
dw_conv->AddInput(TensorID(weights_nodes_[i]->GetID(), 0));
dw_conv->AddInput(data_min);
dw_conv->AddInput(data_max);
dw_conv->AddInput(TensorID(weights_min_node_->GetID(), 0));
dw_conv->AddInput(TensorID(weights_max_node_->GetID(), 0));
dw_conv->AddInput(TensorID(stride_node->GetID(), 0));
dw_conv->AddInput(TensorID(bias_nodes_[i]->GetID(), 0));
dw_conv->AddInput(TensorID(bias_min_node_->GetID(), 0));
dw_conv->AddInput(TensorID(bias_max_node_->GetID(), 0));
dw_conv->AddInput(TensorID(conv_output_min_const->GetID(), 0));
dw_conv->AddInput(TensorID(conv_output_max_const->GetID(), 0));
dw_conv->AddInput(
TensorID(per_channel_quant_.channel_scales_nodes[i]->GetID(), 0));
dconv_outputs.push_back(dw_conv->AddOutput(
sizeof(uint8_t), 4,
{output_batch_size, output_height_size, output_width_size,
kDwConv5x5Filt2x2StrideChannelCount}));
dconv_min.push_back(dw_conv->AddOutput(sizeof(float), 4, kScalarShape));
dconv_max.push_back(dw_conv->AddOutput(sizeof(float), 4, kScalarShape));
}
auto* concat = graph_builder_->AddNode(GetTFLiteNodeID());
concat->SetOpType(OP_QuantizedConcat_8);
concat->AddInput(TensorID(dimension_const->GetID(), 0));
for (auto i = 0; i < per_channel_quant_.splits; i++) {
concat->AddInput(dconv_outputs[i]);
}
for (auto i = 0; i < per_channel_quant_.splits; i++) {
concat->AddInput(dconv_min[i]);
}
for (auto i = 0; i < per_channel_quant_.splits; i++) {
concat->AddInput(dconv_max[i]);
}
concat->AddInput(TensorID(conv_output_min_const->GetID(), 0));
concat->AddInput(TensorID(conv_output_max_const->GetID(), 0));
*output_tensor = concat->AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
*output_min_tensor = concat->AddOutput(sizeof(float), 4, kScalarShape);
*output_max_tensor = concat->AddOutput(sizeof(float), 4, kScalarShape);
}
void Conv2dOpBuilder::BuildStandardConv(
const TfLiteIntArray* inputs, const TfLiteTensor& output_data_tensor,
OpBuilder* data_min_const, OpBuilder* data_max_const,
OpBuilder* conv_output_min_const, OpBuilder* conv_output_max_const,
OpBuilder* stride_node, const TfLitePadding padding_type,
TensorID* output_tensor, TensorID* output_min_tensor,
TensorID* output_max_tensor) {
// Standard case.
// Output dimensions.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, output_data_tensor.dims);
// Padding type.
if (padding_type == kTfLitePaddingSame) {
SetPaddingType(NN_PAD_SAME);
} else if (padding_type == kTfLitePaddingValid) {
SetPaddingType(NN_PAD_VALID);
}
// Inputs
AddInput(graph_builder_->GetHexagonTensorId(inputs->data[0]));
AddInput(graph_builder_->GetHexagonTensorId(inputs->data[1]));
AddInput(TensorID(data_min_const->GetID(), 0));
AddInput(TensorID(data_max_const->GetID(), 0));
AddInput(TensorID(weights_min_node_->GetID(), 0));
AddInput(TensorID(weights_max_node_->GetID(), 0));
AddInput(TensorID(stride_node->GetID(), 0));
AddInput(graph_builder_->GetHexagonTensorId(inputs->data[2]));
AddInput(TensorID(bias_min_node_->GetID(), 0));
AddInput(TensorID(bias_max_node_->GetID(), 0));
AddInput(TensorID(conv_output_min_const->GetID(), 0));
AddInput(TensorID(conv_output_max_const->GetID(), 0));
if (per_channel_quant_.channel_scales_node != nullptr) {
AddInput(TensorID(per_channel_quant_.channel_scales_node->GetID(), 0));
}
// Outputs
*output_tensor = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
*output_min_tensor = AddOutput(sizeof(float), 4, kScalarShape);
*output_max_tensor = AddOutput(sizeof(float), 4, kScalarShape);
}
TfLiteStatus Conv2dOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Input data tensor.
const auto& data_tensor = context->tensors[inputs->data[0]];
const auto& output_data_tensor = context->tensors[outputs->data[0]];
int input_batch_size, input_height_size, input_width_size, input_depth_size;
GetDims(&input_batch_size, &input_height_size, &input_width_size,
&input_depth_size, data_tensor.dims);
float data_min = 0;
float data_max = 0;
TF_LITE_ENSURE_STATUS(
ComputeMinAndMaxQuantValues(data_tensor, &data_min, &data_max));
auto* data_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&data_min), sizeof(data_min));
auto* data_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&data_max), sizeof(data_max));
// Gather information about the Convolution operations.
TfLitePadding padding_type = kTfLitePaddingUnknown;
TfLiteFusedActivation activation = kTfLiteActNone;
int stride_height = 0;
int stride_width = 0;
bool is_dilated_depthwise_conv = false;
int channel_multiplier = 1;
if (op_node_.op_type == OP_Supernode_8x8p32to8) {
const TfLiteConvParams* conv_params =
reinterpret_cast<const TfLiteConvParams*>(builtin_data_);
stride_height = conv_params->stride_height;
stride_width = conv_params->stride_width;
padding_type = conv_params->padding;
activation = conv_params->activation;
} else if (op_node_.op_type == OP_DepthwiseSupernode_8x8p32to8) {
const TfLiteDepthwiseConvParams* conv_params =
reinterpret_cast<const TfLiteDepthwiseConvParams*>(builtin_data_);
stride_height = conv_params->stride_height;
stride_width = conv_params->stride_width;
padding_type = conv_params->padding;
activation = conv_params->activation;
channel_multiplier = conv_params->depth_multiplier;
// We only support dilation for DepthwiseConv.
if (conv_params->dilation_height_factor > 1 ||
conv_params->dilation_width_factor > 1) {
is_dilated_depthwise_conv = true;
dilation_factors_h_w_.push_back(conv_params->dilation_height_factor);
dilation_factors_h_w_.push_back(conv_params->dilation_width_factor);
}
}
// Weights tensor
TF_LITE_ENSURE_STATUS(
InitializeWeightsNodes(inputs, outputs, context, input_depth_size));
// Stride node.
static int dummy = 0;
stride_shape_ = {1, stride_height, stride_width, 1};
auto* stride_node = graph_builder_->AddConstNodeWithData(
stride_shape_.data(), reinterpret_cast<char*>(&dummy), sizeof(dummy));
// Output dimensions.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
// Output bounds.
// TODO(b/129276536): Add support for other activations here. Current
// implementation assumes None/Relu.
float output_min = 0;
float output_max = 0;
TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues(
context->tensors[outputs->data[0]], &output_min, &output_max));
// These denote the bounds fed to Hexagon's Conv mechanism, which will be
// different from the TFLite tensor bounds if there is a RELU activation.
float conv_output_min = output_min;
float conv_output_max = output_max;
if (activation == kTfLiteActRelu6) {
conv_output_min = 0;
conv_output_max = 6;
} else if (activation == kTfLiteActReluN1To1) {
conv_output_min = -1;
conv_output_max = 1;
} else if (activation == kTfLiteActRelu) {
conv_output_min = 0;
}
auto* conv_output_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&conv_output_min),
sizeof(conv_output_min));
auto* conv_output_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&conv_output_max),
sizeof(conv_output_max));
// Bias node.
TF_LITE_ENSURE_STATUS(InitializeBiasNodes(inputs, outputs, context));
// TODO(b/143759564): Simplify this method when depth_multiplier support needs
// generalizing.
if (channel_multiplier > 1 && input_depth_size == 1) {
// Depthwise Conv with input_depth == 1 & channel_multiplier > 1 is
// equivalent to Conv.
SetOpType(OP_Supernode_8x8p32to8);
} else if (channel_multiplier > 1) {
TF_LITE_KERNEL_LOG(
context, "depth_multiplier > 1 not supported with input_depth > 1");
return kTfLiteError;
}
TensorID output_tensor, output_min_tensor, output_max_tensor;
if (is_dilated_depthwise_conv) {
BuildDilatedDwConv(inputs, data_tensor, output_data_tensor, data_min_const,
data_max_const, conv_output_min_const,
conv_output_max_const, stride_node, stride_height,
padding_type, &output_tensor, &output_min_tensor,
&output_max_tensor);
} else if (should_split_dwconv_) {
BuildSplittedDwConv(inputs, data_tensor, output_data_tensor, data_min_const,
data_max_const, conv_output_min_const,
conv_output_max_const, stride_node, padding_type,
&output_tensor, &output_min_tensor, &output_max_tensor);
} else {
BuildStandardConv(inputs, output_data_tensor, data_min_const,
data_max_const, conv_output_min_const,
conv_output_max_const, stride_node, padding_type,
&output_tensor, &output_min_tensor, &output_max_tensor);
}
// Requantize if activation was not None & the TFLite tensor's min/max is
// different (diff > 1e-2) from the RELU bounds.
const float min_bound_diff = std::abs(conv_output_min - output_min);
const float max_bound_diff = std::abs(conv_output_max - output_max);
if (activation != kTfLiteActNone &&
(min_bound_diff > 0.01 || max_bound_diff > 0.01)) {
auto* requantized_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&output_min), sizeof(output_min));
auto* requantized_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&output_max), sizeof(output_max));
auto* requantize_op = graph_builder_->AddNode(GetTFLiteNodeID());
requantize_op->SetOpType(OP_Requantize_8to8);
requantize_op->AddInput(output_tensor);
requantize_op->AddInput(output_min_tensor);
requantize_op->AddInput(output_max_tensor);
requantize_op->AddInput(TensorID(requantized_min_const->GetID(), 0));
requantize_op->AddInput(TensorID(requantized_max_const->GetID(), 0));
node_output_ =
requantize_op->AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
requantize_op->AddOutput(sizeof(float), 4, kScalarShape);
requantize_op->AddOutput(sizeof(float), 4, kScalarShape);
} else {
node_output_ = output_tensor;
}
return kTfLiteOk;
}
TfLiteStatus Conv2dOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
Conv2dOpBuilder::~Conv2dOpBuilder() {}
OpBuilder* CreateConv2DBuilder(GraphBuilder* graph_builder, int op_type) {
return new Conv2dOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,164 @@
/* Copyright 2019 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_CONV_2D_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_CONV_2D_BUILDER_H_
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
// Stores quantization data for Conv/TransposeConv nodes.
// This information is used to handle the per-channel quantized weights & biases
// correctly in the Hexagon delegate.
struct PerChannelQuantData {
// This is initialized while processing quantized weights, and acts as an
// input to Hexagon Conv nodes.
OpBuilder* channel_scales_node = nullptr;
// Scale information is obtained from TfLiteAffineQuantization in the weights
// tensor.
float* scales_data = nullptr;
int num_scale_values = 1;
// Number of splits to workaround DepthwiseConv accuracy issue.
// See Conv2dOpBuilder.should_split_dwconv_ for details.
int splits = 0;
std::vector<OpBuilder*> channel_scales_nodes;
};
class Conv2dOpBuilder : public OpBuilder {
public:
explicit Conv2dOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~Conv2dOpBuilder() override;
private:
TfLiteStatus InitializeWeightsNodes(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context,
const int input_depth);
TfLiteStatus InitializeBiasNodes(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context);
void BuildStandardConv(const TfLiteIntArray* inputs,
const TfLiteTensor& output_data_tensor,
OpBuilder* data_min_const, OpBuilder* data_max_const,
OpBuilder* conv_output_min_const,
OpBuilder* conv_output_max_const,
OpBuilder* stride_node,
const TfLitePadding padding_type,
TensorID* output_tensor, TensorID* output_min_tensor,
TensorID* output_max_tensor);
void BuildDilatedDwConv(const TfLiteIntArray* inputs,
const TfLiteTensor& data_tensor,
const TfLiteTensor& output_data_tensor,
OpBuilder* data_min_const, OpBuilder* data_max_const,
OpBuilder* conv_output_min_const,
OpBuilder* conv_output_max_const,
OpBuilder* stride_node, int stride_height,
const TfLitePadding padding_type,
TensorID* output_tensor, TensorID* output_min_tensor,
TensorID* output_max_tensor);
void BuildSplittedDwConv(
const TfLiteIntArray* inputs, const TfLiteTensor& data_tensor,
const TfLiteTensor& output_data_tensor, OpBuilder* data_min_const,
OpBuilder* data_max_const, OpBuilder* conv_output_min_const,
OpBuilder* conv_output_max_const, OpBuilder* stride_node,
const TfLitePadding padding_type, TensorID* output_tensor,
TensorID* output_min_tensor, TensorID* output_max_tensor);
TensorID node_output_;
std::vector<float> transposed_weights_;
std::vector<int> stride_shape_;
std::vector<int> weight_shape_;
OpBuilder* weights_min_node_ = nullptr;
OpBuilder* weights_max_node_ = nullptr;
OpBuilder* bias_min_node_ = nullptr;
OpBuilder* bias_max_node_ = nullptr;
// TODO(b/228874753)
// We are seeing accuray issues on DepthwiseSupernode_8x8p32to8 in the
// following case:
// * kernel size is 5x5
// * stride size is 2x2
// * per channel quantized
// * input depth more than 32
//
// To workaround the issue, the DepthwiseSupernode_8x8p32to8 is splitted
// into 32 channel batches and concatenated afterwards.
// Input tensor, weights, bias and channel scales are splitted into 32
// channel sizes and fed to multiple DepthwiseSupernode_8x8p32to8 ops.
// The results are stitched back with a Concat op.
//
// Checks if it has DepthwiseSupernode_8x8p32to8 accuracy issues.
void CheckShouldSplitDwConv(TfLiteType weights_type, int input_depth,
bool is_per_channel_quant,
int channel_multiplier);
// Split weights into multiple 32-channel nodes.
// `converted_data` is MSB flipped int8 weight values.
void SplitWeightsForDwConv(const std::vector<uint8_t>& converted_data,
int input_depth, int channel_multiplier);
// Split bias into 32 element batches.
// `preprocessed_bias_data` is the output of ProcessPerChannelQuantizedBias.
void SplitBiasForDwConv(std::vector<int>& preprocessed_bias_data);
bool should_split_dwconv_ = false;
std::vector<TensorID> data_nodes_;
std::vector<OpBuilder*> bias_nodes_;
std::vector<OpBuilder*> weights_nodes_;
// Modified only if node has per-channel quantized weights/biases.
PerChannelQuantData per_channel_quant_;
// Only used for dilated Depthwise Conv.
std::vector<int> dilation_factors_h_w_;
std::vector<int> space_to_batch_paddings_;
std::vector<int> batch_to_space_crops_;
};
// ProcessPerChannelQuantizedWeights & ProcessPerChannelQuantizedBias can be
// used to pre-process per-channel quantized weights & biases for Hexagon.
// NOTE: ProcessPerChannelQuantizedWeights should be run before
// ProcessPerChannelQuantizedBias. This is becase we set PerChannelQuantData
// based on the weights tensor, which is utilized while preprocessing bias.
TfLiteStatus ProcessPerChannelQuantizedWeights(
const TfLiteTensor& weights_tensor, TfLiteContext* context,
float* weights_min, float* weights_max, GraphBuilder* graph_builder,
PerChannelQuantData* per_channel_quant);
TfLiteStatus ProcessPerChannelQuantizedBias(
const TfLiteTensor& data_tensor, const TfLiteTensor& bias_tensor,
const int bias_tensor_idx, TfLiteContext* context, float* bias_min,
float* bias_max, GraphBuilder* graph_builder,
PerChannelQuantData* per_channel_quant,
std::vector<int>* preprocessed_bias_data,
OpBuilder** bias_const_node = nullptr);
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_CONV_2D_BUILDER_H_
@@ -0,0 +1,394 @@
/* 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 <stdint.h>
#include <algorithm>
#include <cmath>
#include <vector>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/hexagon/builders/conv_2d_builder.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h"
#include "tensorflow/lite/kernels/internal/runtime_shape.h"
#include "tensorflow/lite/kernels/internal/types.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
namespace {
constexpr uint8_t k8BitSignFlipConstant = 0x80;
// 1/1024 ~ 0.0009766 is a restriction set by Hexagon's kernels.
// TODO(b/151103818): Figure out a way to retrieve this constant reliably.
constexpr float kHexagonMinRelativeScale = 0.0009766f;
// Channel count to split depthwise convolution op.
// See Conv2dOpBuilder.should_split_dwconv_ for details.
constexpr int kDwConv5x5Filt2x2StrideChannelCount = 32;
} // namespace
TfLiteStatus ProcessPerChannelQuantizedWeights(
const TfLiteTensor& weights_tensor, TfLiteContext* context,
float* weights_min, float* weights_max, GraphBuilder* graph_builder,
PerChannelQuantData* per_channel_quant) {
if (!per_channel_quant) return kTfLiteError;
TfLiteAffineQuantization* weights_quant_params =
reinterpret_cast<TfLiteAffineQuantization*>(
weights_tensor.quantization.params);
// Retrieve channel scales.
per_channel_quant->num_scale_values = weights_quant_params->scale->size;
// Normalize the scales as expected by Hexagon.
per_channel_quant->scales_data = weights_quant_params->scale->data;
std::vector<float> normalized_scales;
normalized_scales.reserve(per_channel_quant->num_scale_values);
float scale_max = 0.0;
for (int i = 0; i < per_channel_quant->num_scale_values; ++i) {
normalized_scales.push_back(per_channel_quant->scales_data[i]);
if (per_channel_quant->scales_data[i] > scale_max) {
scale_max = per_channel_quant->scales_data[i];
}
}
if (scale_max == 0.0) {
TF_LITE_KERNEL_LOG(context, "Scale max is zero for: %s",
weights_tensor.name);
return kTfLiteError;
}
for (int i = 0; i < per_channel_quant->num_scale_values; ++i) {
normalized_scales[i] =
std::max(normalized_scales[i] / scale_max, kHexagonMinRelativeScale);
}
// Add node for channel scales data.
const std::vector<int> scales_shape = {1, 1, 1,
per_channel_quant->num_scale_values};
per_channel_quant->channel_scales_node = graph_builder->AddConstNodeWithData(
scales_shape.data(), reinterpret_cast<char*>(normalized_scales.data()),
normalized_scales.size() * sizeof(normalized_scales[0]));
if (per_channel_quant->splits) {
// Split channel scales to 32 channel batches.
const std::vector<int> sliced_scales_shape = {
1, 1, 1, kDwConv5x5Filt2x2StrideChannelCount};
for (auto i = 0; i < per_channel_quant->splits; ++i) {
auto offset = kDwConv5x5Filt2x2StrideChannelCount * i;
auto* node = graph_builder->AddConstNodeWithData(
sliced_scales_shape.data(),
reinterpret_cast<char*>(normalized_scales.data() + offset),
kDwConv5x5Filt2x2StrideChannelCount * sizeof(normalized_scales[0]));
per_channel_quant->channel_scales_nodes.push_back(node);
}
}
*weights_min = -128 * scale_max;
*weights_max = 127 * scale_max;
return kTfLiteOk;
}
TfLiteStatus ProcessPerChannelQuantizedBias(
const TfLiteTensor& data_tensor, const TfLiteTensor& bias_tensor,
const int bias_tensor_idx, TfLiteContext* context, float* bias_min,
float* bias_max, GraphBuilder* graph_builder,
PerChannelQuantData* per_channel_quant,
std::vector<int>* preprocessed_bias_data, OpBuilder** bias_const_node) {
const TfLiteAffineQuantization* input_quant_params =
static_cast<const TfLiteAffineQuantization*>(
data_tensor.quantization.params);
const float input_scale = input_quant_params->scale->data[0];
// Now dequantize bias values to float first, to adjust for the
// normalization of channel scales.
auto* bias_data = bias_tensor.data.i32;
const int bias_size = NumElements(&bias_tensor);
if (bias_size != per_channel_quant->num_scale_values) {
TF_LITE_KERNEL_LOG(
context, "Bias/channel scales number mismatch for bias tensor: %s",
bias_tensor.name);
return kTfLiteError;
}
std::vector<float> dequantized_bias;
dequantized_bias.reserve(bias_size);
for (int i = 0; i < bias_size; ++i) {
const float dequantized_value =
bias_data[i] * input_scale * per_channel_quant->scales_data[i];
const float abs_dequantized_value = std::abs(dequantized_value);
if (abs_dequantized_value > *bias_max) {
*bias_max = abs_dequantized_value;
}
dequantized_bias.push_back(dequantized_value);
}
*bias_max = *bias_max * 8;
*bias_min = -1 * *bias_max;
// Now requantize the bias values to the new min/max values.
preprocessed_bias_data->reserve(per_channel_quant->num_scale_values);
for (int i = 0; i < bias_size; ++i) {
preprocessed_bias_data->push_back(static_cast<int>(
std::round(std::pow(2, 31) * (dequantized_bias[i] / *bias_max))));
}
// Add nodes for bias.
const std::vector<int> bias_shape = {1, 1, 1, bias_size};
auto* bias_data_node = graph_builder->AddConstNodeWithData(
bias_shape.data(),
reinterpret_cast<char*>(preprocessed_bias_data->data()),
preprocessed_bias_data->size() * sizeof((*preprocessed_bias_data)[0]));
if (bias_const_node) {
*bias_const_node = bias_data_node;
}
graph_builder->AddTensorWithID(bias_tensor_idx, bias_data_node->GetID(), 0,
/*overwrite=*/true);
return kTfLiteOk;
}
void Conv2dOpBuilder::CheckShouldSplitDwConv(TfLiteType weights_type,
int input_depth,
bool is_per_channel_quant,
int channel_multiplier) {
const TfLiteDepthwiseConvParams* conv_params =
reinterpret_cast<const TfLiteDepthwiseConvParams*>(builtin_data_);
int weights_height = weight_shape_[0];
int weights_width = weight_shape_[1];
// input_depth * channel_multiplier
int weights_depth_size = input_depth * weight_shape_[3];
if (op_node_.op_type == OP_DepthwiseSupernode_8x8p32to8 &&
weights_type == kTfLiteInt8 &&
// weight_shape_ is [fh,fw,din,dmul]
weights_height == 5 && weights_width == 5 &&
// Stride larger than 2x2
conv_params->stride_height >= 2 && conv_params->stride_width >= 2 &&
// Depth more than 32 and is multiples of 32 so can be split.
input_depth > kDwConv5x5Filt2x2StrideChannelCount &&
input_depth % kDwConv5x5Filt2x2StrideChannelCount == 0 &&
is_per_channel_quant && channel_multiplier == 1) {
should_split_dwconv_ = true;
// Splits the inputs to 32 channel batches.
per_channel_quant_.splits =
weights_depth_size / kDwConv5x5Filt2x2StrideChannelCount;
per_channel_quant_.channel_scales_nodes.reserve(per_channel_quant_.splits);
}
}
void Conv2dOpBuilder::SplitWeightsForDwConv(
const std::vector<uint8_t>& converted_data, int input_depth,
int channel_multiplier) {
int weights_height_size = weight_shape_[0];
int weights_width_size = weight_shape_[1];
// Split the weight tensor into 32 channel batches.
SplitParams split_params{
.num_split = static_cast<uint16_t>(per_channel_quant_.splits),
.axis = 2,
};
std::vector<RuntimeShape> split_shapes;
std::vector<const tflite::RuntimeShape*> split_shapes_data;
std::vector<std::vector<uint8_t>> splitted_weights;
std::vector<uint8_t*> splitted_weights_data;
split_shapes.reserve(per_channel_quant_.splits);
split_shapes_data.reserve(per_channel_quant_.splits);
splitted_weights.reserve(per_channel_quant_.splits);
splitted_weights_data.reserve(per_channel_quant_.splits);
for (auto s = 0; s < per_channel_quant_.splits; s++) {
split_shapes.push_back({weights_height_size, weights_width_size,
kDwConv5x5Filt2x2StrideChannelCount,
channel_multiplier});
split_shapes_data.push_back(&split_shapes.back());
splitted_weights.emplace_back(weights_height_size * weights_width_size *
channel_multiplier *
kDwConv5x5Filt2x2StrideChannelCount,
0);
splitted_weights_data.push_back(splitted_weights.back().data());
}
RuntimeShape weight_shape = {weights_height_size, weights_width_size,
input_depth, channel_multiplier};
optimized_ops::Split(split_params, weight_shape, converted_data.data(),
split_shapes_data.data(), splitted_weights_data.data());
for (auto s = 0; s < per_channel_quant_.splits; s++) {
auto splitted_weights_node = graph_builder_->AddConstNodeWithData(
split_shapes[s].DimsData(),
reinterpret_cast<char*>(splitted_weights_data[s]),
splitted_weights[s].size() * sizeof(splitted_weights_data[s][0]));
weights_nodes_.push_back(splitted_weights_node);
}
}
TfLiteStatus Conv2dOpBuilder::InitializeWeightsNodes(
const TfLiteIntArray* inputs, const TfLiteIntArray* outputs,
TfLiteContext* context, const int input_depth) {
const std::vector<int> quant_bound_shape = {1, 1, 1, 1};
const auto& weights_tensor = context->tensors[inputs->data[1]];
if (weights_tensor.allocation_type != kTfLiteMmapRo) {
TF_LITE_KERNEL_LOG(
context, "Weights tensor doesn't have correct allocation type: %s",
weights_tensor.name);
return kTfLiteError;
}
int weights_batch_size, weights_height_size, weights_width_size,
weights_depth_size;
// Hexagon lib expects the weight tensor in HWCN, TFLite uses NHWC.
// Transpose NHWC -> HWCN
GetDims(&weights_batch_size, &weights_height_size, &weights_width_size,
&weights_depth_size, weights_tensor.dims);
// Weights tensor could be int8 even for per-tensor quantization.
// Therefore, we look at the number of scale values to check if it is
// per-channel quantized.
TfLiteAffineQuantization* weights_quant_params =
reinterpret_cast<TfLiteAffineQuantization*>(
weights_tensor.quantization.params);
const bool is_per_channel_quant = weights_quant_params->scale->size > 1;
// WEIGHTS DATA.
OpBuilder* weights_data_node = nullptr;
if (op_node_.op_type == OP_Supernode_8x8p32to8) {
// Hexagon lib expects the weight tensor in HWCN, TFLite uses NHWC.
// Transpose NHWC -> HWCN
weight_shape_ = {weights_height_size, weights_width_size,
weights_depth_size, weights_batch_size};
RuntimeShape nhwc_shape({weights_batch_size, weights_height_size,
weights_width_size, weights_depth_size});
RuntimeShape hwcn_shape({weights_height_size, weights_width_size,
weights_depth_size, weights_batch_size});
std::vector<uint8_t> hwcn(NumElements(&weights_tensor));
TransposeParams transpose_params;
transpose_params.perm_count = 4;
transpose_params.perm[0] = 1;
transpose_params.perm[1] = 2;
transpose_params.perm[2] = 3;
transpose_params.perm[3] = 0;
// TODO(b/151103818): Try merging Transpose & bit flip.
if (weights_tensor.type == kTfLiteInt8) {
optimized_ops::Transpose<int8_t>(transpose_params, nhwc_shape,
weights_tensor.data.int8, hwcn_shape,
reinterpret_cast<int8_t*>(hwcn.data()));
// Flip bits on the weight values so that the int8 values are treated
// as uint8.
for (int i = 0; i < hwcn.size(); ++i) {
hwcn[i] = hwcn[i] ^ k8BitSignFlipConstant;
}
} else {
optimized_ops::Transpose<uint8_t>(transpose_params, nhwc_shape,
weights_tensor.data.uint8, hwcn_shape,
hwcn.data());
}
weights_data_node = graph_builder_->AddConstNodeWithData(
weight_shape_.data(), reinterpret_cast<char*>(hwcn.data()),
hwcn.size() * sizeof(hwcn[0]));
} else if (op_node_.op_type == OP_DepthwiseSupernode_8x8p32to8) {
// Hexagon treats depthwise conv like tf.nn.depthwise_conv2d, where the
// expected filter shape is [fh,fw,din,dmul].
// The data itself will remain the same, since TFLite's representation is
// just a 'flattening' of Hexagon's version.
const int channel_multiplier = weights_depth_size / input_depth;
weight_shape_ = {weights_height_size, weights_width_size, input_depth,
channel_multiplier};
// Check if the op hits the Depthwise conv accuracy issue.
// See Conv2dOpBuilder.should_split_dwconv_ for details.
CheckShouldSplitDwConv(weights_tensor.type, input_depth,
is_per_channel_quant, channel_multiplier);
if (weights_tensor.type == kTfLiteInt8) {
// Flip bits on the weight values so that the int8 values are treated
// as uint8.
std::vector<uint8_t> converted_data(NumElements(&weights_tensor));
for (int i = 0; i < converted_data.size(); ++i) {
converted_data[i] = weights_tensor.data.int8[i] ^ k8BitSignFlipConstant;
}
weights_data_node = graph_builder_->AddConstNodeWithData(
weight_shape_.data(), reinterpret_cast<char*>(converted_data.data()),
converted_data.size() * sizeof(converted_data[0]));
if (should_split_dwconv_)
SplitWeightsForDwConv(converted_data, input_depth, channel_multiplier);
} else {
weights_data_node = graph_builder_->AddConstNodeWithData(
weight_shape_.data(), weights_tensor.data.raw,
NumElements(&weights_tensor) * sizeof(weights_tensor.data.uint8[0]));
}
}
graph_builder_->AddTensorWithID(inputs->data[1], weights_data_node->GetID(),
0, /*overwrite=*/true);
// WEIGHTS QUANTIZATION.
float weights_min = 0;
float weights_max = 0;
if (is_per_channel_quant) {
ProcessPerChannelQuantizedWeights(weights_tensor, context, &weights_min,
&weights_max, graph_builder_,
&per_channel_quant_);
} else {
TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues(
weights_tensor, &weights_min, &weights_max));
}
weights_min_node_ = graph_builder_->AddConstNodeWithData(
quant_bound_shape.data(), reinterpret_cast<char*>(&weights_min),
sizeof(weights_min));
weights_max_node_ = graph_builder_->AddConstNodeWithData(
quant_bound_shape.data(), reinterpret_cast<char*>(&weights_max),
sizeof(weights_max));
return kTfLiteOk;
}
void Conv2dOpBuilder::SplitBiasForDwConv(
std::vector<int>& preprocessed_bias_data) {
// Splits bias to 32 channel batches.
std::vector<int> bias_shape = {1, 1, 1, kDwConv5x5Filt2x2StrideChannelCount};
for (auto i = 0; i < per_channel_quant_.splits; i++) {
auto offset = kDwConv5x5Filt2x2StrideChannelCount * i;
auto* bias_data_node = graph_builder_->AddConstNodeWithData(
bias_shape.data(),
reinterpret_cast<char*>(preprocessed_bias_data.data() + offset),
kDwConv5x5Filt2x2StrideChannelCount *
sizeof(preprocessed_bias_data[0]));
bias_nodes_.push_back(bias_data_node);
}
}
TfLiteStatus Conv2dOpBuilder::InitializeBiasNodes(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
const std::vector<int> quant_bound_shape = {1, 1, 1, 1};
const auto& bias_tensor = context->tensors[inputs->data[2]];
float bias_min = 0;
float bias_max = 0;
if (per_channel_quant_.channel_scales_node != nullptr) {
std::vector<int> preprocessed_bias_data;
ProcessPerChannelQuantizedBias(
context->tensors[inputs->data[0]], bias_tensor, inputs->data[2],
context, &bias_min, &bias_max, graph_builder_, &per_channel_quant_,
&preprocessed_bias_data);
if (should_split_dwconv_) SplitBiasForDwConv(preprocessed_bias_data);
} else {
auto* bias_data_node =
graph_builder_->AddConstNodeWithData(inputs->data[2], bias_tensor);
graph_builder_->AddTensorWithID(inputs->data[2], bias_data_node->GetID(), 0,
/*overwrite=*/true);
TF_LITE_ENSURE_STATUS(
ComputeMinAndMaxQuantValues(bias_tensor, &bias_min, &bias_max));
}
bias_min_node_ = graph_builder_->AddConstNodeWithData(
quant_bound_shape.data(), reinterpret_cast<char*>(&bias_min),
sizeof(bias_min));
bias_max_node_ = graph_builder_->AddConstNodeWithData(
quant_bound_shape.data(), reinterpret_cast<char*>(&bias_max),
sizeof(bias_max));
return kTfLiteOk;
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,68 @@
/* 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 "tensorflow/lite/delegates/hexagon/builders/hardswish_builder.h"
#include <stdint.h>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus HardSwishOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
// input data tensor.
int tensor_id = inputs->data[0];
const auto& input1_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input1_tensor));
// Output min/max
TF_LITE_ENSURE_STATUS(
ComputeAndAddMinAndMax(context, context->tensors[outputs->data[0]]));
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus HardSwishOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
HardSwishOpBuilder::~HardSwishOpBuilder() {}
OpBuilder* CreateHardSwishBuilder(GraphBuilder* graph_builder, int op_type) {
return new HardSwishOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,49 @@
/* 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_HARDSWISH_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_HARDSWISH_BUILDER_H_
#include <stdint.h>
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class HardSwishOpBuilder : public OpBuilder {
public:
explicit HardSwishOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~HardSwishOpBuilder() override;
private:
TensorID node_output_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_HARDSWISH_BUILDER_H_
@@ -0,0 +1,67 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/l2_normalization_builder.h"
#include <stdint.h>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus L2NormalizationOpBuilder::PopulateSubGraph(
const TfLiteIntArray* inputs, const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Input data tensor.
int tensor_id = inputs->data[0];
const auto& input_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input_tensor));
// Hexagon outputs for this node.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus L2NormalizationOpBuilder::RegisterOutputs(
const TfLiteIntArray* outputs, TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
L2NormalizationOpBuilder::~L2NormalizationOpBuilder() {}
OpBuilder* CreateL2NormalizationBuilder(GraphBuilder* graph_builder,
int op_type) {
return new L2NormalizationOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,47 @@
/* Copyright 2019 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_L2_NORMALIZATION_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_L2_NORMALIZATION_BUILDER_H_
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class L2NormalizationOpBuilder : public OpBuilder {
public:
explicit L2NormalizationOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~L2NormalizationOpBuilder() override;
private:
TensorID node_output_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_L2_NORMALIZATION_BUILDER_H_
@@ -0,0 +1,294 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/matmul_builder.h"
#include <stdint.h>
#include <vector>
#include "hexagon/hexagon_nn_ops.h"
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
namespace {
void GetDims(int* batch_size, int* height_size, int* width_size,
int* depth_size, const TfLiteIntArray* dims) {
int* dim[] = {batch_size, height_size, width_size, depth_size};
for (int i = 0; i < 4; ++i) *(dim[i]) = 1;
for (int i = 4 - dims->size; i < 4; ++i) {
*dim[i] = dims->data[i - (4 - dims->size)];
}
}
constexpr uint8_t k8BitSignFlipConstant = 0x80;
TfLiteStatus AddFullyConnectedHelper(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
const OpBuilder::TensorID weights_id,
const OpBuilder::TensorID weights_min_id,
const OpBuilder::TensorID weights_max_id,
GraphBuilder* graph_builder,
TfLiteContext* context,
OpBuilder* matmul_op,
OpBuilder::TensorID* node_output) {
static int scalar_shape[] = {1, 1, 1, 1};
// Data tensor.
int data_tensor_id = inputs->data[0];
const auto& data_tensor = context->tensors[data_tensor_id];
float data_min, data_max;
TF_LITE_ENSURE_STATUS(OpBuilder::ComputeMinAndMaxQuantValues(
data_tensor, &data_min, &data_max));
auto* data_min_const = graph_builder->AddConstNodeWithData(
scalar_shape, reinterpret_cast<char*>(&data_min), sizeof(data_min));
auto* data_max_const = graph_builder->AddConstNodeWithData(
scalar_shape, reinterpret_cast<char*>(&data_max), sizeof(data_max));
// Data and weight tensors in required order.
matmul_op->AddInput(graph_builder->GetHexagonTensorId(data_tensor_id));
matmul_op->AddInput(weights_id);
matmul_op->AddInput(OpBuilder::TensorID(data_min_const->GetID(), 0));
matmul_op->AddInput(OpBuilder::TensorID(data_max_const->GetID(), 0));
matmul_op->AddInput(weights_min_id);
matmul_op->AddInput(weights_max_id);
// Outputs for the MatMul node, which are in int32 format.
// Output shape should still be the same.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
const auto& matmul_out =
matmul_op->AddOutput(sizeof(int), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
const auto& matmul_out_min =
matmul_op->AddOutput(sizeof(float), 4, scalar_shape);
const auto& matmul_out_max =
matmul_op->AddOutput(sizeof(float), 4, scalar_shape);
// Bias tensor.
int bias_tensor_id = inputs->data[2];
OpBuilder::TensorID matmul_and_bias_out = matmul_out,
matmul_and_bias_out_min = matmul_out_min,
matmul_and_bias_out_max = matmul_out_max;
if (bias_tensor_id != -1) {
const auto& bias_tensor = context->tensors[bias_tensor_id];
float bias_min, bias_max;
OpBuilder::ComputeMinAndMaxQuantValues(bias_tensor, &bias_min, &bias_max);
auto* bias_min_const = graph_builder->AddConstNodeWithData(
scalar_shape, reinterpret_cast<char*>(&bias_min), sizeof(bias_min));
auto* bias_max_const = graph_builder->AddConstNodeWithData(
scalar_shape, reinterpret_cast<char*>(&bias_max), sizeof(bias_max));
// MatMul + Bias.
auto* bias_add_op = graph_builder->AddNode(matmul_op->GetTFLiteNodeID());
bias_add_op->SetOpType(OP_QuantizedBiasAdd_32p32to32);
bias_add_op->AddInput(matmul_out);
bias_add_op->AddInput(graph_builder->GetHexagonTensorId(bias_tensor_id));
bias_add_op->AddInput(matmul_out_min);
bias_add_op->AddInput(matmul_out_max);
bias_add_op->AddInput(OpBuilder::TensorID(bias_min_const->GetID(), 0));
bias_add_op->AddInput(OpBuilder::TensorID(bias_max_const->GetID(), 0));
matmul_and_bias_out =
bias_add_op->AddOutput(sizeof(int), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
matmul_and_bias_out_min =
bias_add_op->AddOutput(sizeof(float), 4, scalar_shape);
matmul_and_bias_out_max =
bias_add_op->AddOutput(sizeof(float), 4, scalar_shape);
}
float output_min, output_max;
// Quantize 32-bit result into 8-bit format using output tensor min/max.
OpBuilder::ComputeMinAndMaxQuantValues(context->tensors[outputs->data[0]],
&output_min, &output_max);
auto* output_min_const = graph_builder->AddConstNodeWithData(
scalar_shape, reinterpret_cast<char*>(&output_min), sizeof(output_min));
auto* output_max_const = graph_builder->AddConstNodeWithData(
scalar_shape, reinterpret_cast<char*>(&output_max), sizeof(output_max));
auto* quantize_biasadd_op =
graph_builder->AddNode(matmul_op->GetTFLiteNodeID());
quantize_biasadd_op->SetOpType(OP_Requantize_32to8);
quantize_biasadd_op->AddInput(matmul_and_bias_out);
quantize_biasadd_op->AddInput(matmul_and_bias_out_min);
quantize_biasadd_op->AddInput(matmul_and_bias_out_max);
quantize_biasadd_op->AddInput(
OpBuilder::TensorID(output_min_const->GetID(), 0));
quantize_biasadd_op->AddInput(
OpBuilder::TensorID(output_max_const->GetID(), 0));
*node_output =
quantize_biasadd_op->AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
quantize_biasadd_op->AddOutput(sizeof(float), 4, scalar_shape);
quantize_biasadd_op->AddOutput(sizeof(float), 4, scalar_shape);
return kTfLiteOk;
}
} // namespace
// The TFLite 'Fully-connected' quantized op corresponds to the following
// subgraph in Hexagon:
// Data (8-bit), Weights (const, 8-bit) => MatMul => MatMul out (int32)
// MatMul out (int32), Bias (int32) => QuantizedBiasAdd => BiasAdd out (int32)
// BiasAdd out (int32) => Requantize_32to8 => Output (8-bit)
TfLiteStatus MatMulWithConstWeightsOpBuilder::PopulateSubGraph(
const TfLiteIntArray* inputs, const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Weights vector.
int weights_tensor_id = inputs->data[1];
const auto& weights_tensor = context->tensors[weights_tensor_id];
if (weights_tensor.allocation_type != kTfLiteMmapRo) {
TF_LITE_KERNEL_LOG(
context, "Weights tensor doesn't have correct allocation type: %s",
weights_tensor.name);
return kTfLiteError;
}
int batch_size, height_size, width_size, depth_size;
// Hexagon lib expects the weight tensor in NHCW, TFLite uses NHWC.
// Transpose NHWC -> NHCW
GetDims(&batch_size, &height_size, &width_size, &depth_size,
weights_tensor.dims);
weights_shape_ = {batch_size, height_size, depth_size, width_size};
RuntimeShape nhwc_shape({batch_size, height_size, width_size, depth_size});
RuntimeShape nhcw_shape({batch_size, height_size, depth_size, width_size});
std::vector<uint8_t> nhcw(NumElements(&weights_tensor));
TransposeParams transpose_params;
transpose_params.perm_count = 4;
transpose_params.perm[0] = 0;
transpose_params.perm[1] = 1;
transpose_params.perm[2] = 3;
transpose_params.perm[3] = 2;
if (weights_tensor.type == kTfLiteInt8) {
optimized_ops::Transpose<int8_t>(transpose_params, nhwc_shape,
weights_tensor.data.int8, nhcw_shape,
reinterpret_cast<int8_t*>(nhcw.data()));
// Flip bits on the weight values so that the int8 values are treated
// as uint8.
for (int i = 0; i < nhcw.size(); ++i) {
nhcw[i] = nhcw[i] ^ k8BitSignFlipConstant;
}
} else {
optimized_ops::Transpose<uint8_t>(transpose_params, nhwc_shape,
weights_tensor.data.uint8, nhcw_shape,
nhcw.data());
}
auto* const_weights_node = graph_builder_->AddConstNodeWithData(
weights_shape_.data(), reinterpret_cast<char*>(nhcw.data()),
weights_tensor.bytes);
graph_builder_->AddTensorWithID(weights_tensor_id,
const_weights_node->GetID(), 0, true);
ComputeMinAndMaxQuantValues(weights_tensor, &weights_min_, &weights_max_);
auto* weights_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&weights_min_),
sizeof(weights_min_));
auto* weights_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&weights_max_),
sizeof(weights_max_));
return AddFullyConnectedHelper(
inputs, outputs, graph_builder_->GetHexagonTensorId(weights_tensor_id),
TensorID(weights_min_const->GetID(), 0),
TensorID(weights_max_const->GetID(), 0), graph_builder_, context, this,
&node_output_);
}
TfLiteStatus MatMulWithConstWeightsOpBuilder::RegisterOutputs(
const TfLiteIntArray* outputs, TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
TfLiteStatus MatMulOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
const int weights_tensor_id = inputs->data[1];
const auto& weights_tensor = context->tensors[weights_tensor_id];
int batch_size, height_size, width_size, depth_size;
GetDims(&batch_size, &height_size, &width_size, &depth_size,
weights_tensor.dims);
weights_shape_ = {batch_size, height_size, depth_size, width_size};
// Permutation for transposing.
int permutation[] = {0, 1, 3, 2};
const int permutation_shape[] = {1, 1, 1, 4};
auto permutation_node = graph_builder_->AddConstNodeWithData(
permutation_shape, reinterpret_cast<char*>(permutation),
4 * sizeof(permutation[0]));
AddInput(graph_builder_->GetHexagonTensorId(weights_tensor_id));
AddInput(TensorID(permutation_node->GetID(), 0));
ComputeMinAndMaxQuantValues(weights_tensor, &weights_min_, &weights_max_);
auto* weights_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&weights_min_),
sizeof(weights_min_));
auto* weights_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&weights_max_),
sizeof(weights_max_));
AddInput(TensorID(weights_min_const->GetID(), 0));
AddInput(TensorID(weights_max_const->GetID(), 0));
auto transposed_weights = AddOutput(sizeof(uint8_t), 4, weights_shape_);
auto transposed_weights_min = AddOutput(sizeof(float), 4, kScalarShape);
auto transposed_weights_max = AddOutput(sizeof(float), 4, kScalarShape);
auto* matmul_op = graph_builder_->AddNode(GetTFLiteNodeID());
matmul_op->SetOpType(OP_QuantizedMatMul_8x8to32);
AddFullyConnected(inputs, outputs, transposed_weights, transposed_weights_min,
transposed_weights_max, context, matmul_op);
return kTfLiteOk;
}
TfLiteStatus MatMulOpBuilder::AddFullyConnected(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
const TensorID weights_id,
const TensorID weights_min_id,
const TensorID weights_max_id,
TfLiteContext* context,
OpBuilder* matmul_op) {
return AddFullyConnectedHelper(inputs, outputs, weights_id, weights_min_id,
weights_max_id, graph_builder_, context,
matmul_op, &node_output_);
}
TfLiteStatus MatMulOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
OpBuilder* CreateMatMulWithConstWeightsOpBuilder(GraphBuilder* graph_builder,
int op_type) {
return new MatMulWithConstWeightsOpBuilder(graph_builder, op_type);
}
OpBuilder* CreateMatMulOpBuilder(GraphBuilder* graph_builder, int op_type) {
return new MatMulOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,77 @@
/* Copyright 2019 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_MATMUL_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_MATMUL_BUILDER_H_
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
// Builder for FullyConnected op in Hexagon with weights as const.
class MatMulWithConstWeightsOpBuilder : public OpBuilder {
public:
explicit MatMulWithConstWeightsOpBuilder(GraphBuilder* graph_builder,
int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
private:
TensorID node_output_;
std::vector<int> weights_shape_, bias_shape_;
std::vector<float> transposed_weights_;
float weights_min_, weights_max_;
};
// Builder for FullyConnected op in Hexagon with non const weights.
class MatMulOpBuilder : public OpBuilder {
public:
explicit MatMulOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
private:
// Adds Fully connected op related ops to the graph.
TfLiteStatus AddFullyConnected(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
const TensorID weights_id,
const TensorID weights_min_id,
const TensorID weights_max_id,
TfLiteContext* context, OpBuilder* matmul_op);
TensorID node_output_;
std::vector<int> weights_shape_, bias_shape_;
std::vector<float> transposed_weights_;
float weights_min_, weights_max_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_MATMUL_BUILDER_H_
@@ -0,0 +1,73 @@
/* 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 "tensorflow/lite/delegates/hexagon/builders/min_max_builder.h"
#include <cstdint>
#include "tensorflow/lite/core/c/common.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus MinMaxOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Input tensors a and b.
int a_tensor_id = inputs->data[0];
int b_tensor_id = inputs->data[1];
const auto& a_tensor = context->tensors[a_tensor_id];
const auto& b_tensor = context->tensors[b_tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(a_tensor_id));
AddInput(graph_builder_->GetHexagonTensorId(b_tensor_id));
// Add Inputs A & B min/max
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, a_tensor));
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, b_tensor));
// Add output min/max
const int output_tensor_id = outputs->data[0];
const auto& output_tensor = context->tensors[output_tensor_id];
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, output_tensor));
// Add outputs.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus MinMaxOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
OpBuilder* CreateMinMaxBuilder(GraphBuilder* graph_builder, int op_type) {
return new MinMaxOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,44 @@
/* 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_MIN_MAX_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_MIN_MAX_BUILDER_H_
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class MinMaxOpBuilder : public OpBuilder {
public:
explicit MinMaxOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
private:
TensorID node_output_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_MIN_MAX_BUILDER_H_
@@ -0,0 +1,100 @@
/* 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 "tensorflow/lite/delegates/hexagon/builders/mirror_pad_builder.h"
#include <stdint.h>
#include <vector>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus MirrorPadOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Input data tensor.
int tensor_id = inputs->data[0];
const auto& input_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
// Padding tensor.
// Should be a constant.
tensor_id = inputs->data[1];
const auto& padding_tensor = context->tensors[tensor_id];
if (padding_tensor.dims->size != 2 || padding_tensor.dims->data[0] > 4 ||
padding_tensor.dims->data[1] != 2) {
TF_LITE_KERNEL_LOG(context, "Invalid padding tensor shape");
return kTfLiteError;
}
paddings_shape_ = {1, 1, 4, 2};
std::vector<int> padding_data(8, 0);
// Hexagon always expects padding data for each dimension in order {b, h, w,
// d}. This start value ensures we pad the non-relevant dimensions with 0.
int padding_data_start = 8 - padding_tensor.dims->data[0] * 2;
for (int i = 0; i < padding_tensor.dims->data[0] * 2; ++i) {
padding_data[padding_data_start + i] = padding_tensor.data.i32[i];
}
auto* const_padding_node = graph_builder_->AddConstNodeWithData(
paddings_shape_.data(), reinterpret_cast<char*>(padding_data.data()),
padding_data.size() * sizeof(padding_data[0]));
AddInput(TensorID(const_padding_node->GetID(), 0));
// Padding type.
const TfLiteMirrorPaddingParams* params =
reinterpret_cast<const TfLiteMirrorPaddingParams*>(builtin_data_);
if (params->mode == kTfLiteMirrorPaddingReflect) {
SetPaddingType(NN_PAD_MIRROR_REFLECT);
} else if (params->mode == kTfLiteMirrorPaddingSymmetric) {
SetPaddingType(NN_PAD_MIRROR_SYMMETRIC);
}
// Min/max values for input tensor.
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input_tensor));
// Hexagon outputs for this node.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus MirrorPadOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
MirrorPadOpBuilder::~MirrorPadOpBuilder() {}
OpBuilder* CreateMirrorPadBuilder(GraphBuilder* graph_builder, int op_type) {
return new MirrorPadOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,48 @@
/* 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_MIRROR_PAD_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_MIRROR_PAD_BUILDER_H_
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class MirrorPadOpBuilder : public OpBuilder {
public:
explicit MirrorPadOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~MirrorPadOpBuilder() override;
private:
TensorID node_output_;
std::vector<int> paddings_shape_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_MIRROR_PAD_BUILDER_H_
@@ -0,0 +1,60 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/neg_op_builder.h"
#include <cstdint>
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus NegOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Input data tensor.
int tensor_id = inputs->data[0];
const auto& input_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input_tensor));
// Hexagon outputs for this node.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus NegOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
OpBuilder* CreateNegOpBuilder(GraphBuilder* graph_builder, int op_type) {
return new NegOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,43 @@
/* Copyright 2019 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_NEG_OP_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_NEG_OP_BUILDER_H_
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class NegOpBuilder : public OpBuilder {
public:
explicit NegOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
private:
TensorID node_output_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_NEG_OP_BUILDER_H_
@@ -0,0 +1,424 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
#include <cstdint>
#include <vector>
#include "hexagon/hexagon_nn_ops.h"
#include "tensorflow/lite/builtin_ops.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/hexagon/builders/op_factory.h"
#include <farmhash.h>
namespace tflite {
namespace delegates {
namespace hexagon {
namespace {
// Farmhash Fingerprint
inline uint64_t CombineFingerprints(uint64_t l, uint64_t h) {
// Murmur-inspired hashing.
const uint64_t kMul = 0x9ddfea08eb382d69ULL;
uint64_t a = (l ^ h) * kMul;
a ^= (a >> 47);
uint64_t b = (h ^ a) * kMul;
b ^= (b >> 44);
b *= kMul;
b ^= (b >> 41);
b *= kMul;
return b;
}
inline uint64_t ComputeHash(const int shape[], const char* data,
const int data_len) {
return CombineFingerprints(
::util::Fingerprint64(data, data_len),
::util::Fingerprint64(reinterpret_cast<const char*>(shape),
sizeof(shape[0]) * 4));
}
inline uint64_t ComputeHash(const TfLiteTensor& tensor, const int shape[],
int int8_to_uint8) {
auto data_hash = ComputeHash(shape, tensor.data.raw_const, tensor.bytes);
auto int8_to_uint8_hash = ::util::Fingerprint64(
reinterpret_cast<char*>(&int8_to_uint8), sizeof(int8_to_uint8));
return CombineFingerprints(data_hash, int8_to_uint8_hash);
}
int GetElementSize(TfLiteType type) {
switch (type) {
case kTfLiteFloat32:
return sizeof(float);
case kTfLiteBool:
return sizeof(bool);
case kTfLiteInt32:
return sizeof(int32_t);
case kTfLiteInt8:
return sizeof(int8_t);
case kTfLiteUInt8:
return sizeof(uint8_t);
default:
return sizeof(int8_t);
}
}
} // namespace
OpBuilder* GraphBuilder::CreateOpBuilderFromTfLiteOp(int op_type,
TfLiteNode* node) {
switch (op_type) {
case kTfLiteBuiltinAdd:
return CreateArithmeticBuilder(this, OP_QuantizedAdd_8p8to8);
case kTfLiteBuiltinArgMax:
return CreateArgMinMaxOpBuilder(this, OP_ArgMax_8toInt32);
case kTfLiteBuiltinArgMin:
return CreateArgMinMaxOpBuilder(this, OP_ArgMin_8);
case kTfLiteBuiltinMul:
// The 32-bit version of Mul is more accurate, and robust to disparities
// in input/output ranges.
return CreateArithmeticBuilder(this, OP_QuantizedMul_8x8to32);
case kTfLiteBuiltinSub:
return CreateArithmeticBuilder(this, OP_QuantizedSub_8p8to8);
case kTfLiteBuiltinMean:
return CreateReduceBuilder(this, OP_QuantizedMean_8);
case kTfLiteBuiltinSum:
return CreateReduceBuilder(this, OP_QuantizedSum_8to32);
case kTfLiteBuiltinPad:
return CreatePadBuilder(this, OP_QuantizedPad_8);
case kTfLiteBuiltinMirrorPad:
return CreateMirrorPadBuilder(this, OP_MirrorPad_8);
case kTfLiteBuiltinFullyConnected: {
const auto& weights_tensor = context_->tensors[node->inputs->data[1]];
if (weights_tensor.allocation_type == kTfLiteMmapRo)
return CreateMatMulWithConstWeightsOpBuilder(
this, OP_QuantizedMatMul_8x8to32);
else
return CreateMatMulOpBuilder(this, OP_Transpose_8);
}
case kTfLiteBuiltinAveragePool2d:
return CreatePool2DBuilder(this, OP_QuantizedAvgPool_8);
case kTfLiteBuiltinMaxPool2d:
return CreatePool2DBuilder(this, OP_QuantizedMaxPool_8);
case kTfLiteBuiltinConcatenation:
return CreateConcatBuilder(this, OP_QuantizedConcat_8);
case kTfLiteBuiltinConv2d:
return CreateConv2DBuilder(this, OP_Supernode_8x8p32to8);
case kTfLiteBuiltinTransposeConv:
return CreateTransposeConv2DBuilder(
this, OP_QuantizedTransposeConv2d_8x8p32to8);
case kTfLiteBuiltinDepthwiseConv2d:
return CreateConv2DBuilder(this, OP_DepthwiseSupernode_8x8p32to8);
case kTfLiteBuiltinReshape:
return CreateReshapeBuilder(this, OP_Reshape);
case kTfLiteBuiltinSoftmax:
return CreateSoftmaxBuilder(this, OP_QuantizedSoftmax_8);
case kTfLiteBuiltinResizeNearestNeighbor:
return CreateResizeNearestNeighborBuilder(this,
OP_ResizeNearestNeighbor_8);
case kTfLiteBuiltinL2Normalization:
return CreateL2NormalizationBuilder(this, OP_L2Normalize_8);
case kTfLiteBuiltinRelu:
return CreateActivationBuilder(this, OP_QuantizedRelu_8);
case kTfLiteBuiltinRelu6:
return CreateActivationBuilder(this, OP_QuantizedReluX_8);
case kTfLiteBuiltinTanh:
return CreateActivationBuilder(this, OP_QuantizedTanh_8);
case kTfLiteBuiltinLogistic:
return CreateActivationBuilder(this, OP_QuantizedSigmoid_8);
case kTfLiteBuiltinSplit:
return CreateSplitBuilder(this, OP_QuantizedSplit_8);
case kTfLiteBuiltinResizeBilinear:
return CreateResizeBilinearOpBuilder(this, OP_QuantizedResizeBilinear_8);
case kTfLiteBuiltinNeg:
return CreateNegOpBuilder(this, OP_QuantizedNeg_8);
case kTfLiteBuiltinTranspose:
return CreateTransposeBuilder(this, OP_Transpose_8);
case kTfLiteBuiltinSpaceToDepth:
return CreateSpaceToDepthBuilder(this, OP_SpaceToDepth_8);
case kTfLiteBuiltinDepthToSpace:
return CreateSpaceToDepthBuilder(this, OP_DepthToSpace_8);
case kTfLiteBuiltinQuantize:
return CreateQuantizeBuilder(this, OP_Requantize_8to8);
case kTfLiteBuiltinHardSwish:
return CreateHardSwishBuilder(this, OP_QuantizedHardSwish_8);
case kTfLiteBuiltinMinimum:
return CreateMinMaxBuilder(this, OP_QuantizedMinimum_8);
case kTfLiteBuiltinMaximum:
return CreateMinMaxBuilder(this, OP_QuantizedMaximum_8);
case kTfLiteBuiltinSlice:
return CreateSliceOpBuilder(this, OP_QuantizedSlice_8);
case kTfLiteBuiltinPack:
return CreatePackBuilder(this, OP_QuantizedPack_8);
case kTfLiteBuiltinStridedSlice:
return CreateStridedSliceBuilder(this, OP_QuantizedStridedSlice_8);
case kTfLiteBuiltinSquaredDifference:
return CreateSquaredDifferenceOpBuilder(this, OP_QuantizedSub_8p8to8);
case kTfLiteBuiltinRsqrt:
return CreateRSqrtOpBuilder(this, OP_QuantizedSqrt_8);
default:
context_->ReportError(context_, "Op not supported: %d", op_type);
return nullptr;
}
}
OpBuilder* GraphBuilder::LookupConstData(uint64_t cache_key) {
auto lookup_result = cache_.find(cache_key);
if (lookup_result != cache_.end()) return lookup_result->second;
return nullptr;
}
void GraphBuilder::AddToCache(uint64_t cache_key, OpBuilder* value) {
cache_[cache_key] = value;
}
OpBuilder* GraphBuilder::AddConstNodeWithData(const int shape[], char* data,
int data_size) {
auto cache_key = ComputeHash(shape, data, data_size);
if (auto lookup_result = LookupConstData(cache_key)) return lookup_result;
builders_.emplace_back(new OpBuilder(this, OP_Const));
builders_.back()->SetConstNode();
builders_.back()->SetNodeId(builders_.size());
int error = hexagon_nn_->hexagon_nn_append_const_node(
graph_id_, builders_.size(), shape[0], shape[1], shape[2], shape[3],
reinterpret_cast<const uint8_t*>(data), data_size);
if (error != 0) {
TF_LITE_KERNEL_LOG(context_, "Error adding const node with shape id: %d",
static_cast<int>(builders_.size()));
return nullptr;
}
AddToCache(cache_key, builders_.back().get());
return builders_.back().get();
}
OpBuilder* GraphBuilder::AddConstNodeWithData(int tensor_id,
const TfLiteTensor& tensor,
bool int8_to_uint8) {
// Fetch shape of tensor and pad 1's so it is always 4D.
int batch_size, height_size, width_size, depth_size;
GetDims(&batch_size, &height_size, &width_size, &depth_size, tensor.dims);
const int shape[] = {batch_size, height_size, width_size, depth_size};
auto cache_key = ComputeHash(tensor, shape, int8_to_uint8 ? 1 : 0);
if (auto lookup_result = LookupConstData(cache_key)) {
// If tensor is cached but with no id, that can happen when the same
// data is added from a constant value (not tensor). We can cache the data
// and reuse it.
// We assign the tensor to this cached const node before returning.
if (!HasTensor(tensor_id))
AddTensorWithID(tensor_id, lookup_result->GetID(), 0);
return lookup_result;
}
builders_.emplace_back(new OpBuilder(this, OP_Const));
const int node_id = builders_.size();
builders_.back()->SetConstNode();
builders_.back()->SetNodeId(node_id);
int error = hexagon_nn_->hexagon_nn_append_const_node(
graph_id_, node_id, batch_size, height_size, width_size, depth_size,
reinterpret_cast<const uint8_t*>(tensor.data.raw), tensor.bytes);
if (error > 0) {
context_->ReportError(
context_, "Failed to add const node for tensor with id: %d", tensor_id);
return nullptr;
}
AddTensorWithID(tensor_id, node_id, 0);
// We need to return the builder with result, so we can't rely
// on builders_.back() as it can change while casting, so we hold pointer
// and update with value from casting if needed.
OpBuilder* result_builder = builders_.back().get();
// Cast int8 to uint8 if requested.
// This will add cast op to uint8 and update tensor map to point
// to the casted tensor.
if (int8_to_uint8 && tensor.type == kTfLiteInt8) {
AddCastOp(context_, OP_Quantized_CastInt8ToUInt8, tensor_id,
&result_builder);
}
AddToCache(cache_key, result_builder);
return result_builder;
}
// TODO(b/154604279): Support these casting ops in Hexagon op profiling (which
// seems to key tensors on a single op, which may not be the case now).
TfLiteStatus GraphBuilder::AddCastOp(TfLiteContext* context, int op_type,
int tensor_id,
OpBuilder** cast_op_builder) {
// Create a new OpBuilder for casting the tensor.
OpBuilder* cast_builder = CreateCastBuilder(this, op_type);
builders_.emplace_back(cast_builder);
cast_builder->SetNodeId(builders_.size());
// We cast the tensor in-place, so there is only 1 input & output which is the
// same.
auto* tensor_data = TfLiteIntArrayCreate(1);
tensor_data->data[0] = tensor_id;
TF_LITE_ENSURE_STATUS(
cast_builder->PopulateSubGraph(tensor_data, tensor_data, context));
TF_LITE_ENSURE_STATUS(cast_builder->RegisterOutputs(tensor_data, context));
TfLiteIntArrayFree(tensor_data);
if (cast_op_builder != nullptr) *cast_op_builder = cast_builder;
return kTfLiteOk;
}
TfLiteStatus GraphBuilder::AddInputTensors(const TfLiteIntArray* input_tensors,
TfLiteContext* context) {
auto* input_op = AddNode();
input_op->SetOpType(OP_INPUT);
// We need to track num_inputs since not all input_tensors are actual input
// data. Some are constants.
int num_inputs = 0;
for (int i = 0; i < input_tensors->size; ++i) {
const int tensor_id = input_tensors->data[i];
const auto& tensor = context->tensors[tensor_id];
if (tensor.allocation_type == kTfLiteMmapRo) continue;
input_op->AddOutput(tensor.dims, GetElementSize(tensor.type));
AddTensorWithID(tensor_id, input_op->GetID(), num_inputs);
// If tensor is of type int8, add an op to cast it to uint8.
if (tensor.type == kTfLiteInt8) {
TF_LITE_ENSURE_STATUS(AddCastOp(context, OP_Quantized_CastInt8ToUInt8,
tensor_id, /*cast_op_builder=*/nullptr));
}
++num_inputs;
}
return kTfLiteOk;
}
TfLiteStatus GraphBuilder::AddOutputTensors(
const TfLiteIntArray* output_tensors, TfLiteContext* context) {
std::vector<OpBuilder::TensorID> hexagon_output_ids;
hexagon_output_ids.reserve(output_tensors->size);
for (int i = 0; i < output_tensors->size; ++i) {
const int tensor_id = output_tensors->data[i];
const auto& tensor = context->tensors[tensor_id];
// If tensor is of type int8, add an op to cast it to uint8.
if (tensor.type == kTfLiteInt8) {
TF_LITE_ENSURE_STATUS(AddCastOp(context, OP_Quantized_CastUInt8ToInt8,
tensor_id, /*cast_op_builder=*/nullptr));
}
hexagon_output_ids.push_back(GetHexagonTensorId(tensor_id));
}
// Add Hexagon OUTPUT op.
auto* output_op = AddNode();
output_op->SetOpType(OP_OUTPUT);
for (auto hexagon_output : hexagon_output_ids) {
output_op->AddInput(hexagon_output);
}
return kTfLiteOk;
}
OpBuilder::TensorID OpBuilder::AddOutput(const TfLiteIntArray* dims,
int element_size) {
op_node_.outputs.push_back(hexagon_nn_output());
op_node_.outputs.back().elementsize = element_size;
op_node_.outputs.back().rank = 4;
// TODO(karimnosseir): What is a good to estimate the max size ?
int batch_size, height_size, width_size, depth_size;
GetDims(&batch_size, &height_size, &width_size, &depth_size, dims);
auto& max_sizes = op_node_.outputs.back().max_sizes;
if (graph_builder_->GraphHasDynamicBatch()) {
max_sizes[0] = graph_builder_->GetMaxBatchSize();
} else {
max_sizes[0] = batch_size;
}
max_sizes[1] = height_size;
max_sizes[2] = width_size;
max_sizes[3] = depth_size;
return TensorID(GetID(), op_node_.outputs.size() - 1);
}
OpBuilder::TensorID OpBuilder::AddOutput(int elementsize, int rank,
const int* max_sizes_vect) {
op_node_.outputs.push_back(hexagon_nn_output());
op_node_.outputs.back().elementsize = elementsize;
op_node_.outputs.back().rank = rank;
auto& max_sizes = op_node_.outputs.back().max_sizes;
for (int i = 0; i < rank; ++i) {
max_sizes[i] = max_sizes_vect[i];
}
if (graph_builder_->GraphHasDynamicBatch()) {
max_sizes[0] = graph_builder_->GetMaxBatchSize();
}
return TensorID(GetID(), op_node_.outputs.size() - 1);
}
OpBuilder::TensorID OpBuilder::AddOutput(
int elementsize, int rank, const std::vector<int>& max_sizes_vect) {
return AddOutput(elementsize, rank, max_sizes_vect.data());
}
const OpNode* OpBuilder::Build() {
for (const auto& id : input_ids_) {
op_node_.inputs.push_back(hexagon_nn_input());
op_node_.inputs.back().src_id = id.first;
op_node_.inputs.back().output_idx = id.second;
}
return &op_node_;
}
TfLiteStatus OpBuilder::ComputeAndAddMinAndMax(TfLiteContext* context,
const TfLiteTensor& tensor) {
float tensor_min, tensor_max;
TF_LITE_ENSURE_STATUS(
ComputeMinAndMaxQuantValues(tensor, &tensor_min, &tensor_max));
auto* min_const_node = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&tensor_min), sizeof(tensor_min));
auto* max_const_node = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&tensor_max), sizeof(tensor_max));
AddInput(TensorID(min_const_node->GetID(), 0));
AddInput(TensorID(max_const_node->GetID(), 0));
return kTfLiteOk;
}
// Static
constexpr int OpBuilder::kScalarShape[];
OpBuilder* GraphBuilder::AddNode(int tflite_node_index) {
OpBuilder* op = new OpBuilder(this, OP_Nop);
builders_.emplace_back(op);
op->SetNodeId(builders_.size());
op->SetTFLiteNodeId(tflite_node_index);
return op;
}
OpBuilder* GraphBuilder::AddNodeFromTfLiteOp(int op_type, TfLiteNode* node,
int tflite_node_index) {
OpBuilder* op = CreateOpBuilderFromTfLiteOp(op_type, node);
builders_.emplace_back(op);
op->SetNodeId(builders_.size());
op->SetTFLiteNodeId(tflite_node_index);
op->SetBuiltinData(node->builtin_data);
op->SetTfLiteNode(node);
return op;
}
void GraphBuilder::AddBatchSeqConfig(int max_size_for_batch,
TfLiteIntArray* input_batch_dimensions,
TfLiteIntArray* output_batch_dimensions) {
OpBuilder* batch_seq_node =
CreateBatchSeqBuilder(this, OP_BatchSeqConfig, max_size_for_batch,
input_batch_dimensions, output_batch_dimensions);
builders_.emplace_back(batch_seq_node);
batch_seq_node->SetNodeId(builders_.size());
batch_seq_node->PopulateSubGraph(nullptr, nullptr, nullptr);
max_size_for_batch_ = max_size_for_batch;
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,410 @@
/* Copyright 2019 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_OP_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_OP_BUILDER_H_
#include <limits>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "hexagon/hexagon_nn_ops.h"
#include "tensorflow/lite/builtin_ops.h"
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_implementation.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
namespace tflite {
namespace delegates {
namespace hexagon {
// Wrapper that holds all data representing a single node in the Hexagon graph.
struct OpNode {
std::vector<hexagon_nn_input> inputs;
std::vector<hexagon_nn_output> outputs;
// Value from the Enum of Ops in hexagon_nn_ops
int op_type;
hexagon_nn_padding_type padding_type = NN_PAD_NA;
// Id of node in the Hexagon graph.
int node_id = -1;
// Index/ID of node in the tflite graph.
// This ID can be duplicate if one TFLite node creates multiple Hexagon op
// nodes.
int tflite_node_index = -1;
};
class GraphBuilder;
// Represents a single Op in the TFLite graph.
// For each op in TFLite there should be an OpBuilder, this builder is
// responsible for constructing equivalent node(s) in the hexagon graph. A
// single builder can create one or more ops in the hexagon graph. When adding
// new op* users should inherit from this class and implement
// - PopulateSubgraph: which given inputs/outputs should construct the
// equivalent hexagon nodes.
// - RegisterOutputs: Which should have logic that maps final outputs from a
// given node to the equivalent in Hexagon graph.
class OpBuilder {
public:
// Const representing the shape of a scalar value.
static constexpr int kScalarShape[] = {1, 1, 1, 1};
OpBuilder(GraphBuilder* graph_builder, int hexagon_op_type)
: graph_builder_(graph_builder) {
op_node_.op_type = hexagon_op_type;
}
// A tensor is identified in the graph using a pair of IDs
// (Node ID, output Tensor ID)
// Node producing this tensor, and the index of the tensor in this
// node output list.
using TensorID = std::pair<int, int>;
virtual ~OpBuilder() {}
// Sets the op type in the hexagon graph.
void SetOpType(int op_type) { op_node_.op_type = op_type; }
// Sets the node id in the hexagon graph.
void SetNodeId(int node_id) { op_node_.node_id = node_id; }
// Sets the TfLite node index in the TfLite graph.
void SetTFLiteNodeId(int node_index) {
op_node_.tflite_node_index = node_index;
}
// Marks this node as Const node.
void SetConstNode() { op_node_.op_type = OP_Const; }
// Sets the padding type of the current node.
void SetPaddingType(hexagon_nn_padding_type padding_type) {
op_node_.padding_type = padding_type;
}
// Sets the builtin_data of TFLite node that this Builder is responsible for.
void SetBuiltinData(void* builtin_data) { builtin_data_ = builtin_data; }
// Returns true if the current op is a const Op.
bool IsConstNode() const { return op_node_.op_type == OP_Const; }
// Subclasses should override it and have logic which handles initializing
// hexagon node(s) for the current op, given 'inputs' 'outputs'
virtual TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
return kTfLiteOk;
}
// Subclasses should override it and register the final output(s) from the
// node to the equivalent in hexagon graph.
virtual TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
return kTfLiteOk;
}
// Constructs OpNode which represents a node in the Hexagon graph.
const OpNode* Build();
// Returns the Node index in TFLite graph.
int GetTFLiteNodeID() const { return op_node_.tflite_node_index; }
// Returns the Op type of the current Op (in Hexagon graph)
int GetOpType() const { return op_node_.op_type; }
// Returns the node id in the hexagon graph.
int GetID() const { return op_node_.node_id; }
// Adds tensor identified by 'tensor_id' as input to the current Op.
void AddInput(const TensorID& tensor_id) { input_ids_.push_back(tensor_id); }
// Adds Output to the current node, the output has shape defined in 'dims'.
// The size of each element is defined using 'element_size'.
// Returns the TensorID identifying this output in the graph.
TensorID AddOutput(const TfLiteIntArray* dims, int element_size);
// Adds Output to the current node, each element in the output has
// size 'elementsize' and rank 'rank' and for each dimension in the output
// the maximum size is max_sizes[i].
// Returns the TensorID identifying this output in the graph.
TensorID AddOutput(int elementsize, int rank,
const std::vector<int>& max_sizes);
// Same as above but accepts pointer instead of std::vector.
TensorID AddOutput(int elementsize, int rank, const int* max_sizes_vect);
// Sets the node that corresponds to this builder in TFLite graph.
void SetTfLiteNode(const TfLiteNode* node) { tflite_node_ = node; }
// Static
// Computes the min/max values of 'tensor' and sets the values in
// the out params 'min' and 'max'.
// Returns kTfLiteOk on success.
static TfLiteStatus ComputeMinAndMaxQuantValues(const TfLiteTensor& tensor,
float* min, float* max) {
if (tensor.type == kTfLiteUInt8) {
return ComputeMinAndMaxQuantValues(tensor, min, max,
std::numeric_limits<uint8_t>::min(),
std::numeric_limits<uint8_t>::max());
} else if (tensor.type == kTfLiteInt8) {
return ComputeMinAndMaxQuantValues(tensor, min, max,
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max());
} else if (tensor.type == kTfLiteInt32) {
return ComputeMinAndMaxQuantValues(tensor, min, max,
std::numeric_limits<int>::min(),
std::numeric_limits<int>::max());
}
return kTfLiteError;
}
protected:
// Helper method to fetch dimensions.
// TODO(karimnosseir): Move to a shared place.
void GetDims(int* batch_size, int* height_size, int* width_size,
int* depth_size, const TfLiteIntArray* dims) {
int* dim[] = {batch_size, height_size, width_size, depth_size};
for (int i = 0; i < 4; ++i) *(dim[i]) = 1;
for (int i = 4 - dims->size; i < 4; ++i) {
*dim[i] = dims->data[i - (4 - dims->size)];
}
}
// Computes the min and max for 'tensor' and adds them as input
// to the node.
TfLiteStatus ComputeAndAddMinAndMax(TfLiteContext* context,
const TfLiteTensor& tensor);
// Computes the float min and max for 'tensor', given 'min_value' and
// 'max_value' data range. The float min and max will be set in 'min' and
// 'max' params
template <typename T>
static TfLiteStatus ComputeMinAndMaxQuantValues(const TfLiteTensor& tensor,
float* min, float* max,
T min_value, T max_value) {
*min = 0;
*max = 0;
const TfLiteQuantization& quant = tensor.quantization;
if (quant.type != TfLiteQuantizationType::kTfLiteAffineQuantization) {
printf("Tensor not quantized: %s\n", tensor.name);
return kTfLiteError;
}
const TfLiteAffineQuantization* params =
static_cast<const TfLiteAffineQuantization*>(quant.params);
float scale = params->scale->data[0];
float zero_point = static_cast<float>(params->zero_point->data[0]);
*min = scale * (static_cast<float>(min_value) - zero_point);
*max = scale * (static_cast<float>(max_value) - zero_point);
return kTfLiteOk;
}
OpNode op_node_;
// inputs to the current op. Each pair identifies a single output from
// another node (node_id, output_id).
std::vector<TensorID> input_ids_;
// Pointer to the graph builder.
GraphBuilder* graph_builder_ = nullptr;
// Data needed by this node.
void* builtin_data_ = nullptr;
// TODO(karimnosseir): Currently we only use it for getting output
// size. Can we avoid passing it ?
const TfLiteNode* tflite_node_ = nullptr;
};
class GraphBuilder {
public:
GraphBuilder(const HexagonNN* hexagon_nn, TfLiteContext* context,
int graph_id)
: hexagon_nn_(hexagon_nn), context_(context), graph_id_(graph_id) {}
// Returns per OP builder. 'op_type' is the TfLite builtinOperator.
OpBuilder* AddNodeFromTfLiteOp(int op_type, TfLiteNode* node,
int tflite_node_index);
// Add node to the graph. The caller responsible for setting correct
// data in the Op.
// 'tflite_node_index' is the node index in TFLite that creates this op.
OpBuilder* AddNode(int tflite_node_index = -1);
// Add const node that provides the data held by 'tensor'.
// If `int8_to_uint8` is true, then the data will be casted to uint8 from
// int8.
OpBuilder* AddConstNodeWithData(int tensor_id, const TfLiteTensor& tensor,
bool int8_to_uint8 = false);
// Same as above but takes shape of the tensor that will holds the data.
OpBuilder* AddConstNodeWithData(const int shape[], char* data, int data_size);
OpBuilder* CreateOpBuilderFromTfLiteOp(int op_type, TfLiteNode* node);
// Construct Input node with 'input_tensors' as output.
TfLiteStatus AddInputTensors(const TfLiteIntArray* input_tensors,
TfLiteContext* context);
// Construct Output node with 'output_tensors' as input.
TfLiteStatus AddOutputTensors(const TfLiteIntArray* output_tensors,
TfLiteContext* context);
// Adds BatchSeqConfig node to the graph. This is configuration
// for a dynamic batch size for the graph.
// A graph can have only one node of this type.
void AddBatchSeqConfig(int max_size_for_batch,
TfLiteIntArray* input_batch_dimensions,
TfLiteIntArray* output_batch_dimensions);
// Returns tensor id inside Hexagon graph.
OpBuilder::TensorID GetHexagonTensorId(int tflite_tensor_index) {
if (!HasTensor(tflite_tensor_index)) {
// Return invalid ID.
return OpBuilder::TensorID(-1, -1);
}
return tensors_[tflite_tensor_index];
}
// Return true if this tensor was added before to the graph.
bool HasTensor(int tflite_tensor_index) {
if (tensors_.size() <= tflite_tensor_index) {
return false;
}
// the first field is node ID and id = 0 is reserved
// so anything > 0 is correctly initialized.
return tensors_[tflite_tensor_index].first != 0;
}
void AddDebugNode() {}
void Build() {
for (int i = 0; i < builders_.size(); ++i) {
if (builders_[i]->IsConstNode()) {
continue;
}
const OpNode* op_node = builders_[i]->Build();
int error = hexagon_nn_->hexagon_nn_append_node(
graph_id_, op_node->node_id, op_node->op_type, op_node->padding_type,
op_node->inputs.data(), op_node->inputs.size(),
op_node->outputs.data(), op_node->outputs.size());
if (error != 0) {
printf("Error adding node: id:%d, op_type:%d\n", op_node->node_id,
op_node->op_type);
}
}
}
void print() {
printf("------------------------------\n");
std::vector<unsigned char> buf(10000);
hexagon_nn_->hexagon_nn_snpprint(graph_id_, buf.data(), buf.size());
printf("%s", buf.data());
printf("------------------------------\n");
fflush(stdout);
}
// Add new tensor mapping to the tensor list.
bool AddTensorWithID(int tflite_tensor_id, int hexagon_node_id,
int hexagon_node_output_id, bool overwrite = false) {
if (!overwrite && HasTensor(tflite_tensor_id)) {
TF_LITE_KERNEL_LOG(
context_,
"Trying to add duplicate tensor without overwrite, tflite_tensor_id "
"%d, hexagon_node_id %d, hexagon_node_output_id %d",
tflite_tensor_id, hexagon_node_id, hexagon_node_output_id);
return false;
}
if (tensors_.size() <= tflite_tensor_id) {
tensors_.resize(tflite_tensor_id + 1);
}
if (hexagon_node_id == -1 || hexagon_node_output_id == -1)
TF_LITE_KERNEL_LOG(context_,
"Trying to add invalid id, tflite_tensor_id "
"%d, hexagon_node_id %d, hexagon_node_output_id %d",
tflite_tensor_id, hexagon_node_id,
hexagon_node_output_id);
tensors_[tflite_tensor_id] =
OpBuilder::TensorID(hexagon_node_id, hexagon_node_output_id);
return true;
}
int GetOpTypeId(int node_id) {
if (node_id > builders_.size()) {
return -1;
}
return builders_[node_id - 1]->GetOpType();
}
int GetTFLiteNodeID(int node_id) const {
if (node_id > builders_.size()) {
return -1;
}
return builders_[node_id - 1]->GetTFLiteNodeID();
}
// Returns true if the graph supports dynamic batch. False otherwise.
bool GraphHasDynamicBatch() const { return max_size_for_batch_ != -1; }
// Returns the maximum value for batch dimension the graph supports.
// -1 if the graph doesn't support dynamic batch.
int GetMaxBatchSize() const { return max_size_for_batch_; }
private:
// Lookup in cache if data with key 'cache_key' is present.
// Return OpBuilder* for the data if found, nullptr otherwise.
OpBuilder* LookupConstData(uint64_t cache_key);
// Inserts 'value' in cache, with key equals 'cache_key'.
// If data in cache with same key then it will be overwritten.
void AddToCache(uint64_t cache_key, OpBuilder* value);
// Helper method to fetch dimensions.
// TODO(karimnosseir): Move this method to shared place.
void GetDims(int* batch_size, int* height_size, int* width_size,
int* depth_size, const TfLiteIntArray* dims) {
int* dim[] = {batch_size, height_size, width_size, depth_size};
for (int i = 0; i < 4; ++i) *(dim[i]) = 1;
for (int i = 4 - dims->size; i < 4; ++i) {
*dim[i] = dims->data[i - (4 - dims->size)];
}
}
// Adds a Cast op to convert a tensor from int8 to uint8 (or vice versa).
// The builder which has the casting operator is filled in 'cast_op_builder'
// if not nullptr.
TfLiteStatus AddCastOp(TfLiteContext* context, int op_type, int tensor_id,
OpBuilder** cast_op_builder);
const HexagonNN* hexagon_nn_ = nullptr;
TfLiteContext* context_ = nullptr;
int graph_id_ = -1;
std::vector<std::unique_ptr<OpBuilder>> builders_;
// Index in the vector is the tflite_tensor_index, the value
// is the ID in the hexgon graph.
std::vector<OpBuilder::TensorID> tensors_;
// If the graph being built supports dynamic batch, this represents
// the maximum value for batch.
int max_size_for_batch_ = -1;
// Cache for const data in the graph.
// Key is hash of the data, value is pointer to the OpBuilder* for the added
// data.
std::map<uint64_t, OpBuilder*> cache_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_OP_BUILDER_H_
@@ -0,0 +1,71 @@
/* Copyright 2019 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_OP_FACTORY_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_OP_FACTORY_H_
#include "tensorflow/lite/core/c/common.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class GraphBuilder;
class OpBuilder;
OpBuilder* CreateArgMinMaxOpBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateActivationBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateArithmeticBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateMatMulWithConstWeightsOpBuilder(GraphBuilder* graph_builder,
int op_type);
OpBuilder* CreateConcatBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateConv2DBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateTransposeConv2DBuilder(GraphBuilder* graph_builder,
int op_type);
OpBuilder* CreatePool2DBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateReshapeBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateSoftmaxBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateReduceBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateMirrorPadBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreatePadBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateResizeNearestNeighborBuilder(GraphBuilder* graph_builder,
int op_type);
OpBuilder* CreateL2NormalizationBuilder(GraphBuilder* graph_builder,
int op_type);
OpBuilder* CreateSplitBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateResizeBilinearOpBuilder(GraphBuilder* graph_builder,
int op_type);
OpBuilder* CreateNegOpBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateTransposeBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateSpaceToDepthBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateBatchSeqBuilder(GraphBuilder* graph_builder, int op_type,
int max_size_for_batch,
TfLiteIntArray* input_batch_dimensions,
TfLiteIntArray* output_batch_dimensions);
OpBuilder* CreateQuantizeBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateHardSwishBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateCastBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateMinMaxBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateSliceOpBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreatePackBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateMatMulOpBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateStridedSliceBuilder(GraphBuilder* graph_builder, int op_type);
OpBuilder* CreateSquaredDifferenceOpBuilder(GraphBuilder* graph_builder,
int op_type);
OpBuilder* CreateRSqrtOpBuilder(GraphBuilder* graph_builder, int op_type);
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_OP_FACTORY_H_
@@ -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 "tensorflow/lite/delegates/hexagon/builders/pack_builder.h"
#include <stdint.h>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
namespace {
int GetAxis(int axis, const TfLiteIntArray* inputs, TfLiteContext* context) {
auto& input_tensor = context->tensors[inputs->data[0]];
// Handle -ve axis.
if (axis < 0) {
axis += input_tensor.dims->size + 1;
}
// We need to adjust the axis to be as if the inputs are of rank 4, since
// we represent tensors in Hexagon of rank 4.
return (4 - input_tensor.dims->size) + axis - 1;
}
} // namespace
TfLiteStatus PackOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
auto* params = reinterpret_cast<TfLitePackParams*>(builtin_data_);
int axis = GetAxis(params->axis, inputs, context);
// Add axis
auto* axis_node = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&axis), sizeof(axis));
AddInput(TensorID(axis_node->GetID(), 0));
// Add all input tensors.
minima_.reserve(inputs->size);
maxima_.reserve(inputs->size);
int tensor_id = -1;
float data_min, data_max;
for (int i = 0; i < inputs->size; ++i) {
tensor_id = inputs->data[i];
auto& input_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
TF_LITE_ENSURE_STATUS(
ComputeMinAndMaxQuantValues(input_tensor, &data_min, &data_max));
minima_.push_back(data_min);
maxima_.push_back(data_max);
}
// Minima tensors.
for (int i = 0; i < minima_.size(); ++i) {
auto* data_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&minima_[i]), sizeof(minima_[i]));
AddInput(TensorID(data_min_const->GetID(), 0));
}
// Maxima tensors.
for (int i = 0; i < maxima_.size(); ++i) {
auto* data_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&maxima_[i]), sizeof(maxima_[i]));
AddInput(TensorID(data_max_const->GetID(), 0));
}
// Hexagon outputs for this node.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
TensorID pack_out = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
// Output min/max for requantization.
float output_min, output_max;
TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues(
context->tensors[outputs->data[0]], &output_min, &output_max));
auto* output_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&output_min), sizeof(output_min));
auto* output_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&output_max), sizeof(output_max));
const auto& pack_out_min = AddOutput(sizeof(float), 4, kScalarShape);
const auto& pack_out_max = AddOutput(sizeof(float), 4, kScalarShape);
// Requantize output to the expected min/max.
auto* requantize_op = graph_builder_->AddNode(GetTFLiteNodeID());
requantize_op->SetOpType(OP_Requantize_8to8);
requantize_op->AddInput(pack_out);
requantize_op->AddInput(pack_out_min);
requantize_op->AddInput(pack_out_max);
requantize_op->AddInput(TensorID(output_min_const->GetID(), 0));
requantize_op->AddInput(TensorID(output_max_const->GetID(), 0));
node_output_ =
requantize_op->AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
requantize_op->AddOutput(sizeof(float), 4, kScalarShape);
requantize_op->AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus PackOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
OpBuilder* CreatePackBuilder(GraphBuilder* graph_builder, int op_type) {
return new PackOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,46 @@
/* 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_PACK_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_PACK_BUILDER_H_
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class PackOpBuilder : public OpBuilder {
public:
explicit PackOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
private:
TensorID node_output_;
// Min/max for all inputs.
std::vector<float> minima_, maxima_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_PACK_BUILDER_H_
@@ -0,0 +1,80 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/pad_builder.h"
#include <stdint.h>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus PadOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Input data tensor.
int tensor_id = inputs->data[0];
const auto& input_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
// Min/max values for input tensor.
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input_tensor));
// Padding tensor.
tensor_id = inputs->data[1];
const auto& padding_tensor = context->tensors[tensor_id];
if (padding_tensor.allocation_type == kTfLiteMmapRo) {
// If the padding input is a constant, bake it into the Hexagon graph as a
// Const node.
auto* const_padding_node =
graph_builder_->AddConstNodeWithData(tensor_id, padding_tensor);
AddInput(TensorID(const_padding_node->GetID(), 0));
} else {
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
}
// Hexagon outputs for this node.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus PadOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
PadOpBuilder::~PadOpBuilder() {}
OpBuilder* CreatePadBuilder(GraphBuilder* graph_builder, int op_type) {
return new PadOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,47 @@
/* Copyright 2019 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_PAD_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_PAD_BUILDER_H_
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class PadOpBuilder : public OpBuilder {
public:
explicit PadOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~PadOpBuilder() override;
private:
TensorID node_output_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_PAD_BUILDER_H_
@@ -0,0 +1,122 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/pool_2d_builder.h"
#include <stdint.h>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus Pool2dOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Input data tensor.
int tensor_id = inputs->data[0];
const auto& data_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, data_tensor));
const TfLitePoolParams* pool_params =
reinterpret_cast<const TfLitePoolParams*>(builtin_data_);
// Padding type.
if (pool_params->padding == kTfLitePaddingSame) {
SetPaddingType(NN_PAD_SAME);
} else if (pool_params->padding == kTfLitePaddingValid) {
SetPaddingType(NN_PAD_VALID);
}
// Pooling window (filter) width/height as inputs.
static int dummy = 0;
filter_shape_ = {1, pool_params->filter_height, pool_params->filter_width, 1};
auto* filter_node = graph_builder_->AddConstNodeWithData(
filter_shape_.data(), (char*)&dummy, sizeof(dummy));
AddInput(TensorID(filter_node->GetID(), 0));
// Stride width/height as inputs.
stride_shape_ = {1, pool_params->stride_height, pool_params->stride_width, 1};
auto* stride_node = graph_builder_->AddConstNodeWithData(
stride_shape_.data(), (char*)&dummy, sizeof(dummy));
AddInput(TensorID(stride_node->GetID(), 0));
// Hexagon outputs for this node.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
if (op_node_.op_type == OP_QuantizedMaxPool_8) {
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
} else {
// Hexagon's AvgPool output has different min/max bounds than what TFLite
// expects. Therefore, we add a Requantize op to correct the ranges.
TensorID pool_out = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
const auto& pool_out_min = AddOutput(sizeof(float), 4, kScalarShape);
const auto& pool_out_max = AddOutput(sizeof(float), 4, kScalarShape);
// Output min/max for requantization.
TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues(
context->tensors[outputs->data[0]], &output_min_, &output_max_));
auto* output_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, (char*)&output_min_, sizeof(output_min_));
auto* output_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, (char*)&output_max_, sizeof(output_max_));
auto* requantize_op = graph_builder_->AddNode(GetTFLiteNodeID());
requantize_op->SetOpType(OP_Requantize_8to8);
requantize_op->AddInput(pool_out);
requantize_op->AddInput(pool_out_min);
requantize_op->AddInput(pool_out_max);
requantize_op->AddInput(TensorID(output_min_const->GetID(), 0));
requantize_op->AddInput(TensorID(output_max_const->GetID(), 0));
node_output_ =
requantize_op->AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
requantize_op->AddOutput(sizeof(float), 4, kScalarShape);
requantize_op->AddOutput(sizeof(float), 4, kScalarShape);
}
return kTfLiteOk;
}
TfLiteStatus Pool2dOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
Pool2dOpBuilder::~Pool2dOpBuilder() {}
OpBuilder* CreatePool2DBuilder(GraphBuilder* graph_builder, int op_type) {
return new Pool2dOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,50 @@
/* Copyright 2019 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_POOL_2D_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_POOL_2D_BUILDER_H_
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class Pool2dOpBuilder : public OpBuilder {
public:
explicit Pool2dOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~Pool2dOpBuilder() override;
private:
TensorID node_output_;
std::vector<int> stride_shape_;
std::vector<int> filter_shape_;
float output_min_, output_max_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_POOL_2D_BUILDER_H_
@@ -0,0 +1,67 @@
/* 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 "tensorflow/lite/delegates/hexagon/builders/quantize_builder.h"
#include <stdint.h>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus QuantizeOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
const auto& input_tensor = context->tensors[inputs->data[0]];
const auto& output_tensor = context->tensors[outputs->data[0]];
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, output_tensor.dims);
AddInput(graph_builder_->GetHexagonTensorId(inputs->data[0]));
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input_tensor));
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, output_tensor));
// Hexagon outputs for this node.
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus QuantizeOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
QuantizeOpBuilder::~QuantizeOpBuilder() {}
OpBuilder* CreateQuantizeBuilder(GraphBuilder* graph_builder, int op_type) {
return new QuantizeOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,48 @@
/* 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_QUANTIZE_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_QUANTIZE_BUILDER_H_
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class QuantizeOpBuilder : public OpBuilder {
public:
explicit QuantizeOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
explicit QuantizeOpBuilder(GraphBuilder* graph_builder, int op_type,
int relu_value)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~QuantizeOpBuilder() override;
private:
TensorID node_output_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_QUANTIZE_BUILDER_H_
@@ -0,0 +1,128 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/reduce_builder.h"
#include <stdint.h>
#include <cstddef>
#include <vector>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/kernel_util.h"
#include "tensorflow/lite/util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus ReduceOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Input data tensor.
int tensor_id = inputs->data[0];
const auto& input_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input_tensor));
// Axes tensor should be constant.
int axes_tensor_id = inputs->data[1];
const auto& axes_tensor = context->tensors[axes_tensor_id];
if (axes_tensor.allocation_type != kTfLiteMmapRo) {
TF_LITE_KERNEL_LOG(context, "Reduction op doesn't have constant axis");
return kTfLiteError;
}
// Hexagon assumes a 4-D input tensor. If the input tensor is not 4-D, we
// need to apply the supplemental offset to the axis.
auto* const_axes_node =
graph_builder_->AddConstNodeWithData(tensor_id, axes_tensor);
if (input_tensor.dims->size < 4) {
const int axes_size = NumElements(&axes_tensor);
auto offset = 4 - input_tensor.dims->size;
std::vector<int> axes(axes_size);
for (auto i = 0; i < axes.size(); ++i) {
axes[i] = axes_tensor.data.i32[i] + offset;
}
const std::vector<int> axes_shape = {1, 1, 1, axes_size};
auto axes_node = graph_builder_->AddConstNodeWithData(
axes_shape.data(), reinterpret_cast<char*>(axes.data()),
axes.size() * sizeof(axes[0]));
AddInput(TensorID(axes_node->GetID(), 0));
} else {
AddInput(TensorID(const_axes_node->GetID(), 0));
}
auto& output_tensor = context->tensors[outputs->data[0]];
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, output_tensor.dims);
float output_min = -1, output_max = -1;
ComputeMinAndMaxQuantValues(output_tensor, &output_min, &output_max);
auto* output_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&output_min), sizeof(output_min));
auto* output_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&output_max), sizeof(output_max));
// Min/max values for output tensor.
AddInput(TensorID(output_min_const->GetID(), 0));
AddInput(TensorID(output_max_const->GetID(), 0));
// Add outputs
size_t output_element_size = 0;
TF_LITE_ENSURE_STATUS(
GetSizeOfType(context, output_tensor.type, &output_element_size));
auto mean_output = AddOutput(output_element_size, 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
auto mean_out_min = AddOutput(output_element_size, 4, kScalarShape);
auto mean_out_max = AddOutput(output_element_size, 4, kScalarShape);
// Mean op doesn't honor the passed min/max for output, so we need
// to add requantize.
auto* requantize_op = graph_builder_->AddNode(GetTFLiteNodeID());
requantize_op->SetOpType(OP_Requantize_8to8);
requantize_op->AddInput(mean_output);
requantize_op->AddInput(mean_out_min);
requantize_op->AddInput(mean_out_max);
requantize_op->AddInput(TensorID(output_min_const->GetID(), 0));
requantize_op->AddInput(TensorID(output_max_const->GetID(), 0));
node_output_ =
requantize_op->AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
requantize_op->AddOutput(sizeof(float), 4, kScalarShape);
requantize_op->AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus ReduceOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
ReduceOpBuilder::~ReduceOpBuilder() {}
OpBuilder* CreateReduceBuilder(GraphBuilder* graph_builder, int op_type) {
return new ReduceOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,47 @@
/* Copyright 2019 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_REDUCE_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_REDUCE_BUILDER_H_
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class ReduceOpBuilder : public OpBuilder {
public:
explicit ReduceOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~ReduceOpBuilder() override;
private:
TensorID node_output_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_REDUCE_BUILDER_H_
@@ -0,0 +1,121 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/reshape_builder.h"
#include <stdint.h>
#include <vector>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
namespace {
void PopulateOutputShapeFromTensor(const TfLiteTensor* shape_tensor,
std::vector<int>* output_shape) {
for (int i = 0; i < shape_tensor->dims->data[0]; ++i) {
output_shape->push_back(shape_tensor->data.i32[i]);
}
}
void PopulateShapeFromParam(const TfLiteReshapeParams* params,
std::vector<int>* output_shape) {
// The function is returned above this line if the shape tensor is usable.
// Now fallback to the shape parameter in `TfLiteReshapeParams`.
int num_dimensions = params->num_dimensions;
if (num_dimensions == 1 && params->shape[0] == 0) {
// Legacy tflite models use a shape parameter of [0] to indicate scalars,
// so adjust accordingly. TODO(b/111614235): Allow zero-sized buffers during
// toco conversion.
num_dimensions = 0;
}
for (int i = 0; i < num_dimensions; ++i) {
output_shape->push_back(params->shape[i]);
}
}
} // namespace
TfLiteStatus ReshapeOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Input data tensor.
AddInput(graph_builder_->GetHexagonTensorId(inputs->data[0]));
// Output shape.
TfLiteTensor* shape_tensor = nullptr;
bool output_shape_is_dynamic = false;
if (inputs->size == 2) {
shape_tensor = &context->tensors[inputs->data[1]];
bool is_shape_tensor =
(shape_tensor->dims->size == 1 && shape_tensor->type == kTfLiteInt32);
// If tensor shape is dynamic, pass it along directly.
if (shape_tensor->allocation_type != kTfLiteMmapRo && is_shape_tensor) {
output_shape_is_dynamic = true;
AddInput(graph_builder_->GetHexagonTensorId(inputs->data[1]));
}
if (!is_shape_tensor) {
shape_tensor = nullptr;
}
}
if (!output_shape_is_dynamic) {
if (shape_tensor) {
PopulateOutputShapeFromTensor(shape_tensor, &output_shape_);
} else {
const TfLiteReshapeParams* reshape_params =
reinterpret_cast<const TfLiteReshapeParams*>(builtin_data_);
PopulateShapeFromParam(reshape_params, &output_shape_);
}
int num_elements_in_shape = static_cast<int>(output_shape_.size());
output_shape_shape_ = {1, 1, 1, num_elements_in_shape};
auto* shape_node = graph_builder_->AddConstNodeWithData(
output_shape_shape_.data(),
reinterpret_cast<char*>(output_shape_.data()),
sizeof(int) * num_elements_in_shape);
AddInput(TensorID(shape_node->GetID(), 0));
}
// Hexagon output for this node.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
return kTfLiteOk;
}
TfLiteStatus ReshapeOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
ReshapeOpBuilder::~ReshapeOpBuilder() {}
OpBuilder* CreateReshapeBuilder(GraphBuilder* graph_builder, int op_type) {
return new ReshapeOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,49 @@
/* Copyright 2019 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_RESHAPE_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_RESHAPE_BUILDER_H_
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class ReshapeOpBuilder : public OpBuilder {
public:
explicit ReshapeOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~ReshapeOpBuilder() override;
private:
TensorID node_output_;
std::vector<int> output_shape_;
std::vector<int> output_shape_shape_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_RESHAPE_BUILDER_H_
@@ -0,0 +1,101 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/resize_bilinear_builder.h"
#include <cstdint>
#include <vector>
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus ResizeBilinearOpBuilder::PopulateSubGraph(
const TfLiteIntArray* inputs, const TfLiteIntArray* outputs,
TfLiteContext* context) {
if (inputs->size != 2) {
TF_LITE_KERNEL_LOG(context, "Expecting 2 inputs %d != 2\n", inputs->size);
return kTfLiteError;
}
// Input data tensor.
int input_tensor_id = inputs->data[0];
const auto& input_tensor = context->tensors[input_tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(input_tensor_id));
const auto& size_tensor = context->tensors[inputs->data[1]];
if (!IsConstantTensor(&size_tensor)) {
TF_LITE_KERNEL_LOG(context,
"Hexagon Delegate doesn't support dynamic shape.\n");
return kTfLiteError;
}
// dims tensor.
const int dims_shape[] = {1, 1, 1, 2};
std::vector<int> dims = {size_tensor.data.i32[0], size_tensor.data.i32[1]};
auto* dims_const = graph_builder_->AddConstNodeWithData(
dims_shape, reinterpret_cast<char*>(dims.data()),
sizeof(int) * dims.size());
AddInput(TensorID(dims_const->GetID(), 0));
// Input min/max
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input_tensor));
// Align Corners & half-pixel-centers.
const TfLiteResizeBilinearParams* params =
reinterpret_cast<const TfLiteResizeBilinearParams*>(builtin_data_);
int align_corners = params->align_corners ? 1 : 0;
auto* align_corners_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&align_corners),
sizeof(align_corners));
AddInput(TensorID(align_corners_const->GetID(), 0));
int half_pixel_centers = params->half_pixel_centers ? 1 : 0;
auto* half_pixel_centers_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&half_pixel_centers),
sizeof(half_pixel_centers));
AddInput(TensorID(half_pixel_centers_const->GetID(), 0));
// Output
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
auto resize_bilinear_out = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
node_output_ = resize_bilinear_out;
return kTfLiteOk;
}
TfLiteStatus ResizeBilinearOpBuilder::RegisterOutputs(
const TfLiteIntArray* outputs, TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
ResizeBilinearOpBuilder::~ResizeBilinearOpBuilder() {}
OpBuilder* CreateResizeBilinearOpBuilder(GraphBuilder* graph_builder,
int op_type) {
return new ResizeBilinearOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,45 @@
/* Copyright 2019 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_RESIZE_BILINEAR_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_RESIZE_BILINEAR_BUILDER_H_
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class ResizeBilinearOpBuilder : public OpBuilder {
public:
explicit ResizeBilinearOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~ResizeBilinearOpBuilder() override;
private:
TensorID node_output_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_RESIZE_BILINEAR_BUILDER_H_
@@ -0,0 +1,95 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/resize_nearest_neighbor_builder.h"
#include <stdint.h>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus ResizeNearestNeighborOpBuilder::PopulateSubGraph(
const TfLiteIntArray* inputs, const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Input data tensor.
int tensor_id = inputs->data[0];
const auto& input_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
// Output dimensions tensor.
tensor_id = inputs->data[1];
const auto& output_dim_tensor = context->tensors[tensor_id];
if (output_dim_tensor.allocation_type == kTfLiteMmapRo) {
// If the output dimensions input is a constant, bake it into the Hexagon
// graph as a Const node.
auto* const_output_dim_node =
graph_builder_->AddConstNodeWithData(tensor_id, output_dim_tensor);
AddInput(TensorID(const_output_dim_node->GetID(), 0));
} else {
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
}
// Min/max values for input tensor.
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input_tensor));
// Align corners.
const TfLiteResizeNearestNeighborParams* params =
reinterpret_cast<const TfLiteResizeNearestNeighborParams*>(builtin_data_);
int align_corners = params->align_corners ? 1 : 0;
auto* align_corners_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&align_corners),
sizeof(align_corners));
AddInput(TensorID(align_corners_const->GetID(), 0));
int half_pixel_centers = params->half_pixel_centers ? 1 : 0;
auto* half_pixel_centers_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&half_pixel_centers),
sizeof(half_pixel_centers));
AddInput(TensorID(half_pixel_centers_const->GetID(), 0));
// Hexagon outputs for this node.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus ResizeNearestNeighborOpBuilder::RegisterOutputs(
const TfLiteIntArray* outputs, TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
ResizeNearestNeighborOpBuilder::~ResizeNearestNeighborOpBuilder() {}
OpBuilder* CreateResizeNearestNeighborBuilder(GraphBuilder* graph_builder,
int op_type) {
return new ResizeNearestNeighborOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,48 @@
/* Copyright 2019 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_RESIZE_NEAREST_NEIGHBOR_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_RESIZE_NEAREST_NEIGHBOR_BUILDER_H_
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class ResizeNearestNeighborOpBuilder : public OpBuilder {
public:
explicit ResizeNearestNeighborOpBuilder(GraphBuilder* graph_builder,
int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~ResizeNearestNeighborOpBuilder() override;
private:
TensorID node_output_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_RESIZE_NEAREST_NEIGHBOR_BUILDER_H_
@@ -0,0 +1,174 @@
/* 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 <cstdint>
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
// Adds Rsqrt op to the Hexagon graph by constructing
// 1/Sqrt(input).
class RsqrtOpBuilder : public OpBuilder {
public:
explicit RsqrtOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
private:
void AddNumerator();
TensorID node_output_;
TensorID numerator_out_;
TensorID numerator_min_;
TensorID numerator_max_;
// Total number of elements in the input tensor.
int num_elements_;
};
void RsqrtOpBuilder::AddNumerator() {
// Numerator is a constant with value 1. We add it as float and quantize it.
std::vector<uint8_t> numerator;
// Hexagon NN Div implementation assumes output to be of shape as first
// input, so it doesn't broadcast.
// So here we create the constant numerator with value 1 to be of same
// flattened shape as the denominator.
numerator.resize(num_elements_);
int flat_shape[] = {1, 1, 1, num_elements_};
std::fill(numerator.begin(), numerator.end(), 0);
float kNumeratorMin = 1.0, kNumeratorMax = 1.0;
auto* const_node = graph_builder_->AddConstNodeWithData(
flat_shape, reinterpret_cast<char*>(numerator.data()),
sizeof(numerator[0]) * numerator.size());
auto* numerator_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&kNumeratorMin),
sizeof(kNumeratorMin));
auto* numerator_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&kNumeratorMax),
sizeof(kNumeratorMax));
numerator_out_ = TensorID(const_node->GetID(), 0);
numerator_min_ = TensorID(numerator_min_const->GetID(), 0);
numerator_max_ = TensorID(numerator_max_const->GetID(), 0);
}
TfLiteStatus RsqrtOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
TfLiteStatus RsqrtOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
const int tensor_id = inputs->data[0];
const auto& tensor = context->tensors[tensor_id];
float min_value = 0;
float max_value = 0;
int batch_size, height_size, width_size, depth_size;
GetDims(&batch_size, &height_size, &width_size, &depth_size, tensor.dims);
TF_LITE_ENSURE_STATUS(
ComputeMinAndMaxQuantValues(tensor, &min_value, &max_value));
num_elements_ = batch_size * height_size * width_size * depth_size;
int flat_shape[] = {1, 1, 1, num_elements_};
auto* min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&min_value), sizeof(min_value));
auto* max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&max_value), sizeof(max_value));
// Create SQRT op as denominator.
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
AddInput(TensorID(min_const->GetID(), 0));
AddInput(TensorID(max_const->GetID(), 0));
auto sqrt_output = AddOutput(
sizeof(uint8_t), 4, {batch_size, height_size, width_size, depth_size});
auto sqrt_output_min = AddOutput(sizeof(float), 4, kScalarShape);
auto sqrt_output_max = AddOutput(sizeof(float), 4, kScalarShape);
// Reshape result of Sqrt to be [1,1,1,NumElements] since Hexagon Div
// has limitation on the shape of the tensor.
const int reshape_shape[] = {1, 1, 1, 4};
auto* target_shape_node = graph_builder_->AddConstNodeWithData(
reshape_shape, reinterpret_cast<char*>(flat_shape),
sizeof(flat_shape[0]) * 4);
auto* reshape_op = graph_builder_->AddNode(GetTFLiteNodeID());
reshape_op->SetOpType(OP_Reshape);
reshape_op->AddInput(sqrt_output);
reshape_op->AddInput(TensorID(target_shape_node->GetID(), 0));
auto reshape_out = reshape_op->AddOutput(sizeof(uint8_t), 4, flat_shape);
// Create the numerator and add to the graph.
AddNumerator();
// Fetch output details
float output_min = -1, output_max = 1;
// Output details.
TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues(
context->tensors[outputs->data[0]], &output_min, &output_max));
auto* output_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&output_min), sizeof(output_min));
auto* output_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&output_max), sizeof(output_max));
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
// Add Div op to compute 1/Sqrt
auto* div_op = graph_builder_->AddNode(GetTFLiteNodeID());
div_op->SetOpType(OP_QuantizedDiv_8);
div_op->AddInput(numerator_out_);
div_op->AddInput(reshape_out);
div_op->AddInput(numerator_min_);
div_op->AddInput(numerator_max_);
div_op->AddInput(sqrt_output_min);
div_op->AddInput(sqrt_output_max);
div_op->AddInput(TensorID(output_min_const->GetID(), 0));
div_op->AddInput(TensorID(output_max_const->GetID(), 0));
auto div_output = div_op->AddOutput(sizeof(uint8_t), 4, flat_shape);
div_op->AddOutput(sizeof(float), 4, kScalarShape);
div_op->AddOutput(sizeof(float), 4, kScalarShape);
// Reshape output back to the expected shape.
int output_shape[] = {output_batch_size, output_height_size,
output_width_size, output_depth_size};
target_shape_node = graph_builder_->AddConstNodeWithData(
reshape_shape, reinterpret_cast<char*>(output_shape),
sizeof(output_shape[0]) * 4);
reshape_op = graph_builder_->AddNode(GetTFLiteNodeID());
reshape_op->SetOpType(OP_Reshape);
reshape_op->AddInput(div_output);
reshape_op->AddInput(TensorID(target_shape_node->GetID(), 0));
node_output_ = reshape_op->AddOutput(sizeof(uint8_t), 4, output_shape);
return kTfLiteOk;
}
OpBuilder* CreateRSqrtOpBuilder(GraphBuilder* graph_builder, int op_type) {
return new RsqrtOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,96 @@
/* 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 "tensorflow/lite/delegates/hexagon/builders/slice_builder.h"
#include <cstdint>
#include <vector>
#include "tensorflow/lite/kernels/internal/tensor.h"
namespace tflite {
namespace delegates {
namespace hexagon {
namespace {
template <typename T>
void GetBeginAndSizeVectors(int dimensions, const TfLiteTensor* begin,
const TfLiteTensor* size, std::vector<int>* begins,
std::vector<int>* sizes) {
for (int i = 0; i < dimensions; ++i) {
begins->push_back(GetTensorData<T>(begin)[i]);
sizes->push_back(GetTensorData<T>(size)[i]);
}
}
} // namespace
TfLiteStatus SliceOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Input data tensor.
const int tensor_id = inputs->data[0];
const auto& input_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
// Start / Size
const auto& begin_tensor = context->tensors[inputs->data[1]];
const auto& size_tensor = context->tensors[inputs->data[2]];
std::vector<int> begins, sizes;
if (begin_tensor.type == kTfLiteInt32) {
GetBeginAndSizeVectors<int>(input_tensor.dims->size, &begin_tensor,
&size_tensor, &begins, &sizes);
} else if (begin_tensor.type == kTfLiteInt64) {
GetBeginAndSizeVectors<int64_t>(input_tensor.dims->size, &begin_tensor,
&size_tensor, &begins, &sizes);
} else {
return kTfLiteError;
}
const int begins_shape[] = {1, 1, 1, static_cast<int>(begins.size())};
auto begins_node = graph_builder_->AddConstNodeWithData(
begins_shape, reinterpret_cast<char*>(begins.data()),
sizeof(int) * begins.size());
auto sizes_node = graph_builder_->AddConstNodeWithData(
begins_shape, reinterpret_cast<char*>(sizes.data()),
sizeof(int) * begins.size());
AddInput(TensorID(begins_node->GetID(), 0));
AddInput(TensorID(sizes_node->GetID(), 0));
// Input min/max
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input_tensor));
// Outputs
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus SliceOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
OpBuilder* CreateSliceOpBuilder(GraphBuilder* graph_builder, int op_type) {
return new SliceOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,44 @@
/* 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_SLICE_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_SLICE_BUILDER_H_
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class SliceOpBuilder : public OpBuilder {
public:
explicit SliceOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
private:
TensorID node_output_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_SLICE_BUILDER_H_
@@ -0,0 +1,75 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/softmax_builder.h"
#include <stdint.h>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_nn/hexagon_nn.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus SoftmaxOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Input data tensor.
int tensor_id = inputs->data[0];
const auto& input_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input_tensor));
// beta value
const TfLiteSoftmaxParams* softmax_params =
reinterpret_cast<const TfLiteSoftmaxParams*>(builtin_data_);
beta_value_ = softmax_params->beta;
auto* beta_const = graph_builder_->AddConstNodeWithData(
kScalarShape, (char*)&beta_value_, sizeof(beta_value_));
AddInput(TensorID(beta_const->GetID(), 0));
// Hexagon outputs for this node.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus SoftmaxOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
SoftmaxOpBuilder::~SoftmaxOpBuilder() {}
OpBuilder* CreateSoftmaxBuilder(GraphBuilder* graph_builder, int op_type) {
return new SoftmaxOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,48 @@
/* Copyright 2019 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_SOFTMAX_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_SOFTMAX_BUILDER_H_
#include <vector>
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class SoftmaxOpBuilder : public OpBuilder {
public:
explicit SoftmaxOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~SoftmaxOpBuilder() override;
private:
TensorID node_output_;
float beta_value_ = 1.0f;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_SOFTMAX_BUILDER_H_
@@ -0,0 +1,76 @@
/* 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 "tensorflow/lite/delegates/hexagon/builders/space_to_depth_builder.h"
#include <stdint.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus SpaceToDepthOpBuilder::PopulateSubGraph(
const TfLiteIntArray* inputs, const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Input tensor.
int tensor_id = inputs->data[0];
// Block size.
const TfLiteSpaceToDepthParams* space_to_depth_params =
reinterpret_cast<const TfLiteSpaceToDepthParams*>(builtin_data_);
block_size_ = space_to_depth_params->block_size;
auto* block_size_node = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&block_size_), sizeof(int));
// All inputs.
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
AddInput(TensorID(block_size_node->GetID(), 0));
TF_LITE_ENSURE_STATUS(
ComputeAndAddMinAndMax(context, context->tensors[tensor_id]));
// Hexagon outputs for this node.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus SpaceToDepthOpBuilder::RegisterOutputs(
const TfLiteIntArray* outputs, TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
SpaceToDepthOpBuilder::~SpaceToDepthOpBuilder() {}
OpBuilder* CreateSpaceToDepthBuilder(GraphBuilder* graph_builder, int op_type) {
return new SpaceToDepthOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,52 @@
/* 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_SPACE_TO_DEPTH_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_SPACE_TO_DEPTH_BUILDER_H_
#include <vector>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
// Supports both ways:
// Space -> Depth & Depth -> Space.
class SpaceToDepthOpBuilder : public OpBuilder {
public:
explicit SpaceToDepthOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~SpaceToDepthOpBuilder() override;
private:
TensorID node_output_;
int block_size_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_SPACE_TO_DEPTH_BUILDER_H_
@@ -0,0 +1,90 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/split_builder.h"
#include <stdint.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus SplitOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
const int input_tensor_id = inputs->data[1];
const auto& input_tensor = context->tensors[input_tensor_id];
// Axis tensor.
const int axis_tensor_id = inputs->data[0];
const auto& axis = context->tensors[axis_tensor_id];
if (axis.allocation_type != kTfLiteMmapRo) {
TF_LITE_KERNEL_LOG(context,
"Axis tensor doesn't have correct allocation type: %s",
axis.name);
return kTfLiteError;
}
int axis_value = axis.data.i32[0];
if (axis_value < 0) axis_value += input_tensor.dims->size;
// We pad Hexagon tensor dimensions with 1 if dims.size < 4.
// (4 - input_tensor.dims->size) helps maps the input axis value in such
// cases.
axis_value += (4 - input_tensor.dims->size);
auto* input_axis_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&axis_value), sizeof(int));
AddInput(TensorID(input_axis_const->GetID(), 0));
// Input data tensor & min/max.
AddInput(graph_builder_->GetHexagonTensorId(input_tensor_id));
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input_tensor));
// Output data tensors.
for (int i = 0; i < outputs->size; ++i) {
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[i]].dims);
TensorID output = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
node_outputs_.push_back(output);
}
// For Hexagon output min/max.
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus SplitOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
for (int i = 0; i < node_outputs_.size(); ++i) {
graph_builder_->AddTensorWithID(outputs->data[i], node_outputs_[i].first,
node_outputs_[i].second);
}
return kTfLiteOk;
}
SplitOpBuilder::~SplitOpBuilder() {}
OpBuilder* CreateSplitBuilder(GraphBuilder* graph_builder, int op_type) {
return new SplitOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,49 @@
/* Copyright 2019 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_SPLIT_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_SPLIT_BUILDER_H_
#include <vector>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class SplitOpBuilder : public OpBuilder {
public:
explicit SplitOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~SplitOpBuilder() override;
private:
std::vector<TensorID> node_outputs_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_SPLIT_BUILDER_H_
@@ -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 <cstdint>
#include "hexagon/hexagon_nn_ops.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
// Builder for SquaredDifference op by computing Mul(Sub(A,B), Sub(A,B))
class SquaredDifferenceOpBuilder : public OpBuilder {
public:
explicit SquaredDifferenceOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
private:
TensorID node_output_;
};
TfLiteStatus SquaredDifferenceOpBuilder::PopulateSubGraph(
const TfLiteIntArray* inputs, const TfLiteIntArray* outputs,
TfLiteContext* context) {
// We model Squared Diff as Mul(Sub(a,b), Sub(a,b))
// Add first Sub op.
const int tensor_a_index = inputs->data[0];
const int tensor_b_index = inputs->data[1];
const auto& tensor_a = context->tensors[tensor_a_index];
const auto& tensor_b = context->tensors[tensor_b_index];
AddInput(graph_builder_->GetHexagonTensorId(tensor_a_index));
AddInput(graph_builder_->GetHexagonTensorId(tensor_b_index));
// Inputs min/max
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, tensor_a));
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, tensor_b));
// Output details.
float output_min = -1, output_max = -1;
TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues(
context->tensors[outputs->data[0]], &output_min, &output_max));
auto* output_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&output_min), sizeof(output_min));
auto* output_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&output_max), sizeof(output_max));
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
auto sub_out = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
auto sub_min = AddOutput(sizeof(float), 4, kScalarShape);
auto sub_max = AddOutput(sizeof(float), 4, kScalarShape);
// Add Mul
auto* mul_op = graph_builder_->AddNode(GetTFLiteNodeID());
mul_op->SetOpType(OP_QuantizedMul_8x8to8);
mul_op->AddInput(sub_out);
mul_op->AddInput(sub_out);
mul_op->AddInput(sub_min);
mul_op->AddInput(sub_max);
mul_op->AddInput(sub_min);
mul_op->AddInput(sub_max);
mul_op->AddInput(TensorID(output_min_const->GetID(), 0));
mul_op->AddInput(TensorID(output_max_const->GetID(), 0));
node_output_ = mul_op->AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
mul_op->AddOutput(sizeof(float), 4, kScalarShape);
mul_op->AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus SquaredDifferenceOpBuilder::RegisterOutputs(
const TfLiteIntArray* outputs, TfLiteContext* context) {
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
OpBuilder* CreateSquaredDifferenceOpBuilder(GraphBuilder* graph_builder,
int op_type) {
return new SquaredDifferenceOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,101 @@
/* 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 "tensorflow/lite/delegates/hexagon/builders/strided_slice_builder.h"
#include <cstdint>
#include <vector>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
namespace {} // namespace
TfLiteStatus StridedSliceOpBuilder::PopulateSubGraph(
const TfLiteIntArray* inputs, const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Input data tensor.
const auto& input_tensor = context->tensors[inputs->data[0]];
AddInput(graph_builder_->GetHexagonTensorId(inputs->data[0]));
// Begin/End/Step.
const auto& begin_tensor = context->tensors[inputs->data[1]];
const auto& end_tensor = context->tensors[inputs->data[2]];
const auto& step_tensor = context->tensors[inputs->data[3]];
auto begins_node =
graph_builder_->AddConstNodeWithData(inputs->data[1], begin_tensor);
auto ends_node =
graph_builder_->AddConstNodeWithData(inputs->data[2], end_tensor);
auto steps_node =
graph_builder_->AddConstNodeWithData(inputs->data[3], step_tensor);
AddInput(TensorID(begins_node->GetID(), 0));
AddInput(TensorID(ends_node->GetID(), 0));
AddInput(TensorID(steps_node->GetID(), 0));
// Begin/End/Shrink-Axis masks.
// Hexagon's op always expects bits at 0, 1, 2 & 3 to correspond to BHWD.
// So we have to left-shift the mask by (4 - begins.size()).
const TfLiteStridedSliceParams* params =
reinterpret_cast<const TfLiteStridedSliceParams*>(builtin_data_);
int begin_mask = params->begin_mask;
int end_mask = params->end_mask;
int shrink_axis_mask = params->shrink_axis_mask;
int original_mask_size = input_tensor.dims->size;
begin_mask = begin_mask << (4 - original_mask_size);
end_mask = end_mask << (4 - original_mask_size);
shrink_axis_mask = shrink_axis_mask << (4 - original_mask_size);
auto* begin_mask_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&begin_mask), sizeof(begin_mask));
AddInput(TensorID(begin_mask_const->GetID(), 0));
auto* end_mask_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&end_mask), sizeof(end_mask));
AddInput(TensorID(end_mask_const->GetID(), 0));
auto* shrink_axis_mask_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&shrink_axis_mask),
sizeof(shrink_axis_mask));
AddInput(TensorID(shrink_axis_mask_const->GetID(), 0));
// Input min/max
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input_tensor));
// Slice outputs.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus StridedSliceOpBuilder::RegisterOutputs(
const TfLiteIntArray* outputs, TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
OpBuilder* CreateStridedSliceBuilder(GraphBuilder* graph_builder, int op_type) {
return new StridedSliceOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,46 @@
/* 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_STRIDED_SLICE_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_STRIDED_SLICE_BUILDER_H_
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class StridedSliceOpBuilder : public OpBuilder {
public:
explicit StridedSliceOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
private:
TensorID node_output_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_STRIDED_SLICE_BUILDER_H_
@@ -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
@@ -0,0 +1,64 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/transpose_builder.h"
#include <stdint.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
TfLiteStatus TransposeOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) {
// Input data tensor.
int tensor_id = inputs->data[0];
const auto& input_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
// permutation tensor.
AddInput(graph_builder_->GetHexagonTensorId(inputs->data[1]));
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, input_tensor));
// Hexagon outputs for this node.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus TransposeOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) {
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
OpBuilder* CreateTransposeBuilder(GraphBuilder* graph_builder, int op_type) {
return new TransposeOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,44 @@
/* Copyright 2019 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_TRANSPOSE_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_TRANSPOSE_BUILDER_H_
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class TransposeOpBuilder : public OpBuilder {
public:
explicit TransposeOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
private:
TensorID node_output_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_TRANSPOSE_BUILDER_H_
@@ -0,0 +1,209 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/builders/transpose_conv_2d_builder.h"
#include <stdint.h>
#include <vector>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/delegates/hexagon/builders/conv_2d_builder.h"
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
#include "tensorflow/lite/kernels/padding.h"
namespace tflite {
namespace delegates {
namespace hexagon {
namespace {
constexpr uint8_t k8BitSignFlipConstant = 0x80;
// 1/1024 ~ 0.0009766 is a restriction set by Hexagon's kernels.
// TODO(b/151103818): Figure out a way to retrieve this constant reliably.
constexpr float kHexagonMinRelativeScale = 0.0009766f;
} // namespace
TfLiteStatus TransposeConv2dOpBuilder::PopulateSubGraph(
const TfLiteIntArray* inputs, const TfLiteIntArray* outputs,
TfLiteContext* context) {
// DATA TENSOR.
int tensor_id = inputs->data[2];
const auto& data_tensor = context->tensors[tensor_id];
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
// WEIGHTS.
tensor_id = inputs->data[1];
const auto& weights_tensor = context->tensors[tensor_id];
if (weights_tensor.allocation_type != kTfLiteMmapRo) {
TF_LITE_KERNEL_LOG(
context, "Weights tensor doesn't have correct allocation type: %s",
weights_tensor.name);
return kTfLiteError;
}
int filter_batch_size, filter_height_size, filter_width_size,
filter_depth_size;
GetDims(&filter_batch_size, &filter_height_size, &filter_width_size,
&filter_depth_size, weights_tensor.dims);
// Weights tensor could be int8 even for per-tensor quantization.
// Therefore, we look at the number of scale values to check if it is
// per-channel quantized.
TfLiteAffineQuantization* weights_quant_params =
reinterpret_cast<TfLiteAffineQuantization*>(
weights_tensor.quantization.params);
const bool is_per_channel_quant = weights_quant_params->scale->size > 1;
AddInput(graph_builder_->GetHexagonTensorId(tensor_id));
// Handle weights quantization.
float weights_min = 0;
float weights_max = 0;
if (is_per_channel_quant) {
ProcessPerChannelQuantizedWeights(weights_tensor, context, &weights_min,
&weights_max, graph_builder_,
&per_channel_quant_);
} else {
TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues(
weights_tensor, &weights_min, &weights_max));
}
auto* weights_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&weights_min), sizeof(weights_min));
auto* weights_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&weights_max), sizeof(weights_max));
// Min/max inputs for data & weights tensors.
TF_LITE_ENSURE_STATUS(ComputeAndAddMinAndMax(context, data_tensor));
AddInput(TensorID(weights_min_const->GetID(), 0));
AddInput(TensorID(weights_max_const->GetID(), 0));
// Output dims are required to compute padding.
int output_batch_size, output_height_size, output_width_size,
output_depth_size;
GetDims(&output_batch_size, &output_height_size, &output_width_size,
&output_depth_size, context->tensors[outputs->data[0]].dims);
// PADDING & STRIDE.
// Hexagon TransposeConv requires an explicit padding tensor. So we compute
// the same using stride, input & output info.
const TfLiteTransposeConvParams* params =
reinterpret_cast<const TfLiteTransposeConvParams*>(builtin_data_);
int unused_output_height, unused_output_width;
TfLitePaddingValues padding = ComputePaddingHeightWidth(
params->stride_height, params->stride_width, 1, 1, output_height_size,
output_width_size, filter_height_size, filter_width_size, params->padding,
&unused_output_height, &unused_output_width);
std::vector<int> padding_tensor = {padding.height, padding.height,
padding.width, padding.width};
std::vector<int> padding_tensor_shape = {1, 1, 2, 2};
auto* padding_const = graph_builder_->AddConstNodeWithData(
padding_tensor_shape.data(),
reinterpret_cast<char*>(padding_tensor.data()), (sizeof(int) * 4));
AddInput(TensorID(padding_const->GetID(), 0));
// Stride shape.
int stride_height = params->stride_height;
int stride_width = params->stride_width;
static int dummy = 0;
stride_shape_ = {1, stride_height, stride_width, 1};
auto* stride_node = graph_builder_->AddConstNodeWithData(
stride_shape_.data(), reinterpret_cast<char*>(&dummy), sizeof(dummy));
AddInput(TensorID(stride_node->GetID(), 0));
// BIAS.
const bool has_bias = inputs->size == 4;
OpBuilder* bias_const = nullptr;
OpBuilder* bias_min_const = nullptr;
OpBuilder* bias_max_const = nullptr;
if (!has_bias) {
// If the TFLite node does not have a bias, we simply feed in 0s.
std::vector<int> bias_data(output_depth_size, 0);
bias_shape_ = {1, 1, 1, output_depth_size};
bias_const = graph_builder_->AddConstNodeWithData(
bias_shape_.data(), reinterpret_cast<char*>(bias_data.data()),
sizeof(bias_data[0]) * bias_data.size());
float zero_bound = 0;
bias_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&zero_bound), sizeof(zero_bound));
bias_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&zero_bound), sizeof(zero_bound));
} else {
const auto& bias_tensor = context->tensors[inputs->data[3]];
if (bias_tensor.allocation_type != kTfLiteMmapRo) {
TF_LITE_KERNEL_LOG(context,
"Bias tensor doesn't have correct allocation type: %s",
bias_tensor.name);
return kTfLiteError;
}
float bias_min = 0;
float bias_max = 0;
if (per_channel_quant_.channel_scales_node != nullptr) {
std::vector<int> preprocessed_bias_data;
ProcessPerChannelQuantizedBias(data_tensor, bias_tensor, inputs->data[3],
context, &bias_min, &bias_max,
graph_builder_, &per_channel_quant_,
&preprocessed_bias_data, &bias_const);
} else {
bias_const =
graph_builder_->AddConstNodeWithData(inputs->data[3], bias_tensor);
TF_LITE_ENSURE_STATUS(
ComputeMinAndMaxQuantValues(bias_tensor, &bias_min, &bias_max));
}
bias_min_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&bias_min), sizeof(bias_min));
bias_max_const = graph_builder_->AddConstNodeWithData(
kScalarShape, reinterpret_cast<char*>(&bias_max), sizeof(bias_max));
}
AddInput(TensorID(bias_const->GetID(), 0));
AddInput(TensorID(bias_min_const->GetID(), 0));
AddInput(TensorID(bias_max_const->GetID(), 0));
// Output quantization.
TF_LITE_ENSURE_STATUS(
ComputeAndAddMinAndMax(context, context->tensors[outputs->data[0]]));
// Channel scales, if this op is per-channel quantized.
if (per_channel_quant_.channel_scales_node != nullptr) {
AddInput(TensorID(per_channel_quant_.channel_scales_node->GetID(), 0));
}
// Hexagon outputs for this node.
node_output_ = AddOutput(sizeof(uint8_t), 4,
{output_batch_size, output_height_size,
output_width_size, output_depth_size});
AddOutput(sizeof(float), 4, kScalarShape);
AddOutput(sizeof(float), 4, kScalarShape);
return kTfLiteOk;
}
TfLiteStatus TransposeConv2dOpBuilder::RegisterOutputs(
const TfLiteIntArray* outputs, TfLiteContext* context) {
// Should be only 1 output.
graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first,
node_output_.second);
return kTfLiteOk;
}
TransposeConv2dOpBuilder::~TransposeConv2dOpBuilder() {}
OpBuilder* CreateTransposeConv2DBuilder(GraphBuilder* graph_builder,
int op_type) {
return new TransposeConv2dOpBuilder(graph_builder, op_type);
}
} // namespace hexagon
} // namespace delegates
} // namespace tflite
@@ -0,0 +1,56 @@
/* Copyright 2019 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_TRANSPOSE_CONV_2D_BUILDER_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_TRANSPOSE_CONV_2D_BUILDER_H_
#include <vector>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/hexagon/builders/conv_2d_builder.h"
#include "tensorflow/lite/delegates/hexagon/builders/op_builder.h"
namespace tflite {
namespace delegates {
namespace hexagon {
class TransposeConv2dOpBuilder : public OpBuilder {
public:
explicit TransposeConv2dOpBuilder(GraphBuilder* graph_builder, int op_type)
: OpBuilder(graph_builder, op_type) {}
TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs,
const TfLiteIntArray* outputs,
TfLiteContext* context) override;
TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs,
TfLiteContext* context) override;
~TransposeConv2dOpBuilder() override;
private:
TensorID node_output_;
std::vector<float> transposed_weights_;
std::vector<int> stride_shape_;
std::vector<int> bias_shape_;
// Modified only if node has per-channel quantized weights/biases.
PerChannelQuantData per_channel_quant_;
};
} // namespace hexagon
} // namespace delegates
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_BUILDERS_TRANSPOSE_CONV_2D_BUILDER_H_
@@ -0,0 +1,154 @@
/* Copyright 2019 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 "tensorflow/lite/delegates/hexagon/hexagon_delegate.h"
#include <memory>
#include <string>
#include <utility>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_delegate_kernel.h"
#include "tensorflow/lite/delegates/hexagon/hexagon_implementation.h"
#include "tensorflow/lite/delegates/hexagon/utils.h"
#include "tensorflow/lite/delegates/utils/simple_delegate.h"
#include "tensorflow/lite/logger.h"
#include "tensorflow/lite/minimal_logging.h"
namespace tflite {
namespace {
// Should be > 0. > 16 causes problems.
constexpr int kMaxHexagonGraphs = 4;
constexpr int kMaxMaxHexagonGraphs = 16;
constexpr int kMinNodesPerHexagonGraph = 2;
class HexagonDelegate : public SimpleDelegateInterface {
public:
explicit HexagonDelegate(const TfLiteHexagonDelegateOptions* params)
: params_(params != nullptr ? *params
: TfLiteHexagonDelegateOptions({0})) {
if (params_.max_delegated_partitions <= 0) {
params_.max_delegated_partitions = kMaxHexagonGraphs;
} else if (params_.max_delegated_partitions > kMaxMaxHexagonGraphs) {
TFLITE_LOG_PROD(tflite::TFLITE_LOG_WARNING,
"Hexagon delegate: cannot have this many %d partitions, "
"and will cap to at most %d partitions.\n",
params_.max_delegated_partitions, kMaxMaxHexagonGraphs);
params_.max_delegated_partitions = kMaxMaxHexagonGraphs;
}
if (params_.min_nodes_per_partition <= 0) {
params_.min_nodes_per_partition = kMinNodesPerHexagonGraph;
}
}
bool IsNodeSupportedByDelegate(const TfLiteRegistration* registration,
const TfLiteNode* node,
TfLiteContext* context) const override {
return IsNodeSupportedByHexagon(registration, node, context);
}
TfLiteStatus Initialize(TfLiteContext* context) override { return kTfLiteOk; }
const char* Name() const override { return "TfLiteHexagonDelegate"; }
std::unique_ptr<SimpleDelegateKernelInterface> CreateDelegateKernelInterface()
override {
return std::make_unique<HexagonDelegateKernel>(params_);
}
SimpleDelegateInterface::Options DelegateOptions() const override {
auto options = SimpleDelegateInterface::Options();
options.max_delegated_partitions = params_.max_delegated_partitions;
options.min_nodes_per_partition = params_.min_nodes_per_partition;
return options;
}
bool VerifyDelegate() {
auto* hexagon_nn = HexagonNNImplementation();
if (hexagon_nn == nullptr) {
return false;
}
if (hexagon_nn->hexagon_nn_version != nullptr &&
hexagon_nn->hexagon_nn_hexagon_interface_version) {
int hexagon_nn_version = -1;
int hexagon_interface_version =
hexagon_nn->hexagon_nn_hexagon_interface_version();
if (hexagon_nn->hexagon_nn_version(&hexagon_nn_version) != 0) {
TFLITE_LOG_PROD(tflite::TFLITE_LOG_WARNING,
"Failed to fetch Hexagon NN version. This might be "
"because you're using incompatible versions of "
"libhexagon_interface and libhexagon_nn_skel. "
"You must use compatible versions. "
"Refer to Tensorflow Lite Hexagon Delegate Guide.");
return false;
}
if (hexagon_nn_version != hexagon_interface_version) {
TFLITE_LOG_PROD(
tflite::TFLITE_LOG_WARNING,
"Incompatible versions between interface library and "
"libhexagon_skel %d vs %d. You must use compatible versions. "
"Refer to Tensorflow Lite Hexagon Delegate Guide.",
hexagon_interface_version, hexagon_nn_version);
return false;
}
}
return hexagon_nn->hexagon_nn_is_device_supported &&
hexagon_nn->hexagon_nn_is_device_supported();
}
private:
TfLiteHexagonDelegateOptions params_;
};
} // namespace
} // namespace tflite
TfLiteDelegate* TfLiteHexagonDelegateCreate(
const TfLiteHexagonDelegateOptions* options) {
auto hexagon_delegate_interface =
std::make_unique<tflite::HexagonDelegate>(options);
if (!hexagon_delegate_interface->VerifyDelegate()) {
TFLITE_LOG_PROD_ONCE(tflite::TFLITE_LOG_INFO,
"Hexagon Delegate is not supported.\n");
return nullptr;
}
auto* initialized_delegate =
tflite::TfLiteDelegateFactory::CreateSimpleDelegate(
std::move(hexagon_delegate_interface));
if (options->enable_dynamic_batch_size) {
initialized_delegate->flags |= kTfLiteDelegateFlagsAllowDynamicTensors;
}
return initialized_delegate;
}
TfLiteHexagonDelegateOptions TfLiteHexagonDelegateOptionsDefault() {
TfLiteHexagonDelegateOptions result{0};
return result;
}
void TfLiteHexagonDelegateDelete(TfLiteDelegate* delegate) {
tflite::TfLiteDelegateFactory::DeleteSimpleDelegate(delegate);
}
void TfLiteHexagonInit() { tflite::HexagonDelegateKernel::InitState(); }
void TfLiteHexagonInitWithPath(const char* lib_directory_path) {
if (lib_directory_path != nullptr) {
std::string env_var_value = lib_directory_path;
env_var_value += ";/system/lib/rfsa/adsp;/system/vendor/lib/rfsa/adsp;/dsp";
setenv("ADSP_LIBRARY_PATH", env_var_value.c_str(), 1 /* overwrite */);
}
tflite::HexagonDelegateKernel::InitState();
}
void TfLiteHexagonTearDown() { tflite::HexagonDelegateKernel::Teardown(); }
@@ -0,0 +1,122 @@
/* Copyright 2019 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_HEXAGON_DELEGATE_H_
#define TENSORFLOW_LITE_DELEGATES_HEXAGON_HEXAGON_DELEGATE_H_
#include "tensorflow/lite/core/c/common.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// Use TfLiteHexagonDelegateOptionsDefault() for Default options.
struct TFL_CAPI_EXPORT TfLiteHexagonDelegateOptions {
// This corresponds to the debug level in the hexagon SDK. 0 (default)
// means no debug.
int debug_level;
// This corresponds to powersave_level in the hexagon SDK.
// where 0 (default) means high performance which means more power
// consumption.
int powersave_level;
// If set to true, performance information about the graph will be dumped
// to Standard output, this includes cpu cycles.
// WARNING: Experimental and subject to change anytime.
bool print_graph_profile;
// If set to true, graph structure will be dumped to Standard output.
// This is usually beneficial to see what actual nodes executed on
// the DSP. Combining with 'debug_level' more information will be printed.
// WARNING: Experimental and subject to change anytime.
bool print_graph_debug;
// This sets the maximum number of Hexagon graphs created with
// hexagon_nn_init. Each graph corresponds to one delegated node subset in the
// TFLite model.
int max_delegated_partitions;
// This sets the minimum number of nodes per graph created with
// hexagon_nn_init. Defaults to 2.
int min_nodes_per_partition;
// If true, then the hexagon graph will adapt for inputs with dynamic batch.
// See below options are needed to be set.
// Currently, Only supported when the whole graph is delegated, and
// with batch as index 0.
// WARNING: Experimental and subject to change anytime.
bool enable_dynamic_batch_size;
// Maximum value for a batch dimension when evaluating graphs with
// dynamic batch. The input to the graph can have value for batch bigger than
// this number, internally the graph will run multiple times each with
// batch dimension <= max_batch_size. you should decide the value of this
// based on memory/latency tradeoffs.
// This needs to be set only if 'enable_dynamic_batch_size' is true.
// Not needed for fixed graphs.
// WARNING: Experimental and subject to change anytime.
int max_batch_size;
// Each element identifies the index of the batch dimension in a single input.
// input_batch_dimensions->data[i] is the index of the batch dimension for
// input[i]. If the graph has 1 input then the size of the array should be 1,
// and so on. This needs to be set only if 'enable_dynamic_batch_size' is
// true. Not needed for fixed graphs.
// If input[i] doesn't have dynamic batch, then input_batch_dimensions[i]
// should be -1.
// Delegate will take ownership of the pointer.
// WARNING: Experimental and subject to change anytime.
TfLiteIntArray* input_batch_dimensions;
// Each element identifies the index of the batch dimension in a single
// output. output_batch_dimensions->data[i] is the index of the batch
// dimension for output[i]. If the graph has 1 output then the size of the
// array should be 1, and so on. This needs to be set only if
// 'enable_dynamic_batch_size' is true. Not needed for fixed graphs. If
// output[i] has doesn't have dynamic batch, then output_batch_dimensions[i]
// should be -1. Delegate will take ownership of the pointer. WARNING:
// Experimental and subject to change anytime.
TfLiteIntArray* output_batch_dimensions;
};
// Return a delegate that uses Hexagon SDK for ops execution.
// Must outlive the interpreter.
TfLiteDelegate* TFL_CAPI_EXPORT
TfLiteHexagonDelegateCreate(const TfLiteHexagonDelegateOptions* options);
// Returns TfLiteHexagonDelegateOptions populated with default values.
TFL_CAPI_EXPORT TfLiteHexagonDelegateOptions
TfLiteHexagonDelegateOptionsDefault();
// Do any needed cleanup and delete 'delegate'.
void TFL_CAPI_EXPORT TfLiteHexagonDelegateDelete(TfLiteDelegate* delegate);
// Initializes the DSP connection.
// This should be called before doing any usage of the delegate.
// "lib_directory_path": Path to the directory which holds the
// shared libraries for the Hexagon NN libraries on the device.
void TFL_CAPI_EXPORT TfLiteHexagonInitWithPath(const char* lib_directory_path);
// Same as above method but doesn't accept the path params.
// Assumes the environment setup is already done. Only initialize Hexagon.
void TFL_CAPI_EXPORT TfLiteHexagonInit();
// Clean up and switch off the DSP connection.
// This should be called after all processing is done and delegate is deleted.
void TFL_CAPI_EXPORT TfLiteHexagonTearDown();
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // TENSORFLOW_LITE_DELEGATES_HEXAGON_HEXAGON_DELEGATE_H_

Some files were not shown because too many files have changed in this diff Show More