mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-07-29 20:14:01 +00:00
[QNN EP] Enable Pad op support for QNN EP (#17508)
### Description Enable Pad op support for QNN EP to support more models
This commit is contained in:
parent
198d468849
commit
46fe08226f
11 changed files with 651 additions and 4 deletions
|
|
@ -330,6 +330,31 @@ bool WhereNodeGroupSelector::Check(const GraphViewer& graph_viewer, const Node&
|
|||
dt_input_1 == dt_output;
|
||||
}
|
||||
|
||||
bool PadNodeGroupSelector::Check(const GraphViewer& graph_viewer, const Node& node,
|
||||
const std::vector<const Node*>& dq_nodes,
|
||||
const std::vector<const Node*>& q_nodes) const {
|
||||
// Pad can have 1 or 2 dq input, the optional input constant_value can be quantized or non-quantized.
|
||||
// QNN supports data input quantized with constant_value input non-quantized.
|
||||
int num_dq_inputs = static_cast<int>(dq_nodes.size());
|
||||
if (num_dq_inputs > 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckQDQNodes(graph_viewer, node, dq_nodes, q_nodes, num_dq_inputs)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32_t dt_input_1 = dq_nodes[0]->InputDefs()[0]->TypeAsProto()->tensor_type().elem_type();
|
||||
const int32_t dt_output = q_nodes[0]->OutputDefs()[0]->TypeAsProto()->tensor_type().elem_type();
|
||||
if (dq_nodes.size() > 1) {
|
||||
const int32_t dt_input_2 = dq_nodes[1]->InputDefs()[0]->TypeAsProto()->tensor_type().elem_type();
|
||||
return dt_input_1 == dt_input_2 &&
|
||||
dt_input_1 == dt_output;
|
||||
} else {
|
||||
return dt_input_1 == dt_output;
|
||||
}
|
||||
}
|
||||
|
||||
bool InstanceAndLayerNormalizationNodeGroupSelector::Check(const GraphViewer& graph_viewer,
|
||||
const Node& node,
|
||||
const std::vector<const Node*>& dq_nodes,
|
||||
|
|
|
|||
|
|
@ -110,6 +110,16 @@ class WhereNodeGroupSelector : public NodeGroupSelector {
|
|||
const std::vector<const Node*>& q_nodes) const override;
|
||||
};
|
||||
|
||||
class PadNodeGroupSelector : public NodeGroupSelector {
|
||||
public:
|
||||
PadNodeGroupSelector() = default;
|
||||
|
||||
private:
|
||||
bool Check(const GraphViewer& graph_viewer, const Node& node,
|
||||
const std::vector<const Node*>& dq_nodes,
|
||||
const std::vector<const Node*>& q_nodes) const override;
|
||||
};
|
||||
|
||||
// 2 DQ nodes for input -> node -> optional Q if QLinearMatMul, MatMulIntegerToFloat if not
|
||||
// The lack of a trailing Q isn't really a QDQ node group, so we default support for that to off.
|
||||
class MatMulNodeGroupSelector : public NodeGroupSelector {
|
||||
|
|
|
|||
|
|
@ -123,6 +123,9 @@ static const OpVersionsAndSelector::OpVersionsMap GetLogicalComparisonOpVersions
|
|||
static const OpVersionsAndSelector::OpVersionsMap GetWhereOpVersionsMap() {
|
||||
return {{"Where", {}}};
|
||||
}
|
||||
static const OpVersionsAndSelector::OpVersionsMap GetPadOpVersionsMap() {
|
||||
return {{"Pad", {}}};
|
||||
}
|
||||
|
||||
/* Selector rules registration related */
|
||||
void RegisterMiscSelectors(Selectors& qdq_selectors) {
|
||||
|
|
@ -217,6 +220,13 @@ void RegisterWhereSelectors(Selectors& qdq_selectors) {
|
|||
std::move(selector));
|
||||
}
|
||||
|
||||
void RegisterPadSelectors(Selectors& qdq_selectors) {
|
||||
/* register selectors for Pad ops */
|
||||
std::unique_ptr<NodeGroupSelector> selector = std::make_unique<PadNodeGroupSelector>();
|
||||
qdq_selectors.RegisterSelector(GetPadOpVersionsMap(),
|
||||
std::move(selector));
|
||||
}
|
||||
|
||||
void SelectorManager::CreateSelectors() {
|
||||
RegisterMiscSelectors(qdq_selectors_);
|
||||
RegisterDropDQSelectors(qdq_selectors_);
|
||||
|
|
@ -231,6 +241,7 @@ void SelectorManager::CreateSelectors() {
|
|||
RegisterBatchNormalizationSelector(qdq_selectors_);
|
||||
RegisterLogicalComparisonSelectors(qdq_selectors_);
|
||||
RegisterWhereSelectors(qdq_selectors_);
|
||||
RegisterPadSelectors(qdq_selectors_);
|
||||
}
|
||||
|
||||
void SelectorManager::InitializeSelectorsMap() {
|
||||
|
|
|
|||
|
|
@ -154,6 +154,10 @@ OpBuilderRegistrations::OpBuilderRegistrations() {
|
|||
{
|
||||
CreateTransposeOpBuilder("Transpose", *this);
|
||||
}
|
||||
|
||||
{
|
||||
CreatePadOpBuilder("Pad", *this);
|
||||
}
|
||||
}
|
||||
|
||||
const IOpBuilder* GetOpBuilder(const std::string& onnx_op_type) {
|
||||
|
|
|
|||
|
|
@ -88,5 +88,7 @@ void CreateLRNOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_r
|
|||
|
||||
void CreateTransposeOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
|
||||
|
||||
void CreatePadOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
|
||||
|
||||
} // namespace qnn
|
||||
} // namespace onnxruntime
|
||||
|
|
|
|||
|
|
@ -162,7 +162,9 @@ class BaseOpBuilder : public IOpBuilder {
|
|||
{"BatchNormalization", QNN_OP_BATCHNORM},
|
||||
{"LayerNormalization", QNN_OP_LAYER_NORM},
|
||||
|
||||
{"LRN", QNN_OP_LRN}};
|
||||
{"LRN", QNN_OP_LRN},
|
||||
|
||||
{"Pad", QNN_OP_PAD}};
|
||||
auto it = onnx_op_type_to_qnn_op_type.find(onnx_op_type);
|
||||
ORT_ENFORCE(it != onnx_op_type_to_qnn_op_type.end());
|
||||
return it->second;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
namespace onnxruntime {
|
||||
namespace qnn {
|
||||
|
||||
// Operator which only need to hanle node inputs & outputs, no attributes or no need to handle attributes
|
||||
class GatherOpBuilder : public BaseOpBuilder {
|
||||
public:
|
||||
GatherOpBuilder() : BaseOpBuilder("GatherOpBuilder") {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,247 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "core/providers/common.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 "core/providers/cpu/tensor/slice_helper.h"
|
||||
#include "core/providers/qnn/builder/op_builder_factory.h"
|
||||
#include "core/common/safeint.h"
|
||||
|
||||
#include "core/providers/qnn/builder/opbuilder/base_op_builder.h"
|
||||
|
||||
namespace onnxruntime {
|
||||
namespace qnn {
|
||||
class PadOpBuilder : public BaseOpBuilder {
|
||||
public:
|
||||
PadOpBuilder() : BaseOpBuilder("PadOpBuilder") {}
|
||||
ORT_DISALLOW_COPY_ASSIGNMENT_AND_MOVE(PadOpBuilder);
|
||||
|
||||
protected:
|
||||
Status ProcessInputs(QnnModelWrapper& qnn_model_wrapper,
|
||||
const NodeUnit& node_unit,
|
||||
const logging::Logger& logger,
|
||||
std::vector<std::string>& input_names,
|
||||
bool do_op_validation) const override ORT_MUST_USE_RESULT;
|
||||
|
||||
Status ProcessAttributesAndOutputs(QnnModelWrapper& qnn_model_wrapper,
|
||||
const NodeUnit& node_unit,
|
||||
std::vector<std::string>&& input_names,
|
||||
const logging::Logger& logger,
|
||||
bool do_op_validation) const override ORT_MUST_USE_RESULT;
|
||||
};
|
||||
|
||||
Status PadOpBuilder::ProcessInputs(QnnModelWrapper& qnn_model_wrapper,
|
||||
const NodeUnit& node_unit,
|
||||
const logging::Logger& logger,
|
||||
std::vector<std::string>& input_names,
|
||||
bool do_op_validation) const {
|
||||
const auto& inputs = node_unit.Inputs();
|
||||
// QNN Pad only has 1 input, the pads input & constant_value input need to be initializer and set as Qnn node parameter, axes input is not supported.
|
||||
if (do_op_validation) {
|
||||
ORT_RETURN_IF(inputs.size() > 3, "QNN Pad doesn't support axes.");
|
||||
ORT_RETURN_IF(inputs.size() < 2, "QNN Pad requires the pads input.");
|
||||
|
||||
std::vector<uint32_t> input_shape;
|
||||
ORT_RETURN_IF_NOT(qnn_model_wrapper.GetOnnxShape(inputs[0].node_arg, input_shape), "Cannot get shape of input 0.");
|
||||
ORT_RETURN_IF(input_shape.size() > 5, "QNN Pad doesn't support more than 5 dimension");
|
||||
|
||||
auto& pads_input_name = inputs[1].node_arg.Name();
|
||||
ORT_RETURN_IF_NOT(qnn_model_wrapper.IsInitializerInput(pads_input_name),
|
||||
"Qnn doesn't support dynamic pad input");
|
||||
if (node_unit.Inputs().size() > 2) {
|
||||
auto& constant_value_input_name = inputs[2].node_arg.Name();
|
||||
ORT_RETURN_IF_NOT(qnn_model_wrapper.IsInitializerInput(constant_value_input_name),
|
||||
"Qnn doesn't support dynamic constant_value input");
|
||||
}
|
||||
}
|
||||
|
||||
ORT_RETURN_IF_ERROR(ProcessInput(qnn_model_wrapper, inputs[0], logger, input_names));
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
float DequantizeValue(T value, int32_t offset, float scale) {
|
||||
return static_cast<float>(static_cast<int32_t>(value) - offset) * scale;
|
||||
}
|
||||
|
||||
Status ProcessConstantValue(QnnModelWrapper& qnn_model_wrapper,
|
||||
std::vector<std::string>& param_tensor_names,
|
||||
const NodeUnit& node_unit,
|
||||
const NodeUnitIODef& input) {
|
||||
OnnxInputInfo input_info = {};
|
||||
ORT_RETURN_IF_ERROR(qnn_model_wrapper.GetOnnxInputInfo(input, input_info));
|
||||
std::vector<uint8_t> unpacked_tensor;
|
||||
// Already confirmed constant_value input is initializer in ProcessInputs()
|
||||
ORT_RETURN_IF_ERROR(qnn_model_wrapper.UnpackInitializerData(*input_info.initializer_tensor, unpacked_tensor));
|
||||
Qnn_Scalar_t constant_value_qnn_scalar = QNN_SCALAR_INIT;
|
||||
// constant_value is quantized
|
||||
if (input.quant_param.has_value()) {
|
||||
// QNN prefers pad_constant_value quantized with quantization params same as in[0], and data stored as 32-bit signed integer
|
||||
// Onnx doesn't guarantee it has same quantization parameter as in[0], so get back the float32 value and use non-quantized data directly
|
||||
constant_value_qnn_scalar.dataType = QNN_DATATYPE_FLOAT_32;
|
||||
float constant_value = 0;
|
||||
switch (input_info.qnn_data_type) {
|
||||
case QNN_DATATYPE_SFIXED_POINT_8: {
|
||||
auto int8_span = ReinterpretAsSpan<const int8_t>(gsl::make_span(unpacked_tensor));
|
||||
constant_value = DequantizeValue(int8_span.data()[0],
|
||||
input_info.quant_param.scaleOffsetEncoding.offset,
|
||||
input_info.quant_param.scaleOffsetEncoding.scale);
|
||||
break;
|
||||
}
|
||||
case QNN_DATATYPE_SFIXED_POINT_16: {
|
||||
auto int16_span = ReinterpretAsSpan<const int16_t>(gsl::make_span(unpacked_tensor));
|
||||
constant_value = DequantizeValue(int16_span.data()[0],
|
||||
input_info.quant_param.scaleOffsetEncoding.offset,
|
||||
input_info.quant_param.scaleOffsetEncoding.scale);
|
||||
break;
|
||||
}
|
||||
case QNN_DATATYPE_SFIXED_POINT_32: {
|
||||
auto int32_span = ReinterpretAsSpan<const int32_t>(gsl::make_span(unpacked_tensor));
|
||||
constant_value = DequantizeValue(int32_span.data()[0],
|
||||
input_info.quant_param.scaleOffsetEncoding.offset,
|
||||
input_info.quant_param.scaleOffsetEncoding.scale);
|
||||
break;
|
||||
}
|
||||
case QNN_DATATYPE_UFIXED_POINT_8: {
|
||||
constant_value = DequantizeValue(unpacked_tensor.data()[0],
|
||||
input_info.quant_param.scaleOffsetEncoding.offset,
|
||||
input_info.quant_param.scaleOffsetEncoding.scale);
|
||||
break;
|
||||
}
|
||||
case QNN_DATATYPE_UFIXED_POINT_16: {
|
||||
auto uint16_span = ReinterpretAsSpan<const uint16_t>(gsl::make_span(unpacked_tensor));
|
||||
constant_value = DequantizeValue(uint16_span.data()[0],
|
||||
input_info.quant_param.scaleOffsetEncoding.offset,
|
||||
input_info.quant_param.scaleOffsetEncoding.scale);
|
||||
break;
|
||||
}
|
||||
case QNN_DATATYPE_UFIXED_POINT_32: {
|
||||
auto uint32_span = ReinterpretAsSpan<const uint32_t>(gsl::make_span(unpacked_tensor));
|
||||
constant_value = DequantizeValue(uint32_span.data()[0],
|
||||
input_info.quant_param.scaleOffsetEncoding.offset,
|
||||
input_info.quant_param.scaleOffsetEncoding.scale);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Type not supported for Pad constant_value.");
|
||||
}
|
||||
constant_value_qnn_scalar.floatValue = constant_value;
|
||||
} else { // constant_value is non-quantized
|
||||
constant_value_qnn_scalar.dataType = input_info.qnn_data_type;
|
||||
switch (input_info.qnn_data_type) {
|
||||
case QNN_DATATYPE_UINT_8: {
|
||||
constant_value_qnn_scalar.uint8Value = unpacked_tensor.data()[0];
|
||||
break;
|
||||
}
|
||||
case QNN_DATATYPE_INT_8: {
|
||||
auto int8_span = ReinterpretAsSpan<const int8_t>(gsl::make_span(unpacked_tensor));
|
||||
constant_value_qnn_scalar.int8Value = int8_span.data()[0];
|
||||
break;
|
||||
}
|
||||
case QNN_DATATYPE_INT_16: {
|
||||
auto int16_span = ReinterpretAsSpan<const int16_t>(gsl::make_span(unpacked_tensor));
|
||||
constant_value_qnn_scalar.int16Value = int16_span.data()[0];
|
||||
break;
|
||||
}
|
||||
case QNN_DATATYPE_INT_32: {
|
||||
auto int32_span = ReinterpretAsSpan<const int32_t>(gsl::make_span(unpacked_tensor));
|
||||
constant_value_qnn_scalar.int32Value = int32_span.data()[0];
|
||||
break;
|
||||
}
|
||||
case QNN_DATATYPE_INT_64: {
|
||||
auto int64_span = ReinterpretAsSpan<const int64_t>(gsl::make_span(unpacked_tensor));
|
||||
constant_value_qnn_scalar.int64Value = int64_span.data()[0];
|
||||
break;
|
||||
}
|
||||
case QNN_DATATYPE_FLOAT_32: {
|
||||
auto float_span = ReinterpretAsSpan<const float>(gsl::make_span(unpacked_tensor));
|
||||
constant_value_qnn_scalar.floatValue = float_span.data()[0];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Type not supported.");
|
||||
} // switch
|
||||
} // if-else
|
||||
|
||||
QnnParamWrapper constant_value_param(node_unit.Index(),
|
||||
node_unit.Name(),
|
||||
QNN_OP_PAD_PARAM_PAD_CONSTANT_VALUE,
|
||||
constant_value_qnn_scalar);
|
||||
param_tensor_names.push_back(constant_value_param.GetParamTensorName());
|
||||
qnn_model_wrapper.AddParamWrapper(std::move(constant_value_param));
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status PadOpBuilder::ProcessAttributesAndOutputs(QnnModelWrapper& qnn_model_wrapper,
|
||||
const NodeUnit& node_unit,
|
||||
std::vector<std::string>&& input_names,
|
||||
const logging::Logger& logger,
|
||||
bool do_op_validation) const {
|
||||
std::vector<std::string> param_tensor_names;
|
||||
// Process pads input
|
||||
// Already confirmed pads input is initializer in ProcessInputs()
|
||||
const auto& inputs = node_unit.Inputs();
|
||||
const auto& pads_input_name = inputs[1].node_arg.Name();
|
||||
|
||||
std::vector<uint8_t> unpacked_tensor;
|
||||
const auto& input_tensor = qnn_model_wrapper.GetInitializerTensors().at(pads_input_name);
|
||||
ORT_RETURN_IF_ERROR(qnn_model_wrapper.UnpackInitializerData(*input_tensor, unpacked_tensor));
|
||||
// Onnx Pads are int64, Qnn use uint32
|
||||
const int64_t* tensor_data = reinterpret_cast<const int64_t*>(unpacked_tensor.data());
|
||||
size_t tensor_byte_size = unpacked_tensor.size();
|
||||
size_t size = tensor_byte_size / sizeof(int64_t);
|
||||
|
||||
std::vector<uint32_t> pad_amount;
|
||||
std::transform(tensor_data, tensor_data + size, std::back_inserter(pad_amount),
|
||||
[](int64_t item) { return SafeInt<uint32_t>(item); });
|
||||
// Onnx format is begin_0, begin_1, ..., end_0, end_1, ...
|
||||
// Qnn format is begin_0, end_0, begin_1, end_1, ...
|
||||
ReArranagePads(pad_amount);
|
||||
|
||||
std::vector<uint32_t> pad_amount_dim{static_cast<uint32_t>(pad_amount.size() / 2), static_cast<uint32_t>(2)};
|
||||
QnnParamWrapper multiples_param(node_unit.Index(), node_unit.Name(), QNN_OP_PAD_PARAM_PAD_AMOUNT, std::move(pad_amount_dim),
|
||||
std::move(pad_amount));
|
||||
param_tensor_names.push_back(multiples_param.GetParamTensorName());
|
||||
qnn_model_wrapper.AddParamWrapper(std::move(multiples_param));
|
||||
|
||||
// Process optional input constant_value
|
||||
if (node_unit.Inputs().size() > 2) {
|
||||
ORT_RETURN_IF_ERROR(ProcessConstantValue(qnn_model_wrapper, param_tensor_names, node_unit, inputs[2]));
|
||||
} // constant_value
|
||||
|
||||
NodeAttrHelper node_helper(node_unit);
|
||||
std::string mode = node_helper.Get("mode", "constant");
|
||||
Qnn_Scalar_t mode_qnn_scalar = QNN_SCALAR_INIT;
|
||||
mode_qnn_scalar.dataType = QNN_DATATYPE_UINT_32;
|
||||
if ("constant" == mode) {
|
||||
mode_qnn_scalar.uint32Value = QNN_OP_PAD_SCHEME_CONSTANT;
|
||||
} else if ("reflect" == mode) {
|
||||
mode_qnn_scalar.uint32Value = QNN_OP_PAD_SCHEME_MIRROR_REFLECT;
|
||||
} else if ("edge" == mode) {
|
||||
mode_qnn_scalar.uint32Value = QNN_OP_PAD_SCHEME_EDGE;
|
||||
} else {
|
||||
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Pad mode only support constant.");
|
||||
}
|
||||
|
||||
QnnParamWrapper mode_param(node_unit.Index(), node_unit.Name(), QNN_OP_PAD_PARAM_SCHEME, mode_qnn_scalar);
|
||||
param_tensor_names.push_back(mode_param.GetParamTensorName());
|
||||
qnn_model_wrapper.AddParamWrapper(std::move(mode_param));
|
||||
|
||||
ORT_RETURN_IF_ERROR(ProcessOutputs(qnn_model_wrapper, node_unit,
|
||||
std::move(input_names),
|
||||
std::move(param_tensor_names),
|
||||
logger, do_op_validation, GetQnnOpType(node_unit.OpType())));
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void CreatePadOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) {
|
||||
op_registrations.AddOpBuilder(op_type, std::make_unique<PadOpBuilder>());
|
||||
}
|
||||
|
||||
} // namespace qnn
|
||||
} // namespace onnxruntime
|
||||
|
|
@ -118,9 +118,9 @@ Status ProcessModeAttribute(QnnModelWrapper& qnn_model_wrapper,
|
|||
Qnn_Scalar_t mode_qnn_scalar = QNN_SCALAR_INIT;
|
||||
mode_qnn_scalar.dataType = QNN_DATATYPE_UINT_32;
|
||||
if ("DCR" == mode) {
|
||||
mode_qnn_scalar.uint32Value = 0;
|
||||
mode_qnn_scalar.uint32Value = QNN_OP_DEPTH_TO_SPACE_MODE_DCR;
|
||||
} else if ("CRD" == mode) {
|
||||
mode_qnn_scalar.uint32Value = 1; // CRD mode
|
||||
mode_qnn_scalar.uint32Value = QNN_OP_DEPTH_TO_SPACE_MODE_CRD; // CRD mode
|
||||
} else {
|
||||
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "DepthToSpace mode only support DCR & CRD.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ class QnnModelWrapper {
|
|||
return input_index_map_.find(tensor_name) != input_index_map_.end();
|
||||
}
|
||||
|
||||
// TODO(hecli) rename to GetTensorInfo
|
||||
Status GetOnnxInputInfo(const NodeUnitIODef& input, OnnxInputInfo& input_info) const;
|
||||
|
||||
Status AddReshapeNode(const std::string& input_name,
|
||||
|
|
|
|||
346
onnxruntime/test/providers/qnn/pad_op_test.cpp
Normal file
346
onnxruntime/test/providers/qnn/pad_op_test.cpp
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#if !defined(ORT_MINIMAL_BUILD)
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "core/graph/node_attr_utils.h"
|
||||
#include "test/optimizer/qdq_test_utils.h"
|
||||
#include "test/providers/qnn/qnn_test_utils.h"
|
||||
|
||||
#include "onnx/onnx_pb.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace onnxruntime {
|
||||
namespace test {
|
||||
|
||||
// Returns a function that creates a graph with a single Pad operator.
|
||||
static GetTestModelFn BuildPadTestCase(const TestInputDef<float>& data_def,
|
||||
const TestInputDef<int64_t>& pads_def,
|
||||
const TestInputDef<float>& constant_value_def,
|
||||
const std::vector<ONNX_NAMESPACE::AttributeProto>& attrs,
|
||||
bool has_constant_value = true) {
|
||||
return [data_def, pads_def, constant_value_def, attrs, has_constant_value](ModelTestBuilder& builder) {
|
||||
NodeArg* data = MakeTestInput(builder, data_def);
|
||||
NodeArg* pads = MakeTestInput(builder, pads_def);
|
||||
std::vector<NodeArg*> inputs{data, pads};
|
||||
if (has_constant_value) {
|
||||
NodeArg* constant_value = MakeTestInput(builder, constant_value_def);
|
||||
inputs.push_back(constant_value);
|
||||
}
|
||||
NodeArg* output = builder.MakeOutput();
|
||||
Node& pad_node = builder.AddNode("Pad", inputs, {output});
|
||||
|
||||
for (const auto& attr : attrs) {
|
||||
pad_node.AddAttributeProto(attr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Returns a function that creates a graph with a QDQ Pad operator.
|
||||
template <typename QuantType>
|
||||
GetTestQDQModelFn<QuantType> BuildPadQDQTestCase(const TestInputDef<float>& data_def,
|
||||
const TestInputDef<int64_t>& pads_def,
|
||||
const TestInputDef<float>& constant_value_def,
|
||||
const std::vector<ONNX_NAMESPACE::AttributeProto>& attrs,
|
||||
bool has_constant_value,
|
||||
bool constant_value_quantized) {
|
||||
return [data_def, pads_def, constant_value_def, attrs, has_constant_value, constant_value_quantized](ModelTestBuilder& builder,
|
||||
std::vector<QuantParams<QuantType>>& output_qparams) {
|
||||
std::vector<NodeArg*> inputs;
|
||||
// data -> Q -> DQ ->
|
||||
NodeArg* data = MakeTestInput(builder, data_def);
|
||||
QuantParams<QuantType> data_qparams = GetTestInputQuantParams<QuantType>(data_def);
|
||||
NodeArg* data_qdq = AddQDQNodePair<QuantType>(builder, data, data_qparams.scale, data_qparams.zero_point);
|
||||
inputs.push_back(data_qdq);
|
||||
|
||||
// pads
|
||||
NodeArg* pads = MakeTestInput(builder, pads_def);
|
||||
inputs.push_back(pads);
|
||||
|
||||
// constant_value -- QNN support both quantized and non-quantized
|
||||
if (has_constant_value) {
|
||||
if (constant_value_quantized) {
|
||||
// constant_value -> Q -> DQ ->
|
||||
NodeArg* constant_value = MakeTestInput(builder, constant_value_def);
|
||||
QuantParams<QuantType> constant_value_qparams = GetTestInputQuantParams<QuantType>(constant_value_def);
|
||||
NodeArg* constant_value_qdq = AddQDQNodePair<QuantType>(builder, constant_value,
|
||||
constant_value_qparams.scale,
|
||||
constant_value_qparams.zero_point);
|
||||
inputs.push_back(constant_value_qdq);
|
||||
} else {
|
||||
NodeArg* constant_value = MakeTestInput(builder, constant_value_def);
|
||||
inputs.push_back(constant_value);
|
||||
}
|
||||
}
|
||||
|
||||
NodeArg* output = builder.MakeIntermediate();
|
||||
Node& pad_node = builder.AddNode("Pad", inputs, {output});
|
||||
|
||||
for (const auto& attr : attrs) {
|
||||
pad_node.AddAttributeProto(attr);
|
||||
}
|
||||
|
||||
// op_output -> Q -> DQ -> output
|
||||
AddQDQNodePairWithOutputAsGraphOutput<QuantType>(builder, output, output_qparams[0].scale,
|
||||
output_qparams[0].zero_point);
|
||||
};
|
||||
}
|
||||
|
||||
// Runs an Pad model on the QNN CPU backend. Checks the graph node assignment, and that inference
|
||||
// outputs for QNN and CPU match.
|
||||
static void RunPadOpTest(const TestInputDef<float>& data_def,
|
||||
const TestInputDef<int64_t>& pads_def,
|
||||
const TestInputDef<float>& constant_value_def,
|
||||
const std::vector<ONNX_NAMESPACE::AttributeProto>& attrs,
|
||||
ExpectedEPNodeAssignment expected_ep_assignment,
|
||||
bool has_constant_value = true,
|
||||
int opset = 18) {
|
||||
ProviderOptions provider_options;
|
||||
#if defined(_WIN32)
|
||||
provider_options["backend_path"] = "QnnCpu.dll";
|
||||
#else
|
||||
provider_options["backend_path"] = "libQnnCpu.so";
|
||||
#endif
|
||||
|
||||
RunQnnModelTest(BuildPadTestCase(data_def, pads_def, constant_value_def, attrs, has_constant_value),
|
||||
provider_options,
|
||||
opset,
|
||||
expected_ep_assignment);
|
||||
}
|
||||
|
||||
// Runs a QDQ Pad model on the QNN HTP backend. Checks the graph node assignment, and that inference
|
||||
// outputs for QNN and CPU match.
|
||||
template <typename QuantType>
|
||||
static void RunQDQPadOpTest(const TestInputDef<float>& data_def,
|
||||
const TestInputDef<int64_t>& pads_def,
|
||||
const TestInputDef<float>& constant_value_def,
|
||||
const std::vector<ONNX_NAMESPACE::AttributeProto>& attrs,
|
||||
ExpectedEPNodeAssignment expected_ep_assignment,
|
||||
bool has_constant_value = true,
|
||||
bool constant_value_quantized = true,
|
||||
int opset = 18) {
|
||||
ProviderOptions provider_options;
|
||||
#if defined(_WIN32)
|
||||
provider_options["backend_path"] = "QnnHtp.dll";
|
||||
#else
|
||||
provider_options["backend_path"] = "libQnnHtp.so";
|
||||
#endif
|
||||
|
||||
TestQDQModelAccuracy(BuildPadTestCase(data_def, pads_def, constant_value_def, attrs),
|
||||
BuildPadQDQTestCase<QuantType>(data_def, pads_def, constant_value_def, attrs,
|
||||
has_constant_value, constant_value_quantized),
|
||||
provider_options,
|
||||
opset,
|
||||
expected_ep_assignment,
|
||||
1e-5f);
|
||||
}
|
||||
|
||||
//
|
||||
// CPU tests:
|
||||
//
|
||||
|
||||
// Pad 2d
|
||||
TEST_F(QnnCPUBackendTests, Pad2d) {
|
||||
RunPadOpTest(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
|
||||
TestInputDef<int64_t>({4}, true, {0, 2, 0, 0}),
|
||||
TestInputDef<float>({1}, true, {0.0f}),
|
||||
{utils::MakeAttribute("mode", "constant")},
|
||||
ExpectedEPNodeAssignment::All);
|
||||
}
|
||||
|
||||
// Pad 2d, pads input not initializer
|
||||
TEST_F(QnnCPUBackendTests, Pad2dPadsNotIni) {
|
||||
RunPadOpTest(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
|
||||
TestInputDef<int64_t>({4}, false, {0, 2, 0, 0}),
|
||||
TestInputDef<float>({1}, true, {0.0f}),
|
||||
{utils::MakeAttribute("mode", "constant")},
|
||||
ExpectedEPNodeAssignment::None);
|
||||
}
|
||||
|
||||
// Pad reflect mode
|
||||
// Expected: contains 12 values, where each value and its corresponding value in 16-byte object <0C-00 00-00 00-00 00-00 40-01 23-05 EC-01 00-00> are an almost-equal pair
|
||||
// Actual: 16-byte object <0C-00 00-00 00-00 00-00 40-01 12-05 EC-01 00-00>, where the value pair (1.2, 0) at index #1 don't match, which is -1.2 from 1.2
|
||||
TEST_F(QnnCPUBackendTests, DISABLED_PadModeReflect) {
|
||||
bool has_constant_value = false;
|
||||
RunPadOpTest(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
|
||||
TestInputDef<int64_t>({4}, true, {0, 2, 0, 0}),
|
||||
TestInputDef<float>({1}, true, {0.0f}),
|
||||
{utils::MakeAttribute("mode", "reflect")},
|
||||
ExpectedEPNodeAssignment::All,
|
||||
has_constant_value);
|
||||
}
|
||||
|
||||
// Pad edge mode
|
||||
TEST_F(QnnCPUBackendTests, PadModeEdge) {
|
||||
bool has_constant_value = false;
|
||||
RunPadOpTest(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
|
||||
TestInputDef<int64_t>({4}, true, {0, 2, 0, 0}),
|
||||
TestInputDef<float>({1}, true, {0.0f}),
|
||||
{utils::MakeAttribute("mode", "edge")},
|
||||
ExpectedEPNodeAssignment::All,
|
||||
has_constant_value);
|
||||
}
|
||||
|
||||
// Pad wrap mode not supported
|
||||
TEST_F(QnnCPUBackendTests, PadModeWrap) {
|
||||
bool has_constant_value = false;
|
||||
RunPadOpTest(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
|
||||
TestInputDef<int64_t>({4}, true, {0, 2, 0, 0}),
|
||||
TestInputDef<float>({1}, true, {0.0f}),
|
||||
{utils::MakeAttribute("mode", "wrap")},
|
||||
ExpectedEPNodeAssignment::None, // not supported
|
||||
has_constant_value);
|
||||
}
|
||||
|
||||
// Pad 4d
|
||||
TEST_F(QnnCPUBackendTests, Pad4d) {
|
||||
RunPadOpTest(TestInputDef<float>({1, 2, 2, 2}, false,
|
||||
{1.0f, 1.0f,
|
||||
1.0f, 1.0f,
|
||||
1.0f, 1.0f,
|
||||
1.0f, 1.0f}),
|
||||
TestInputDef<int64_t>({8}, true, {0, 0, 0, 1, 0, 0, 0, 1}),
|
||||
TestInputDef<float>({1}, true, {0.0f}),
|
||||
{utils::MakeAttribute("mode", "constant")},
|
||||
ExpectedEPNodeAssignment::All);
|
||||
}
|
||||
|
||||
// Pad 5d supported
|
||||
TEST_F(QnnCPUBackendTests, Pad5d) {
|
||||
RunPadOpTest(TestInputDef<float>({1, 2, 2, 2, 2}, false, GetFloatDataInRange(1.0f, 10.0f, 16)),
|
||||
TestInputDef<int64_t>({10}, true, {0, 0, 0, 1, 0, 0, 0, 1, 0, 0}),
|
||||
TestInputDef<float>({1}, true, {5.0f}),
|
||||
{utils::MakeAttribute("mode", "constant")},
|
||||
ExpectedEPNodeAssignment::All);
|
||||
}
|
||||
|
||||
// Pad 6d supported
|
||||
TEST_F(QnnCPUBackendTests, Pad6d) {
|
||||
RunPadOpTest(TestInputDef<float>({1, 2, 2, 2, 2, 2}, false, GetFloatDataInRange(1.0f, 10.0f, 32)),
|
||||
TestInputDef<int64_t>({12}, true, {0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0}),
|
||||
TestInputDef<float>({1}, true, {0.0f}),
|
||||
{utils::MakeAttribute("mode", "constant")},
|
||||
ExpectedEPNodeAssignment::None);
|
||||
}
|
||||
|
||||
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__linux__)
|
||||
//
|
||||
// HTP tests:
|
||||
//
|
||||
// QDQ Pad
|
||||
TEST_F(QnnHTPBackendTests, PadNoConstantValue) {
|
||||
bool has_constant_value_input = false;
|
||||
RunQDQPadOpTest<uint8_t>(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
|
||||
TestInputDef<int64_t>({4}, true, {0, 2, 0, 0}),
|
||||
TestInputDef<float>({1}, true, {0.0f}),
|
||||
{utils::MakeAttribute("mode", "constant")},
|
||||
ExpectedEPNodeAssignment::All,
|
||||
has_constant_value_input);
|
||||
}
|
||||
|
||||
TEST_F(QnnHTPBackendTests, PadHasConstantValueNonQuantized) {
|
||||
bool has_constant_value_input = true;
|
||||
bool constant_value_quantized = false;
|
||||
RunQDQPadOpTest<uint8_t>(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
|
||||
TestInputDef<int64_t>({4}, true, {0, 2, 0, 0}),
|
||||
TestInputDef<float>({1}, true, {0.0f}),
|
||||
{utils::MakeAttribute("mode", "constant")},
|
||||
ExpectedEPNodeAssignment::All,
|
||||
has_constant_value_input,
|
||||
constant_value_quantized);
|
||||
}
|
||||
|
||||
TEST_F(QnnHTPBackendTests, PadHasConstantValueQuantized) {
|
||||
bool has_constant_value_input = true;
|
||||
bool constant_value_quantized = true;
|
||||
RunQDQPadOpTest<uint8_t>(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
|
||||
TestInputDef<int64_t>({4}, true, {0, 2, 0, 0}),
|
||||
TestInputDef<float>({1}, true, {0.0f}),
|
||||
{utils::MakeAttribute("mode", "constant")},
|
||||
ExpectedEPNodeAssignment::All,
|
||||
has_constant_value_input,
|
||||
constant_value_quantized);
|
||||
}
|
||||
|
||||
// QNN graph execute error. Error code: 6031
|
||||
TEST_F(QnnHTPBackendTests, DISABLED_PadReflectMode) {
|
||||
bool has_constant_value_input = false;
|
||||
RunQDQPadOpTest<uint8_t>(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
|
||||
TestInputDef<int64_t>({4}, true, {0, 2, 0, 0}),
|
||||
TestInputDef<float>({1}, true, {0.0f}),
|
||||
{utils::MakeAttribute("mode", "reflect")},
|
||||
ExpectedEPNodeAssignment::All,
|
||||
has_constant_value_input);
|
||||
}
|
||||
|
||||
TEST_F(QnnHTPBackendTests, PadEdgeMode) {
|
||||
bool has_constant_value_input = false;
|
||||
RunQDQPadOpTest<uint8_t>(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
|
||||
TestInputDef<int64_t>({4}, true, {0, 2, 0, 0}),
|
||||
TestInputDef<float>({1}, true, {0.0f}),
|
||||
{utils::MakeAttribute("mode", "edge")},
|
||||
ExpectedEPNodeAssignment::All,
|
||||
has_constant_value_input);
|
||||
}
|
||||
|
||||
// wrap mode not supported
|
||||
TEST_F(QnnHTPBackendTests, PadWrapMode) {
|
||||
bool has_constant_value_input = false;
|
||||
RunQDQPadOpTest<uint8_t>(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
|
||||
TestInputDef<int64_t>({4}, true, {0, 2, 0, 0}),
|
||||
TestInputDef<float>({1}, true, {0.0f}),
|
||||
{utils::MakeAttribute("mode", "wrap")},
|
||||
ExpectedEPNodeAssignment::None,
|
||||
has_constant_value_input);
|
||||
}
|
||||
|
||||
TEST_F(QnnHTPBackendTests, Pad4d) {
|
||||
RunQDQPadOpTest<uint8_t>(TestInputDef<float>({1, 2, 2, 2}, false,
|
||||
{1.0f, 2.0f,
|
||||
3.0f, 4.0f,
|
||||
5.0f, 6.0f,
|
||||
7.0f, 8.0f}),
|
||||
TestInputDef<int64_t>({8}, true, {0, 0, 0, 1, 0, 0, 0, 1}),
|
||||
TestInputDef<float>({1}, true, {5.0f}),
|
||||
{utils::MakeAttribute("mode", "constant")},
|
||||
ExpectedEPNodeAssignment::All);
|
||||
}
|
||||
|
||||
// Inaccuracy detected for output 'output', element 0.
|
||||
// Output quant params: scale=0.035294119268655777, zero_point=0.
|
||||
// Expected val: 9
|
||||
// QNN QDQ val: 8.0117654800415039 (err 0.98823451995849609)
|
||||
// CPU QDQ val: 9 (err 0)
|
||||
// QNN limitation? pad_constant_value has to be within the range of input[0].
|
||||
// Here pad_constant_value = 9.0 > max(input[0]) = 8.0
|
||||
TEST_F(QnnHTPBackendTests, DISABLED_Pad4dOutOfRangePadConstantValue) {
|
||||
RunQDQPadOpTest<uint8_t>(TestInputDef<float>({1, 2, 2, 2}, false,
|
||||
{1.0f, 2.0f,
|
||||
3.0f, 4.0f,
|
||||
5.0f, 6.0f,
|
||||
7.0f, 8.0f}),
|
||||
TestInputDef<int64_t>({8}, true, {0, 0, 0, 1, 0, 0, 0, 1}),
|
||||
TestInputDef<float>({1}, true, {9.0f}), // pad_constant_value out of input[0] range
|
||||
{utils::MakeAttribute("mode", "constant")},
|
||||
ExpectedEPNodeAssignment::All);
|
||||
}
|
||||
|
||||
// Pad 5d supported, but Quantize & Dequantize doesn't support 5d
|
||||
TEST_F(QnnHTPBackendTests, DISABLED_Pad5d) {
|
||||
RunQDQPadOpTest<uint8_t>(TestInputDef<float>({1, 2, 2, 2, 2}, false, GetFloatDataInRange(1.0f, 10.0f, 16)),
|
||||
TestInputDef<int64_t>({10}, true, {0, 0, 0, 1, 0, 0, 0, 1, 0, 0}),
|
||||
TestInputDef<float>({1}, true, {2.0f}),
|
||||
{utils::MakeAttribute("mode", "constant")},
|
||||
ExpectedEPNodeAssignment::All);
|
||||
}
|
||||
|
||||
#endif // defined(__aarch64__) || defined(_M_ARM64) || defined(__linux__)
|
||||
|
||||
} // namespace test
|
||||
} // namespace onnxruntime
|
||||
|
||||
#endif // !defined(ORT_MINIMAL_BUILD)
|
||||
Loading…
Reference in a new issue