nnapi add min max support (#6117)

This commit is contained in:
Guoyu Wang 2020-12-15 22:31:28 -08:00 committed by GitHub
parent 939cc9b410
commit b648bf641f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 167 additions and 13 deletions

View file

@ -145,6 +145,35 @@ Status GetNCHWInput(ModelBuilder& model_builder, const Node& node, size_t input_
return Status::OK();
}
// Transpose layouts if necessary for element wise operators with 2 inputs
// and return the layout type of output tensor
// If both inputs have same layout, the output will have the same layout
// Otherwise we will need transpose the nhwc input back to nchw, and output will be nchw
Status TransposeBinaryOpInputLayout(ModelBuilder& model_builder, const Node& node,
size_t input1_idx, size_t input2_idx,
std::string& input1, std::string& input2,
bool& output_is_nhwc) ORT_MUST_USE_RESULT;
Status TransposeBinaryOpInputLayout(ModelBuilder& model_builder, const Node& node,
size_t input1_idx, size_t input2_idx,
std::string& input1, std::string& input2,
bool& output_is_nhwc) {
bool input1_is_nhwc = model_builder.IsOperandNHWC(input1);
bool input2_is_nhwc = model_builder.IsOperandNHWC(input2);
output_is_nhwc = false;
if (input1_is_nhwc == input2_is_nhwc) {
output_is_nhwc = input1_is_nhwc;
} else if (input1_is_nhwc) {
// need transpose input1 back to nchw
ORT_RETURN_IF_ERROR(GetNCHWInput(model_builder, node, input1_idx, input1));
} else { // input2_is_nhwc
// need transpose input2 back to nchw
ORT_RETURN_IF_ERROR(GetNCHWInput(model_builder, node, input2_idx, input2));
}
return Status::OK();
}
static Status AddBinaryOperator(int32_t op_type,
ModelBuilder& model_builder,
const std::string& input1,
@ -679,19 +708,9 @@ Status BinaryOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const
std::string input2 = input_defs[b_idx]->Name();
const auto& output = node.OutputDefs()[0]->Name();
bool input1_is_nhwc = model_builder.IsOperandNHWC(input1);
bool input2_is_nhwc = model_builder.IsOperandNHWC(input2);
bool output_is_nhwc = false;
if (input1_is_nhwc == input2_is_nhwc) {
output_is_nhwc = input1_is_nhwc;
} else if (input1_is_nhwc) {
// need transpose input1 back to nchw
ORT_RETURN_IF_ERROR(GetNCHWInput(model_builder, node, a_idx, input1));
} else { // input2_is_nhwc
// need transpose input2 back to nchw
ORT_RETURN_IF_ERROR(GetNCHWInput(model_builder, node, b_idx, input2));
}
ORT_RETURN_IF_ERROR(
TransposeBinaryOpInputLayout(model_builder, node, a_idx, b_idx, input1, input2, output_is_nhwc));
float a_scale = 0.0f,
b_scale = 0.0f,
@ -2221,7 +2240,75 @@ Status FlattenOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, cons
return ReshapeOpBuilder::AddReshapeOperator(model_builder, node, input, shape);
}
#pragma endregion op_reshape
#pragma endregion
#pragma region op_minmax
class MinMaxOpBuilder : public BaseOpBuilder {
public:
static void CreateSharedOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
static Status AddMinMaxOperator(ModelBuilder& model_builder, const Node& node,
const std::string& input1, const std::string& input2,
bool output_is_nhwc) ORT_MUST_USE_RESULT;
private:
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node) const override ORT_MUST_USE_RESULT;
};
/* static */ void MinMaxOpBuilder::CreateSharedOpBuilder(
const std::string& op_type, OpBuilderRegistrations& op_registrations) {
CreateSharedOpBuilderImpl<MinMaxOpBuilder>(
op_type, op_registrations,
{
"Min",
"Max",
});
}
/* static */ Status MinMaxOpBuilder::AddMinMaxOperator(ModelBuilder& model_builder, const Node& node,
const std::string& input1, const std::string& input2,
bool output_is_nhwc) {
auto& shaper(model_builder.GetShaper());
const auto& operand_indices(model_builder.GetOperandIndices());
const auto& operand_types(model_builder.GetOperandTypes());
const auto& output = node.OutputDefs()[0]->Name();
const auto& op_type(node.OpType());
int32_t op_code;
if (op_type == "Min")
op_code = ANEURALNETWORKS_MINIMUM;
else if (op_type == "Max")
op_code = ANEURALNETWORKS_MAXIMUM;
else {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "MinMaxOpBuilder, unknown op: ", op_type);
}
std::vector<uint32_t> input_indices;
input_indices.push_back(operand_indices.at(input1)); // input 1
input_indices.push_back(operand_indices.at(input2)); // input 2
ORT_RETURN_IF_ERROR(shaper.Eltwise(input1, input2, output));
const OperandType output_operand_type(operand_types.at(input1).type, shaper[output]);
ORT_RETURN_IF_ERROR(model_builder.AddOperation(op_code, input_indices,
{output}, {output_operand_type}, {output_is_nhwc}));
return Status::OK();
}
Status MinMaxOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node) const {
const auto input_defs(node.InputDefs());
std::string input1 = input_defs[0]->Name();
std::string input2 = input_defs[1]->Name();
bool output_is_nhwc = false;
ORT_RETURN_IF_ERROR(TransposeBinaryOpInputLayout(model_builder, node,
0 /* input1_idx */,
1 /* input2_idx */,
input1, input2, output_is_nhwc));
return AddMinMaxOperator(model_builder, node, input1, input2, output_is_nhwc);
}
#pragma endregion
#pragma region CreateGetOpBuilders
@ -2297,6 +2384,11 @@ static OpBuilderRegistrations CreateOpBuilderRegistrations() {
NNAPI_EP_ADD_SINGLE_OP_BUILDER("Resize", ResizeOpBuilder);
NNAPI_EP_ADD_SINGLE_OP_BUILDER("Flatten", FlattenOpBuilder);
{
NNAPI_EP_ADD_SHARED_OP_BUILDER("Min", MinMaxOpBuilder);
NNAPI_EP_ADD_SHARED_OP_BUILDER("Max", MinMaxOpBuilder);
}
return op_registrations;
}

