Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 112 additions & 5 deletions onnxruntime/core/providers/qnn/builder/opbuilder/pad_op_builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,18 @@
size_t tensor_byte_size = unpacked_tensor.size();
size_t size = tensor_byte_size / sizeof(int64_t);

bool has_negative = std::any_of(tensor_data, tensor_data + size, [](int64_t item) { return item < 0; });
bool has_positive = std::any_of(tensor_data, tensor_data + size, [](int64_t item) { return item > 0; });

// Zero padding value gives 3110 error on QNN.
if (!has_positive && !has_negative) {
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Got QNN invalid zero only padding value.");
}

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); });
[](int64_t item) { return item < 0 ? SafeInt<uint32_t>(0) : 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, ...
ReArrangePads(pad_amount);
Expand All @@ -200,6 +209,11 @@

NodeAttrHelper node_helper(node_unit);
std::string mode = node_helper.Get("mode", "constant");

if ("reflect" == mode && has_negative) {
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "reflect mode doesn't support negative padding value.");
}

Qnn_Scalar_t mode_qnn_scalar = QNN_SCALAR_INIT;
mode_qnn_scalar.dataType = QNN_DATATYPE_UINT_32;
if ("constant" == mode) {
Expand Down Expand Up @@ -231,10 +245,103 @@
ORT_RETURN_IF_ERROR(ProcessConstantValue(qnn_model_wrapper, param_tensor_names, node_unit, inputs[2]));
} // constant_value

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())));
std::vector<uint32_t> pad_output_shape;
if (has_negative) {
for (uint32_t i = 0; i < input_shape.size(); ++i) {
pad_output_shape.push_back(input_shape[i] + pad_amount[2 * i] + pad_amount[2 * i + 1]);
}
}

std::string pad_output_name = utils::GetUniqueName(node_unit, "_Pad_output");
// Step 1. Add pad if has_positive
if (has_positive) {
// Only positive.
if (!has_negative) {
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())));
// Mixed sign pad value.
} else {
TensorInfo input_info = {};
ORT_RETURN_IF_ERROR(qnn_model_wrapper.GetTensorInfo(node_unit.Inputs()[0], input_info));

std::string pad_name = utils::GetUniqueName(node_unit, "_Pad");
QnnTensorWrapper pad_output(pad_output_name,
QNN_TENSOR_TYPE_NATIVE,
input_info.qnn_data_type,
input_info.quant_param.Copy(),
std::vector<uint32_t>(pad_output_shape));
ORT_RETURN_IF_NOT(qnn_model_wrapper.AddTensorWrapper(std::move(pad_output)),
"Failed to add Pad output tensor.");
ORT_RETURN_IF_NOT(qnn_model_wrapper.CreateQnnNode(pad_name,
QNN_OP_PACKAGE_NAME_QTI_AISW,
GetQnnOpType(node_unit.OpType()),
std::move(input_names),
{pad_output_name},
std::move(param_tensor_names),
do_op_validation),
"Failed to add Pad node.");
}
}

