Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -672,13 +672,51 @@ Status ConvOpBuilder::ProcessAttributesAndOutputs(QnnModelWrapper& qnn_model_wra
ORT_RETURN_IF(auto_pad != "NOTSET" && auto_pad != "SAME_LOWER" && auto_pad != "SAME_UPPER" && auto_pad != "VALID",
"QNN Conv operators do not support 'auto_pad' value: ", auto_pad.c_str());

if (auto_pad != "NOTSET") {
std::vector<int64_t> output_shape_attribute_value = node_helper.Get("output_shape", std::vector<int64_t>());
bool has_output_shape_attr = !output_shape_attribute_value.empty();

if (conv_type == OnnxConvType::kConvTranspose && has_output_shape_attr) {
// Pads are auto generated using the formula:
// total_padding[i] = stride[i] * (input_size[i] - 1) + output_padding[i] + ((kernel_shape[i] - 1) * dilations[i] + 1) - output_shape[i]
// Then distributed using auto_pad rules.

LOGS(logger, VERBOSE) << "ConvTranspose with 'output_shape' attribute. Calculating pads since output_shape is specified, pad values are ignored";

// input_dims for calculation are (H, W, D...) excluding N, C
std::vector<uint32_t> input_dims(input_0_shape.begin() + 1, input_0_shape.end() - 1);

if (is_1d_conv) { // Adjust input_dims and output_shape_attribute_value for 1D conv logic
input_dims.insert(input_dims.begin(), 1);
output_shape_attribute_value.insert(output_shape_attribute_value.begin(), 1);
}

pads.assign(kernel_shape.size() * 2, 0); // Reset pads before filling
size_t rank = input_dims.size();

ORT_RETURN_IF_NOT(rank == output_shape_attribute_value.size(),
"QNN EP: ConvTranspose 'output_shape' attribute rank mismatch "
"with input dims for padding calculation.");

for (size_t dim = 0; dim < rank; ++dim) {
int64_t pad_head = 0;
int64_t pad_tail = 0;
AutoPadType pad_type = StringToAutoPadType(auto_pad); // Use current auto_pad for distribution

auto total_pad = ComputeTotalPad(input_dims[dim], strides[dim], output_padding[dim],
kernel_shape[dim], dilations[dim], output_shape_attribute_value[dim]);
DistributePadding(pad_type, total_pad, pad_head, pad_tail);

pads[dim] = narrow<uint32_t>(pad_head);
pads[rank + dim] = narrow<uint32_t>(pad_tail);
}

} else if (auto_pad != "NOTSET") { // Case: auto_pad is SAME_UPPER/LOWER/VALID, no output_shape attribute
auto pad_type = StringToAutoPadType(auto_pad);
// skip N, C, input0 shape NHWC
std::vector<uint32_t> input_dims(input_0_shape.begin() + 1, input_0_shape.end() - 1);
std::vector<uint32_t> output_dims(output_shape.begin() + 1, output_shape.end() - 1);
if (is_1d_conv) {
// insert Hight = 1 for 1D
// insert Height = 1 for 1D
input_dims.insert(input_dims.begin(), 1);
output_dims.insert(output_dims.begin(), 1);
}
Expand Down
85 changes: 77 additions & 8 deletions onnxruntime/test/providers/qnn/conv_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ static GetTestModelFn BuildF32ConvTestCase(const std::string& conv_op_type, cons
const std::vector<int64_t>& dilations,
std::optional<int64_t> group,
const std::string& auto_pad = "NOTSET",
std::optional<OutputActivationInfo> output_activation = std::nullopt) {
std::optional<OutputActivationInfo> output_activation = std::nullopt,
std::optional<std::vector<int64_t>> output_shape = std::nullopt) {
return [conv_op_type, input_def, weights_def, bias_def, strides, pads,
dilations, group, auto_pad, output_activation](ModelTestBuilder& builder) {
dilations, group, auto_pad, output_activation, output_shape](ModelTestBuilder& builder) {
std::vector<NodeArg*> conv_inputs = {
MakeTestInput(builder, input_def),
MakeTestInput(builder, weights_def)};
Expand Down Expand Up @@ -62,6 +63,10 @@ static GetTestModelFn BuildF32ConvTestCase(const std::string& conv_op_type, cons
conv_node.AddAttribute("dilations", dilations);
}

if (output_shape.has_value()) {
conv_node.AddAttribute("output_shape", output_shape.value());
}

if (output_activation.has_value()) {
NodeArg* output = builder.MakeOutput();
std::vector<NodeArg*> activation_inputs = {conv_output};
Expand Down Expand Up @@ -113,11 +118,12 @@ static GetTestQDQModelFn<ActivationQType> BuildQDQConvTestCase(
std::optional<int64_t> group,
const std::string& auto_pad = "NOTSET",
bool use_contrib_qdq = false,
std::optional<OutputActivationInfo> output_activation = std::nullopt) {
std::optional<OutputActivationInfo> output_activation = std::nullopt,
std::optional<std::vector<int64_t>> output_shape = std::nullopt) {
return [conv_op_type, input_def, weights_def, bias_def, strides, pads,
dilations, group, auto_pad,
use_contrib_qdq, output_activation](ModelTestBuilder& builder,
std::vector<QuantParams<ActivationQType>>& output_qparams) {
use_contrib_qdq, output_activation, output_shape](ModelTestBuilder& builder,
std::vector<QuantParams<ActivationQType>>& output_qparams) {
std::vector<NodeArg*> conv_inputs;

// input -> Q/DQ ->
Expand Down Expand Up @@ -160,6 +166,9 @@ static GetTestQDQModelFn<ActivationQType> BuildQDQConvTestCase(
if (!dilations.empty()) {
conv_node.AddAttribute("dilations", dilations);
}
if (output_shape.has_value()) {
conv_node.AddAttribute("output_shape", output_shape.value());
}

NodeArg* q_input = conv_output;
if (output_activation.has_value()) {
Expand Down Expand Up @@ -307,17 +316,18 @@ static void RunHTPConvOpTest(const std::string& conv_op_type, const TestInputDef
bool use_contrib_qdq = false,
int opset = 13,
QDQTolerance tolerance = QDQTolerance(),
std::optional<OutputActivationInfo> output_activation = std::nullopt) {
std::optional<OutputActivationInfo> output_activation = std::nullopt,
std::optional<std::vector<int64_t>> output_shape = std::nullopt) {
ProviderOptions provider_options;
provider_options["backend_type"] = "htp";
provider_options["offload_graph_io_quantization"] = "0";

TestQDQModelAccuracy(BuildF32ConvTestCase(conv_op_type, input_def, weights_def, bias_def, strides, pads, dilations,
group, auto_pad, output_activation),
group, auto_pad, output_activation, output_shape),
BuildQDQConvTestCase<ActivationQType, WeightQType>(conv_op_type, input_def, weights_def,
bias_def, strides, pads, dilations,
group, auto_pad, use_contrib_qdq,
output_activation),
output_activation, output_shape),
provider_options,
opset,
expected_ep_assignment,
Expand Down Expand Up @@ -2169,6 +2179,65 @@ TEST_F(QnnHTPBackendTests, ConvTransposeU8U8S32_AutoPadValid) {
13);
}

// Test ConvTranspose with output_shape attribute
// This test verifies that when 'output_shape' is provided, the QNN EP correctly
// calculates and applies padding for ConvTranspose, overriding any 'pads' attribute,
// and correctly distributes the padding according to 'auto_pad' rules.
TEST_F(QnnHTPBackendTests, ConvTransposeU8U8S32_OutputShape) {
std::vector<int64_t> output_shape = {6, 6};
RunHTPConvOpTest<uint8_t, uint8_t>("ConvTranspose",
TestInputDef<float>({1, 1, 4, 4}, false, 0.f, 10.f), // Dynamic input
TestInputDef<float>({1, 1, 2, 2}, true, -1.f, 1.f), // Static weights
TestInputDef<float>({1}, true, {1.0f}), // Initializer bias
{2, 2}, // strides
{0, 0, 0, 0}, // pads
{1, 1}, // dilations
1, // group
"SAME_UPPER", // auto_pad
ExpectedEPNodeAssignment::All,
false, // use_contrib_qdq
13, // opset
QDQTolerance(),
std::nullopt, // No output activation
output_shape); // Pass the output_shape attribute

std::vector<int64_t> output_shape_3d = {6, 6, 6};
RunHTPConvOpTest<uint8_t, uint8_t>("ConvTranspose",
TestInputDef<float>({1, 1, 4, 4, 4}, false, 0.f, 10.f), // Dynamic input
TestInputDef<float>({1, 1, 2, 2, 2}, true, -1.f, 1.f), // Static weights
TestInputDef<float>({1}, true, {1.0f}), // Initializer bias
{2, 2, 2}, // strides
{0, 0, 0, 0, 0, 0}, // pads
{1, 1, 1}, // dilations
1, // group
"SAME_UPPER", // auto_pad
ExpectedEPNodeAssignment::All,
false, // use_contrib_qdq
13, // opset
QDQTolerance(),
std::nullopt, // No output activation
output_shape_3d); // Pass the output_shape attribute
}

TEST_F(QnnHTPBackendTests, ConvTranspose1DU8U8S32_OutputShape) {
std::vector<int64_t> output_shape = {6};
RunHTPConvOpTest<uint8_t, uint8_t>("ConvTranspose",
TestInputDef<float>({1, 1, 4}, false, 0.f, 10.f), // Dynamic input
TestInputDef<float>({1, 1, 2}, true, -1.f, 1.f), // Static weights
TestInputDef<float>({1}, true, {1.0f}), // Initializer bias
{2}, // strides
{0, 0}, // pads
{1}, // dilations
1, // group
"SAME_UPPER", // auto_pad
ExpectedEPNodeAssignment::All,
false, // use_contrib_qdq
13, // opset
QDQTolerance(),
std::nullopt, // No output activation
output_shape); // Pass the output_shape attribute
}

// Tests Conv1d auto_pad value "VALID" on HTP backend (compares to CPU EP).
TEST_F(QnnHTPBackendTests, Conv1DU8U8S32_AutoPadValid) {
std::vector<float> input_data = {0.f, 1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f};
Expand Down