[QNN EP] Integrate Resize op fixes from QNN 2.14.1 (#17641)

### Description
QNN SDK version 2.14.1 fixed several issues with the QNN Resize
operator. This PR integrates the fixes and simplifies the
implementation.

### Motivation and Context
Improve Resize operator and test coverage.
This commit is contained in:
Adrian Lizarraga 2023-09-22 10:52:47 -07:00 committed by GitHub
parent 6d7bc2a097
commit e70a23f8dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 313 additions and 338 deletions

View file

@ -2,7 +2,8 @@
// Licensed under the MIT License.
#include <array>
#include <string_view>
#include <cassert>
#include <unordered_map>
#include "core/providers/common.h"
#include "core/providers/shared/utils/utils.h"
@ -42,76 +43,6 @@ class ResizeOpBuilder : public BaseOpBuilder {
bool do_op_validation) const override ORT_MUST_USE_RESULT;
private:
/**
* Returns the QNN integer value that corresponds to the given ONNX mode (string).
*
* /param onnx_modes Array of ONNX modes supported by QNN. The index of each mode corresponds to the QNN value.
* /param onnx_mode The ONNX mode for which to get the corresponding QNN value.
* /param onnx_model_label Mode label to print out in case of error (e.g., "nearest_mode").
* /param qnn_mode Output parameter that is set to the appropriate QNN value from the given ONNX mode.
*
* /returns A status indicating failure or success.
*/
template <typename QnnValType, std::size_t N>
Status GetQnnModeFromString(const std::array<std::string_view, N>& onnx_modes, std::string_view onnx_mode,
const char* onnx_mode_label, QnnValType& qnn_mode) const ORT_MUST_USE_RESULT;
/**
* Called by IsOpSupported to validate the op for non-quantized models.
*
* /param qnn_model_wrapper The QNN model wrapper instance.
* /param node_unit The node unit containing metadata for the ONNX Resize operator.
*
* /returns A status indicating failure or success.
*/
Status ValidateOp(QnnModelWrapper& qnn_model_wrapper, const NodeUnit& node_unit) const ORT_MUST_USE_RESULT;
/**
* Called by IsOpSupported to validate the op for quantized models.
*
* /param qnn_model_wrapper The QNN model wrapper instance.
* /param node_unit The node unit containing metadata for the ONNX Resize operator and its Q/DQ nodes.
*
* /returns A status indicating failure or success.
*/
Status ValidateQDQOp(QnnModelWrapper& qnn_model_wrapper, const NodeUnit& node_unit) const ORT_MUST_USE_RESULT;
/**
* Called by ProcessAttributesAndOutputs to process the op's attributes and outputs
* for non-quantized models.
*
* /param qnn_model_wrapper The QNN model wrapper instance.
* /param node_unit The node unit containing metadata for the ONNX Resize operator.
* /param input_names The operator's input names.
* /param logger A logger.
* /param do_op_validation Set to true if the op should be validated using QNN's validation API.
*
* /returns A status indicating failure or success.
*/
Status ProcessOpAttrsAndOutputs(QnnModelWrapper& qnn_model_wrapper,
const NodeUnit& node_unit,
std::vector<std::string>&& input_names,
const logging::Logger& logger,
bool do_op_validation) const ORT_MUST_USE_RESULT;
/**
* Called by ProcessAttributesAndOutputs to process the op's attributes and outputs
* for quantized models.
*
* /param qnn_model_wrapper The QNN model wrapper instance.
* /param node_unit The node unit containing metadata for the ONNX Resize operator and its Q/DQ nodes.
* /param input_names The operator's input names.
* /param logger A logger.
* /param do_op_validation Set to true if the op should be validated using QNN's validation API.
*
* /returns A status indicating failure or success.
*/
Status ProcessQDQOpAttrsAndOutputs(QnnModelWrapper& qnn_model_wrapper,
const NodeUnit& node_unit,
std::vector<std::string>&& input_names,
const logging::Logger& logger,
bool do_op_validation) const ORT_MUST_USE_RESULT;
// Info for each ONNX attribute of interest (attribute name + default value)
static const OnnxAttrInfo<std::string> onnx_mode_attr;
static const OnnxAttrInfo<std::string> onnx_coord_transf_mode_attr;
@ -119,22 +50,30 @@ class ResizeOpBuilder : public BaseOpBuilder {
static const OnnxAttrInfo<int64_t> onnx_antialias_attr;
static const OnnxAttrInfo<int64_t> onnx_exclude_outside_attr;
// Arrays of supported QNN modes for QNN's Resize op. The index of each mode is used as the corresponding
// QNN parameter value. Ex: The "nearest" mode is represented as the value 0 in QNN. Note, that
// not all modes are supported by every QNN backend.
// QNN values: NEAREST = 0, LINEAR = 1
static constexpr std::array<std::string_view, 2> supported_modes = {"nearest", "linear"};
// QNN values: HALF_PIXEL = 0, PYTORCH_HALF_PIXEL = 1, ALIGN_CORNERS = 2, ASYMMETRIC = 3
static constexpr std::array<std::string_view, 4> supported_coord_transf_modes = {"half_pixel", "pytorch_half_pixel",
"align_corners", "asymmetric"};
// QNN values: ROUND_PREFER_FLOOR = 0, ROUND_PREFER_CEIL = 1, FLOOR = 2, CEIL = 3
static constexpr std::array<std::string_view, 4> supported_nearest_modes = {"round_prefer_floor", "round_prefer_ceil",
"floor", "ceil"};
// Tables that map an ONNX attribute value (string) to the corresponding integer (enum) QNN parameter value.
// Ex: The "half_pixel" coordinate_transformation_mode is represented as the value 0 in QNN.
// Only the modes supported by QNN Resize are mapped by these tables.
static const std::unordered_map<std::string, uint32_t> supported_modes;
static const std::unordered_map<std::string, uint32_t> supported_coord_transf_modes;
static const std::unordered_map<std::string, uint32_t> supported_nearest_modes;
};
const std::unordered_map<std::string, uint32_t> ResizeOpBuilder::supported_modes = {
{"nearest", QNN_OP_RESIZE_INTERPOLATION_MODE_NEAREST},
{"linear", QNN_OP_RESIZE_INTERPOLATION_MODE_LINEAR}};
const std::unordered_map<std::string, uint32_t> ResizeOpBuilder::supported_coord_transf_modes = {
{"half_pixel", QNN_OP_RESIZE_TRANSFORMATION_MODE_HALF_PIXEL},
{"pytorch_half_pixel", QNN_OP_RESIZE_TRANSFORMATION_MODE_PYTORCH_HALF_PIXEL},
{"align_corners", QNN_OP_RESIZE_TRANSFORMATION_MODE_ALIGN_CORNERS},
{"asymmetric", QNN_OP_RESIZE_TRANSFORMATION_MODE_ASYMMETRIC}};
const std::unordered_map<std::string, uint32_t> ResizeOpBuilder::supported_nearest_modes = {
{"round_prefer_floor", QNN_OP_RESIZE_NEAREST_MODE_ROUND_PREFER_FLOOR},
{"round_prefer_ceil", QNN_OP_RESIZE_NEAREST_MODE_ROUND_PREFER_CEIL},
{"floor", QNN_OP_RESIZE_NEAREST_MODE_FLOOR},
{"ceil", QNN_OP_RESIZE_NEAREST_MODE_CEIL}};
const OnnxAttrInfo<std::string> ResizeOpBuilder::onnx_mode_attr = {"mode", "nearest"};
const OnnxAttrInfo<std::string> ResizeOpBuilder::onnx_coord_transf_mode_attr = {"coordinate_transformation_mode",
"half_pixel"};
@ -143,19 +82,26 @@ const OnnxAttrInfo<std::string> ResizeOpBuilder::onnx_nearest_mode_attr = {"near
const OnnxAttrInfo<int64_t> ResizeOpBuilder::onnx_antialias_attr = {"antialias", 0};
const OnnxAttrInfo<int64_t> ResizeOpBuilder::onnx_exclude_outside_attr = {"exclude_outside", 0};
template <typename QnnValType, std::size_t N>
Status ResizeOpBuilder::GetQnnModeFromString(const std::array<std::string_view, N>& onnx_modes,
std::string_view onnx_mode, const char* onnx_mode_label,
QnnValType& qnn_mode) const {
for (size_t i = 0; i < onnx_modes.size(); ++i) {
if (onnx_modes[i] == onnx_mode) {
qnn_mode = SafeInt<QnnValType>(i);
return Status::OK();
}
// Returns the QNN parameter integer value that corresponds to the given ONNX attribute mode string value.
static Status GetQnnModeValFromOnnxString(const std::unordered_map<std::string, uint32_t>& supported_qnn_modes,
const std::string& onnx_attr_value,
const char* onnx_attr_name,
uint32_t& qnn_mode_value) {
auto it = supported_qnn_modes.find(onnx_attr_value);
if (it != supported_qnn_modes.end()) {
qnn_mode_value = it->second;
return Status::OK();
}
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "QNN EP: Resize operator does not support ", onnx_mode_label,
" ", std::string(onnx_mode));
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "QNN EP: Resize operator does not support ", onnx_attr_name,
" ", std::string(onnx_attr_value));
}
// Returns true if the given ONNX attribute mode value is generally supported on QNN. Note that
// different QNN backends may support a smaller subset of modes.
static bool IsOnnxAttrModeSupported(const std::unordered_map<std::string, uint32_t>& supported_qnn_modes,
const std::string& onnx_attr_value) {
return supported_qnn_modes.find(onnx_attr_value) != supported_qnn_modes.end();
}
// Resize ops are sensitive with data layout, no special validation so far
@ -169,118 +115,95 @@ Status ResizeOpBuilder::IsOpSupported(QnnModelWrapper& qnn_model_wrapper,
return AddToModelBuilder(qnn_model_wrapper, node_unit, logger, true);
}
const bool is_npu_backend = IsNpuBackend(qnn_model_wrapper.GetQnnBackendType());
NodeAttrHelper node_helper(node_unit);
// QNN doesn't support anti-aliasing (added in opset 18)
if (node_unit.SinceVersion() >= 18) {
NodeAttrHelper node_helper(node_unit);
const bool antialias = GetOnnxAttr(node_helper, onnx_antialias_attr) != 0;
ORT_RETURN_IF(antialias, "QNN EP: Resize doesn't support anti-aliasing.");
}
// The QNN Resize op does not currently work with the QNN cpu backend, but works with the HTP backend. Therefore, we
// currently use QNN's Resize op for quantized models and either ResizeBilinear or ResizeNearestNeighbor for
// non-quantized models. This requires separate validation for quantized models.
// TODO: Use only Resize once QNN's Resize op works in the QNN cpu backend.
bool is_npu_backend = IsNpuBackend(qnn_model_wrapper.GetQnnBackendType());
return is_npu_backend ? ValidateQDQOp(qnn_model_wrapper, node_unit) : ValidateOp(qnn_model_wrapper, node_unit);
}
Status ResizeOpBuilder::ValidateOp(QnnModelWrapper& qnn_model_wrapper, const NodeUnit& node_unit) const {
NodeAttrHelper node_helper(node_unit);
const std::string resize_mode = GetOnnxAttr(node_helper, onnx_mode_attr);
ORT_RETURN_IF((resize_mode != "nearest") && (resize_mode != "linear"),
"QNN EP: Resize doesn't support mode '", resize_mode.c_str(), "'.",
"Only 'nearest' and 'linear' are supported.");
const std::string coordinate_mode = GetOnnxAttr(node_helper, onnx_coord_transf_mode_attr);
ORT_RETURN_IF((coordinate_mode != "half_pixel") && (coordinate_mode != "align_corners"),
"QNN EP: coordinate transformation mode '", coordinate_mode.c_str(), "' not supported for Resize op.",
"Only 'align_corners' and 'half_pixel' are supported.");
// Check for a valid "nearest_mode" if the mode is "nearest".
if (resize_mode == "nearest") {
// NOTE: QNN's ResizeNearestNeighbor operator does not have a way to specify rounding (i.e., "nearest_mode").
// The output of the QNN ResizeNearestNeighbor operator is not always equivalent to ONNX's Resize
// operator with any single specific "nearest_mode".
//
// For some input/output shapes, QNN's ResizeNearestNeighbor is equivalent to ONNX's Resize with "round_prefer_floor".
// For other shapes, QNN's ResizeNearestNeighbor is equivalent to ONNX Resize with "round_prefer_ceil".
//
// From unit tests, I've found a relationship between input/output shapes and the equivalent ONNX "nearest_mode".
// If the new and old spatial dimensions are evenly divisible, the "nearest_mode" is "round_prefer_floor".
// Otherwise, the "nearest_mode" is "round_prefer_ceil".
//
// This relationship is probably incomplete/wrong.
//
// TODO: Ask Qualcomm what the correct "nearest_mode" should be,
// OR use QNN's own Resize operator once it works on QnnCpu.
const std::string& nearest_mode = GetOnnxAttr(node_helper, onnx_nearest_mode_attr);
ORT_RETURN_IF_NOT("floor" == nearest_mode, "QNN Resize only supports nearest_mode: floor!"); // This is wrong!
}
auto& input_0 = node_unit.Inputs()[0];
std::vector<uint32_t> input_shape;
ORT_RETURN_IF_NOT(qnn_model_wrapper.GetOnnxShape(input_0.node_arg, input_shape),
"QNN EP: Cannot get input shape for Resize op");
const auto& output_0 = node_unit.Outputs()[0];
std::vector<uint32_t> output_shape;
ORT_RETURN_IF_NOT(qnn_model_wrapper.GetOnnxShape(output_0.node_arg, output_shape),
"QNN EP: Cannot get output shape for Resize op");
ORT_RETURN_IF(input_shape.size() != 4 || output_shape.size() != 4, "QNN Resize only supports 4D!");
ONNX_NAMESPACE::DataType input_data_type = input_0.node_arg.Type();
ORT_RETURN_IF(input_data_type != ONNX_NAMESPACE::Utils::DataTypeUtils::ToType("float"),
"QNN EP: Data type ", input_data_type->c_str(),
" is not supported for Resize operator in CPU backend.");
return Status::OK();
}
Status ResizeOpBuilder::ValidateQDQOp(QnnModelWrapper& qnn_model_wrapper, const NodeUnit& node_unit) const {
NodeAttrHelper node_helper(node_unit);
using namespace onnxruntime::qnn::utils;
// Check mode
const std::string interp_mode = GetOnnxAttr(node_helper, onnx_mode_attr);
ORT_RETURN_IF_NOT(ArrayHasString(supported_modes, interp_mode), "QNN EP: Resize does not support mode ",
ORT_RETURN_IF_NOT(IsOnnxAttrModeSupported(supported_modes, interp_mode), "QNN EP: Resize does not support mode ",
interp_mode.c_str());
// Check coordinate transformation mode
const std::string transformation_mode = GetOnnxAttr(node_helper, onnx_coord_transf_mode_attr);
ORT_RETURN_IF_NOT(ArrayHasString(supported_coord_transf_modes, transformation_mode),
ORT_RETURN_IF_NOT(IsOnnxAttrModeSupported(supported_coord_transf_modes, transformation_mode),
"QNN EP: Resize does not support coordinate_transformation_mode ", transformation_mode.c_str());
// Check nearest mode
if (interp_mode == "nearest") {
const std::string nearest_mode = GetOnnxAttr(node_helper, onnx_nearest_mode_attr);
ORT_RETURN_IF_NOT(ArrayHasString(supported_nearest_modes, nearest_mode),
"QNN EP: Resize does not support nearest_mode ", nearest_mode.c_str());
// TODO: Support 'asymmetric' transformation mode with nearest_mode != 'floor'.
//
// QNN's ONNX converter tool translates 'nearest' + 'asymmetric' (regardless of rounding mode)
// to QNN's ResizeNearestNeighbor with {align_corners: 0, half_pixel: 0}.
// This is only accurate if the rounding mode is "floor". Need to investigate how to handle
// other rounding modes with Qualcomm. Ideally, we would use QNN's Resize operator, but it doesn't support
// the "asymmetric" coordinate transformation mode on HTP.
ORT_RETURN_IF(transformation_mode == "asymmetric" && nearest_mode != "floor",
"QNN EP: Resize with coordinate_transformation_mode 'asymmetric' and nearest_mode '", nearest_mode,
"' is not currently supported on the HTP backend.");
}
// Check that input shape has at least a rank of 3.
const auto& input_0 = node_unit.Inputs()[0];
std::vector<uint32_t> input_shape;
ORT_RETURN_IF_NOT(qnn_model_wrapper.GetOnnxShape(input_0.node_arg, input_shape),
"QNN EP: Cannot get shape for Resize input");
ORT_RETURN_IF(input_shape.size() < 3, "QNN EP: Resize input must have a rank >= 3.");
const size_t input_rank = input_shape.size();
// Validate Resize w/ "nearest" mode.
// Translation matrix of ONNX Resize w/ "nearest" mode on HTP backend.
// Table entries correspond to the QNN operator used for the given configuration
// (Resize = QNN Resize op, RNN = QNN ResizeNearestNeighbor op, X = Unsupported).
//
// nearest_mode:
// coordinate_transformation_mode: | round_prefer_floor round_prefer_ceil floor ceil
// -----------------------------------------------------------------------------------------
// half_pixel | Resize X RNN X
// pytorch_half_pixel | Resize X X X
// align_corners | Resize X RNN X
// asymmetric | Resize X RNN X
if (interp_mode == "nearest") {
const std::string nearest_mode = GetOnnxAttr(node_helper, onnx_nearest_mode_attr);
ORT_RETURN_IF_NOT(IsOnnxAttrModeSupported(supported_nearest_modes, nearest_mode),
"QNN EP: Resize does not support nearest_mode ", nearest_mode.c_str());
if (is_npu_backend) {
// QNN only supports the following nearest_mode values on HTP:
// - "round_prefer_floor" via QNN's Resize operator
// - "floor" via QNN's ResizeNearestNeighbor operator
//
// QNN validation does not throw an error if unsupported nearest_mode values are used, so we have to
// catch them here. Otherwise, accuracy is significantly degraded.
ORT_RETURN_IF_NOT(nearest_mode == "round_prefer_floor" || nearest_mode == "floor",
"QNN EP: Resize on the NPU does not support nearest_mode ", nearest_mode.c_str());
const bool use_resize_nn_op = nearest_mode == "floor";
// If HTP uses ResizeNearestNeighbor ("floor"), then the "pytorch_half_pixel" coordinate_transformation_mode
// is not supported.
ORT_RETURN_IF(use_resize_nn_op && transformation_mode == "pytorch_half_pixel",
"QNN EP: Resize on the NPU does not support the combination of nearest_mode == 'floor' ",
" and coordinate_transformation_mode == 'pytorch_half_pixel'.");
// QNN's ResizeNearestNeighbor requires rank 4 inputs.
ORT_RETURN_IF(use_resize_nn_op && input_rank != 4,
"QNN EP: Resize on the NPU with nearest_mode == 'floor' requires an input with rank 4.");
}
}
// Check that the input shape has at least a rank of 3 (and a max of 5 on HTP).
ORT_RETURN_IF(input_rank < 3 || (is_npu_backend && input_rank > 5),
"QNN EP: Resize input must have a rank >= 3. The maximum rank is 5 on the NPU.");
const auto& output_0 = node_unit.Outputs()[0];
std::vector<uint32_t> output_shape;
ORT_RETURN_IF_NOT(qnn_model_wrapper.GetOnnxShape(output_0.node_arg, output_shape),
"QNN EP: Cannot get shape for Resize output");
ORT_RETURN_IF(output_shape.size() < 3, "QNN EP: Resize output must have a rank >= 3.");
// Check that only the spatial dimensions (width, height) are resized. The batch_size (N) and channels (C) should
// be untouched. This code runs before layout transformation, so we know that the current layout is "channel first"
// (e.g., N, C, S1, S2, ..., SN), and that the minimum rank is 3.
assert(node_unit.Domain() != kMSInternalNHWCDomain);
ORT_RETURN_IF_NOT(input_shape[0] == output_shape[0] && input_shape[1] == output_shape[1],
"QNN EP: Resize may only change the spatial dimensions.");
if (!is_npu_backend) {
ONNX_NAMESPACE::DataType input_data_type = input_0.node_arg.Type();
ORT_RETURN_IF(input_data_type != ONNX_NAMESPACE::Utils::DataTypeUtils::ToType("float"),
"QNN EP: Data type ", input_data_type->c_str(),
" is not supported for Resize operator in CPU backend.");
}
return Status::OK();
}
@ -305,92 +228,34 @@ Status ResizeOpBuilder::ProcessAttributesAndOutputs(QnnModelWrapper& qnn_model_w
std::vector<std::string>&& input_names,
const logging::Logger& logger,
bool do_op_validation) const {
// The QNN Resize op does not currently work with the QNN cpu backend, but works with the HTP backend. Therefore, we
// currently use QNN's Resize op for quantized models and either ResizeBilinear or ResizeNearestNeighbor for
// non-quantized models. This requires separate handling for quantized models.
// TODO: Use only Resize once QNN's Resize op works in the QNN cpu backend.
bool is_quantized_node = NodeUnit::Type::QDQGroup == node_unit.UnitType();
return is_quantized_node ? ProcessQDQOpAttrsAndOutputs(qnn_model_wrapper, node_unit, std::move(input_names), logger, do_op_validation) : ProcessOpAttrsAndOutputs(qnn_model_wrapper, node_unit, std::move(input_names), logger, do_op_validation);
}
Status ResizeOpBuilder::ProcessOpAttrsAndOutputs(QnnModelWrapper& qnn_model_wrapper,
const NodeUnit& node_unit,
std::vector<std::string>&& input_names,
const logging::Logger& logger,
bool do_op_validation) const {
ORT_UNUSED_PARAMETER(logger);
NodeAttrHelper node_helper(node_unit);
const std::string resize_mode = GetOnnxAttr(node_helper, onnx_mode_attr);
std::string qnn_node_type = "ResizeNearestNeighbor";
if ("linear" == resize_mode) {
qnn_node_type = "ResizeBilinear";
}
const std::string coordinate_mode = GetOnnxAttr(node_helper, onnx_coord_transf_mode_attr);
Qnn_Scalar_t qnn_align_corners = QNN_SCALAR_INIT;
qnn_align_corners.dataType = QNN_DATATYPE_BOOL_8;
qnn_align_corners.bool8Value = static_cast<uint8_t>(0);
Qnn_Scalar_t qnn_half_pixel = QNN_SCALAR_INIT;
qnn_half_pixel.dataType = QNN_DATATYPE_BOOL_8;
qnn_half_pixel.bool8Value = static_cast<uint8_t>(0);
if ("align_corners" == coordinate_mode) {
qnn_align_corners.bool8Value = static_cast<uint8_t>(1);
} else if ("half_pixel" == coordinate_mode) {
qnn_half_pixel.bool8Value = static_cast<uint8_t>(1);
}
QnnParamWrapper qnn_align_corners_param(node_unit.Index(), node_unit.Name(),
QNN_OP_RESIZE_BILINEAR_PARAM_ALIGN_CORNERS, qnn_align_corners);
QnnParamWrapper qnn_half_pixel_param(node_unit.Index(), node_unit.Name(),
QNN_OP_RESIZE_BILINEAR_PARAM_HALF_PIXEL_CENTERS, qnn_half_pixel);
std::vector<std::string> param_tensor_names;
param_tensor_names.push_back(qnn_align_corners_param.GetParamTensorName());
qnn_model_wrapper.AddParamWrapper(std::move(qnn_align_corners_param));
param_tensor_names.push_back(qnn_half_pixel_param.GetParamTensorName());
qnn_model_wrapper.AddParamWrapper(std::move(qnn_half_pixel_param));
return ProcessOutputs(qnn_model_wrapper, node_unit, std::move(input_names), std::move(param_tensor_names),
logger, do_op_validation, qnn_node_type);
}
Status ResizeOpBuilder::ProcessQDQOpAttrsAndOutputs(QnnModelWrapper& qnn_model_wrapper,
const NodeUnit& node_unit,
std::vector<std::string>&& input_names,
const logging::Logger& logger,
bool do_op_validation) const {
std::vector<std::string> param_tensor_names;
NodeAttrHelper node_helper(node_unit);
const std::string interp_mode = GetOnnxAttr(node_helper, onnx_mode_attr);
const std::string transformation_mode = GetOnnxAttr(node_helper, onnx_coord_transf_mode_attr);
const std::string nearest_mode = GetOnnxAttr(node_helper, onnx_nearest_mode_attr);
const bool is_npu_backend = IsNpuBackend(qnn_model_wrapper.GetQnnBackendType());
std::string qnn_op_type = "Resize";
// Handle Resize with {mode: "nearest", coordinate_transformation_mode: "asymmetric"} uniquely.
// QNN's ONNX converter tool translates this configuration (regardless of rounding mode)
// to QNN's ResizeNearestNeighbor with {align_corners: 0, half_pixel: 0}.
//
// NOTE: This is only accurate if the rounding mode is "floor". Need to investigate how to handle
// other rounding modes with Qualcomm. Ideally, we would use QNN's Resize operator, but it doesn't support
// the "asymmetric" coordinate transformation mode on HTP.
if (interp_mode == "nearest" && transformation_mode == "asymmetric") {
// Translate Resize with {mode: "nearest", nearest_mode: "floor", coordinate_transformation_mode: XXX} to
// QNN's ResizeNearestNeighbor operator on the HTP backend. This combination of parameters is not supported on HTP
// via QNN's Resize operator. Note that QNN's ResizeNearestNeighbor operator always uses "floor" rounding.
if (is_npu_backend && interp_mode == "nearest" && nearest_mode == "floor") {
qnn_op_type = "ResizeNearestNeighbor";
// Set parameter 'align_corners' to 0
// Parameter 'align_corners'
Qnn_Scalar_t qnn_align_corners = QNN_SCALAR_INIT;
qnn_align_corners.dataType = QNN_DATATYPE_BOOL_8;
qnn_align_corners.bool8Value = static_cast<uint8_t>(0);
qnn_align_corners.bool8Value = static_cast<uint8_t>(transformation_mode == "align_corners");
QnnParamWrapper qnn_align_corners_param(node_unit.Index(), node_unit.Name(),
QNN_OP_RESIZE_BILINEAR_PARAM_ALIGN_CORNERS, qnn_align_corners);
param_tensor_names.push_back(qnn_align_corners_param.GetParamTensorName());
qnn_model_wrapper.AddParamWrapper(std::move(qnn_align_corners_param));
// Set parameter 'half_pixel_centers' to 0
// Parameter 'half_pixel_centers'
Qnn_Scalar_t qnn_half_pixel = QNN_SCALAR_INIT;
qnn_half_pixel.dataType = QNN_DATATYPE_BOOL_8;
qnn_half_pixel.bool8Value = static_cast<uint8_t>(0);
qnn_half_pixel.bool8Value = static_cast<uint8_t>(transformation_mode == "half_pixel");
QnnParamWrapper qnn_half_pixel_param(node_unit.Index(), node_unit.Name(),
QNN_OP_RESIZE_BILINEAR_PARAM_HALF_PIXEL_CENTERS, qnn_half_pixel);
param_tensor_names.push_back(qnn_half_pixel_param.GetParamTensorName());
@ -399,11 +264,12 @@ Status ResizeOpBuilder::ProcessQDQOpAttrsAndOutputs(QnnModelWrapper& qnn_model_w
// Parameter 'transformation_mode'
Qnn_Scalar_t qnn_transformation_mode = QNN_SCALAR_INIT;
qnn_transformation_mode.dataType = QNN_DATATYPE_UINT_32;
ORT_RETURN_IF_ERROR(GetQnnModeFromString(supported_coord_transf_modes, transformation_mode,
"coordinate_transformation_mode", qnn_transformation_mode.uint32Value));
ORT_RETURN_IF_ERROR(GetQnnModeValFromOnnxString(supported_coord_transf_modes, transformation_mode,
"coordinate_transformation_mode",
qnn_transformation_mode.uint32Value));
QnnParamWrapper qnn_transformation_mode_param(node_unit.Index(), node_unit.Name(), QNN_OP_RESIZE_PARAM_TRANSFORMATION_MODE,
qnn_transformation_mode);
QnnParamWrapper qnn_transformation_mode_param(node_unit.Index(), node_unit.Name(),
QNN_OP_RESIZE_PARAM_TRANSFORMATION_MODE, qnn_transformation_mode);
param_tensor_names.push_back(qnn_transformation_mode_param.GetParamTensorName());
qnn_model_wrapper.AddParamWrapper(std::move(qnn_transformation_mode_param));
@ -420,7 +286,7 @@ Status ResizeOpBuilder::ProcessQDQOpAttrsAndOutputs(QnnModelWrapper& qnn_model_w
// Parameter 'interpolation_mode'
Qnn_Scalar_t qnn_interp_mode = QNN_SCALAR_INIT;
qnn_interp_mode.dataType = QNN_DATATYPE_UINT_32;
ORT_RETURN_IF_ERROR(GetQnnModeFromString(supported_modes, interp_mode, "mode", qnn_interp_mode.uint32Value));
ORT_RETURN_IF_ERROR(GetQnnModeValFromOnnxString(supported_modes, interp_mode, "mode", qnn_interp_mode.uint32Value));
QnnParamWrapper qnn_interp_mode_param(node_unit.Index(), node_unit.Name(), QNN_OP_RESIZE_PARAM_INTERPOLATION_MODE,
qnn_interp_mode);
@ -429,11 +295,10 @@ Status ResizeOpBuilder::ProcessQDQOpAttrsAndOutputs(QnnModelWrapper& qnn_model_w
// Parameter 'nearest_mode'. Processed only when 'interpolation_mode' is NEAREST(0).
if (qnn_interp_mode.uint32Value == 0) {
const std::string nearest_mode = GetOnnxAttr(node_helper, onnx_nearest_mode_attr);
Qnn_Scalar_t qnn_nearest_mode = QNN_SCALAR_INIT;
qnn_nearest_mode.dataType = QNN_DATATYPE_UINT_32;
ORT_RETURN_IF_ERROR(GetQnnModeFromString(supported_nearest_modes, nearest_mode, "nearest_mode",
qnn_nearest_mode.uint32Value));
ORT_RETURN_IF_ERROR(GetQnnModeValFromOnnxString(supported_nearest_modes, nearest_mode, "nearest_mode",
qnn_nearest_mode.uint32Value));
QnnParamWrapper qnn_nearest_mode_param(node_unit.Index(), node_unit.Name(), QNN_OP_RESIZE_PARAM_NEAREST_MODE,
qnn_nearest_mode);

View file

@ -99,9 +99,8 @@ TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_tf_crop_and_resize_with_extr
// CUDA: result mismatch due to not implementing NHWC support
// TensorRT: results mismatch
// ROCm: results mismatch
// QNN: conflict with layout transformer, need furture investigation
test.Run(OpTester::ExpectResult::kExpectSuccess, "",
{kCudaExecutionProvider, kTensorrtExecutionProvider, kRocmExecutionProvider, kQnnExecutionProvider});
{kCudaExecutionProvider, kTensorrtExecutionProvider, kRocmExecutionProvider});
}
TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_tf_crop_and_resize_with_extrapolation_uint8) {
@ -131,7 +130,7 @@ TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_tf_crop_and_resize_with_extr
test.AddOutput<uint8_t>("Y", {N, static_cast<int64_t>(H * scales[1]), static_cast<int64_t>(W * scales[2]), C}, Y);
// CUDA: result mismatch due to not implementing NHWC support
// ROCm: results mismatch
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kCudaExecutionProvider, kRocmExecutionProvider, kQnnExecutionProvider});
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kCudaExecutionProvider, kRocmExecutionProvider});
}
TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_tf_crop_and_resize_with_extrapolation_int8) {
@ -159,7 +158,7 @@ TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_tf_crop_and_resize_with_extr
10, 10, 10};
test.AddOutput<int8_t>("Y", {N, static_cast<int64_t>(H * scales[1]), static_cast<int64_t>(W * scales[2]), C}, Y);
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kQnnExecutionProvider});
test.Run();
}
TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_tf_crop_and_resize_without_extrapolation_uint8) {
@ -188,7 +187,7 @@ TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_tf_crop_and_resize_without_e
test.AddOutput<uint8_t>("Y", {N, static_cast<int64_t>(H * scales[1]), static_cast<int64_t>(W * scales[2]), C}, Y);
// CUDA: result mismatch due to not implementing NHWC support
// ROCm: results mismatch
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kCudaExecutionProvider, kRocmExecutionProvider, kQnnExecutionProvider});
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kCudaExecutionProvider, kRocmExecutionProvider});
}
TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_tf_crop_and_resize_without_extrapolation_int8) {
@ -215,7 +214,7 @@ TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_tf_crop_and_resize_without_e
0, 0, 0};
test.AddOutput<int8_t>("Y", {N, static_cast<int64_t>(H * scales[1]), static_cast<int64_t>(W * scales[2]), C}, Y);
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kQnnExecutionProvider});
test.Run();
}
TEST(ResizeOpTest, ResizeOpLinearDownSampleTest_4DBilinear) {
@ -261,9 +260,8 @@ TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_4DBilinear) {
test.AddOutput<float>("Y", {N, static_cast<int64_t>(H * scales[1]), static_cast<int64_t>(W * scales[2]), C}, Y);
// CUDA: result mismatch due to not implementing NHWC support
// ROCm: results mismatch
// QNN: conflict with layout transformer, need furture investigation
test.Run(OpTester::ExpectResult::kExpectSuccess, "",
{kCudaExecutionProvider, kRocmExecutionProvider, kQnnExecutionProvider});
{kCudaExecutionProvider, kRocmExecutionProvider});
}
TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_4DBilinear_uint8) {
@ -287,7 +285,7 @@ TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_4DBilinear_uint8) {
test.AddOutput<uint8_t>("Y", {N, static_cast<int64_t>(H * scales[1]), static_cast<int64_t>(W * scales[2]), C}, Y);
// CUDA: result mismatch due to not implementing NHWC support
// ROCm: results mismatch
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kCudaExecutionProvider, kRocmExecutionProvider, kQnnExecutionProvider});
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kCudaExecutionProvider, kRocmExecutionProvider});
}
TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_4DBilinear_int8) {
@ -309,7 +307,7 @@ TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_4DBilinear_int8) {
std::vector<int8_t> Y = {0, 0};
test.AddOutput<int8_t>("Y", {N, static_cast<int64_t>(H * scales[1]), static_cast<int64_t>(W * scales[2]), C}, Y);
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kQnnExecutionProvider});
test.Run();
}
// Since NNAPI(TFLite) only using the scale calculate using the input/output size
@ -399,7 +397,9 @@ TEST(ResizeOpTest, ResizeOpLinearDownSampleTest_4DBilinear_align_corners) {
std::vector<float> Y = {1.0f, 4.0f};
test.AddOutput<float>("Y", {N, C, static_cast<int64_t>(H * scales[2]), static_cast<int64_t>(W * scales[3])}, Y);
test.Run();
// QNN: result mismatch ("NaN" instead of 1.0f on QNN CPU backend)
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kQnnExecutionProvider});
};
run_test(false);
@ -435,7 +435,7 @@ TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_4DBilinear_align_corners_uin
test.AddOutput<uint8_t>("Y", {N, static_cast<int64_t>(H * scales[1]), static_cast<int64_t>(W * scales[2]), C}, Y);
// CUDA: result mismatch due to not implementing NHWC support
// ROCm: results mismatch
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kCudaExecutionProvider, kRocmExecutionProvider, kQnnExecutionProvider});
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kCudaExecutionProvider, kRocmExecutionProvider});
};
run_test(false);
@ -465,7 +465,7 @@ TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_4DBilinear_align_corners_int
test.AddOutput<int8_t>("Y", {N, static_cast<int64_t>(H * scales[1]), static_cast<int64_t>(W * scales[2]), C}, Y);
// TensorRT: results mismatch
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider, kQnnExecutionProvider});
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider});
};
run_test(false);
@ -532,7 +532,7 @@ TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_4DBilinear_pytorch_half_pixe
test.AddOutput<uint8_t>("Y", {N, sizes[1], sizes[2], C}, Y);
// CUDA: result mismatch due to not implementing NHWC support
// ROCm: results mismatch
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kCudaExecutionProvider, kRocmExecutionProvider, kQnnExecutionProvider});
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kCudaExecutionProvider, kRocmExecutionProvider});
}
TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_4DBilinear_pytorch_half_pixel_int8) {
@ -560,7 +560,7 @@ TEST(ResizeOpTest, NhwcResizeOpLinearDownSampleTest_4DBilinear_pytorch_half_pixe
std::vector<int8_t> Y = {0, 2, -9};
test.AddOutput<int8_t>("Y", {N, sizes[1], sizes[2], C}, Y);
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider, kQnnExecutionProvider}); // TensorRT: results mismatch
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider}); // TensorRT: results mismatch
}
TEST(ResizeOpTest, ResizeOpLinearUpSampleTest_4DBilinear_asymmetric) {
@ -641,7 +641,7 @@ TEST(ResizeOpTest, NhwcResizeOpLinearUpSampleTest_4DBilinear_asymmetric_uint8) {
Y, false, .0f, 1.0f);
// CUDA: result mismatch due to not implementing NHWC support
// ROCm: results mismatch
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kCudaExecutionProvider, kRocmExecutionProvider, kQnnExecutionProvider});
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kCudaExecutionProvider, kRocmExecutionProvider});
};
run_test(false);
@ -683,7 +683,7 @@ TEST(ResizeOpTest, NhwcResizeOpLinearUpSampleTest_4DBilinear_asymmetric_int8) {
test.AddOutput<int8_t>("Y", {N, static_cast<int64_t>(H * scales[1]), static_cast<int64_t>(W * scales[2]), C},
Y, false, .0f, 1.0f);
// TensorRT: results mismatch
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider, kQnnExecutionProvider});
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider});
};
run_test(false);
@ -1079,7 +1079,7 @@ TEST(ResizeOpTest, ResizeOpNearestUpSample_Floor_Align_Corners) {
13.0f, 13.0f, 13.0f, 14.0f, 14.0f, 15.0f, 15.0f, 16.0f};
test.AddOutput<float>("Y", {N, C, static_cast<int64_t>(H * scales[2]), static_cast<int64_t>(W * scales[3])}, Y);
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kQnnExecutionProvider}); // QNN: result diff
test.Run();
}
TEST(ResizeOpTest, ResizeOpNearest_OneToOneMappingBetweenInputAndOutputDataDims) {
@ -1887,7 +1887,7 @@ void TestAntialiasing(std::map<std::string, std::string> attributes,
test.AddOutput<T>("Y", output_shape, output_data);
// TensorRT 8.5 supports operators up to Opset 17. Temporarily exclude TensorRT EP due to accurarcy issue.
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider, kQnnExecutionProvider});
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider});
}
TEST(ResizeOpTest, Antialias_Bilinear_No_ExcludeOutside) {

View file

@ -120,7 +120,7 @@ static void RunCPUResizeOpTest(const TestInputDef<float>& input_def, const std::
const std::string& mode, const std::string& coordinate_transformation_mode,
const std::string& nearest_mode,
ExpectedEPNodeAssignment expected_ep_assignment,
int opset = 11) {
int opset = 19) {
ProviderOptions provider_options;
#if defined(_WIN32)
provider_options["backend_path"] = "QnnCpu.dll";
@ -138,7 +138,7 @@ static void RunCPUResizeOpTestWithScales(const TestInputDef<float>& input_def, c
const std::string& mode, const std::string& coordinate_transformation_mode,
const std::string& nearest_mode,
ExpectedEPNodeAssignment expected_ep_assignment,
int opset = 11) {
int opset = 19) {
ProviderOptions provider_options;
#if defined(_WIN32)
provider_options["backend_path"] = "QnnCpu.dll";
@ -157,7 +157,8 @@ static void RunQDQResizeOpTest(const TestInputDef<float>& input_def,
const std::vector<int64_t>& sizes_data,
const std::string& mode, const std::string& coordinate_transformation_mode,
const std::string& nearest_mode,
ExpectedEPNodeAssignment expected_ep_assignment) {
ExpectedEPNodeAssignment expected_ep_assignment,
int opset = 19) {
ProviderOptions provider_options;
#if defined(_WIN32)
provider_options["backend_path"] = "QnnHtp.dll";
@ -169,27 +170,20 @@ static void RunQDQResizeOpTest(const TestInputDef<float>& input_def,
GetQDQResizeModelBuilder<QuantType>(input_def, sizes_data, mode, coordinate_transformation_mode,
nearest_mode),
provider_options,
18, // opset
expected_ep_assignment,
1e-5f);
opset,
expected_ep_assignment);
}
//
// CPU tests:
//
// TODO: Our QNN CPU translation of ONNX Resize with "nearest" mode uses QNN's ResizeNearestNeighbor
// operator, which does not have a way to specify rounding (i.e., "nearest_mode" in ONNX). It is not clear
// what kind of rounding QNN's ResizeNearestNeighbor uses. Therefore, we do not yet know how to compare
// ONNX Resize to QNN ResizeNearestNeighbor. These tests should remain disabled until this behavior is
// clarified. If, for example, it turns out that ResizeNearestNeighbor uses "floor" rounding, then we should
// only compare against ONNX resize with "floor" rounding.
// Upsample that uses "round_prefer_floor" as the "nearest_mode".
// coordinate_transformation_mode: "half_pixel"
TEST_F(QnnCPUBackendTests, DISABLED_ResizeUpsampleNearestHalfPixel_rpf) {
RunCPUResizeOpTest(TestInputDef<float>({1, 2, 7, 5}, false, -10.0f, 10.0f), // Random input w/ range [-10, 10]
{1, 2, 21, 10}, // Sizes
TEST_F(QnnCPUBackendTests, ResizeUpsampleNearestHalfPixel_rpf) {
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 70);
RunCPUResizeOpTest(TestInputDef<float>({1, 2, 7, 5}, false, input_data),
{1, 2, 21, 10}, // Sizes
"nearest",
"half_pixel",
"round_prefer_floor",
@ -198,57 +192,72 @@ TEST_F(QnnCPUBackendTests, DISABLED_ResizeUpsampleNearestHalfPixel_rpf) {
// Upsample that uses "round_prefer_ceil" as the "nearest_mode".
// coordinate_transformation_mode: "half_pixel"
TEST_F(QnnCPUBackendTests, DISABLED_ResizeUpsampleNearestHalfPixel_rpc) {
RunCPUResizeOpTest(TestInputDef<float>({1, 1, 2, 4}, false, -10.0f, 10.0f),
TEST_F(QnnCPUBackendTests, ResizeUpsampleNearestHalfPixel_rpc) {
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 8);
RunCPUResizeOpTest(TestInputDef<float>({1, 1, 2, 4}, false, input_data),
{1, 1, 7, 5}, "nearest", "half_pixel", "round_prefer_ceil",
ExpectedEPNodeAssignment::All);
}
// Downsample that uses "round_prefer_ceil" as the "nearest_mode".
// coordinate_transformation_mode: "half_pixel"
TEST_F(QnnCPUBackendTests, DISABLED_ResizeDownsampleNearestHalfPixel_rpc) {
RunCPUResizeOpTest(TestInputDef<float>({1, 1, 2, 4}, false, -10.0f, 10.0f),
TEST_F(QnnCPUBackendTests, ResizeDownsampleNearestHalfPixel_rpc) {
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 8);
RunCPUResizeOpTest(TestInputDef<float>({1, 1, 2, 4}, false, input_data),
{1, 1, 1, 3}, "nearest", "half_pixel", "round_prefer_ceil",
ExpectedEPNodeAssignment::All);
}
// Downsample that uses "round_prefer_floor" as the "nearest_mode".
// coordinate_transformation_mode: "half_pixel"
TEST_F(QnnCPUBackendTests, DISABLED_ResizeDownsampleNearestHalfPixel_rpf) {
RunCPUResizeOpTest(TestInputDef<float>({1, 1, 2, 4}, false, -10.0f, 10.0f),
TEST_F(QnnCPUBackendTests, ResizeDownsampleNearestHalfPixel_rpf) {
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 8);
RunCPUResizeOpTest(TestInputDef<float>({1, 1, 2, 4}, false, input_data),
{1, 1, 1, 2}, "nearest", "half_pixel", "round_prefer_ceil",
ExpectedEPNodeAssignment::All);
}
// Upsample that uses "round_prefer_floor" as the "nearest_mode".
// coordinate_transformation_mode: "align_corners"
// QNN v2.13: index #50 don't match, which is 4.67152 from -1.93515
TEST_F(QnnCPUBackendTests, DISABLED_ResizeUpsampleNearestAlignCorners_rpf) {
RunCPUResizeOpTest(TestInputDef<float>({1, 2, 7, 5}, false, -10.0f, 10.0f),
TEST_F(QnnCPUBackendTests, ResizeUpsampleNearestAlignCorners_rpf) {
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 70);
RunCPUResizeOpTest(TestInputDef<float>({1, 2, 7, 5}, false, input_data),
{1, 2, 21, 10}, "nearest", "align_corners", "round_prefer_floor",
ExpectedEPNodeAssignment::All);
}
// Upsample that uses "round_prefer_floor" as the "nearest_mode".
// coordinate_transformation_mode: "asymmetric"
TEST_F(QnnCPUBackendTests, ResizeUpsampleNearestAsymmetric_rpf) {
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 70);
RunCPUResizeOpTest(TestInputDef<float>({1, 2, 7, 5}, false, input_data),
{1, 2, 21, 10}, "nearest", "asymmetric", "round_prefer_floor",
ExpectedEPNodeAssignment::All);
}
// Upsample that uses "round_prefer_ceil" as the "nearest_mode".
// coordinate_transformation_mode: "align_corners"
TEST_F(QnnCPUBackendTests, DISABLED_ResizeUpsampleNearestAlignCorners_rpc) {
RunCPUResizeOpTest(TestInputDef<float>({1, 1, 2, 4}, false, -10.0f, 10.0f),
TEST_F(QnnCPUBackendTests, ResizeUpsampleNearestAlignCorners_rpc) {
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 8);
RunCPUResizeOpTest(TestInputDef<float>({1, 1, 2, 4}, false, input_data),
{1, 1, 7, 5}, "nearest", "align_corners", "round_prefer_ceil",
ExpectedEPNodeAssignment::All);
}
// Downsample that uses "round_prefer_ceil" as the "nearest_mode".
// coordinate_transformation_mode: "align_corners"
TEST_F(QnnCPUBackendTests, DISABLED_ResizeDownsampleNearestAlignCorners_rpc) {
RunCPUResizeOpTest(TestInputDef<float>({1, 1, 2, 4}, false, -10.0f, 10.0f),
TEST_F(QnnCPUBackendTests, ResizeDownsampleNearestAlignCorners_rpc) {
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 8);
RunCPUResizeOpTest(TestInputDef<float>({1, 1, 2, 4}, false, input_data),
{1, 1, 1, 3}, "nearest", "align_corners", "round_prefer_ceil",
ExpectedEPNodeAssignment::All);
}
// Downsample that uses "round_prefer_floor" as the "nearest_mode".
// coordinate_transformation_mode: "align_corners"
TEST_F(QnnCPUBackendTests, DISABLED_ResizeDownsampleNearestAlignCorners_rpf) {
RunCPUResizeOpTest(TestInputDef<float>({1, 1, 2, 4}, false, -10.0f, 10.0f),
TEST_F(QnnCPUBackendTests, ResizeDownsampleNearestAlignCorners_rpf) {
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 8);
RunCPUResizeOpTest(TestInputDef<float>({1, 1, 2, 4}, false, input_data),
{1, 1, 1, 2}, "nearest", "align_corners", "round_prefer_floor",
ExpectedEPNodeAssignment::All);
}
@ -258,76 +267,177 @@ TEST_F(QnnCPUBackendTests, DISABLED_ResizeDownsampleNearestAlignCorners_rpf) {
//
TEST_F(QnnCPUBackendTests, Resize2xLinearHalfPixel) {
RunCPUResizeOpTest(TestInputDef<float>({1, 3, 4, 5}, false, -10.0f, 10.0f),
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 60);
RunCPUResizeOpTest(TestInputDef<float>({1, 3, 4, 5}, false, input_data),
{1, 3, 8, 10}, "linear", "half_pixel", "",
ExpectedEPNodeAssignment::All);
}
TEST_F(QnnCPUBackendTests, Resize2xLinearHalfPixel_scales) {
RunCPUResizeOpTestWithScales(TestInputDef<float>({1, 3, 4, 5}, false, -10.0f, 10.0f),
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 60);
RunCPUResizeOpTestWithScales(TestInputDef<float>({1, 3, 4, 5}, false, input_data),
{1.0f, 1.0f, 2.0f, 2.0f}, "linear", "half_pixel", "",
ExpectedEPNodeAssignment::All);
}
TEST_F(QnnCPUBackendTests, Resize2xLinearAlignCorners) {
RunCPUResizeOpTest(TestInputDef<float>({1, 3, 4, 5}, false, -10.0f, 10.0f),
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 60);
RunCPUResizeOpTest(TestInputDef<float>({1, 3, 4, 5}, false, input_data),
{1, 3, 8, 10}, "linear", "align_corners", "",
ExpectedEPNodeAssignment::All);
}
TEST_F(QnnCPUBackendTests, Resize2xLinearAlignCorners_scales) {
RunCPUResizeOpTestWithScales(TestInputDef<float>({1, 3, 4, 5}, false, -10.0f, 10.0f),
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 60);
RunCPUResizeOpTestWithScales(TestInputDef<float>({1, 3, 4, 5}, false, input_data),
{1.0f, 1.0f, 2.0f, 2.0f}, "linear", "align_corners", "",
ExpectedEPNodeAssignment::All);
}
// Test Resize downsample with mode: "linear", coordinate_transformation_mode: "align_corners"
// TODO: Enable ResizeOpTest.ResizeOpLinearDownSampleTest_4DBilinear_align_corners in cpu resize_op tests when fixed.
//
// Input f32[1,1,2,4]: 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0
// Expected output f32[1, 1, 1, 2]: 1.0, 4.0
// Actual output f32[1, 1, 1, 2]: NaN, NaN
TEST_F(QnnCPUBackendTests, DISABLED_Resize_DownSample_Linear_AlignCorners_scales) {
std::vector<float> input_data = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
RunCPUResizeOpTestWithScales(TestInputDef<float>({1, 1, 2, 4}, false, input_data),
{1.0f, 1.0f, 0.6f, 0.6f}, "linear", "align_corners", "",
ExpectedEPNodeAssignment::All);
}
// Test Resize downsample with mode: "linear", coordinate_transformation_mode: "half_pixel"
// TODO: Enable ResizeOpTest.ResizeOpLinearDownSampleTest_4DBilinear cpu resize_op tests when fixed.
//
// Input f32[1,1,2,4]: 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0
// Expected output f32[1, 1, 1, 2]: 2.6666 4.3333
// Actual output f32[1, 1, 1, 2]: NaN, NaN
TEST_F(QnnCPUBackendTests, DISABLED_Resize_DownSample_Linear_HalfPixel_scales) {
std::vector<float> input_data = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
RunCPUResizeOpTestWithScales(TestInputDef<float>({1, 1, 2, 4}, false, input_data),
{1.0f, 1.0f, 0.6f, 0.6f}, "linear", "half_pixel", "",
ExpectedEPNodeAssignment::All);
}
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__linux__)
//
// HTP tests:
//
// Test QDQ Resize downsample with mode: "linear", coordinate_transformation_mode: "align_corners"
TEST_F(QnnHTPBackendTests, Resize_DownSample_Linear_AlignCorners) {
std::vector<float> input_data = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 1, 2, 4}, false, input_data),
{1, 1, 1, 2}, "linear", "align_corners", "",
ExpectedEPNodeAssignment::All);
}
// Test QDQ Resize downsample with mode: "linear", coordinate_transformation_mode: "half_pixel"
TEST_F(QnnHTPBackendTests, Resize_DownSample_Linear_HalfPixel) {
std::vector<float> input_data = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 1, 2, 4}, false, input_data),
{1, 1, 1, 2}, "linear", "half_pixel", "",
ExpectedEPNodeAssignment::All);
}
// Test 2x QDQ Resize mode: "linear", coordinate_transformation_mode: "pytorch_half_pixel"
// QNN EP uses QNN's Resize op.
TEST_F(QnnHTPBackendTests, ResizeU8_2xLinearPytorchHalfPixel) {
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 3, 4, 4}, false, -10.0f, 10.0f),
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 48);
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 3, 4, 4}, false, input_data),
{1, 3, 8, 8}, "linear", "pytorch_half_pixel", "",
ExpectedEPNodeAssignment::All);
}
// Test 2x QDQ Resize mode: "linear", coordinate_transformation_mode: "half_pixel"
// QNN EP uses QNN's Resize op.
TEST_F(QnnHTPBackendTests, ResizeU8_2xLinearHalfPixel) {
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 48);
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 3, 4, 4}, false, input_data),
{1, 3, 8, 8}, "linear", "half_pixel", "",
ExpectedEPNodeAssignment::All);
}
// Test 2x QDQ Resize mode: "linear", coordinate_transformation_mode: "align_corners"
// QNN EP uses QNN's Resize op.
TEST_F(QnnHTPBackendTests, ResizeU8_2xLinearAlignCorners) {
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 48);
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 3, 4, 4}, false, input_data),
{1, 3, 8, 8}, "linear", "align_corners", "",
ExpectedEPNodeAssignment::All);
}
// Test 2x QDQ Resize mode: "linear", coordinate_transformation_mode: "asymmetric"
// QNN EP uses QNN's Resize op.
TEST_F(QnnHTPBackendTests, ResizeU8_2xLinearAsymmetric) {
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 48);
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 3, 4, 4}, false, input_data),
{1, 3, 8, 8}, "linear", "asymmetric", "",
ExpectedEPNodeAssignment::All);
}
// Test 2x QDQ Resize mode: "nearest", coordinate_transformation_mode: "half_pixel", nearest_mode: "round_prefer_floor"
// QNN EP uses QNN's Resize op.
TEST_F(QnnHTPBackendTests, ResizeU8_2xNearestHalfPixelRoundPreferFloor) {
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 3, 4, 4}, false, -10.0f, 10.0f),
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 48);
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 3, 4, 4}, false, input_data),
{1, 3, 8, 8}, "nearest", "half_pixel", "round_prefer_floor",
ExpectedEPNodeAssignment::All);
}
TEST_F(QnnHTPBackendTests, ResizeU8_2xNearestAsymmetricFloor) {
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 3, 4, 4}, false, -10.0f, 10.0f),
{1, 3, 8, 8}, "nearest", "asymmetric", "floor",
ExpectedEPNodeAssignment::All);
}
// TODO: Investigate with Qualcomm. The qnn-onnx-converter tool translates ONNX Resize [nearest, asymmetric, ceil] to
// QNN ResizeNearestNeighbor {align_corners: 0, half_pixel: 0}, which is NOT equivalent. It would be better to use
// QNN's own Resize operator (instead of ResizeNearestNeighbor), but it doesn't support the "asymmetric" coordinate
// transform mode.
//
// QNN v2.13: Inaccuracy detected for output 'output', element 189.
// Output quant params: scale=0.078431375324726105, zero_point=127.
// Expected val: -2.663428783416748
// QNN QDQ val: 7.4509806632995605 (err 10.114409446716309)
// CPU QDQ val: -2.6666667461395264 (err 0.0032379627227783203)
TEST_F(QnnHTPBackendTests, DISABLED_ResizeU8_2xNearestAsymmetricCeil) {
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 3, 4, 4}, false, -10.0f, 10.0f),
// Test that the nearest_mode "ceil" is not supported on the HTP backend.
TEST_F(QnnHTPBackendTests, ResizeU8_NearestModeCeil_Unsupported) {
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 48);
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 3, 4, 4}, false, input_data),
{1, 3, 8, 8}, "nearest", "asymmetric", "ceil",
ExpectedEPNodeAssignment::All);
ExpectedEPNodeAssignment::None);
}
// Test 3x QDQ Resize mode: "nearest", coordinate_transformation_mode: "asymmetric", nearest_mode: "floor".
// QNN EP uses QNN's ResizeNearestNeighbor op.
TEST_F(QnnHTPBackendTests, ResizeU8_3xNearestAsymmetricFloor) {
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 3, 4, 4}, false, -10.0f, 10.0f),
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 48);
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 3, 4, 4}, false, input_data),
{1, 3, 12, 12}, "nearest", "asymmetric", "floor",
ExpectedEPNodeAssignment::All);
}
// Test 2x QDQ Resize mode: "nearest", coordinate_transformation_mode: "asymmetric", nearest_mode: "round_prefer_floor"
// QNN EP uses QNN's Resize op.
TEST_F(QnnHTPBackendTests, ResizeU8_2xNearestAsymmetricRoundPreferFloor) {
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 8);
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 2, 2, 2}, false, input_data),
{1, 2, 4, 4}, "nearest", "asymmetric", "round_prefer_floor",
ExpectedEPNodeAssignment::All);
}
// Test 3x QDQ Resize mode: "nearest", coordinate_transformation_mode: "asymmetric", nearest_mode: "round_prefer_floor"
// QNN EP uses QNN's Resize op.
//
// TODO: Inaccuracy detected for output 'output_0', element 2.
// Output quant params: scale=0.078431375324726105, zero_point=127.
// Expected val: -3.3333334922790527
// QNN QDQ val: -9.960784912109375 (err 6.6274514198303223)
// CPU QDQ val: -3.2941176891326904 (err 0.039215803146362305)
//
// More debugging info:
// Input elements f32[1,1,2,2] = -10.0000000 -3.33333349 3.33333302 10.0000000
// ORT CPU EP (f32 model) outputs: -10.0000000 -10.0000000 -3.33333349 -3.33333349 -3.33333349 -3.33333349 -10.00 ...
// ORT CPU EP (qdq model) outputs: -9.96078491 -9.96078491 -3.29411769 -3.29411769 -3.29411769 -3.29411769 -9.961 ...
// ORT QNN EP (qdq model) outputs: -9.96078491 -9.96078491 -9.96078491 -3.37254906 -3.37254906 -3.37254906 -9.961 ...
TEST_F(QnnHTPBackendTests, DISABLED_ResizeU8_3xNearestAsymmetricRoundPreferFloor) {
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 4);
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 1, 2, 2}, false, input_data),
{1, 1, 6, 6}, "nearest", "asymmetric", "round_prefer_floor",
ExpectedEPNodeAssignment::All);
}
// Test 0.5x QDQ Resize mode: "nearest", coordinate_transformation_mode: "asymmetric", nearest_mode: "floor"
// QNN EP uses QNN's ResizeNearestNeighbor op.
TEST_F(QnnHTPBackendTests, ResizeU8_HalfNearestAsymmetricFloor) {
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 3, 4, 4}, false, -10.0f, 10.0f),
std::vector<float> input_data = GetFloatDataInRange(-10.0f, 10.0f, 48);
RunQDQResizeOpTest<uint8_t>(TestInputDef<float>({1, 3, 4, 4}, false, input_data),
{1, 3, 2, 2}, "nearest", "asymmetric", "floor",
ExpectedEPNodeAssignment::All);
}