// Step 2. Add Slice if has_negative
if (has_negative) {
TensorInfo output_info = {};
ORT_RETURN_IF_ERROR(qnn_model_wrapper.GetTensorInfo(node_unit.Outputs()[0], output_info));

const std::string& org_output_name = node_unit.Outputs()[0].node_arg.Name();
const bool is_graph_output = qnn_model_wrapper.IsGraphOutput(org_output_name);
Qnn_TensorType_t op_output_tensor_type = is_graph_output ? QNN_TENSOR_TYPE_APP_READ : QNN_TENSOR_TYPE_NATIVE;

// Create output tensor
std::vector<uint32_t> output_shape = output_info.shape;
QnnTensorWrapper org_output(org_output_name,
op_output_tensor_type,
output_info.qnn_data_type,
output_info.quant_param.Copy(),
std::vector<uint32_t>(output_shape));
ORT_RETURN_IF_NOT(qnn_model_wrapper.AddTensorWrapper(std::move(org_output)),
"Failed to add Pad output tensor.");

// Create Slice param
const size_t input_rank = input_shape.size();
std::vector<uint32_t> ranges_dims{static_cast<uint32_t>(input_rank), 3};
std::vector<uint32_t> ranges_data;
ranges_data.reserve(input_rank);

std::vector<int64_t> slice_amount;
std::transform(tensor_data, tensor_data + size, std::back_inserter(slice_amount),

Check warning on line 314 in onnxruntime/core/providers/qnn/builder/opbuilder/pad_op_builder.cc

View workflow job for this annotation

GitHub Actions / Optional Lint C++

[cpplint] reported by reviewdog 🐶 Add #include <algorithm> for transform [build/include_what_you_use] [4] Raw Output: onnxruntime/core/providers/qnn/builder/opbuilder/pad_op_builder.cc:314: Add #include <algorithm> for transform [build/include_what_you_use] [4]
[](int64_t item) { return item > 0 ? SafeInt<int64_t>(0) : SafeInt<int64_t>(item); });

// slice_amount in ONNX format: begin_0, begin_1, ..., end_0, end_1, ...
for (size_t i = 0; i < input_rank; i++) {
ranges_data.push_back(static_cast<uint32_t>(0 - slice_amount[i])); // starts
ranges_data.push_back(static_cast<uint32_t>(static_cast<int64_t>(pad_output_shape[i]) + slice_amount[i + input_rank])); // ends
ranges_data.push_back(static_cast<uint32_t>(1)); // steps
}

QnnParamWrapper ranges_paramwrapper(node_unit.Index(),
node_unit.Name(),
QNN_OP_STRIDED_SLICE_PARAM_RANGES,
std::move(ranges_dims),
std::move(ranges_data),
true);
std::string slice_param_tensor_name(ranges_paramwrapper.GetParamTensorName());
qnn_model_wrapper.AddParamWrapper(std::move(ranges_paramwrapper));

// Create Slice Node
std::vector<std::string> slice_input_name = has_positive ? std::vector<std::string>{pad_output_name} : input_names;

Check warning on line 334 in onnxruntime/core/providers/qnn/builder/opbuilder/pad_op_builder.cc

View workflow job for this annotation

GitHub Actions / Optional Lint C++

[cpplint] reported by reviewdog 🐶 Add #include <vector> for vector<> [build/include_what_you_use] [4] Raw Output: onnxruntime/core/providers/qnn/builder/opbuilder/pad_op_builder.cc:334: Add #include <vector> for vector<> [build/include_what_you_use] [4]
std::string slice_name = utils::GetUniqueName(node_unit, "_Slice");
ORT_RETURN_IF_NOT(qnn_model_wrapper.CreateQnnNode(slice_name,
QNN_OP_PACKAGE_NAME_QTI_AISW,
QNN_OP_STRIDED_SLICE,
std::move(slice_input_name),

Check warning on line 339 in onnxruntime/core/providers/qnn/builder/opbuilder/pad_op_builder.cc

View workflow job for this annotation

GitHub Actions / Optional Lint C++

[cpplint] reported by reviewdog 🐶 Add #include <utility> for move [build/include_what_you_use] [4] Raw Output: onnxruntime/core/providers/qnn/builder/opbuilder/pad_op_builder.cc:339: Add #include <utility> for move [build/include_what_you_use] [4]
{org_output_name},
{slice_param_tensor_name},
do_op_validation),
"Failed to add Pad node.");
}

return Status::OK();
}
Expand Down
135 changes: 135 additions & 0 deletions onnxruntime/test/providers/qnn/pad_op_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,22 @@ TEST_F(QnnCPUBackendTests, Pad2d) {
ExpectedEPNodeAssignment::All);
}

TEST_F(QnnCPUBackendTests, Pad2dNeg) {
RunPadOpTest(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
TestInputDef<int64_t>({4}, true, {0, -1, -1, 0}),
TestInputDef<float>({1}, true, {0.0f}),
{utils::MakeAttribute("mode", "constant")},
ExpectedEPNodeAssignment::All);
}

TEST_F(QnnCPUBackendTests, Pad2dMix) {
RunPadOpTest(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
TestInputDef<int64_t>({4}, true, {1, -1, -1, 1}),
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}),
Expand All @@ -175,6 +191,15 @@ TEST_F(QnnCPUBackendTests, PadModeReflect) {
has_constant_value);
}

