[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:
Hector Li 2023-09-14 14:22:45 -07:00 committed by GitHub
parent 198d468849
commit 46fe08226f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 651 additions and 4 deletions

View file

@ -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,

View file

@ -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 {

View file

@ -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() {

View file

@ -154,6 +154,10 @@ OpBuilderRegistrations::OpBuilderRegistrations() {
{
CreateTransposeOpBuilder("Transpose", *this);
}
{
CreatePadOpBuilder("Pad", *this);
}
}
const IOpBuilder* GetOpBuilder(const std::string& onnx_op_type) {

View file

@ -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

View file

@ -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;

View file

@ -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") {}

View file

@ -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

View file

@ -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.");
}

View file

@ -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,

View 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)