From 3c6d4099374c1608e8a736774c853aa7b7724249 Mon Sep 17 00:00:00 2001 From: Hector Li Date: Mon, 10 Jun 2024 09:16:25 -0700 Subject: [PATCH] Enable Hardsigmoid for QNN EP using SDK support direct support (#20956) ### Description Enable Hardsigmoid for QNN EP using SDK support direct support instead of decomposing to its constituent ops so it can support the quantized model --- .../qnn/builder/op_builder_factory.cc | 5 +- .../qnn/builder/opbuilder/base_op_builder.h | 1 + .../opbuilder/hard_sigmoid_op_builder.cc | 239 ------------------ .../builder/opbuilder/simple_op_builder.cc | 202 ++++++++++++++- .../test/providers/qnn/simple_op_htp_test.cc | 40 ++- 5 files changed, 225 insertions(+), 262 deletions(-) delete mode 100644 onnxruntime/core/providers/qnn/builder/opbuilder/hard_sigmoid_op_builder.cc diff --git a/onnxruntime/core/providers/qnn/builder/op_builder_factory.cc b/onnxruntime/core/providers/qnn/builder/op_builder_factory.cc index 8362dd9b29..8c34a7a60e 100644 --- a/onnxruntime/core/providers/qnn/builder/op_builder_factory.cc +++ b/onnxruntime/core/providers/qnn/builder/op_builder_factory.cc @@ -58,6 +58,7 @@ OpBuilderRegistrations::OpBuilderRegistrations() { CreateSimpleOpBuilder("DequantizeLinear", *this); CreateSimpleOpBuilder("HardSwish", *this); + CreateSimpleOpBuilder("HardSigmoid", *this); CreateSimpleOpBuilder("DepthToSpace", *this); CreateSimpleOpBuilder("SpaceToDepth", *this); @@ -167,10 +168,6 @@ OpBuilderRegistrations::OpBuilderRegistrations() { { CreateExpandOpBuilder("Expand", *this); } - - { - CreateHardSigmoidOpBuilder("HardSigmoid", *this); - } } const IOpBuilder* GetOpBuilder(const std::string& onnx_op_type) { diff --git a/onnxruntime/core/providers/qnn/builder/opbuilder/base_op_builder.h b/onnxruntime/core/providers/qnn/builder/opbuilder/base_op_builder.h index af81e5c698..6886845ff3 100644 --- a/onnxruntime/core/providers/qnn/builder/opbuilder/base_op_builder.h +++ b/onnxruntime/core/providers/qnn/builder/opbuilder/base_op_builder.h @@ -163,6 +163,7 @@ class BaseOpBuilder : public IOpBuilder { {"Relu", QNN_OP_RELU}, {"Gelu", QNN_OP_GELU}, + {"HardSigmoid", QNN_OP_ELEMENT_WISE_NEURON}, {"HardSwish", QNN_OP_HARD_SWISH}, {"DepthToSpace", QNN_OP_DEPTH_TO_SPACE}, {"SpaceToDepth", QNN_OP_SPACE_TO_DEPTH}, diff --git a/onnxruntime/core/providers/qnn/builder/opbuilder/hard_sigmoid_op_builder.cc b/onnxruntime/core/providers/qnn/builder/opbuilder/hard_sigmoid_op_builder.cc deleted file mode 100644 index 664ba77cf0..0000000000 --- a/onnxruntime/core/providers/qnn/builder/opbuilder/hard_sigmoid_op_builder.cc +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -#include -#include -#include "core/framework/float16.h" -#include "core/providers/qnn/builder/opbuilder/base_op_builder.h" -#include "core/providers/qnn/builder/qnn_utils.h" -#include "core/providers/shared/utils/utils.h" -#include "core/providers/qnn/builder/qnn_model_wrapper.h" -#include "core/providers/qnn/builder/op_builder_factory.h" - -#include "QnnOpDef.h" -#include "QnnTypes.h" - -namespace onnxruntime { -namespace qnn { - -class HardSigmoidOpBuilder : public BaseOpBuilder { - public: - HardSigmoidOpBuilder() : BaseOpBuilder("HardSigmoidOpBuilder") {} - ORT_DISALLOW_COPY_ASSIGNMENT_AND_MOVE(HardSigmoidOpBuilder); - - Status IsOpSupported(QnnModelWrapper& qnn_model_wrapper, - const NodeUnit& node_unit, - const logging::Logger& logger) const override ORT_MUST_USE_RESULT; - - protected: - Status ProcessInputs(QnnModelWrapper& qnn_model_wrapper, const NodeUnit& node_unit, - const logging::Logger& logger, - std::vector& input_names, - bool do_op_validation = false) const override ORT_MUST_USE_RESULT; - - Status ProcessAttributesAndOutputs(QnnModelWrapper& qnn_model_wrapper, - const NodeUnit& node_unit, - std::vector&& input_names, - const logging::Logger& logger, - bool do_op_validation) const override ORT_MUST_USE_RESULT; - - private: - static const OnnxAttrInfo onnx_alpha_attr; - static const OnnxAttrInfo onnx_beta_attr; -}; - -const OnnxAttrInfo HardSigmoidOpBuilder::onnx_alpha_attr = {"alpha", 0.2f}; -const OnnxAttrInfo HardSigmoidOpBuilder::onnx_beta_attr = {"beta", 0.5}; - -// HardSigmoid is not natively supported by QNN. This builder must decompose HardSigmoid into -// HardSigmoid(X) = max(0, min(1, alpha*X + beta)). This is only valid for float (non-quantized) HardSigmoid ops -// because we don't compute internal quantization parameters (scale/zp) for any new nodes. -Status HardSigmoidOpBuilder::IsOpSupported(QnnModelWrapper& qnn_model_wrapper, - const NodeUnit& node_unit, - const logging::Logger& logger) const { - ORT_RETURN_IF_NOT(node_unit.UnitType() == NodeUnit::Type::SingleNode, - "QNN EP does not support quantized (QDQ) HardSigmoid"); - - const auto& inputs = node_unit.Inputs(); - ORT_RETURN_IF(inputs.size() != 1, "HardSigmoid operator must have 1 input."); - const auto& input = inputs[0]; - - int32_t onnx_data_type = 0; - ORT_RETURN_IF_ERROR(utils::GetOnnxTensorElemDataType(input.node_arg, onnx_data_type)); - - const bool is_float_type = (onnx_data_type == ONNX_NAMESPACE::TensorProto_DataType_FLOAT) || - (onnx_data_type == ONNX_NAMESPACE::TensorProto_DataType_FLOAT16); - ORT_RETURN_IF_NOT(is_float_type, "QNN EP only supports HardSigmoid with float/float16 inputs"); - - return AddToModelBuilder(qnn_model_wrapper, node_unit, logger, true); -} - -Status HardSigmoidOpBuilder::ProcessInputs(QnnModelWrapper& qnn_model_wrapper, - const NodeUnit& node_unit, - const logging::Logger& logger, - std::vector& input_names, - bool do_op_validation) const { - ORT_UNUSED_PARAMETER(do_op_validation); - const auto& inputs = node_unit.Inputs(); - - return ProcessInput(qnn_model_wrapper, inputs[0], logger, input_names); -} - -static Status GetFloatBytes(float f32_val, Qnn_DataType_t qnn_data_type, std::vector& bytes) { - switch (qnn_data_type) { - case QNN_DATATYPE_FLOAT_32: { - bytes.resize(sizeof(float)); - std::memcpy(bytes.data(), &f32_val, bytes.size()); - break; - } - case QNN_DATATYPE_FLOAT_16: { - bytes.resize(sizeof(MLFloat16)); - const MLFloat16 f16_val(f32_val); - std::memcpy(bytes.data(), &f16_val, bytes.size()); - break; - } - default: - return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Qnn Data Type: ", qnn_data_type, " is not supported"); - } - - return Status::OK(); -} - -Status HardSigmoidOpBuilder::ProcessAttributesAndOutputs(QnnModelWrapper& qnn_model_wrapper, - const NodeUnit& node_unit, - std::vector&& input_names, - const logging::Logger& logger, - bool do_op_validation) const { - ORT_UNUSED_PARAMETER(logger); - const auto& onnx_node_name = utils::GetNodeName(node_unit); - const auto& input = node_unit.Inputs()[0]; - const auto& output = node_unit.Outputs()[0]; - - std::vector input_shape; - ORT_RETURN_IF_NOT(qnn_model_wrapper.GetOnnxShape(input.node_arg, input_shape), "Cannot get shape of input 0"); - - Qnn_DataType_t qnn_data_type = QNN_DATATYPE_FLOAT_32; - ORT_RETURN_IF_ERROR(utils::GetQnnDataType(false /*is_quantized*/, input.node_arg.TypeAsProto(), qnn_data_type)); - - NodeAttrHelper node_helper(node_unit); - - // - // Create Mul node. - // - - std::string alpha_input_name = MakeString("ort_qnn_ep_", onnx_node_name, "_HardSigmoid_Mul_alpha"); - std::vector alpha_bytes; - ORT_RETURN_IF_ERROR(GetFloatBytes(GetOnnxAttr(node_helper, onnx_alpha_attr), qnn_data_type, alpha_bytes)); - - QnnTensorWrapper alpha_input(alpha_input_name, - QNN_TENSOR_TYPE_STATIC, - qnn_data_type, - QnnQuantParamsWrapper(), - {1}, // shape - std::move(alpha_bytes)); - ORT_RETURN_IF_NOT(qnn_model_wrapper.AddTensorWrapper(std::move(alpha_input)), "Failed to add alpha input tensor."); - - std::string mul_output_name = MakeString("ort_qnn_ep_", onnx_node_name, "_HardSigmoid_Mul_output"); - std::string mul_node_name = MakeString("ort_qnn_ep_", onnx_node_name, "_HardSigmoid_Mul_node"); - QnnTensorWrapper mul_output(mul_output_name, - QNN_TENSOR_TYPE_NATIVE, - qnn_data_type, - QnnQuantParamsWrapper(), - std::vector(input_shape)); - ORT_RETURN_IF_NOT(qnn_model_wrapper.AddTensorWrapper(std::move(mul_output)), "Failed to add Mul output tensor."); - ORT_RETURN_IF_NOT(qnn_model_wrapper.CreateQnnNode(mul_node_name, - QNN_OP_PACKAGE_NAME_QTI_AISW, - QNN_OP_ELEMENT_WISE_MULTIPLY, - {input_names[0], alpha_input_name}, // input names - {mul_output_name}, // output names - {}, - do_op_validation), - "Failed to add Mul node."); - - // - // Create Add node. - // - - std::string beta_input_name = MakeString("ort_qnn_ep_", onnx_node_name, "_HardSigmoid_Mul_beta"); - std::vector beta_bytes; - ORT_RETURN_IF_ERROR(GetFloatBytes(GetOnnxAttr(node_helper, onnx_beta_attr), qnn_data_type, beta_bytes)); - - QnnTensorWrapper beta_input(beta_input_name, - QNN_TENSOR_TYPE_STATIC, - qnn_data_type, - QnnQuantParamsWrapper(), - {1}, // shape - std::move(beta_bytes)); - ORT_RETURN_IF_NOT(qnn_model_wrapper.AddTensorWrapper(std::move(beta_input)), "Failed to add beta input tensor."); - - std::string add_output_name = MakeString("ort_qnn_ep_", onnx_node_name, "_HardSigmoid_Add_output"); - std::string add_node_name = MakeString("ort_qnn_ep_", onnx_node_name, "_HardSigmoid_Add_node"); - QnnTensorWrapper add_output(add_output_name, - QNN_TENSOR_TYPE_NATIVE, - qnn_data_type, - QnnQuantParamsWrapper(), - std::vector(input_shape)); - ORT_RETURN_IF_NOT(qnn_model_wrapper.AddTensorWrapper(std::move(add_output)), "Failed to add Add output tensor."); - ORT_RETURN_IF_NOT(qnn_model_wrapper.CreateQnnNode(add_node_name, - QNN_OP_PACKAGE_NAME_QTI_AISW, - QNN_OP_ELEMENT_WISE_ADD, - {mul_output_name, beta_input_name}, // input names - {add_output_name}, // output names - {}, - do_op_validation), - "Failed to add Add node."); - - // - // Create ReluMinMax node. - // - - std::vector param_tensor_names; - - // Parameter 'min_value' - { - Qnn_Scalar_t min_value = QNN_SCALAR_INIT; - min_value.dataType = QNN_DATATYPE_FLOAT_32; - min_value.floatValue = 0.0f; - - QnnParamWrapper qnn_param(node_unit.Index(), node_unit.Name(), QNN_OP_RELU_MIN_MAX_PARAM_MIN_VALUE, min_value); - param_tensor_names.push_back(qnn_param.GetParamTensorName()); - qnn_model_wrapper.AddParamWrapper(std::move(qnn_param)); - } - - // Parameter 'max_value' - { - Qnn_Scalar_t max_value = QNN_SCALAR_INIT; - max_value.dataType = QNN_DATATYPE_FLOAT_32; - max_value.floatValue = 1.0f; - - QnnParamWrapper qnn_param(node_unit.Index(), node_unit.Name(), QNN_OP_RELU_MIN_MAX_PARAM_MAX_VALUE, max_value); - param_tensor_names.push_back(qnn_param.GetParamTensorName()); - qnn_model_wrapper.AddParamWrapper(std::move(qnn_param)); - } - - const std::string& output_name = output.node_arg.Name(); - std::string relu_min_max_node_name = MakeString("ort_qnn_ep_", onnx_node_name, "_HardSigmoid_ReluMinMax_node"); - QnnTensorWrapper output_tensor(output_name, - qnn_model_wrapper.GetTensorType(output_name), - qnn_data_type, - QnnQuantParamsWrapper(), - std::vector(input_shape)); - ORT_RETURN_IF_NOT(qnn_model_wrapper.AddTensorWrapper(std::move(output_tensor)), "Failed to add output tensor."); - ORT_RETURN_IF_NOT(qnn_model_wrapper.CreateQnnNode(relu_min_max_node_name, - QNN_OP_PACKAGE_NAME_QTI_AISW, - QNN_OP_RELU_MIN_MAX, - {add_output_name}, // input names - {output_name}, // output names - std::move(param_tensor_names), - do_op_validation), - "Failed to add ReluMinMax node."); - - return Status::OK(); -} - -void CreateHardSigmoidOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) { - op_registrations.AddOpBuilder(op_type, std::make_unique()); -} - -} // namespace qnn -} // namespace onnxruntime diff --git a/onnxruntime/core/providers/qnn/builder/opbuilder/simple_op_builder.cc b/onnxruntime/core/providers/qnn/builder/opbuilder/simple_op_builder.cc index 185276d116..7e2d1ef05b 100644 --- a/onnxruntime/core/providers/qnn/builder/opbuilder/simple_op_builder.cc +++ b/onnxruntime/core/providers/qnn/builder/opbuilder/simple_op_builder.cc @@ -161,16 +161,20 @@ Status SimpleOpBuilder::ExplicitOpCheck(const NodeUnit& node_unit) const { return Status::OK(); } -Status ProcessAlphaAttribute(QnnModelWrapper& qnn_model_wrapper, - const NodeUnit& node_unit, - std::vector& param_tensor_names) { +// Limit to float type for now +Status ProcessNodeAttribute(QnnModelWrapper& qnn_model_wrapper, + const NodeUnit& node_unit, + const std::string& onnx_attr_key, + const std::string& qnn_param_key, + std::vector& param_tensor_names, + const float default_value = 1.0f) { NodeAttrHelper node_helper(node_unit); - float alpha = node_helper.Get("alpha", 1.0f); - Qnn_Scalar_t alpha_qnn_scalar = QNN_SCALAR_INIT; - alpha_qnn_scalar.dataType = QNN_DATATYPE_FLOAT_32; - alpha_qnn_scalar.floatValue = alpha; + float attr_value = node_helper.Get(onnx_attr_key, default_value); + Qnn_Scalar_t attr_qnn_scalar = QNN_SCALAR_INIT; + attr_qnn_scalar.dataType = QNN_DATATYPE_FLOAT_32; + attr_qnn_scalar.floatValue = attr_value; - QnnParamWrapper alpha_param(node_unit.Index(), node_unit.Name(), QNN_OP_ELU_PARAM_ALPHA, alpha_qnn_scalar); + QnnParamWrapper alpha_param(node_unit.Index(), node_unit.Name(), qnn_param_key, attr_qnn_scalar); param_tensor_names.push_back(alpha_param.GetParamTensorName()); qnn_model_wrapper.AddParamWrapper(std::move(alpha_param)); @@ -306,6 +310,159 @@ Status ProcessGridSampleAttributes(QnnModelWrapper& qnn_model_wrapper, return Status::OK(); } +static Status GetFloatBytes(float f32_val, Qnn_DataType_t qnn_data_type, std::vector& bytes) { + switch (qnn_data_type) { + case QNN_DATATYPE_FLOAT_32: { + bytes.resize(sizeof(float)); + std::memcpy(bytes.data(), &f32_val, bytes.size()); + break; + } + case QNN_DATATYPE_FLOAT_16: { + bytes.resize(sizeof(MLFloat16)); + const MLFloat16 f16_val(f32_val); + std::memcpy(bytes.data(), &f16_val, bytes.size()); + break; + } + default: + return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Qnn Data Type: ", qnn_data_type, " is not supported"); + } + + return Status::OK(); +} + +static Status DecomposeHardSigmoid(QnnModelWrapper& qnn_model_wrapper, + const NodeUnit& node_unit, + std::vector&& input_names, + const logging::Logger& logger, + bool do_op_validation) { + ORT_UNUSED_PARAMETER(logger); + const auto& onnx_node_name = utils::GetNodeName(node_unit); + const auto& input = node_unit.Inputs()[0]; + const auto& output = node_unit.Outputs()[0]; + + std::vector input_shape; + ORT_RETURN_IF_NOT(qnn_model_wrapper.GetOnnxShape(input.node_arg, input_shape), "Cannot get shape of input 0"); + + Qnn_DataType_t qnn_data_type = QNN_DATATYPE_FLOAT_32; + ORT_RETURN_IF_ERROR(utils::GetQnnDataType(false /*is_quantized*/, input.node_arg.TypeAsProto(), qnn_data_type)); + + NodeAttrHelper node_helper(node_unit); + + // + // Create Mul node. + // + const OnnxAttrInfo onnx_alpha_attr{"alpha", 0.2f}; + const OnnxAttrInfo onnx_beta_attr{"beta", 0.5}; + std::string alpha_input_name = MakeString("ort_qnn_ep_", onnx_node_name, "_HardSigmoid_Mul_alpha"); + std::vector alpha_bytes; + ORT_RETURN_IF_ERROR(GetFloatBytes(GetOnnxAttr(node_helper, onnx_alpha_attr), qnn_data_type, alpha_bytes)); + + QnnTensorWrapper alpha_input(alpha_input_name, + QNN_TENSOR_TYPE_STATIC, + qnn_data_type, + QnnQuantParamsWrapper(), + {1}, // shape + std::move(alpha_bytes)); + ORT_RETURN_IF_NOT(qnn_model_wrapper.AddTensorWrapper(std::move(alpha_input)), "Failed to add alpha input tensor."); + + std::string mul_output_name = MakeString("ort_qnn_ep_", onnx_node_name, "_HardSigmoid_Mul_output"); + std::string mul_node_name = MakeString("ort_qnn_ep_", onnx_node_name, "_HardSigmoid_Mul_node"); + QnnTensorWrapper mul_output(mul_output_name, + QNN_TENSOR_TYPE_NATIVE, + qnn_data_type, + QnnQuantParamsWrapper(), + std::vector(input_shape)); + ORT_RETURN_IF_NOT(qnn_model_wrapper.AddTensorWrapper(std::move(mul_output)), "Failed to add Mul output tensor."); + ORT_RETURN_IF_NOT(qnn_model_wrapper.CreateQnnNode(mul_node_name, + QNN_OP_PACKAGE_NAME_QTI_AISW, + QNN_OP_ELEMENT_WISE_MULTIPLY, + {input_names[0], alpha_input_name}, // input names + {mul_output_name}, // output names + {}, + do_op_validation), + "Failed to add Mul node."); + + // + // Create Add node. + // + + std::string beta_input_name = MakeString("ort_qnn_ep_", onnx_node_name, "_HardSigmoid_Mul_beta"); + std::vector beta_bytes; + ORT_RETURN_IF_ERROR(GetFloatBytes(GetOnnxAttr(node_helper, onnx_beta_attr), qnn_data_type, beta_bytes)); + + QnnTensorWrapper beta_input(beta_input_name, + QNN_TENSOR_TYPE_STATIC, + qnn_data_type, + QnnQuantParamsWrapper(), + {1}, // shape + std::move(beta_bytes)); + ORT_RETURN_IF_NOT(qnn_model_wrapper.AddTensorWrapper(std::move(beta_input)), "Failed to add beta input tensor."); + + std::string add_output_name = MakeString("ort_qnn_ep_", onnx_node_name, "_HardSigmoid_Add_output"); + std::string add_node_name = MakeString("ort_qnn_ep_", onnx_node_name, "_HardSigmoid_Add_node"); + QnnTensorWrapper add_output(add_output_name, + QNN_TENSOR_TYPE_NATIVE, + qnn_data_type, + QnnQuantParamsWrapper(), + std::vector(input_shape)); + ORT_RETURN_IF_NOT(qnn_model_wrapper.AddTensorWrapper(std::move(add_output)), "Failed to add Add output tensor."); + ORT_RETURN_IF_NOT(qnn_model_wrapper.CreateQnnNode(add_node_name, + QNN_OP_PACKAGE_NAME_QTI_AISW, + QNN_OP_ELEMENT_WISE_ADD, + {mul_output_name, beta_input_name}, // input names + {add_output_name}, // output names + {}, + do_op_validation), + "Failed to add Add node."); + + // + // Create ReluMinMax node. + // + + std::vector param_tensor_names; + + // Parameter 'min_value' + { + Qnn_Scalar_t min_value = QNN_SCALAR_INIT; + min_value.dataType = QNN_DATATYPE_FLOAT_32; + min_value.floatValue = 0.0f; + + QnnParamWrapper qnn_param(node_unit.Index(), node_unit.Name(), QNN_OP_RELU_MIN_MAX_PARAM_MIN_VALUE, min_value); + param_tensor_names.push_back(qnn_param.GetParamTensorName()); + qnn_model_wrapper.AddParamWrapper(std::move(qnn_param)); + } + + // Parameter 'max_value' + { + Qnn_Scalar_t max_value = QNN_SCALAR_INIT; + max_value.dataType = QNN_DATATYPE_FLOAT_32; + max_value.floatValue = 1.0f; + + QnnParamWrapper qnn_param(node_unit.Index(), node_unit.Name(), QNN_OP_RELU_MIN_MAX_PARAM_MAX_VALUE, max_value); + param_tensor_names.push_back(qnn_param.GetParamTensorName()); + qnn_model_wrapper.AddParamWrapper(std::move(qnn_param)); + } + + const std::string& output_name = output.node_arg.Name(); + std::string relu_min_max_node_name = MakeString("ort_qnn_ep_", onnx_node_name, "_HardSigmoid_ReluMinMax_node"); + QnnTensorWrapper output_tensor(output_name, + qnn_model_wrapper.GetTensorType(output_name), + qnn_data_type, + QnnQuantParamsWrapper(), + std::vector(input_shape)); + ORT_RETURN_IF_NOT(qnn_model_wrapper.AddTensorWrapper(std::move(output_tensor)), "Failed to add output tensor."); + ORT_RETURN_IF_NOT(qnn_model_wrapper.CreateQnnNode(relu_min_max_node_name, + QNN_OP_PACKAGE_NAME_QTI_AISW, + QNN_OP_RELU_MIN_MAX, + {add_output_name}, // input names + {output_name}, // output names + std::move(param_tensor_names), + do_op_validation), + "Failed to add ReluMinMax node."); + + return Status::OK(); +} + Status SimpleOpBuilder::ProcessAttributesAndOutputs(QnnModelWrapper& qnn_model_wrapper, const NodeUnit& node_unit, std::vector&& input_names, @@ -369,7 +526,34 @@ Status SimpleOpBuilder::ProcessAttributesAndOutputs(QnnModelWrapper& qnn_model_w } if (op_type == "Elu") { - ORT_RETURN_IF_ERROR(ProcessAlphaAttribute(qnn_model_wrapper, node_unit, param_tensor_names)); + ORT_RETURN_IF_ERROR(ProcessNodeAttribute(qnn_model_wrapper, node_unit, "alpha", + QNN_OP_ELU_PARAM_ALPHA, param_tensor_names)); + } + + if (op_type == "HardSigmoid") { + // direct conversion to ElementWiseNeuron has issue to finalize the graph for FP16 data type + // still decompose it to Mul, Add, ReluMinMax + int32_t onnx_data_type = 0; + ORT_RETURN_IF_ERROR(utils::GetOnnxTensorElemDataType(node_unit.Inputs()[0].node_arg, onnx_data_type)); + if (onnx_data_type == ONNX_NAMESPACE::TensorProto_DataType_FLOAT16) { + return DecomposeHardSigmoid(qnn_model_wrapper, node_unit, std::move(input_names), logger, do_op_validation); + } + + ORT_RETURN_IF_ERROR(ProcessNodeAttribute(qnn_model_wrapper, node_unit, "alpha", + QNN_OP_ELEMENT_WISE_NEURON_PARAM_ALPHA, + param_tensor_names, 0.2f)); + ORT_RETURN_IF_ERROR(ProcessNodeAttribute(qnn_model_wrapper, node_unit, "beta", + QNN_OP_ELEMENT_WISE_NEURON_PARAM_BETA, + param_tensor_names, 0.5f)); + Qnn_Scalar_t neuron_operation = QNN_SCALAR_INIT; + neuron_operation.dataType = QNN_DATATYPE_UINT_32; + neuron_operation.uint32Value = QNN_OP_ELEMENT_WISE_NEURON_OPERATION_HARD_SIGMOID; + + QnnParamWrapper operation_param(node_unit.Index(), node_unit.Name(), + QNN_OP_ELEMENT_WISE_NEURON_PARAM_OPERATION, + neuron_operation); + param_tensor_names.push_back(operation_param.GetParamTensorName()); + qnn_model_wrapper.AddParamWrapper(std::move(operation_param)); } if (op_type == "DepthToSpace") { diff --git a/onnxruntime/test/providers/qnn/simple_op_htp_test.cc b/onnxruntime/test/providers/qnn/simple_op_htp_test.cc index 9b055b382c..f7dc5779ec 100644 --- a/onnxruntime/test/providers/qnn/simple_op_htp_test.cc +++ b/onnxruntime/test/providers/qnn/simple_op_htp_test.cc @@ -323,11 +323,6 @@ TEST_F(QnnHTPBackendTests, UnaryOp_HardSwish) { } // Tests accuracy of 16-bit QDQ HardSwish -// TODO(adrianlizarraga): Inaccuracy detected for output 'output', element 5. -// Output quant params: scale=0.00015259021893143654, zero_point=0. -// Expected val: 10 -// QNN QDQ val: 9.999237060546875 (err 0.000762939453125) -// CPU QDQ val: 9.999847412109375 (err 0.000152587890625) TEST_F(QnnHTPBackendTests, UnaryOp_HardSwish_U16) { const std::vector input_data = {-10.0f, -8.4f, 0.0f, 4.3f, 7.1f, 10.0f}; RunQDQOpTest("HardSwish", @@ -1211,16 +1206,33 @@ TEST_F(QnnHTPBackendTests, Add_U8_U16_Convert) { ExpectedEPNodeAssignment::All); } -// Test that QDQ HardSigmoid is *not* supported by QNN EP. -TEST_F(QnnHTPBackendTests, UnaryOp_HardSigmoid_QDQ_NotSupported) { +TEST_F(QnnHTPBackendTests, UnaryOp_HardSigmoid_QU8) { + RunQDQOpTest("HardSigmoid", + {TestInputDef({1, 2, 3}, false, GetFloatDataInRange(-10.0f, 10.0f, 6))}, + {utils::MakeAttribute("alpha", 0.1f), + utils::MakeAttribute("beta", 0.4f)}, + 21, + ExpectedEPNodeAssignment::All); +} + +TEST_F(QnnHTPBackendTests, UnaryOp_HardSigmoid_QU16) { + RunQDQOpTest("HardSigmoid", + {TestInputDef({1, 2, 3}, false, GetFloatDataInRange(-10.0f, 10.0f, 6))}, + {}, + 21, + ExpectedEPNodeAssignment::All); +} + +// Test that QDQ HardSigmoid is supported by QNN EP. +TEST_F(QnnHTPBackendTests, UnaryOp_HardSigmoid_QDQ_Supported) { RunQDQOpTest("HardSigmoid", {TestInputDef({1, 2, 2, 2}, false, -10.0f, 10.0f)}, {}, 19, - ExpectedEPNodeAssignment::None); // Not assigned to QNN EP + ExpectedEPNodeAssignment::All); } -// Check that QNN EP can support float32 HardSigmoid on HTP by decomposing to its constituent ops. +// Check that QNN EP can support float32 HardSigmoid on HTP. // Enables running f32 ops using fp16 precision. TEST_F(QnnHTPBackendTests, UnaryOp_HardSigmoid_F32_as_FP16) { std::vector input_data = GetFloatDataInRange(-5.0f, 5.0f, 16); @@ -1246,7 +1258,15 @@ TEST_F(QnnHTPBackendTests, UnaryOp_HardSigmoid_F32_as_FP16) { true); // enable_htp_fp16_precision } -// Check that QNN EP can support float16 HardSigmoid on HTP by decomposing to its constituent ops. +// Check that QNN EP can support float16 HardSigmoid on HTP +// It is using decompose way for FP16 since ElementWiseNeuron failed to finalize the graph with the error below: +// \HTP\src\hexagon\prepare\tcm_migration.cc:1829:ERROR:no properties registered for q::QNN_HardSigmoid +// \HTP\HTP\src\hexagon\prepare\graph_prepare.cc:203:ERROR:could not create op: q::QNN_HardSigmoid +// \HTP\HTP\src\hexagon\prepare\graph_prepare.cc:1238:ERROR:Op 0x101000000010 preparation failed with err:-1 +// Completed stage: Graph Transformations and Optimizations (16361 us) +// QnnDsp "node" generated: could not create op +// QnnDsp RouterWindows graph prepare failed 12 +// QnnDsp Failed to finalize graph (id: 1) with err 1002 TEST_F(QnnHTPBackendTests, UnaryOp_HardSigmoid_FP16) { std::vector input_data = GetFloatDataInRange(-5.0f, 5.0f, 16);