TEST_F(QnnCPUBackendTests, PadModeReflectNeg) {
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, 1, -1, 0}),
TestInputDef<float>({1}, true, {0.0f}),
{utils::MakeAttribute("mode", "reflect")}, // reflect mode doesn't support negative padding value.
ExpectedEPNodeAssignment::None,
has_constant_value);
}
// Pad edge mode
TEST_F(QnnCPUBackendTests, PadModeEdge) {
bool has_constant_value = false;
Expand Down Expand Up @@ -210,6 +235,30 @@ TEST_F(QnnCPUBackendTests, Pad4d) {
ExpectedEPNodeAssignment::All);
}

TEST_F(QnnCPUBackendTests, Pad4dNeg) {
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, -1, 0}),
TestInputDef<float>({1}, true, {0.0f}),
{utils::MakeAttribute("mode", "constant")},
ExpectedEPNodeAssignment::All);
}

TEST_F(QnnCPUBackendTests, Pad4dMix) {
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, {1, 0, 0, -1, 0, 0, -1, 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)),
Expand Down Expand Up @@ -285,6 +334,38 @@ TEST_F(QnnHTPBackendTests, DISABLED_PadReflectMode_FP16_big_data) {
2e-3f);
}

TEST_F(QnnHTPBackendTests, PadNoConstantNegValue_fp16_test) {
bool has_constant_value_input = false;
bool use_htp = true;
bool enable_fp16_precision = true;
RunPadOpTest(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
TestInputDef<int64_t>({4}, true, {0, -1, -1, 0}),
TestInputDef<float>({1}, true, {0.0f}),
{utils::MakeAttribute("mode", "constant")},
ExpectedEPNodeAssignment::All,
has_constant_value_input,
18, // opset
use_htp,
enable_fp16_precision,
2e-3f);
}

TEST_F(QnnHTPBackendTests, PadNoConstantMixValue_fp16_test) {
bool has_constant_value_input = false;
bool use_htp = true;
bool enable_fp16_precision = true;
RunPadOpTest(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
TestInputDef<int64_t>({4}, true, {1, -1, -1, 1}),
TestInputDef<float>({1}, true, {0.0f}),
{utils::MakeAttribute("mode", "constant")},
ExpectedEPNodeAssignment::All,
has_constant_value_input,
18, // opset
use_htp,
enable_fp16_precision,
2e-3f);
}

//
// QDQ Pad
TEST_F(QnnHTPBackendTests, PadNoConstantValue) {
Expand All @@ -297,6 +378,26 @@ TEST_F(QnnHTPBackendTests, PadNoConstantValue) {
has_constant_value_input);
}

TEST_F(QnnHTPBackendTests, PadNoConstantNegValue) {
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, -1, -1, 0}),
TestInputDef<float>({1}, true, {0.0f}),
{utils::MakeAttribute("mode", "constant")},
ExpectedEPNodeAssignment::All,
has_constant_value_input);
}

TEST_F(QnnHTPBackendTests, PadNoConstantMixValue) {
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, {1, -1, -1, 1}),
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;
Expand Down Expand Up @@ -331,6 +432,16 @@ TEST_F(QnnHTPBackendTests, PadReflectMode) {
has_constant_value_input);
}

TEST_F(QnnHTPBackendTests, PadReflectModeNeg) {
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, -1, -1, 0}),
TestInputDef<float>({1}, true, {0.0f}),
{utils::MakeAttribute("mode", "reflect")},
ExpectedEPNodeAssignment::None, // reflect mode doesn't support negative padding value.
has_constant_value_input);
}

// Pad amount should not be greater than shape(input[0])[i] - 1
TEST_F(QnnHTPBackendTests, PadReflectModeOutOfRangePadAmount) {
bool has_constant_value_input = false;
Expand Down Expand Up @@ -389,6 +500,30 @@ TEST_F(QnnHTPBackendTests, Pad4d) {
ExpectedEPNodeAssignment::All);
}

TEST_F(QnnHTPBackendTests, Pad4dNeg) {
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, -1, 0}),
TestInputDef<float>({1}, true, {5.0f}),
{utils::MakeAttribute("mode", "constant")},
ExpectedEPNodeAssignment::All);
}

TEST_F(QnnHTPBackendTests, Pad4dMix) {
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, 1, 0, -1, 0, 1, 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
Expand Down
Loading