Skip to content

Commit 9d650a4

Browse files
authored
[QNN-EP] Add negtive padding value support. (microsoft#25928)
### Description Add negative padding value support for QNN EP by lower padding to pad + stride_slice(if negative padding value exists.) ### Motivation and Context ONNX and QNN complier in QAIRT both supports negative padding value by lowering to slice/resize_crop. Adding this change to align EP with QNN and ONNX --------- Signed-off-by: Mu-Chein Hsu <[email protected]>
1 parent 234cc63 commit 9d650a4

File tree

2 files changed

+247
-5
lines changed

2 files changed

+247
-5
lines changed

onnxruntime/core/providers/qnn/builder/opbuilder/pad_op_builder.cc

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,18 @@ Status PadOpBuilder::ProcessAttributesAndOutputs(QnnModelWrapper& qnn_model_wrap
188188
size_t tensor_byte_size = unpacked_tensor.size();
189189
size_t size = tensor_byte_size / sizeof(int64_t);
190190

191+
bool has_negative = std::any_of(tensor_data, tensor_data + size, [](int64_t item) { return item < 0; });
192+
bool has_positive = std::any_of(tensor_data, tensor_data + size, [](int64_t item) { return item > 0; });
193+
194+
// Zero padding value gives 3110 error on QNN.
195+
if (!has_positive && !has_negative) {
196+
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Got QNN invalid zero only padding value.");
197+
}
198+
191199
std::vector<uint32_t> pad_amount;
192200
std::transform(tensor_data, tensor_data + size, std::back_inserter(pad_amount),
193-
[](int64_t item) { return SafeInt<uint32_t>(item); });
201+
[](int64_t item) { return item < 0 ? SafeInt<uint32_t>(0) : SafeInt<uint32_t>(item); });
202+
194203
// Onnx format is begin_0, begin_1, ..., end_0, end_1, ...
195204
// Qnn format is begin_0, end_0, begin_1, end_1, ...
196205
ReArrangePads(pad_amount);
@@ -200,6 +209,11 @@ Status PadOpBuilder::ProcessAttributesAndOutputs(QnnModelWrapper& qnn_model_wrap
200209

201210
NodeAttrHelper node_helper(node_unit);
202211
std::string mode = node_helper.Get("mode", "constant");
212+
213+
if ("reflect" == mode && has_negative) {
214+
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "reflect mode doesn't support negative padding value.");
215+
}
216+
203217
Qnn_Scalar_t mode_qnn_scalar = QNN_SCALAR_INIT;
204218
mode_qnn_scalar.dataType = QNN_DATATYPE_UINT_32;
205219
if ("constant" == mode) {
@@ -231,10 +245,103 @@ Status PadOpBuilder::ProcessAttributesAndOutputs(QnnModelWrapper& qnn_model_wrap
231245
ORT_RETURN_IF_ERROR(ProcessConstantValue(qnn_model_wrapper, param_tensor_names, node_unit, inputs[2]));
232246
} // constant_value
233247

234-
ORT_RETURN_IF_ERROR(ProcessOutputs(qnn_model_wrapper, node_unit,
235-
std::move(input_names),
236-
std::move(param_tensor_names),
237-
logger, do_op_validation, GetQnnOpType(node_unit.OpType())));
248+
std::vector<uint32_t> pad_output_shape;
249+
if (has_negative) {
250+
for (uint32_t i = 0; i < input_shape.size(); ++i) {
251+
pad_output_shape.push_back(input_shape[i] + pad_amount[2 * i] + pad_amount[2 * i + 1]);
252+
}
253+
}
254+
255+
std::string pad_output_name = utils::GetUniqueName(node_unit, "_Pad_output");
256+
// Step 1. Add pad if has_positive
257+
if (has_positive) {
258+
// Only positive.
259+
if (!has_negative) {
260+
ORT_RETURN_IF_ERROR(ProcessOutputs(qnn_model_wrapper, node_unit,
261+
std::move(input_names),
262+
std::move(param_tensor_names),
263+
logger, do_op_validation, GetQnnOpType(node_unit.OpType())));
264+
// Mixed sign pad value.
265+
} else {
266+
TensorInfo input_info = {};
267+
ORT_RETURN_IF_ERROR(qnn_model_wrapper.GetTensorInfo(node_unit.Inputs()[0], input_info));
268+
269+
std::string pad_name = utils::GetUniqueName(node_unit, "_Pad");
270+
QnnTensorWrapper pad_output(pad_output_name,
271+
QNN_TENSOR_TYPE_NATIVE,
272+
input_info.qnn_data_type,
273+
input_info.quant_param.Copy(),
274+
std::vector<uint32_t>(pad_output_shape));
275+
ORT_RETURN_IF_NOT(qnn_model_wrapper.AddTensorWrapper(std::move(pad_output)),
276+
"Failed to add Pad output tensor.");
277+
ORT_RETURN_IF_NOT(qnn_model_wrapper.CreateQnnNode(pad_name,
278+
QNN_OP_PACKAGE_NAME_QTI_AISW,
279+
GetQnnOpType(node_unit.OpType()),
280+
std::move(input_names),
281+
{pad_output_name},
282+
std::move(param_tensor_names),
283+
do_op_validation),
284+
"Failed to add Pad node.");
285+
}
286+
}
287+
288+
// Step 2. Add Slice if has_negative
289+
if (has_negative) {
290+
TensorInfo output_info = {};
291+
ORT_RETURN_IF_ERROR(qnn_model_wrapper.GetTensorInfo(node_unit.Outputs()[0], output_info));
292+
293+
const std::string& org_output_name = node_unit.Outputs()[0].node_arg.Name();
294+
const bool is_graph_output = qnn_model_wrapper.IsGraphOutput(org_output_name);
295+
Qnn_TensorType_t op_output_tensor_type = is_graph_output ? QNN_TENSOR_TYPE_APP_READ : QNN_TENSOR_TYPE_NATIVE;
296+
297+
// Create output tensor
298+
std::vector<uint32_t> output_shape = output_info.shape;
299+
QnnTensorWrapper org_output(org_output_name,
300+
op_output_tensor_type,
301+
output_info.qnn_data_type,
302+
output_info.quant_param.Copy(),
303+
std::vector<uint32_t>(output_shape));
304+
ORT_RETURN_IF_NOT(qnn_model_wrapper.AddTensorWrapper(std::move(org_output)),
305+
"Failed to add Pad output tensor.");
306+
307+
// Create Slice param
308+
const size_t input_rank = input_shape.size();
309+
std::vector<uint32_t> ranges_dims{static_cast<uint32_t>(input_rank), 3};
310+
std::vector<uint32_t> ranges_data;
311+
ranges_data.reserve(input_rank);
312+
313+
std::vector<int64_t> slice_amount;
314+
std::transform(tensor_data, tensor_data + size, std::back_inserter(slice_amount),
315+
[](int64_t item) { return item > 0 ? SafeInt<int64_t>(0) : SafeInt<int64_t>(item); });
316+
317+
// slice_amount in ONNX format: begin_0, begin_1, ..., end_0, end_1, ...
318+
for (size_t i = 0; i < input_rank; i++) {
319+
ranges_data.push_back(static_cast<uint32_t>(0 - slice_amount[i])); // starts
320+
ranges_data.push_back(static_cast<uint32_t>(static_cast<int64_t>(pad_output_shape[i]) + slice_amount[i + input_rank])); // ends
321+
ranges_data.push_back(static_cast<uint32_t>(1)); // steps
322+
}
323+
324+
QnnParamWrapper ranges_paramwrapper(node_unit.Index(),
325+
node_unit.Name(),
326+
QNN_OP_STRIDED_SLICE_PARAM_RANGES,
327+
std::move(ranges_dims),
328+
std::move(ranges_data),
329+
true);
330+
std::string slice_param_tensor_name(ranges_paramwrapper.GetParamTensorName());
331+
qnn_model_wrapper.AddParamWrapper(std::move(ranges_paramwrapper));
332+
333+
// Create Slice Node
334+
std::vector<std::string> slice_input_name = has_positive ? std::vector<std::string>{pad_output_name} : input_names;
335+
std::string slice_name = utils::GetUniqueName(node_unit, "_Slice");
336+
ORT_RETURN_IF_NOT(qnn_model_wrapper.CreateQnnNode(slice_name,
337+
QNN_OP_PACKAGE_NAME_QTI_AISW,
338+
QNN_OP_STRIDED_SLICE,
339+
std::move(slice_input_name),
340+
{org_output_name},
341+
{slice_param_tensor_name},
342+
do_op_validation),
343+
"Failed to add Pad node.");
344+
}
238345

239346
return Status::OK();
240347
}

onnxruntime/test/providers/qnn/pad_op_test.cpp

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,22 @@ TEST_F(QnnCPUBackendTests, Pad2d) {
152152
ExpectedEPNodeAssignment::All);
153153
}
154154

