Skip to content
1 change: 1 addition & 0 deletions docs/execution_providers/QNN-ExecutionProvider.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ ort.unregister_execution_provider_library(ep_registration_name)
|ai.onnx:MatMulInteger|Supported exclusively via DynamicQuantizeLinear → MatMulInteger fusion pattern|
|ai.onnx:Max||
|ai.onnx:MaxPool||
|ai.onnx:MaxRoiPool|rois must be a constant initializer|
|ai.onnx:Mean||
|ai.onnx:Min||
|ai.onnx:Mod||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ OpBuilderRegistrations::OpBuilderRegistrations() {
CreateLSTMOpBuilder("LSTM", *this);
CreateMatMulOpBuilder("MatMul", *this);
CreateMatMulNBitsOpBuilder("MatMulNBits", *this);
CreateMaxRoiPoolOpBuilder("MaxRoiPool", *this);
CreateMeanOpBuilder("Mean", *this);
CreateModOpBuilder("Mod", *this);
CreateNonZeroOpBuilder("NonZero", *this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ void CreateLRNOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_r
void CreateLSTMOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateMatMulOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateMatMulNBitsOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateMaxRoiPoolOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateMeanOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateModOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateNonZeroOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,6 @@ namespace qnn {

namespace {

// Accepts either a real initializer or a previously-folded STATIC tensor.
Ort::Status GetConstantTensorBytes(QnnModelWrapper& qnn_model_wrapper,
const std::string& tensor_name,
/*out*/ std::vector<uint8_t>& bytes) {
if (qnn_model_wrapper.IsConstantInput(tensor_name)) {
const OrtValueInfo* init = qnn_model_wrapper.GetConstantTensor(tensor_name);
RETURN_IF(init == nullptr, "Constant initializer not found for tensor.");
return qnn_model_wrapper.UnpackInitializerData(init, bytes);
}
if (qnn_model_wrapper.IsFoldedConstant(tensor_name) &&
qnn_model_wrapper.IsQnnTensorWrapperExist(tensor_name)) {
const QnnTensorWrapper& wrapper = qnn_model_wrapper.GetQnnTensorWrapper(tensor_name);
const Qnn_ClientBuffer_t& buf = GetQnnTensorClientBuf(wrapper.GetQnnTensor());
const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(buf.data);
bytes.assign(data_ptr, data_ptr + buf.dataSize);
return Ort::Status();
}
return MAKE_EP_FAIL("Tensor is not a constant initializer or folded constant.");
}

// SafeInt guards against overflow from an adversarial shape before allocation.
Ort::Status ComputeNumElements(gsl::span<const uint32_t> shape, /*out*/ size_t& num_elems) {
SafeInt<size_t> safe_num_elems = 1;
Expand Down Expand Up @@ -108,7 +88,7 @@ Ort::Status FoldConstantDequantizeLinear(QnnModelWrapper& qnn_model_wrapper,
"Folded DequantizeLinear only supports float32 output.");

std::vector<uint8_t> quant_bytes;
RETURN_IF_ERROR(GetConstantTensorBytes(qnn_model_wrapper, input_def.name, quant_bytes));
RETURN_IF_ERROR(qnn_model_wrapper.UnpackEffectiveConstantBytes(input_def.name, quant_bytes));

TensorInfo input_info = {};
RETURN_IF_ERROR(qnn_model_wrapper.GetTensorInfo(input_def, input_info));
Expand Down Expand Up @@ -148,7 +128,7 @@ Ort::Status FoldConstantQuantizeLinear(QnnModelWrapper& qnn_model_wrapper,
RETURN_IF(!output_def.quant_param.has_value(), "Q output has no quant param.");

std::vector<uint8_t> input_bytes;
RETURN_IF_ERROR(GetConstantTensorBytes(qnn_model_wrapper, input_def.name, input_bytes));
RETURN_IF_ERROR(qnn_model_wrapper.UnpackEffectiveConstantBytes(input_def.name, input_bytes));

TensorInfo input_info = {};
RETURN_IF_ERROR(qnn_model_wrapper.GetTensorInfo(input_def, input_info));
Expand Down
17 changes: 17 additions & 0 deletions onnxruntime/core/providers/qnn/builder/qnn_model_wrapper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1093,5 +1093,22 @@ Ort::Status QnnModelWrapper::UnpackInitializerData(const OrtValueInfo* initializ
return Ort::Status();
}

Ort::Status QnnModelWrapper::UnpackEffectiveConstantBytes(const std::string& tensor_name,
std::vector<uint8_t>& bytes) {
if (IsConstantInput(tensor_name)) {
const OrtValueInfo* init = GetConstantTensor(tensor_name);
RETURN_IF(init == nullptr, "Constant initializer not found for tensor.");
return UnpackInitializerData(init, bytes);
}
if (IsFoldedConstant(tensor_name) && IsQnnTensorWrapperExist(tensor_name)) {
const QnnTensorWrapper& wrapper = GetQnnTensorWrapper(tensor_name);
const Qnn_ClientBuffer_t& buf = GetQnnTensorClientBuf(wrapper.GetQnnTensor());
const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(buf.data);
bytes.assign(data_ptr, data_ptr + buf.dataSize);
return Ort::Status();
}
return MAKE_EP_FAIL("Tensor is not a constant initializer or folded constant.");
}

} // namespace qnn
} // namespace onnxruntime
3 changes: 3 additions & 0 deletions onnxruntime/core/providers/qnn/builder/qnn_model_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,9 @@ class QnnModelWrapper {
std::vector<uint8_t>& unpacked_tensor,
const bool unpack_sub_byte_to_8_bit = true) const;

Ort::Status UnpackEffectiveConstantBytes(const std::string& tensor_name,
std::vector<uint8_t>& bytes);

QnnBackendType GetQnnBackendType() const { return qnn_backend_type_; }

const OrtGraph& GetOrtGraph() const { return ort_graph_; }
Expand Down
6 changes: 6 additions & 0 deletions onnxruntime/core/providers/qnn/qnn_execution_provider.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2374,6 +2374,12 @@ OrtStatus* ORT_API_CALL QnnEp::ShouldConvertDataLayoutForOpImpl(_In_ OrtEp* this
*should_convert = 1;
}

if (std::string(domain) == kOnnxDomain && std::string(op_type) == "MaxRoiPool") {
// MaxRoiPool is decomposed into StridedSlice/ReduceMax/Concat, which require the NHWC layout
// for processing.
*should_convert = 1;
}

if (std::string(domain) == kOnnxDomain && std::string(op_type) == "LpPool") {
// LpPool is translated to a QNN AvgPool-based decomposition, which requires the NHWC layout
// for processing.
Expand Down
Loading