View file

@ -1295,6 +1295,49 @@ bool FlattenOpSupportChecker::IsOpSupportedImpl(const InitializedTensorSet& /* i
#pragma endregion
#pragma region op_minmax
class MinMaxOpSupportChecker : public BaseOpSupportChecker {
public:
static void CreateSharedOpSupportChecker(
const std::string& op_type, OpSupportCheckerRegistrations& op_registrations);
private:
int32_t GetMinSupportedSdkVer(const Node& /* node */, const OpSupportCheckParams& /* params */) const override {
return 29;
}
// Min/Max opset 5- uses consumed_inputs attribute which is not supported for now
int GetMinSupportedOpSet(const Node& /* node */) const override { return 6; }
bool IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
const OpSupportCheckParams& params) const override;
};
/* static */ void MinMaxOpSupportChecker::CreateSharedOpSupportChecker(
const std::string& op_type, OpSupportCheckerRegistrations& op_registrations) {
CreateSharedOpSupportCheckerImpl<MinMaxOpSupportChecker>(
op_type, op_registrations,
{
"Min",
"Max",
});
}
bool MinMaxOpSupportChecker::IsOpSupportedImpl(const InitializedTensorSet& /* initializers */, const Node& node,
const OpSupportCheckParams& /* params */) const {
// TODO support 2+ inputs for Min/Max op
if (node.InputDefs().size() != 2) {
LOGS_DEFAULT(VERBOSE) << "[" << node.OpType() << "] only supports 2 inputs, "
<< "actual input number, " << node.InputDefs().size();
return false;
}
return true;
}
#pragma endregion
#pragma region CreateGetOpSupportCheckers
// The reason we use macros to create OpBuilders is for easy exclusion in build if certain op(s) are not used
@ -1373,6 +1416,10 @@ static OpSupportCheckerRegistrations CreateOpSupportCheckerRegistrations() {
NNAPI_EP_ADD_SINGLE_OP_SUPPORT_CHECKER("Resize", ResizeOpSupportChecker);
NNAPI_EP_ADD_SINGLE_OP_SUPPORT_CHECKER("Flatten", FlattenOpSupportChecker);
{
NNAPI_EP_ADD_SHARED_OP_SUPPORT_CHECKER("Min", MinMaxOpSupportChecker);
NNAPI_EP_ADD_SHARED_OP_SUPPORT_CHECKER("Max", MinMaxOpSupportChecker);
}
return op_registrations;
}

View file

@ -1020,6 +1020,21 @@ TEST(MathOpTest, Min_12_Float) {
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider, kOpenVINOExecutionProvider}); //TensorRT: Input batch size is inconsistent
}
TEST(MathOpTest, Min_12_Float_2_Input) {
OpTester test("Min", 12);
test.AddInput<float>("data_2", {3, 3},
{10.0f, 20.0f, 30.0f,
40.0f, 50.0f, 60.0f,
-70.0f, -80.0f, -90.0f});
test.AddInput<float>("data_1", {3, 1},
{-1.0f, 20.0f, 300.0f});
test.AddOutput<float>("min", {3, 3},
{-1.0f, -1.0f, -1.0f,
20.0f, 20.0f, 20.0f,
-70.0f, -80.0f, -90.0f});
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider, kOpenVINOExecutionProvider}); //TensorRT: Input batch size is inconsistent
}
TEST(MathOpTest, Min_12_Double) {
OpTester test("Min", 12);
test.AddInput<double>("data_0", {1, 3},