155+
TEST_F(QnnCPUBackendTests, Pad2dNeg) {
156+
RunPadOpTest(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
157+
TestInputDef<int64_t>({4}, true, {0, -1, -1, 0}),
158+
TestInputDef<float>({1}, true, {0.0f}),
159+
{utils::MakeAttribute("mode", "constant")},
160+
ExpectedEPNodeAssignment::All);
161+
}
162+
163+
TEST_F(QnnCPUBackendTests, Pad2dMix) {
164+
RunPadOpTest(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
165+
TestInputDef<int64_t>({4}, true, {1, -1, -1, 1}),
166+
TestInputDef<float>({1}, true, {0.0f}),
167+
{utils::MakeAttribute("mode", "constant")},
168+
ExpectedEPNodeAssignment::All);
169+
}
170+
155171
// Pad 2d, pads input not initializer
156172
TEST_F(QnnCPUBackendTests, Pad2dPadsNotIni) {
157173
RunPadOpTest(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
@@ -175,6 +191,15 @@ TEST_F(QnnCPUBackendTests, PadModeReflect) {
175191
has_constant_value);
176192
}
177193

194+
TEST_F(QnnCPUBackendTests, PadModeReflectNeg) {
195+
bool has_constant_value = false;
196+
RunPadOpTest(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
197+
TestInputDef<int64_t>({4}, true, {0, 1, -1, 0}),
198+
TestInputDef<float>({1}, true, {0.0f}),
199+
{utils::MakeAttribute("mode", "reflect")}, // reflect mode doesn't support negative padding value.
200+
ExpectedEPNodeAssignment::None,
201+
has_constant_value);
202+
}
178203
// Pad edge mode
179204
TEST_F(QnnCPUBackendTests, PadModeEdge) {
180205
bool has_constant_value = false;
@@ -210,6 +235,30 @@ TEST_F(QnnCPUBackendTests, Pad4d) {
210235
ExpectedEPNodeAssignment::All);
211236
}
212237

238+
TEST_F(QnnCPUBackendTests, Pad4dNeg) {
239+
RunPadOpTest(TestInputDef<float>({1, 2, 2, 2}, false,
240+
{1.0f, 1.0f,
241+
1.0f, 1.0f,
242+
1.0f, 1.0f,
243+
1.0f, 1.0f}),
244+
TestInputDef<int64_t>({8}, true, {0, 0, 0, -1, 0, 0, -1, 0}),
245+
TestInputDef<float>({1}, true, {0.0f}),
246+
{utils::MakeAttribute("mode", "constant")},
247+
ExpectedEPNodeAssignment::All);
248+
}
249+
250+
TEST_F(QnnCPUBackendTests, Pad4dMix) {
251+
RunPadOpTest(TestInputDef<float>({1, 2, 2, 2}, false,
252+
{1.0f, 1.0f,
253+
1.0f, 1.0f,
254+
1.0f, 1.0f,
255+
1.0f, 1.0f}),
256+
TestInputDef<int64_t>({8}, true, {1, 0, 0, -1, 0, 0, -1, 1}),
257+
TestInputDef<float>({1}, true, {0.0f}),
258+
{utils::MakeAttribute("mode", "constant")},
259+
ExpectedEPNodeAssignment::All);
260+
}
261+
213262
// Pad 5d supported
214263
TEST_F(QnnCPUBackendTests, Pad5d) {
215264
RunPadOpTest(TestInputDef<float>({1, 2, 2, 2, 2}, false, GetFloatDataInRange(1.0f, 10.0f, 16)),
@@ -285,6 +334,38 @@ TEST_F(QnnHTPBackendTests, DISABLED_PadReflectMode_FP16_big_data) {
285334
2e-3f);
286335
}
287336

337+
TEST_F(QnnHTPBackendTests, PadNoConstantNegValue_fp16_test) {
338+
bool has_constant_value_input = false;
339+
bool use_htp = true;
340+
bool enable_fp16_precision = true;
341+
RunPadOpTest(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
342+
TestInputDef<int64_t>({4}, true, {0, -1, -1, 0}),
343+
TestInputDef<float>({1}, true, {0.0f}),
344+
{utils::MakeAttribute("mode", "constant")},
345+
ExpectedEPNodeAssignment::All,
346+
has_constant_value_input,
347+
18, // opset
348+
use_htp,
349+
enable_fp16_precision,
350+
2e-3f);
351+
}
352+
353+
TEST_F(QnnHTPBackendTests, PadNoConstantMixValue_fp16_test) {
354+
bool has_constant_value_input = false;
355+
bool use_htp = true;
356+
bool enable_fp16_precision = true;
357+
RunPadOpTest(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
358+
TestInputDef<int64_t>({4}, true, {1, -1, -1, 1}),
359+
TestInputDef<float>({1}, true, {0.0f}),
360+
{utils::MakeAttribute("mode", "constant")},
361+
ExpectedEPNodeAssignment::All,
362+
has_constant_value_input,
363+
18, // opset
364+
use_htp,
365+
enable_fp16_precision,
366+
2e-3f);
367+
}
368+
288369
//
289370
// QDQ Pad
290371
TEST_F(QnnHTPBackendTests, PadNoConstantValue) {
@@ -297,6 +378,26 @@ TEST_F(QnnHTPBackendTests, PadNoConstantValue) {
297378
has_constant_value_input);
298379
}
299380

381+
TEST_F(QnnHTPBackendTests, PadNoConstantNegValue) {
382+
bool has_constant_value_input = false;
383+
RunQDQPadOpTest<uint8_t>(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
384+
TestInputDef<int64_t>({4}, true, {0, -1, -1, 0}),
385+
TestInputDef<float>({1}, true, {0.0f}),
386+
{utils::MakeAttribute("mode", "constant")},
387+
ExpectedEPNodeAssignment::All,
388+
has_constant_value_input);
389+
}
390+
391+
TEST_F(QnnHTPBackendTests, PadNoConstantMixValue) {
392+
bool has_constant_value_input = false;
393+
RunQDQPadOpTest<uint8_t>(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
394+
TestInputDef<int64_t>({4}, true, {1, -1, -1, 1}),
395+
TestInputDef<float>({1}, true, {0.0f}),
396+
{utils::MakeAttribute("mode", "constant")},
397+
ExpectedEPNodeAssignment::All,
398+
has_constant_value_input);
399+
}
400+
300401
TEST_F(QnnHTPBackendTests, PadHasConstantValueNonQuantized) {
301402
bool has_constant_value_input = true;
302403
bool constant_value_quantized = false;
@@ -331,6 +432,16 @@ TEST_F(QnnHTPBackendTests, PadReflectMode) {
331432
has_constant_value_input);
332433
}
333434

435+
TEST_F(QnnHTPBackendTests, PadReflectModeNeg) {
436+
bool has_constant_value_input = false;
437+
RunQDQPadOpTest<uint8_t>(TestInputDef<float>({3, 2}, false, {1.0f, 1.2f, 2.3f, 3.4f, 4.5f, 5.6f}),
438+
TestInputDef<int64_t>({4}, true, {0, -1, -1, 0}),
439+
TestInputDef<float>({1}, true, {0.0f}),
440+
{utils::MakeAttribute("mode", "reflect")},
441+
ExpectedEPNodeAssignment::None, // reflect mode doesn't support negative padding value.
442+
has_constant_value_input);
443+
}
444+
334445
// Pad amount should not be greater than shape(input[0])[i] - 1
335446
TEST_F(QnnHTPBackendTests, PadReflectModeOutOfRangePadAmount) {
336447
bool has_constant_value_input = false;
@@ -389,6 +500,30 @@ TEST_F(QnnHTPBackendTests, Pad4d) {
389500
ExpectedEPNodeAssignment::All);
390501
}
391502

503+
TEST_F(QnnHTPBackendTests, Pad4dNeg) {
504+
RunQDQPadOpTest<uint8_t>(TestInputDef<float>({1, 2, 2, 2}, false,
505+
{1.0f, 2.0f,
506+
3.0f, 4.0f,
507+
5.0f, 6.0f,
508+
7.0f, 8.0f}),
509+
TestInputDef<int64_t>({8}, true, {0, 0, 0, -1, 0, 0, -1, 0}),
510+
TestInputDef<float>({1}, true, {5.0f}),
511+
{utils::MakeAttribute("mode", "constant")},
512+
ExpectedEPNodeAssignment::All);
513+
}
514+
515+
TEST_F(QnnHTPBackendTests, Pad4dMix) {
516+
RunQDQPadOpTest<uint8_t>(TestInputDef<float>({1, 2, 2, 2}, false,
517+
{1.0f, 2.0f,
518+
3.0f, 4.0f,
519+
5.0f, 6.0f,
520+
7.0f, 8.0f}),
521+
TestInputDef<int64_t>({8}, true, {0, 1, 0, -1, 0, 1, 0, 1}),
522+
TestInputDef<float>({1}, true, {5.0f}),
523+
{utils::MakeAttribute("mode", "constant")},
524+
ExpectedEPNodeAssignment::All);
525+
}
526+
392527
// Inaccuracy detected for output 'output', element 0.
393528
// Output quant params: scale=0.035294119268655777, zero_point=0.
394529
// Expected val: 9

0 commit comments

Comments
 (0)