[NNAPI EP] Add Resize and Clip support (#5427)

* Add resize and clip support in NNAPI EP

* Try to get around tensor rt test failure

* Addressed PR comments
This commit is contained in:
Guoyu Wang 2020-10-12 22:29:19 -07:00 committed by GitHub
parent c444b9d76a
commit ce5465d5f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 631 additions and 114 deletions

View file

@ -235,7 +235,7 @@ Status ModelBuilder::RegisterInitializers() {
shape.push_back(SafeInt<uint32_t>(dim));
}
ORT_RETURN_IF_NOT(!shape.empty(), "NNAPI does not support scalar initializer");
ORT_RETURN_IF_NOT(!shape.empty(), "NNAPI does not support scalar initializer, tensor name, ", name);
Type type = Type::TENSOR_FLOAT32;
switch (tensor.data_type()) {

View file

@ -1852,13 +1852,13 @@ bool SoftMaxOpBuilder::IsOpSupportedImpl(ModelBuilder& model_builder, const Node
return false;
}
const auto android_skd_ver = model_builder.GetAndroidSdkVer();
if (android_skd_ver < 29) {
const auto android_sdk_ver = model_builder.GetAndroidSdkVer();
if (android_sdk_ver < 29) {
NodeAttrHelper helper(node);
int32_t axis = helper.Get("axis", 1);
if (axis != 1) {
LOGS_DEFAULT(VERBOSE)
<< "SoftMax only support axis 1 on Android API level: " << android_skd_ver
<< "SoftMax only support axis 1 on Android API level: " << android_sdk_ver
<< " input axis: " << axis;
return false;
}
@ -1871,13 +1871,13 @@ Status SoftMaxOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, cons
auto& shaper(model_builder.GetShaper());
const auto& operand_indices(model_builder.GetOperandIndices());
const auto& operand_types(model_builder.GetOperandTypes());
const auto android_skd_ver = model_builder.GetAndroidSdkVer();
const auto android_sdk_ver = model_builder.GetAndroidSdkVer();
NodeAttrHelper helper(node);
auto input = node.InputDefs()[0]->Name();
bool input_is_nhwc = model_builder.IsOperandNHWC(input);
bool output_is_nhwc = input_is_nhwc;
if (android_skd_ver < 29) {
if (android_sdk_ver < 29) {
if (model_builder.IsOperandNHWC(input)) {
output_is_nhwc = false;
// We want to transpose nhwc operand back to nchw before softmax
@ -1901,7 +1901,7 @@ Status SoftMaxOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, cons
input_indices.push_back(operand_indices.at(input));
ADD_SCALAR_OPERAND(model_builder, input_indices, beta);
if (android_skd_ver > 28) {
if (android_sdk_ver > 28) {
// you can only specify axis for android api level 29+
ADD_SCALAR_OPERAND(model_builder, input_indices, axis);
}
@ -2494,8 +2494,6 @@ Status QuantizeLinearOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builde
ORT_RETURN_IF_ERROR(GetQuantizationZeroPoint(model_builder, node, 2, zero_point));
}
LOGS_DEFAULT(VERBOSE) << "scale: " << scale << " zp: " << zero_point;
ORT_RETURN_IF_ERROR(shaper.Identity(input, output));
const OperandType output_operand_type(output_type, shaper[output], scale, zero_point);
std::vector<uint32_t> input_indices;
@ -2624,12 +2622,12 @@ Status LRNOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const No
const auto& operand_indices(model_builder.GetOperandIndices());
const auto& operand_types(model_builder.GetOperandTypes());
NodeAttrHelper helper(node);
const auto android_skd_ver = model_builder.GetAndroidSdkVer();
const auto android_sdk_ver = model_builder.GetAndroidSdkVer();
auto input = node.InputDefs()[0]->Name();
const auto& output = node.OutputDefs()[0]->Name();
bool output_is_nhwc = model_builder.IsOperandNHWC(input);
if (android_skd_ver < 29) {
if (android_sdk_ver < 29) {
// on android api level 28, we need to transpose the nchw input to nhwc
output_is_nhwc = true;
if (!model_builder.IsOperandNHWC(input)) {
@ -2657,7 +2655,7 @@ Status LRNOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const No
ADD_SCALAR_OPERAND(model_builder, input_indices, beta);
// specify axis is only available on api level >= 29
if (android_skd_ver > 28) {
if (android_sdk_ver > 28) {
// ONNX LRN is always performed on C dimension
int32_t axis = output_is_nhwc
? 3 // nhwc
@ -2674,6 +2672,311 @@ Status LRNOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const No
#pragma endregion
#pragma region op_clip
class ClipOpBuilder : public BaseOpBuilder {
public:
void AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) override;
private:
bool IsOpSupportedImpl(ModelBuilder& model_builder, const Node& node) override;
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node) override ORT_MUST_USE_RESULT;
static bool GetMinMax(ModelBuilder& model_builder, const Node& node, float& min, float& max);
};
void ClipOpBuilder::AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) {
if (node.InputDefs().size() > 1)
model_builder.AddInitializerToSkip(node.InputDefs()[1]->Name()); // min
if (node.InputDefs().size() > 2)
model_builder.AddInitializerToSkip(node.InputDefs()[2]->Name()); // max
}
/* static */ bool ClipOpBuilder::GetMinMax(ModelBuilder& model_builder, const Node& node, float& min, float& max) {
if (node.SinceVersion() < 11) { // Clip opset 1, 6 is using attributes for min/max
NodeAttrHelper helper(node);
min = helper.Get("min", std::numeric_limits<float>::lowest());
max = helper.Get("max", std::numeric_limits<float>::max());
} else {
const auto& initializers(model_builder.GetInitializerTensors());
if (node.InputDefs().size() > 1) { // we have input min
const auto& min_name = node.InputDefs()[1]->Name();
if (!Contains(initializers, min_name)) {
LOGS_DEFAULT(VERBOSE) << "Input min of Clip must be known";
return false;
}
min = GetTensorFloatData(initializers.at(min_name))[0];
}
if (node.InputDefs().size() > 2) { // we have input max
const auto& max_name = node.InputDefs()[2]->Name();
if (!Contains(initializers, max_name)) {
LOGS_DEFAULT(VERBOSE) << "Input max of Clip must be known";
return false;
}
max = GetTensorFloatData(initializers.at(max_name))[0];
}
}
return true;
}
bool ClipOpBuilder::IsOpSupportedImpl(ModelBuilder& model_builder, const Node& node) {
float min = std::numeric_limits<float>::lowest();
float max = std::numeric_limits<float>::max();
if (!GetMinMax(model_builder, node, min, max))
return false;
// We only supoort relu6 or relu1
// TODO, support clip between 2 arbitrary numbers
if ((min == 0.0f && max == 6.0f) || (min == -1.0f && max == 1.0f)) {
return true;
} else {
LOGS_DEFAULT(VERBOSE) << "Clip only supports [min, max] = [0, 6] or [-1, 1], the input is ["
<< min << ", " << max << "]";
return false;
}
return true;
}
Status ClipOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node) {
auto& shaper(model_builder.GetShaper());
const auto& operand_indices(model_builder.GetOperandIndices());
const auto& operand_types(model_builder.GetOperandTypes());
const auto& input = node.InputDefs()[0]->Name();
const auto& output = node.OutputDefs()[0]->Name();
bool output_is_nhwc = model_builder.IsOperandNHWC(input);
ORT_RETURN_IF_ERROR(shaper.Identity(input, output));
const OperandType output_operand_type(operand_types.at(input).type, shaper[output]);
float min = std::numeric_limits<float>::lowest();
float max = std::numeric_limits<float>::max();
GetMinMax(model_builder, node, min, max);
int32_t op_code;
if (min == 0.0f && max == 6.0f)
op_code = ANEURALNETWORKS_RELU6;
else if (min == -1.0f && max == 1.0f)
op_code = ANEURALNETWORKS_RELU1;
else
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "ClipOpBuilder, unsupported input [", min, ", ", max, "].",
"We should not reach here, ClipOpBuilder::IsOpSupportedImpl should have caught this.");
std::vector<uint32_t> input_indices;
input_indices.push_back(operand_indices.at(input));
ORT_RETURN_IF_ERROR(model_builder.AddOperation(op_code, input_indices,
{output}, {output_operand_type}, {output_is_nhwc}));
return Status::OK();
}
#pragma endregion
#pragma region op_Resize
class ResizeOpBuilder : public BaseOpBuilder {
public:
void AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) override;
private:
bool IsOpSupportedImpl(ModelBuilder& model_builder, const Node& node) override;
int32_t GetMinSupportedSdkVer(ModelBuilder& model_builder, const Node& node) const override;
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node) override ORT_MUST_USE_RESULT;
};
int32_t ResizeOpBuilder::GetMinSupportedSdkVer(ModelBuilder& /* model_builder */, const Node& /* node */) const {
return 28;
}
void ResizeOpBuilder::AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) {
// We will still add scales to the skipped list even sizes are present
// since there is no use of it, we will not process it later
model_builder.AddInitializerToSkip(node.InputDefs()[2]->Name()); // scales
if (node.InputDefs().size() > 3)
model_builder.AddInitializerToSkip(node.InputDefs()[3]->Name()); // sizes
}
bool ResizeOpBuilder::IsOpSupportedImpl(ModelBuilder& model_builder, const Node& node) {
// Resize opset 10- is very different than Resize opset 11+, with many key attributes missing
// We only support Resize opset 11+ here
if (node.SinceVersion() < 11) {
LOGS_DEFAULT(VERBOSE) << "Resize only supports opset 11+";
return false;
}
Shape input_shape;
if (!GetShape(*node.InputDefs()[0], input_shape))
return false;
const auto input_size = input_shape.size();
if (input_size != 4) {
LOGS_DEFAULT(VERBOSE) << "Resize only support 4d shape, input is "
<< input_size << "d shape";
return false;
}
{ // check attributes
const auto android_sdk_ver = model_builder.GetAndroidSdkVer();
NodeAttrHelper helper(node);
const auto mode = helper.Get("mode", "nearest");
if (mode != "linear") {
LOGS_DEFAULT(VERBOSE) << "Resize unsupported input mode, " << mode;
return false;
}
const auto coord_trans_mode = helper.Get("coordinate_transformation_mode", "half_pixel");
bool using_half_pixel = coord_trans_mode == "half_pixel";
bool using_align_corners = coord_trans_mode == "align_corners";
if (!using_half_pixel && !using_align_corners && coord_trans_mode != "asymmetric") {
LOGS_DEFAULT(VERBOSE) << "Resize, unsupported coord_trans_mode, " << coord_trans_mode;
return false;
}
if (android_sdk_ver < 30 && (using_half_pixel || using_align_corners)) {
LOGS_DEFAULT(VERBOSE) << "Resize only support half_pixel/align_corners on API level 30+, current API level is "
<< android_sdk_ver;
return false;
}
const auto exclude_outside = helper.Get("exclude_outside", 0);
if (exclude_outside != 0) {
LOGS_DEFAULT(VERBOSE) << "Resize does not support exclude_outside for now";
return false;
}
}
{ // scales and sizes (if present) must be initializers
const auto& initializers(model_builder.GetInitializerTensors());
const auto input_defs = node.InputDefs();
// scales
if (input_defs.size() < 3 || !Contains(initializers, input_defs[2]->Name())) {
LOGS_DEFAULT(VERBOSE) << "Input scales of Resize must be known";
return false;
}
// sizes
if (input_defs.size() > 3 && !Contains(initializers, input_defs[3]->Name())) {
LOGS_DEFAULT(VERBOSE) << "Input sizes of Resize must be known";
return false;
}
// We want to check if the scales or sizes are not trying to resize on N/C channels here
if (input_defs.size() == 3) { // we are using scales
const auto& scales_tensor = initializers.at(input_defs[2]->Name());
const float* scales_data = GetTensorFloatData(scales_tensor);
float scale_n = scales_data[0];
float scale_c = scales_data[1];
if (scale_n != 1.0f || scale_c != 1.0f) {
LOGS_DEFAULT(VERBOSE) << "Scales of N/C channel should be 1"
<< "Resize of N/C channels are not supported"
<< ", scale_n, " << scale_n << ", scale_c, " << scale_c;
return false;
}
} else {
// we are using sizes
const auto& sizes_name = input_defs[3]->Name();
const auto& sizes_tensor = initializers.at(sizes_name);
const int64_t* sizes_data = GetTensorInt64Data(sizes_tensor);
uint32_t size_n = SafeInt<uint32_t>(sizes_data[0]);
uint32_t size_c = SafeInt<uint32_t>(sizes_data[1]);
if (size_n != input_shape[0] || size_c != input_shape[1]) {
LOGS_DEFAULT(VERBOSE) << "Output sizes of N/C chanel should match the input sizes, "
<< "Resize of N/C channels are not supported"
<< ", input_size_n, " << input_shape[0] << ", output_size_n, " << size_n
<< ". input_size_c, " << input_shape[1] << ", output_size_c, " << size_c;
return false;
}
}
}
return true;
}
Status ResizeOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node) {
auto& shaper(model_builder.GetShaper());
const auto& operand_indices(model_builder.GetOperandIndices());
const auto& operand_types(model_builder.GetOperandTypes());
const auto& initializers(model_builder.GetInitializerTensors());
NodeAttrHelper helper(node);
const auto input_defs = node.InputDefs();
const auto android_sdk_ver = model_builder.GetAndroidSdkVer();
const auto& output = node.OutputDefs()[0]->Name();
auto input = input_defs[0]->Name();
bool use_nchw = model_builder.UseNCHW();
bool input_is_nhwc = model_builder.IsOperandNHWC(input);
bool output_is_nhwc = false;
if (use_nchw) {
ORT_RETURN_IF_NOT(!input_is_nhwc, "model_builder.UseNCHW() but input is NHWC");
} else {
output_is_nhwc = true;
if (!input_is_nhwc) {
const auto& nchw_input = input_defs[0]->Name();
if (!model_builder.GetNHWCOperand(nchw_input, input)) {
input = model_builder.GetUniqueName(nchw_input + "_nchw_to_nhwc");
ORT_RETURN_IF_ERROR(TransposeNCHWToNHWC(model_builder, nchw_input, input));
}
}
}
// TODO, add support for nearest neighbor
int32_t operationCode = ANEURALNETWORKS_RESIZE_BILINEAR;
const auto coord_trans_mode = helper.Get("coordinate_transformation_mode", "half_pixel");
bool using_half_pixel = coord_trans_mode == "half_pixel";
bool using_align_corners = coord_trans_mode == "align_corners";
if (input_defs.size() == 3) { // we are using scales
const auto& scales_name = input_defs[2]->Name();
const auto& scales_tensor = initializers.at(scales_name);
const float* scales_data = GetTensorFloatData(scales_tensor);
float scale_h = scales_data[2];
float scale_w = scales_data[3];
ORT_RETURN_IF_ERROR(
shaper.ResizeUsingScales(input, scale_h, scale_w, use_nchw, output));
} else { // we are using sizes
const auto& sizes_name = input_defs[3]->Name();
const auto& sizes_tensor = initializers.at(sizes_name);
const int64_t* sizes_data = GetTensorInt64Data(sizes_tensor);
ORT_RETURN_IF_ERROR(
shaper.ResizeUsingOutputSizes(input, SafeInt<uint32_t>(sizes_data[2]), SafeInt<uint32_t>(sizes_data[3]), use_nchw, output));
}
const auto& output_shape = shaper[output];
int32_t output_h = use_nchw ? output_shape[2] : output_shape[1];
int32_t output_w = use_nchw ? output_shape[3] : output_shape[2];
std::vector<uint32_t> input_indices;
input_indices.push_back(operand_indices.at(input));
ADD_SCALAR_OPERAND(model_builder, input_indices, output_w);
ADD_SCALAR_OPERAND(model_builder, input_indices, output_h);
if (android_sdk_ver > 28) {
// using nchw is only available on API level 29
ADD_SCALAR_OPERAND(model_builder, input_indices, use_nchw);
}
if (android_sdk_ver > 29 && (using_align_corners || using_half_pixel)) {
ADD_SCALAR_OPERAND(model_builder, input_indices, using_align_corners);
if (using_half_pixel)
ADD_SCALAR_OPERAND(model_builder, input_indices, using_half_pixel);
}
const OperandType output_operand_type(operand_types.at(input).type, output_shape);
ORT_RETURN_IF_ERROR(model_builder.AddOperation(operationCode, input_indices,
{output}, {output_operand_type}, {output_is_nhwc}));
return Status::OK();
}
#pragma endregion
#pragma region CreateOpBuilders
std::unordered_map<std::string, std::shared_ptr<IOpBuilder>>
@ -2736,6 +3039,8 @@ CreateOpBuilders() {
op_map.emplace("QuantizeLinear", std::make_shared<QuantizeLinearOpBuilder>());
op_map.emplace("DequantizeLinear", std::make_shared<DequantizeLinearOpBuilder>());
op_map.emplace("LRN", std::make_shared<LRNOpBuilder>());
op_map.emplace("Clip", std::make_shared<ClipOpBuilder>());
op_map.emplace("Resize", std::make_shared<ResizeOpBuilder>());
return op_map;
}

View file

@ -130,6 +130,20 @@ Status Shaper::Squeeze(const std::string& input_name,
SHAPER_FUNC(Squeeze, input_name, axes, output_name);
}
Status Shaper::ResizeUsingScales(const std::string& input_name,
const float scale_h, const float scale_w,
bool nchw,
const std::string& output_name) {
SHAPER_FUNC(ResizeUsingScales, input_name, scale_h, scale_w, nchw, output_name);
}
Status Shaper::ResizeUsingOutputSizes(const std::string& input_name,
const uint32_t output_h, const uint32_t output_w,
bool nchw,
const std::string& output_name) {
SHAPER_FUNC(ResizeUsingOutputSizes, input_name, output_h, output_w, nchw, output_name);
}
#undef SHAPER_FUNC
Status Shaper::ConvImpl(const std::string& input_name,
@ -384,6 +398,38 @@ Status Shaper::SqueezeImpl(const std::string& input_name,
return Status::OK();
}
Status Shaper::ResizeUsingScalesImpl(const std::string& input_name,
const float scale_h, const float scale_w,
bool nchw,
const std::string& output_name) {
Shape output_dimen = shape_map_.at(input_name);
if (nchw) {
output_dimen[2] *= scale_h;
output_dimen[3] *= scale_w;
} else { // nhwc
output_dimen[1] *= scale_h;
output_dimen[2] *= scale_w;
}
shape_map_[output_name] = output_dimen;
return Status::OK();
}
Status Shaper::ResizeUsingOutputSizesImpl(const std::string& input_name,
const uint32_t output_h, const uint32_t output_w,
bool nchw,
const std::string& output_name) {
Shape output_dimen = shape_map_.at(input_name);
if (nchw) {
output_dimen[2] = output_h;
output_dimen[3] = output_w;
} else { // nhwc
output_dimen[1] = output_h;
output_dimen[2] = output_w;
}
shape_map_[output_name] = output_dimen;
return Status::OK();
}
void Shaper::AddShape(const std::string& name, const Shape& shape) {
shape_map_[name] = shape;
}

View file

@ -57,9 +57,18 @@ class Shaper {
Status Concat(const std::vector<std::string>& input_names, const int32_t axis, const std::string& output_name)
ORT_MUST_USE_RESULT;
Status Squeeze(const std::string& input, const std::vector<int32_t>& axes, const std::string& output)
Status Squeeze(const std::string& input_name, const std::vector<int32_t>& axes, const std::string& output_name)
ORT_MUST_USE_RESULT;
Status ResizeUsingScales(const std::string& input_name,
const float scale_h, const float scale_w,
bool nchw,
const std::string& output_name) ORT_MUST_USE_RESULT;
Status ResizeUsingOutputSizes(const std::string& input_name,
const uint32_t output_h, const uint32_t output_w,
bool nchw,
const std::string& output_name) ORT_MUST_USE_RESULT;
// If the shape of certain input is dynamic
// Use the following 2 functions to update the particular shape
// and calculate the new output shape
@ -104,8 +113,16 @@ class Shaper {
ORT_MUST_USE_RESULT;
Status ConcatImpl(const std::vector<std::string>& input_names, const int32_t axis, const std::string& output_name)
ORT_MUST_USE_RESULT;
Status SqueezeImpl(const std::string& input, const std::vector<int32_t>& axes, const std::string& output)
Status SqueezeImpl(const std::string& input_names, const std::vector<int32_t>& axes, const std::string& output_name)
ORT_MUST_USE_RESULT;
Status ResizeUsingScalesImpl(const std::string& input_name,
const float scale_h, const float scale_w,
bool nchw,
const std::string& output_name) ORT_MUST_USE_RESULT;
Status ResizeUsingOutputSizesImpl(const std::string& input_name,
const uint32_t output_h, const uint32_t output_w,
bool nchw,
const std::string& output_name) ORT_MUST_USE_RESULT;
std::unordered_map<std::string, Shape> shape_map_;
std::vector<std::function<Status(Shaper&)>> shape_ops_;

View file

@ -303,8 +303,12 @@ common::Status NnapiExecutionProvider::Compile(const std::vector<onnxruntime::No
OperandType input_type = model_input_type;
input_type.SetDimensions(dimensions);
if (input_type.GetOperandBlobByteSize() == 0)
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "The actual input cannot have 0 dim (dynamic)");
// We have some op has input can have {0} shapes, such as Resize.scales/roi, these are valid input
// We still want to log the shape info, in case we get an input shape with some zero dim and some non-zero dim
if (input_type.GetOperandBlobByteSize() == 0) {
LOGS_DEFAULT(INFO) << "The actual input [" << input_name << "] has "
<< nnapi::Shape2String(dimensions) << " shape";
}
if (input_type.dimensions != model_input_type.dimensions && model_input_type.GetOperandBlobByteSize() != 0) {
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL,

View file

@ -22,11 +22,11 @@ TEST(MathOpTest, Clip_6) {
{10.0f, 4.4f, 10.0f,
-1.3f, 3.5f, 10.0f,
-5.4f, 9.3f, 10.0f});
#if defined(OPENVINO_CONFIG_MYRIAD) || defined(OPENVINO_CONFIG_VAD_M) || defined(OPENVINO_CONFIG_CPU_FP32)
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kOpenVINOExecutionProvider});
#else
test.Run();
#endif
#if defined(OPENVINO_CONFIG_MYRIAD) || defined(OPENVINO_CONFIG_VAD_M) || defined(OPENVINO_CONFIG_CPU_FP32)
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kOpenVINOExecutionProvider});
#else
test.Run();
#endif
}
TEST(MathOpTest, Clip_Default) {
@ -42,12 +42,12 @@ TEST(MathOpTest, Clip_Default) {
-1.3f, 3.5f, 64.0f,
-5.4f, 9.3f, 82.4f});
#if defined(OPENVINO_CONFIG_MYRIAD) || defined(OPENVINO_CONFIG_VAD_M)
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kOpenVINOExecutionProvider, kNGraphExecutionProvider});
#else
#if defined(OPENVINO_CONFIG_MYRIAD) || defined(OPENVINO_CONFIG_VAD_M)
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kOpenVINOExecutionProvider, kNGraphExecutionProvider});
#else
// nGraph does not support Clip opset 12 yet.
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kNGraphExecutionProvider});
#endif
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kNGraphExecutionProvider});
#endif
}
TEST(MathOpTest, Clip_Default_int8) {
@ -55,13 +55,13 @@ TEST(MathOpTest, Clip_Default_int8) {
std::vector<int64_t> dims{3, 3};
test.AddInput<int8_t>("X", dims,
{11, 4, 127,
-1, 3, 64,
-5, 9, 82});
test.AddOutput<int8_t>("Y", dims,
{11, 4, 127,
-1, 3, 64,
-5, 9, 82});
test.AddOutput<int8_t>("Y", dims,
{11, 4, 127,
-1, 3, 64,
-5, 9, 82});
// TensorRT, nGraph does not support Clip opset 12 yet.
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider, kNGraphExecutionProvider});
@ -72,13 +72,13 @@ TEST(MathOpTest, Clip_Default_uint8) {
std::vector<int64_t> dims{3, 3};
test.AddInput<uint8_t>("X", dims,
{11, 4, 255,
1, 3, 64,
5, 9, 82});
test.AddOutput<uint8_t>("Y", dims,
{11, 4, 255,
1, 3, 64,
5, 9, 82});
test.AddOutput<uint8_t>("Y", dims,
{11, 4, 255,
1, 3, 64,
5, 9, 82});
// TensorRT, nGraph does not support Clip opset 12 yet.
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider, kNGraphExecutionProvider});
@ -89,53 +89,110 @@ TEST(MathOpTest, Clip_Default_int64) {
std::vector<int64_t> dims{3, 3};
test.AddInput<int64_t>("X", dims,
{11, 4, 432,
-1, 3, 64,
-5, 9, 82});
test.AddOutput<int64_t>("Y", dims,
{11, 4, 432,
-1, 3, 64,
-5, 9, 82});
test.AddOutput<int64_t>("Y", dims,
{11, 4, 432,
-1, 3, 64,
-5, 9, 82});
// TensorRT, nGraph does not support Clip opset 12 yet.
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider, kNGraphExecutionProvider});
}
TEST(MathOpTest, Clip_Default_uint64) {
OpTester test("Clip", 12);
std::vector<int64_t> dims{3, 3};
test.AddInput<uint64_t>("X", dims,
{11, 4, 432,
1, 3, 64,
5, 9, 82});
test.AddOutput<uint64_t>("Y", dims,
{11, 4, 432,
1, 3, 64,
5, 9, 82});
test.AddOutput<uint64_t>("Y", dims,
{11, 4, 432,
1, 3, 64,
5, 9, 82});
// TensorRT, nGraph does not support Clip opset 12 yet.
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider, kNGraphExecutionProvider});
}
TEST(MathOpTest, Clip) {
OpTester test("Clip", 11);
// To test NNAPI EP, we need the min/max to be in initializers
auto run_test = [](bool min_max_are_initializer) {
OpTester test("Clip", 11);
std::vector<int64_t> dims{3, 3};
test.AddInput<float>("X", dims,
{-1.0f, 0.0f, 1.0f,
-6.0f, 0.0f, 6.0f,
-5.4f, 2.0f, 6.0f});
test.AddInput<float>("min", {}, {-5});
test.AddInput<float>("max", {}, {5});
test.AddOutput<float>("Y", dims,
{-1.0f, 0.0f, 1.0f,
-5.0f, 0.0f, 5.0f,
-5.0f, 2.0f, 5.0f});
std::vector<int64_t> dims{3, 3};
test.AddInput<float>("X", dims,
{-1.0f, 0.0f, 1.0f,
-6.0f, 0.0f, 6.0f,
-5.4f, 2.0f, 6.0f});
test.AddInput<float>("min", {}, {-5}, min_max_are_initializer);
test.AddInput<float>("max", {}, {5}, min_max_are_initializer);
test.AddOutput<float>("Y", dims,
{-1.0f, 0.0f, 1.0f,
-5.0f, 0.0f, 5.0f,
-5.0f, 2.0f, 5.0f});
// TensorRT, nGraph and Tensorrt does not support Clip opset 11 yet.
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kNGraphExecutionProvider, kTensorrtExecutionProvider});
// TensorRT, nGraph and Tensorrt does not support Clip opset 11 yet.
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kNGraphExecutionProvider, kTensorrtExecutionProvider});
};
run_test(false);
run_test(true);
}
// Use clip between [0, 6] as Relu6 (for some EPs, such as NNAPI)
TEST(MathOpTest, Clip_Relu6) {
// To test NNAPI EP, we need the min/max to be in initializers
auto run_test = [](bool min_max_are_initializer) {
OpTester test("Clip", 11);
std::vector<int64_t> dims{3, 3};
test.AddInput<float>("X", dims,
{-1.0f, 0.0f, 1.0f,
-6.0f, 3.5f, 6.0f,
-5.4f, 2.0f, 8.0f});
test.AddInput<float>("min", {}, {0.0f}, min_max_are_initializer);
test.AddInput<float>("max", {}, {6.0f}, min_max_are_initializer);
test.AddOutput<float>("Y", dims,
{0.0f, 0.0f, 1.0f,
0.0f, 3.5f, 6.0f,
0.0f, 2.0f, 6.0f});
// TensorRT, nGraph and Tensorrt does not support Clip opset 11 yet.
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kNGraphExecutionProvider, kTensorrtExecutionProvider});
};
run_test(false);
run_test(true);
}
// Use clip between [-1, 1] as Relu1 (for some EPs, such as NNAPI)
TEST(MathOpTest, Clip_Relu1) {
// To test NNAPI EP, we need the min/max to be in initializers
auto run_test = [](bool min_max_are_initializer) {
OpTester test("Clip", 11);
std::vector<int64_t> dims{3, 3};
test.AddInput<float>("X", dims,
{-1.0f, 0.0f, 1.0f,
-6.0f, 3.5f, 6.0f,
-5.4f, 2.0f, 8.0f});
test.AddInput<float>("min", {}, {-1.0f}, min_max_are_initializer);
test.AddInput<float>("max", {}, {1.0f}, min_max_are_initializer);
test.AddOutput<float>("Y", dims,
{-1.0f, 0.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 1.0f});
// TensorRT, nGraph and Tensorrt does not support Clip opset 11 yet.
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kNGraphExecutionProvider, kTensorrtExecutionProvider});
};
run_test(false);
run_test(true);
}
TEST(MathOpTest, ClipDimWithZero) {

View file

@ -87,27 +87,103 @@ TEST(ResizeOpTest, ResizeOpLinearDownSampleTest_4DBilinear) {
test.Run();
}
// Since NNAPI(TFLite) only using the scale calulate using the input/output size
// For the above test (ResizeOpLinearDownSampleTest_4DBilinear)
// The output size is [1,1,2,4].*[1,1,0.6,0.6]=[1,1,1,2]
// NNAPI will recaluclate the scales as the output size divided by input size
// scales = [1,1,1,2]./[1,1,2,4] = [1,1,0.5,0.5]
// See, https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/kernels/internal/reference/reference_ops.h
// So the result of the above example will be different than CPU EP
// Add the following 2 tests to test with scales valid to NNAPI
TEST(ResizeOpTest, ResizeOpLinearDownSampleTest_4DBilinear1) {
// To test NNAPI EP, we need the sclaes/sizes to be in initializers
auto run_test = [](bool scales_in_initializer) {
OpTester test("Resize", 11);
std::vector<float> roi{};
std::vector<float> scales{1.0f, 1.0f, 0.5f, 0.5f};
test.AddAttribute("mode", "linear");
const int64_t N = 1, C = 1, H = 2, W = 4;
std::vector<float> X = {
1.0f, 2.0f, 3.0f, 4.0f,
5.0f, 6.0f, 7.0f, 8.0f};
test.AddInput<float>("X", {N, C, H, W}, X);
test.AddInput<float>("roi", {0}, roi);
test.AddInput<float>("scales", {4}, scales, scales_in_initializer);
std::vector<float> Y = {3.5f, 5.5f};
test.AddOutput<float>("Y", {N, C, static_cast<int64_t>(H * scales[2]), static_cast<int64_t>(W * scales[3])}, Y);
test.Run();
};
run_test(false);
run_test(true);
}
TEST(ResizeOpTest, ResizeOpLinearDownSampleTest_4DBilinear1_WithSizes) {
// To test NNAPI EP, we need the sclaes/sizes to be in initializers
auto run_test = [](bool scales_and_sizes_in_initializer) {
OpTester test("Resize", 11);
std::vector<float> roi{};
std::vector<float> scales{};
const int64_t N = 1, C = 1, H = 2, W = 4;
std::vector<int64_t> sizes{N, C, 1, 2};
test.AddAttribute("mode", "linear");
std::vector<float> X = {
1.0f, 2.0f, 3.0f, 4.0f,
5.0f, 6.0f, 7.0f, 8.0f};
test.AddInput<float>("X", {N, C, H, W}, X);
test.AddInput<float>("roi", {0}, roi);
test.AddInput<float>("scales", {0}, scales, scales_and_sizes_in_initializer);
test.AddInput<int64_t>("sizes", {4}, sizes, scales_and_sizes_in_initializer);
std::vector<float> Y = {3.5f, 5.5f};
test.AddOutput<float>("Y", sizes, Y);
test.Run();
};
run_test(false);
run_test(true);
}
TEST(ResizeOpTest, ResizeOpLinearDownSampleTest_4DBilinear_align_corners) {
OpTester test("Resize", 11);
std::vector<float> roi{};
std::vector<float> scales{1.0f, 1.0f, 0.6f, 0.6f};
// To test NNAPI EP, we need the sclaes/sizes to be in initializers
auto run_test = [](bool scales_in_initializer) {
OpTester test("Resize", 11);
std::vector<float> roi{};
std::vector<float> scales{1.0f, 1.0f, 0.6f, 0.6f};
test.AddAttribute("mode", "linear");
test.AddAttribute("coordinate_transformation_mode", "align_corners");
test.AddAttribute("mode", "linear");
test.AddAttribute("coordinate_transformation_mode", "align_corners");
const int64_t N = 1, C = 1, H = 2, W = 4;
std::vector<float> X = {
1.0f, 2.0f, 3.0f, 4.0f,
5.0f, 6.0f, 7.0f, 8.0f};
const int64_t N = 1, C = 1, H = 2, W = 4;
std::vector<float> X = {
1.0f, 2.0f, 3.0f, 4.0f,
5.0f, 6.0f, 7.0f, 8.0f};
test.AddInput<float>("X", {N, C, H, W}, X);
test.AddInput<float>("roi", {0}, roi);
test.AddInput<float>("scales", {4}, scales);
test.AddInput<float>("X", {N, C, H, W}, X);
test.AddInput<float>("roi", {0}, roi);
test.AddInput<float>("scales", {4}, scales, scales_in_initializer);
std::vector<float> Y = {1.0f, 4.0f};
std::vector<float> Y = {1.0f, 4.0f};
test.AddOutput<float>("Y", {N, C, static_cast<int64_t>(H * scales[2]), static_cast<int64_t>(W * scales[3])}, Y);
test.Run();
test.AddOutput<float>("Y", {N, C, static_cast<int64_t>(H * scales[2]), static_cast<int64_t>(W * scales[3])}, Y);
test.Run();
};
run_test(false);
#ifdef USE_NNAPI
// NNAPI will need the scales as an initializer
// Also tensor RT EP will fail if scales is an initializer but will pass if it is not
run_test(true);
#endif
}
TEST(ResizeOpTest, ResizeOpLinearDownSampleTest_2DBilinear_pytorch_half_pixel) {
@ -139,37 +215,43 @@ TEST(ResizeOpTest, ResizeOpLinearDownSampleTest_2DBilinear_pytorch_half_pixel) {
}
TEST(ResizeOpTest, ResizeOpLinearUpSampleTest_4DBilinear_asymmetric) {
OpTester test("Resize", 11);
std::vector<float> roi{};
std::vector<float> scales{1.0f, 1.0f, 2.0f, 4.0f};
// To test NNAPI EP, we need the sclaes/sizes to be in initializers
auto run_test = [](bool scales_in_initializer) {
OpTester test("Resize", 11);
std::vector<float> roi{};
std::vector<float> scales{1.0f, 1.0f, 2.0f, 4.0f};
test.AddAttribute("mode", "linear");
test.AddAttribute("coordinate_transformation_mode", "asymmetric");
test.AddAttribute("mode", "linear");
test.AddAttribute("coordinate_transformation_mode", "asymmetric");
const int64_t N = 2, C = 1, H = 2, W = 2;
std::vector<float> X = {1.0f, 3.0f,
4.0f, 8.0f,
const int64_t N = 2, C = 1, H = 2, W = 2;
std::vector<float> X = {1.0f, 3.0f,
4.0f, 8.0f,
6.0f, 2.0f,
7.0f, 11.0f};
6.0f, 2.0f,
7.0f, 11.0f};
test.AddInput<float>("X", {N, C, H, W}, X);
test.AddInput<float>("roi", {0}, roi);
test.AddInput<float>("scales", {4}, scales);
test.AddInput<float>("X", {N, C, H, W}, X);
test.AddInput<float>("roi", {0}, roi);
test.AddInput<float>("scales", {4}, scales, scales_in_initializer);
std::vector<float> Y = {
1.0f, 1.5f, 2.0f, 2.5f, 3.0f, 3.0f, 3.0f, 3.0f,
2.5f, 3.25f, 4.0f, 4.75f, 5.5f, 5.5f, 5.5f, 5.5f,
4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 8.0f, 8.0f, 8.0f,
4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 8.0f, 8.0f, 8.0f,
std::vector<float> Y = {
1.0f, 1.5f, 2.0f, 2.5f, 3.0f, 3.0f, 3.0f, 3.0f,
2.5f, 3.25f, 4.0f, 4.75f, 5.5f, 5.5f, 5.5f, 5.5f,
4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 8.0f, 8.0f, 8.0f,
4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 8.0f, 8.0f, 8.0f,
6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 2.0f, 2.0f, 2.0f,
6.5f, 6.5f, 6.5f, 6.5f, 6.5f, 6.5f, 6.5f, 6.5f,
7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 11.0f, 11.0f, 11.0f,
7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 11.0f, 11.0f, 11.0f};
6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 2.0f, 2.0f, 2.0f,
6.5f, 6.5f, 6.5f, 6.5f, 6.5f, 6.5f, 6.5f, 6.5f,
7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 11.0f, 11.0f, 11.0f,
7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 11.0f, 11.0f, 11.0f};
test.AddOutput<float>("Y", {N, C, static_cast<int64_t>(H * scales[2]), static_cast<int64_t>(W * scales[3])}, Y);
test.Run();
test.AddOutput<float>("Y", {N, C, static_cast<int64_t>(H * scales[2]), static_cast<int64_t>(W * scales[3])}, Y);
test.Run();
};
run_test(false);
run_test(true);
}
TEST(ResizeOpTest, ResizeOpLinearUpSampleTest_2DBilinear_align_corners) {
@ -257,30 +339,36 @@ TEST(ResizeOpTest, ResizeOpLinearUpSampleTest_5DTrilinear_pytorch_half_pixel) {
}
TEST(ResizeOpTest, ResizeOpLinearScalesNoOpTest) {
OpTester test("Resize", 11);
std::vector<float> roi{};
std::vector<float> scales{1.0f, 1.0f, 1.0f, 1.0f};
test.AddAttribute("mode", "linear");
// To test NNAPI EP, we need the sclaes/sizes to be in initializers
auto run_test = [](bool scales_in_initializer) {
OpTester test("Resize", 11);
std::vector<float> roi{};
std::vector<float> scales{1.0f, 1.0f, 1.0f, 1.0f};
test.AddAttribute("mode", "linear");
const int64_t N = 2, C = 1, H = 2, W = 2;
std::vector<float> X = {1.0f, 3.0f,
4.0f, 8.0f,
const int64_t N = 2, C = 1, H = 2, W = 2;
std::vector<float> X = {1.0f, 3.0f,
4.0f, 8.0f,
6.0f, 2.0f,
7.0f, 11.0f};
6.0f, 2.0f,
7.0f, 11.0f};
test.AddInput<float>("X", {N, C, H, W}, X);
test.AddInput<float>("roi", {0}, roi);
test.AddInput<float>("scales", {4}, scales);
test.AddInput<float>("X", {N, C, H, W}, X);
test.AddInput<float>("roi", {0}, roi);
test.AddInput<float>("scales", {4}, scales, scales_in_initializer);
std::vector<float> Y = {1.0f, 3.0f,
4.0f, 8.0f,
std::vector<float> Y = {1.0f, 3.0f,
4.0f, 8.0f,
6.0f, 2.0f,
7.0f, 11.0f};
6.0f, 2.0f,
7.0f, 11.0f};
test.AddOutput<float>("Y", {N, C, H, W}, Y);
test.Run();
test.AddOutput<float>("Y", {N, C, H, W}, Y);
test.Run();
};
run_test(false);
run_test(true);
}
TEST(ResizeOpTest, ResizeOpNearestDownSampleTest) {