Skip to content

Commit e7d7194

Browse files
committed
[QNN-EP] Support for Upsample operator
1 parent 9dcb99c commit e7d7194

File tree

5 files changed

+556
-0
lines changed

5 files changed

+556
-0
lines changed

onnxruntime/core/providers/qnn/builder/op_builder_factory.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ OpBuilderRegistrations::OpBuilderRegistrations() {
132132
CreateResizeOpBuilder("Resize", *this);
133133
}
134134

135+
{
136+
CreateUpsampleOpBuilder("Upsample", *this);
137+
}
138+
135139
{
136140
CreateTopKOpBuilder("TopK", *this);
137141
}

onnxruntime/core/providers/qnn/builder/op_builder_factory.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ void CreateSplitOpBuilder(const std::string& op_type, OpBuilderRegistrations& op
7575

7676
void CreateResizeOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
7777

78+
void CreateUpsampleOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
79+
7880
void CreateTopKOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
7981

8082
void CreateTileOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);

onnxruntime/core/providers/qnn/builder/opbuilder/base_op_builder.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ class BaseOpBuilder : public IOpBuilder {
187187

188188
{"Reshape", QNN_OP_RESHAPE},
189189
{"Resize", QNN_OP_RESIZE},
190+
{"Upsample", QNN_OP_RESIZE},
190191
{"Flatten", QNN_OP_RESHAPE},
191192
{"Squeeze", QNN_OP_RESHAPE},
192193
{"Unsqueeze", QNN_OP_RESHAPE},
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
#include <cassert>
5+
#include <unordered_map>
6+
7+
#include "core/providers/qnn/builder/opbuilder/base_op_builder.h"
8+
#include "core/providers/qnn/builder/qnn_utils.h"
9+
#include "core/providers/qnn/builder/qnn_model_wrapper.h"
10+
#include "core/providers/qnn/builder/op_builder_factory.h"
11+
12+
namespace onnxruntime {
13+
namespace qnn {
14+
15+
class UpsampleOpBuilder : public BaseOpBuilder {
16+
public:
17+
UpsampleOpBuilder() : BaseOpBuilder("UpsampleOpBuilder") {}
18+
ORT_DISALLOW_COPY_ASSIGNMENT_AND_MOVE(UpsampleOpBuilder);
19+
20+
Status IsOpSupported(QnnModelWrapper& qnn_model_wrapper,
21+
const NodeUnit& node_unit,
22+
const logging::Logger& logger) const override final ORT_MUST_USE_RESULT;
23+
24+
protected:
25+
Status ProcessInputs(QnnModelWrapper& qnn_model_wrapper,
26+
const NodeUnit& node_unit,
27+
const logging::Logger& logger,
28+
std::vector<std::string>& input_names,
29+
bool do_op_validation) const override ORT_MUST_USE_RESULT;
30+
31+
Status ProcessAttributesAndOutputs(QnnModelWrapper& qnn_model_wrapper,
32+
const NodeUnit& node_unit,
33+
std::vector<std::string>&& input_names,
34+
const logging::Logger& logger,
35+
bool do_op_validation) const override ORT_MUST_USE_RESULT;
36+
37+
Status OverrideOutputQuantParam(QnnModelWrapper& qnn_model_wrapper,
38+
const NodeUnit& node_unit,
39+
const logging::Logger& logger,
40+
const std::vector<std::string>& input_names,
41+
size_t output_index,
42+
Qnn_DataType_t qnn_data_type,
43+
QnnQuantParamsWrapper& quant_param) const override ORT_MUST_USE_RESULT;
44+
45+
private:
46+
const std::unordered_map<std::string, uint32_t> supported_modes = {
47+
{"nearest", QNN_OP_RESIZE_INTERPOLATION_MODE_NEAREST},
48+
{"linear", QNN_OP_RESIZE_INTERPOLATION_MODE_LINEAR},
49+
{"cubic", QNN_OP_RESIZE_INTERPOLATION_MODE_CUBIC}};
50+
51+
// Info for Onnx Upsample attribute {"<attribute_name>", <default_value>}
52+
const OnnxAttrInfo<std::string> onnx_mode_attr = {"mode", "nearest"};
53+
};
54+
55+
static Status AddQnnScalar(QnnModelWrapper& qnn_model_wrapper,
56+
const NodeUnit& node_unit,
57+
std::vector<std::string>& param_tensor_names,
58+
const Qnn_Scalar_t& qnn_scalar,
59+
const std::string& qnn_scalar_param_name) {
60+
QnnParamWrapper qnn_param_wrapper(node_unit.Index(), node_unit.Name(), qnn_scalar_param_name, qnn_scalar);
61+
param_tensor_names.push_back(qnn_param_wrapper.GetParamTensorName());
62+
qnn_model_wrapper.AddParamWrapper(std::move(qnn_param_wrapper));
63+
64+
return Status::OK();
65+
}
66+
67+
Status UpsampleOpBuilder::IsOpSupported(QnnModelWrapper& qnn_model_wrapper,
68+
const NodeUnit& node_unit,
69+
const logging::Logger& logger) const {
70+
// Resize ops are sensitive with data layout, no special validation so far
71+
// The nodes from 1st call of GetCapability do not get layout transformer applied, it's still NCHW
72+
// The nodes from 2nd call of GetCapability get layout transformer applied, it's NHWC
73+
// Need to do op validation in 1st call of GetCapability
74+
if (node_unit.Domain() == kMSInternalNHWCDomain) {
75+
return AddToModelBuilder(qnn_model_wrapper, node_unit, logger, true);
76+
}
77+
78+
const bool is_npu_backend = IsNpuBackend(qnn_model_wrapper.GetQnnBackendType());
79+
NodeAttrHelper node_helper(node_unit);
80+
81+
// Check mode
82+
const std::string interp_mode = GetOnnxAttr(node_helper, onnx_mode_attr);
83+
ORT_RETURN_IF_NOT(supported_modes.find(interp_mode) != supported_modes.end(),
84+
"QNN EP: Resize does not support mode ", interp_mode.c_str());
85+
86+
const auto& input_0 = node_unit.Inputs()[0];
87+
std::vector<uint32_t> input_shape;
88+
ORT_RETURN_IF_NOT(qnn_model_wrapper.GetOnnxShape(input_0.node_arg, input_shape),
89+
"QNN EP: Cannot get input shape for Onnx Upsample ", input_0.node_arg.Name().c_str());
90+
const size_t input_rank = input_shape.size();
91+
92+
ORT_RETURN_IF(is_npu_backend && (input_rank < 3 || input_rank > 5),
93+
"QNN EP: The input rank for Resize must be at least 3 and no greater than 5 on the HTP.");
94+
95+
const auto& output_0 = node_unit.Outputs()[0];
96+
std::vector<uint32_t> output_shape;
97+
ORT_RETURN_IF_NOT(qnn_model_wrapper.GetOnnxShape(output_0.node_arg, output_shape),
98+
"QNN EP: Cannot get output shape for Onnx Upsample ", output_0.node_arg.Name().c_str(),
99+
". Dynamic scales input is not supported in QNN EP.");
100+
101+
// Check that only the spatial dimensions (width, height) are resized. The batch_size (N) and channels (C) should
102+
// be untouched. This code runs before layout transformation, so we know that the current layout is "channel first"
103+
// (e.g., N, C, S1, S2, ..., SN).
104+
ORT_RETURN_IF_NOT(input_shape[0] == output_shape[0] && input_shape[1] == output_shape[1],
105+
"QNN EP: Resize may only change the spatial dimensions.");
106+
107+
return Status::OK();
108+
}
109+
110+
Status UpsampleOpBuilder::ProcessInputs(QnnModelWrapper& qnn_model_wrapper,
111+
const NodeUnit& node_unit,
112+
const logging::Logger& logger,
113+
std::vector<std::string>& input_names,
114+
bool do_op_validation) const {
115+
ORT_UNUSED_PARAMETER(do_op_validation);
116+
117+
const auto& inputs = node_unit.Inputs();
118+
119+
// Only need to consider the first input of Onnx upsample.
120+
ORT_RETURN_IF_ERROR(ProcessInput(qnn_model_wrapper, inputs[0], logger, input_names));
121+
122+
return Status::OK();
123+
}
124+
125+
Status UpsampleOpBuilder::ProcessAttributesAndOutputs(QnnModelWrapper& qnn_model_wrapper,
126+
const NodeUnit& node_unit,
127+
std::vector<std::string>&& input_names,
128+
const logging::Logger& logger,
129+
bool do_op_validation) const {
130+
std::vector<std::string> param_tensor_names;
131+
NodeAttrHelper node_helper(node_unit);
132+
const std::string interp_mode = GetOnnxAttr(node_helper, onnx_mode_attr);
133+
134+
const auto& input_0 = node_unit.Inputs()[0];
135+
std::vector<uint32_t> input_shape;
136+
ORT_RETURN_IF_NOT(qnn_model_wrapper.GetOnnxShape(input_0.node_arg, input_shape),
137+
"QNN EP: Cannot get input shape for Onnx Upsample ", input_0.node_arg.Name().c_str());
138+
139+
const size_t input_rank = input_shape.size();
140+
const bool is_npu_backend = IsNpuBackend(qnn_model_wrapper.GetQnnBackendType());
141+
std::string qnn_op_type = QNN_OP_RESIZE;
142+
143+
if (is_npu_backend && input_rank == 4 && interp_mode != "cubic") {
144+
// Translate QNN's Resize to QNN's ResizeNearestNeighbor/ResizeBilinear to achieve better performance on
145+
// the HTP backend. QNN's ResizeNearestNeighbor and ResizeBilinear are only supported when input rank is 4.
146+
qnn_op_type = (interp_mode == "nearest") ? QNN_OP_RESIZE_NEAREST_NEIGHBOR : QNN_OP_RESIZE_BILINEAR;
147+
148+
// Parameter 'align_corners'
149+
Qnn_Scalar_t qnn_align_corners = QNN_SCALAR_INIT;
150+
qnn_align_corners.dataType = QNN_DATATYPE_BOOL_8;
151+
qnn_align_corners.bool8Value = false;
152+
const std::string align_corners_param_name = (qnn_op_type == QNN_OP_RESIZE_BILINEAR)
153+
? QNN_OP_RESIZE_BILINEAR_PARAM_ALIGN_CORNERS
154+
: QNN_OP_RESIZE_NEAREST_NEIGHBOR_PARAM_ALIGN_CORNERS;
155+
156+
ORT_RETURN_IF_ERROR(AddQnnScalar(qnn_model_wrapper, node_unit, param_tensor_names,
157+
qnn_align_corners, align_corners_param_name));
158+
159+
// Parameter 'half_pixel_centers'
160+
Qnn_Scalar_t qnn_half_pixel_centers = QNN_SCALAR_INIT;
161+
qnn_half_pixel_centers.dataType = QNN_DATATYPE_BOOL_8;
162+
qnn_half_pixel_centers.bool8Value = false;
163+
const std::string half_pixel_centers_param_name = (qnn_op_type == QNN_OP_RESIZE_BILINEAR)
164+
? QNN_OP_RESIZE_BILINEAR_PARAM_HALF_PIXEL_CENTERS
165+
: QNN_OP_RESIZE_NEAREST_NEIGHBOR_PARAM_HALF_PIXEL_CENTERS;
166+
167+
ORT_RETURN_IF_ERROR(AddQnnScalar(qnn_model_wrapper, node_unit, param_tensor_names,
168+
qnn_half_pixel_centers, half_pixel_centers_param_name));
169+
170+
if (qnn_op_type == QNN_OP_RESIZE_BILINEAR) {
171+
// Parameter 'antialias'
172+
Qnn_Scalar_t qnn_antialias = QNN_SCALAR_INIT;
173+
qnn_antialias.dataType = QNN_DATATYPE_BOOL_8;
174+
qnn_antialias.bool8Value = false;
175+
176+
ORT_RETURN_IF_ERROR(AddQnnScalar(qnn_model_wrapper, node_unit, param_tensor_names,
177+
qnn_antialias, QNN_OP_RESIZE_BILINEAR_PARAM_ANTIALIAS));
178+
}
179+
}
180+
else {
181+
// Remain as QNN's Resize
182+
// Parameter 'exclude_outside'
183+
Qnn_Scalar_t qnn_exclude_outside = QNN_SCALAR_INIT;
184+
qnn_exclude_outside.dataType = QNN_DATATYPE_BOOL_8;
185+
qnn_exclude_outside.bool8Value = false;
186+
187+
ORT_RETURN_IF_ERROR(AddQnnScalar(qnn_model_wrapper, node_unit, param_tensor_names,
188+
qnn_exclude_outside, QNN_OP_RESIZE_PARAM_EXCLUDE_OUTSIDE));
189+
190+
// Parameter 'transformation_mode'
191+
Qnn_Scalar_t qnn_transformation_mode = QNN_SCALAR_INIT;
192+
qnn_transformation_mode.dataType = QNN_DATATYPE_UINT_32;
193+
qnn_transformation_mode.uint32Value = QNN_OP_RESIZE_TRANSFORMATION_MODE_ASYMMETRIC;
194+
195+
ORT_RETURN_IF_ERROR(AddQnnScalar(qnn_model_wrapper, node_unit, param_tensor_names,
196+
qnn_transformation_mode, QNN_OP_RESIZE_PARAM_TRANSFORMATION_MODE));
197+
198+
// Parameter 'interpolation_mode'
199+
Qnn_Scalar_t qnn_interp_mode = QNN_SCALAR_INIT;
200+
qnn_interp_mode.dataType = QNN_DATATYPE_UINT_32;
201+
qnn_interp_mode.uint32Value = supported_modes.at(interp_mode);
202+
203+
ORT_RETURN_IF_ERROR(AddQnnScalar(qnn_model_wrapper, node_unit, param_tensor_names,
204+
qnn_interp_mode, QNN_OP_RESIZE_PARAM_INTERPOLATION_MODE));
205+
206+
// Parameter 'nearest_mode'. Process only when 'interpolation_mode' is NEAREST.
207+
if (qnn_interp_mode.uint32Value == QNN_OP_RESIZE_INTERPOLATION_MODE_NEAREST) {
208+
Qnn_Scalar_t qnn_nearest_mode = QNN_SCALAR_INIT;
209+
qnn_nearest_mode.dataType = QNN_DATATYPE_UINT_32;
210+
qnn_nearest_mode.uint32Value = QNN_OP_RESIZE_NEAREST_MODE_ROUND_PREFER_FLOOR;
211+
212+
ORT_RETURN_IF_ERROR(AddQnnScalar(qnn_model_wrapper, node_unit, param_tensor_names,
213+
qnn_nearest_mode, QNN_OP_RESIZE_PARAM_NEAREST_MODE));
214+
}
215+
}
216+
217+
return ProcessOutputs(qnn_model_wrapper, node_unit, std::move(input_names), std::move(param_tensor_names),
218+
logger, do_op_validation, qnn_op_type);
219+
}
220+
221+
Status UpsampleOpBuilder::OverrideOutputQuantParam(QnnModelWrapper& qnn_model_wrapper,
222+
const NodeUnit& node_unit,
223+
const logging::Logger& logger,
224+
const std::vector<std::string>& input_names,
225+
size_t output_index,
226+
Qnn_DataType_t qnn_data_type,
227+
QnnQuantParamsWrapper& quant_param) const {
228+
if (!quant_param.IsPerTensor()) {
229+
return Status::OK();
230+
}
231+
232+
// Force Resize op's output to use the same quantization parameters as the input if nearly equal.
233+
// This helps the HTP backend employ certain optimizations.
234+
return SetOutputQParamEqualToInputIfNearlyEqual(qnn_model_wrapper, node_unit, logger, input_names,
235+
0 /*input_index*/, output_index, qnn_data_type, quant_param);
236+
}
237+
238+
void CreateUpsampleOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) {
239+
op_registrations.AddOpBuilder(op_type, std::make_unique<UpsampleOpBuilder>());
240+
}
241+
242+
} // namespace qnn
243+
} // namespace onnxruntime

0 commit comments

Comments
 (0)