Skip to content
Merged
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 @@ -183,12 +183,23 @@ bool DropQDQNodeGroupSelector::Check(const GraphViewer& graph_viewer, const Node
return false;
}

// Resize with mode != "nearest" (linear/cubic) is not order-preserving on integers,
// so dropping the surrounding Q/DQ pair would change numerical results. Only the
// default nearest-neighbor mode is safe to fold. See issue #21319.
// Resize output depends on both the interpolation mode and the coordinate transformation mode:
// - "nearest" mode copies existing input values, so it can operate directly on quantized integers.
// - Non-nearest modes (e.g., "linear", "cubic") interpolate using float arithmetic, which is only
// correct in the dequantized (float) domain, so QDQ must be preserved.
// - "tf_crop_and_resize" writes extrapolation_value (authored in the float domain) into out-of-crop
// positions; dropping QDQ would store that float value raw in the quantized domain, even for nearest.
// Only allow dropping QDQ for nearest mode without tf_crop_and_resize coordinate transformation.
if (node.OpType() == "Resize") {
const auto* mode_attr = graph_utils::GetNodeAttribute(node, "mode");
if (mode_attr != nullptr && mode_attr->s() != "nearest") {
const auto& attrs = node.GetAttributes();
// "mode" defaults to "nearest" when absent. It is always present after Graph::Resolve() injects
// schema defaults; the absence check is a defensive fallback for hand-built/unresolved graphs.
const auto mode_iter = attrs.find("mode");
if (mode_iter != attrs.end() && mode_iter->second.s() != "nearest") {
return false;
}
const auto coord_mode_iter = attrs.find("coordinate_transformation_mode");
if (coord_mode_iter != attrs.end() && coord_mode_iter->second.s() == "tf_crop_and_resize") {
return false;
}
}
Expand Down
37 changes: 37 additions & 0 deletions onnxruntime/test/optimizer/qdq_transformer_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1979,6 +1979,43 @@ TEST(QDQTransformerTests, Resize) {
test_case({2, 13, 12, 37}, rand_gen.Uniform<int64_t>(std::vector<int64_t>{4}, 1, 16), true /*use_contrib_qdq*/);
}

// Resize with a non-nearest mode (linear, cubic) interpolates values in float space, so the
// QDQ nodes must be preserved to keep the computation in the dequantized domain. Nearest mode
// with "tf_crop_and_resize" is also guarded in the selector (it writes a float-domain
// extrapolation_value), but that path is not exercised here because the shared test helper does
// not supply a valid roi input.
TEST(QDQTransformerTests, Resize_NonNearest_No_QDQ_Drop) {
auto test_case = [&](const std::vector<int64_t>& input_shape,
const std::vector<int64_t>& sizes_data,
const std::string& mode,
bool use_contrib_qdq = false) {
auto check_graph = [&](InferenceSessionWrapper& session) {
auto op_to_count = CountOpsInGraph(session.GetGraph());
const QDQOpKeys qdq_keys = GetQDQOpKeys(use_contrib_qdq);
EXPECT_EQ(op_to_count["Resize"], 1);
// QDQ nodes must NOT be dropped here because interpolation is computed in float space.
EXPECT_EQ(op_to_count[qdq_keys.quantize_linear], 1);
EXPECT_EQ(op_to_count[qdq_keys.dequantize_linear], 1);
};

TransformerTester(BuildQDQResizeTestCase(input_shape,
sizes_data,
mode,
"half_pixel", // coordinate_transformation_mode
"round_prefer_floor", // nearest_mode (only used for nearest)
false, // add_dq_output_float
use_contrib_qdq),
check_graph,
TransformerLevel::Level1,
TransformerLevel::Level2);
};

test_case({1, 1, 1, 3}, {1, 1, 1, 6}, "linear");
test_case({1, 1, 1, 3}, {1, 1, 1, 6}, "linear", true /*use_contrib_qdq*/);
test_case({1, 1, 3, 3}, {1, 1, 6, 6}, "cubic");
test_case({1, 1, 3, 3}, {1, 1, 6, 6}, "cubic", true /*use_contrib_qdq*/);
}

TEST(QDQTransformerTests, Resize_No_Fusion) {
auto test_case = [&](const std::vector<int64_t>& input_shape,
const std::vector<int64_t>& sizes_shape,
Expand Down
Loading