mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-07-25 19:48:11 +00:00
[QNN EP] Support AveragePool operator (#15419)
### Description Adds support for the AveragePool operator to QNN EP. ### Motivation and Context This is needed to enable more models to run with QNN EP.
This commit is contained in:
parent
139f3df4d2
commit
c294040bac
4 changed files with 297 additions and 13 deletions
|
|
@ -71,6 +71,7 @@ OpBuilderRegistrations::OpBuilderRegistrations() {
|
|||
|
||||
{
|
||||
CreatePoolOpBuilder("GlobalAveragePool", *this);
|
||||
CreatePoolOpBuilder("AveragePool", *this);
|
||||
CreatePoolOpBuilder("MaxPool", *this);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ class BaseOpBuilder : public IOpBuilder {
|
|||
{"Conv", "Conv2d"},
|
||||
|
||||
{"GlobalAveragePool", "PoolAvg2d"},
|
||||
{"AveragePool", "PoolAvg2d"},
|
||||
{"MaxPool", "PoolMax2d"},
|
||||
|
||||
{"Reshape", "Reshape"},
|
||||
|
|
|
|||
|
|
@ -33,11 +33,11 @@ class PoolOpBuilder : public BaseOpBuilder {
|
|||
bool do_op_validation) const override ORT_MUST_USE_RESULT;
|
||||
|
||||
private:
|
||||
Status SetParamForMaxPool(const NodeAttrHelper& node_helper, std::vector<uint32_t>& filter_size,
|
||||
std::vector<uint32_t>& pad_amount, std::vector<uint32_t>& stride,
|
||||
int32_t& ceil_mode,
|
||||
std::vector<uint32_t>&& input_shape,
|
||||
std::vector<uint32_t>&& output_shape) const;
|
||||
Status SetCommonPoolParams(const NodeAttrHelper& node_helper, std::vector<uint32_t>& filter_size,
|
||||
std::vector<uint32_t>& pad_amount, std::vector<uint32_t>& stride,
|
||||
int32_t& ceil_mode,
|
||||
std::vector<uint32_t>&& input_shape,
|
||||
std::vector<uint32_t>&& output_shape) const;
|
||||
};
|
||||
|
||||
// Pool ops are sensitive with data layout, no special validation so far
|
||||
|
|
@ -76,12 +76,12 @@ Status PoolOpBuilder::IsOpSupported(QnnModelWrapper& qnn_model_wrapper,
|
|||
return Status::OK();
|
||||
}
|
||||
|
||||
Status PoolOpBuilder::SetParamForMaxPool(const NodeAttrHelper& node_helper,
|
||||
std::vector<uint32_t>& filter_size,
|
||||
std::vector<uint32_t>& pad_amount, std::vector<uint32_t>& stride,
|
||||
int32_t& ceil_mode,
|
||||
std::vector<uint32_t>&& input_shape,
|
||||
std::vector<uint32_t>&& output_shape) const {
|
||||
Status PoolOpBuilder::SetCommonPoolParams(const NodeAttrHelper& node_helper,
|
||||
std::vector<uint32_t>& filter_size,
|
||||
std::vector<uint32_t>& pad_amount, std::vector<uint32_t>& stride,
|
||||
int32_t& ceil_mode,
|
||||
std::vector<uint32_t>&& input_shape,
|
||||
std::vector<uint32_t>&& output_shape) const {
|
||||
auto kernel_shape = node_helper.Get("kernel_shape", std::vector<int32_t>{1, 1});
|
||||
ORT_RETURN_IF_NOT(kernel_shape.size() == 2, "QNN only support kernel_shape with shape[2].");
|
||||
filter_size.clear();
|
||||
|
|
@ -149,12 +149,13 @@ Status PoolOpBuilder::ProcessAttributesAndOutputs(QnnModelWrapper& qnn_model_wra
|
|||
std::vector<uint32_t> pad_amount{0, 0, 0, 0};
|
||||
std::vector<uint32_t> pad_amount_dim{2, 2};
|
||||
int32_t ceil_mode = 0;
|
||||
if (node_unit.OpType() == "MaxPool") {
|
||||
if (node_unit.OpType() == "MaxPool" || node_unit.OpType() == "AveragePool") {
|
||||
const auto& outputs = node_unit.Outputs();
|
||||
std::vector<uint32_t> output_shape;
|
||||
ORT_RETURN_IF_NOT(qnn_model_wrapper.GetOnnxShape(outputs[0].node_arg, output_shape), "Cannot get shape");
|
||||
|
||||
ORT_RETURN_IF_ERROR(SetParamForMaxPool(node_helper, filter_size, pad_amount, stride, ceil_mode, std::move(input_shape), std::move(output_shape)));
|
||||
ORT_RETURN_IF_ERROR(SetCommonPoolParams(node_helper, filter_size, pad_amount, stride, ceil_mode,
|
||||
std::move(input_shape), std::move(output_shape)));
|
||||
}
|
||||
|
||||
std::vector<std::string> param_tensor_names;
|
||||
|
|
@ -202,7 +203,18 @@ Status PoolOpBuilder::ProcessAttributesAndOutputs(QnnModelWrapper& qnn_model_wra
|
|||
scalar_param);
|
||||
param_tensor_names.push_back(count_pad_for_edges_param.GetParamTensorName());
|
||||
qnn_model_wrapper.AddParamWrapper(std::move(count_pad_for_edges_param));
|
||||
} else if (node_unit.OpType() == "AveragePool") {
|
||||
Qnn_Scalar_t scalar_param = QNN_SCALAR_INIT;
|
||||
scalar_param.dataType = QNN_DATATYPE_BOOL_8;
|
||||
scalar_param.bool8Value = static_cast<uint8_t>(node_helper.Get("count_include_pad", static_cast<int64_t>(0)) != 0);
|
||||
QnnParamWrapper count_pad_for_edges_param(node_unit.Index(),
|
||||
node_unit.Name(),
|
||||
qnn_def::count_pad_for_edges,
|
||||
scalar_param);
|
||||
param_tensor_names.push_back(count_pad_for_edges_param.GetParamTensorName());
|
||||
qnn_model_wrapper.AddParamWrapper(std::move(count_pad_for_edges_param));
|
||||
}
|
||||
|
||||
output_count_ = 1;
|
||||
ORT_RETURN_IF_ERROR(ProcessOutputs(qnn_model_wrapper, node_unit,
|
||||
std::move(input_names),
|
||||
|
|
|
|||
270
onnxruntime/test/providers/qnn/average_pool_test.cc
Normal file
270
onnxruntime/test/providers/qnn/average_pool_test.cc
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#if !defined(ORT_MINIMAL_BUILD)
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#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 AveragePool operator.
|
||||
static GetTestModelFn BuildAveragePoolTestCase(const std::vector<int64_t>& shape,
|
||||
const std::vector<int64_t>& kernel_shape,
|
||||
const std::vector<int64_t>& strides,
|
||||
const std::vector<int64_t>& pads,
|
||||
int64_t count_include_pad,
|
||||
const std::string& auto_pad = "NOTSET") {
|
||||
return [shape, kernel_shape, strides, pads,
|
||||
count_include_pad, auto_pad](ModelTestBuilder& builder) {
|
||||
|
||||
// Random input data
|
||||
auto input = builder.MakeInput<float>(shape, 0.0f, 10.0f);
|
||||
|
||||
auto* output = builder.MakeOutput();
|
||||
Node& pool_node = builder.AddNode("AveragePool", {input}, {output});
|
||||
|
||||
pool_node.AddAttribute("kernel_shape", kernel_shape);
|
||||
|
||||
if (!strides.empty()) {
|
||||
pool_node.AddAttribute("strides", strides);
|
||||
}
|
||||
|
||||
pool_node.AddAttribute("auto_pad", auto_pad);
|
||||
|
||||
if (!pads.empty() && auto_pad == "NOTSET") {
|
||||
pool_node.AddAttribute("pads", pads);
|
||||
}
|
||||
|
||||
if (count_include_pad > 0) {
|
||||
pool_node.AddAttribute("count_include_pad", count_include_pad);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Returns a function that creates a graph with a QDQ AveragePool operator.
|
||||
template <typename QuantType>
|
||||
GetQDQTestCaseFn BuildAveragePoolQDQTestCase(const std::vector<int64_t>& shape,
|
||||
const std::vector<int64_t>& kernel_shape,
|
||||
const std::vector<int64_t>& strides,
|
||||
const std::vector<int64_t>& pads,
|
||||
int64_t count_include_pad,
|
||||
const std::string& auto_pad = "NOTSET") {
|
||||
return [shape, kernel_shape, strides, pads,
|
||||
count_include_pad, auto_pad](ModelTestBuilder& builder) {
|
||||
|
||||
float dq_scale = 0.0038f;
|
||||
float pool_output_scale = 0.0038f;
|
||||
float q_scale = 0.0038f;
|
||||
QuantType dq_zp = std::numeric_limits<QuantType>::max() / 2;
|
||||
QuantType pool_output_zp = std::numeric_limits<QuantType>::max() / 2;
|
||||
QuantType q_zp = std::numeric_limits<QuantType>::max() / 2;
|
||||
|
||||
auto* input_arg = builder.MakeInput<float>(shape, -1.0f, 1.0f);
|
||||
auto* output_arg = builder.MakeOutput();
|
||||
|
||||
// add QDQ + AveragePool
|
||||
auto* dq_output = AddQDQNodePair<QuantType>(builder, input_arg, dq_scale, dq_zp);
|
||||
auto* averagepool_output = builder.MakeIntermediate();
|
||||
Node& pool_node = builder.AddNode("AveragePool", {dq_output}, {averagepool_output});
|
||||
|
||||
pool_node.AddAttribute("kernel_shape", kernel_shape);
|
||||
|
||||
if (!strides.empty()) {
|
||||
pool_node.AddAttribute("strides", strides);
|
||||
}
|
||||
|
||||
pool_node.AddAttribute("auto_pad", auto_pad);
|
||||
|
||||
if (!pads.empty() && auto_pad == "NOTSET") {
|
||||
pool_node.AddAttribute("pads", pads);
|
||||
}
|
||||
|
||||
if (count_include_pad > 0) {
|
||||
pool_node.AddAttribute("count_include_pad", count_include_pad);
|
||||
}
|
||||
|
||||
// add QDQ output
|
||||
auto* q_output = builder.MakeIntermediate();
|
||||
builder.AddQuantizeLinearNode<QuantType>(averagepool_output,
|
||||
pool_output_scale,
|
||||
pool_output_zp,
|
||||
q_output);
|
||||
builder.AddDequantizeLinearNode<QuantType>(q_output,
|
||||
q_scale,
|
||||
q_zp,
|
||||
output_arg);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Runs an AveragePool model on the QNN CPU backend. Checks the graph node assignment, and that inference
|
||||
// outputs for QNN and CPU match.
|
||||
static void RunAveragePoolOpTest(const std::vector<int64_t>& shape,
|
||||
const std::vector<int64_t>& kernel_shape,
|
||||
const std::vector<int64_t>& strides,
|
||||
const std::vector<int64_t>& pads,
|
||||
int64_t count_include_pad,
|
||||
const std::string& auto_pad,
|
||||
ExpectedEPNodeAssignment expected_ep_assignment, const char* test_description,
|
||||
int opset = 18) {
|
||||
ProviderOptions provider_options;
|
||||
#if defined(_WIN32)
|
||||
provider_options["backend_path"] = "QnnCpu.dll";
|
||||
#else
|
||||
provider_options["backend_path"] = "libQnnCpu.so";
|
||||
#endif
|
||||
|
||||
constexpr int expected_nodes_in_partition = 1;
|
||||
RunQnnModelTest(BuildAveragePoolTestCase(shape, kernel_shape, strides, pads, count_include_pad, auto_pad),
|
||||
provider_options,
|
||||
opset,
|
||||
expected_ep_assignment,
|
||||
expected_nodes_in_partition,
|
||||
test_description);
|
||||
}
|
||||
|
||||
// Runs a QDQ AveragePool 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 RunQDQAveragePoolOpTest(const std::vector<int64_t>& shape,
|
||||
const std::vector<int64_t>& kernel_shape,
|
||||
const std::vector<int64_t>& strides,
|
||||
const std::vector<int64_t>& pads,
|
||||
int64_t count_include_pad,
|
||||
const std::string& auto_pad,
|
||||
ExpectedEPNodeAssignment expected_ep_assignment, const char* test_description,
|
||||
int opset = 18, float fp32_abs_err = 1e-5f) {
|
||||
ProviderOptions provider_options;
|
||||
#if defined(_WIN32)
|
||||
provider_options["backend_path"] = "QnnHtp.dll";
|
||||
#else
|
||||
provider_options["backend_path"] = "libQnnHtp.so";
|
||||
#endif
|
||||
|
||||
constexpr int expected_nodes_in_partition = 1;
|
||||
RunQnnModelTest(BuildAveragePoolQDQTestCase<QuantType>(shape, kernel_shape, strides, pads, count_include_pad,
|
||||
auto_pad),
|
||||
provider_options,
|
||||
opset,
|
||||
expected_ep_assignment,
|
||||
expected_nodes_in_partition,
|
||||
test_description,
|
||||
fp32_abs_err);
|
||||
}
|
||||
|
||||
//
|
||||
// CPU tests:
|
||||
//
|
||||
|
||||
// AveragePool with kernel size equal to the spatial dimension of input tensor.
|
||||
TEST(QnnCPUBackendTests, TestAveragePool_Global) {
|
||||
RunAveragePoolOpTest({1, 2, 3, 3}, // shape
|
||||
{3, 3}, // kernel_shape
|
||||
{3, 3}, // strides
|
||||
{0, 0, 0, 0}, // pads
|
||||
0, // count_include_pad
|
||||
"NOTSET",
|
||||
ExpectedEPNodeAssignment::All, "TestAveragePool_Global");
|
||||
}
|
||||
|
||||
// AveragePool that counts padding.
|
||||
TEST(QnnCPUBackendTests, TestAveragePool_CountIncludePad) {
|
||||
RunAveragePoolOpTest({1, 2, 3, 3}, // shape
|
||||
{1, 1}, // kernel_shape
|
||||
{1, 1}, // strides
|
||||
{0, 0, 0, 0}, // pads
|
||||
1, // count_include_pad
|
||||
"NOTSET",
|
||||
ExpectedEPNodeAssignment::All, "TestAveragePool_CountIncludePad");
|
||||
}
|
||||
|
||||
// AveragePool that use auto_pad 'SAME_UPPER'.
|
||||
TEST(QnnCPUBackendTests, TestAveragePool_AutopadSameUpper) {
|
||||
RunAveragePoolOpTest({1, 2, 3, 3}, // shape
|
||||
{1, 1}, // kernel_shape
|
||||
{1, 1}, // strides
|
||||
{}, // pads
|
||||
1, // count_include_pad
|
||||
"SAME_UPPER",
|
||||
ExpectedEPNodeAssignment::All, "TestAveragePool_CountIncludePad");
|
||||
}
|
||||
|
||||
// AveragePool that use auto_pad 'SAME_LOWER'.
|
||||
TEST(QnnCPUBackendTests, TestAveragePool_AutopadSameLower) {
|
||||
RunAveragePoolOpTest({1, 2, 3, 3}, // shape
|
||||
{1, 1}, // kernel_shape
|
||||
{1, 1}, // strides
|
||||
{}, // pads
|
||||
1, // count_include_pad
|
||||
"SAME_LOWER",
|
||||
ExpectedEPNodeAssignment::All, "TestAveragePool_CountIncludePad");
|
||||
}
|
||||
|
||||
|
||||
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__linux__)
|
||||
//
|
||||
// HTP tests:
|
||||
//
|
||||
|
||||
// QDQ AveragePool with kernel size equal to the spatial dimension of input tensor.
|
||||
TEST_F(QnnHTPBackendTests, TestAveragePool_Global_HTP_u8) {
|
||||
RunQDQAveragePoolOpTest<uint8_t>({1, 2, 3, 3}, // shape
|
||||
{3, 3}, // kernel_shape
|
||||
{3, 3}, // strides
|
||||
{0, 0, 0, 0}, // pads
|
||||
0, // count_include_pad
|
||||
"NOTSET",
|
||||
ExpectedEPNodeAssignment::All, "TestAveragePool_Global_HTP_u8");
|
||||
}
|
||||
|
||||
// QDQ AveragePool that counts padding.
|
||||
TEST_F(QnnHTPBackendTests, TestAveragePool_CountIncludePad_HTP_u8) {
|
||||
RunQDQAveragePoolOpTest<uint8_t>({1, 2, 3, 3}, // shape
|
||||
{1, 1}, // kernel_shape
|
||||
{1, 1}, // strides
|
||||
{0, 0, 0, 0}, // pads
|
||||
1, // count_include_pad
|
||||
"NOTSET",
|
||||
ExpectedEPNodeAssignment::All, "TestAveragePool_CountIncludePad_HTP_u8",
|
||||
18, 0.00381f);
|
||||
}
|
||||
|
||||
// QDQ AveragePool that use auto_pad 'SAME_UPPER'.
|
||||
TEST_F(QnnHTPBackendTests, TestAveragePool_AutopadSameUpper_HTP_u8) {
|
||||
RunQDQAveragePoolOpTest<uint8_t>({1, 2, 3, 3}, // shape
|
||||
{1, 1}, // kernel_shape
|
||||
{1, 1}, // strides
|
||||
{}, // pads
|
||||
0, // count_include_pad
|
||||
"SAME_UPPER",
|
||||
ExpectedEPNodeAssignment::All, "TestAveragePool_AutopadSameUpper_HTP_u8",
|
||||
18, 0.00381f);
|
||||
}
|
||||
|
||||
// QDQ AveragePool that use auto_pad 'SAME_LOWER'.
|
||||
TEST_F(QnnHTPBackendTests, TestAveragePool_AutopadSameLower_HTP_u8) {
|
||||
RunQDQAveragePoolOpTest<uint8_t>({1, 2, 3, 3}, // shape
|
||||
{1, 1}, // kernel_shape
|
||||
{1, 1}, // strides
|
||||
{}, // pads
|
||||
0, // count_include_pad
|
||||
"SAME_LOWER",
|
||||
ExpectedEPNodeAssignment::All, "TestAveragePool_AutopadSameLower_HTP_u8",
|
||||
18, 0.00381f);
|
||||
}
|
||||
|
||||
#endif // defined(__aarch64__) || defined(_M_ARM64) || defined(__linux__)
|
||||
|
||||
} // namespace test
|
||||
} // namespace onnxruntime
|
||||
|
||||
#endif // !defined(ORT_MINIMAL_BUILD)
|
||||
Loading…
Reference in a new issue