This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new c1d32438a0 [BugFix][TOPI] Fix resize accuracy issue with non-floor
rounding (#18838)
c1d32438a0 is described below
commit c1d32438a00530e3071d0d5721ff93ff65ea3050
Author: Tianqi Chen <[email protected]>
AuthorDate: Fri Feb 27 11:21:16 2026 -0500
[BugFix][TOPI] Fix resize accuracy issue with non-floor rounding (#18838)
## Summary
The int_div optimization in `topi.image.resize` was applied
unconditionally
for `nearest_neighbor` + `asymmetric` mode, regardless of rounding
method.
This caused accuracy issues when `rounding_method` is not `"floor"`
(e.g.,
`"round"`, `"round_prefer_ceil"`), because integer division truncates
toward
zero rather than rounding.
**Fix**: Gate the int_div optimization on `rounding_method == "floor"`
or
`rounding_method == ""` (the default, which gets resolved to `"floor"`
for
non-align_corners modes).
- Updates `_resize_2d` in `python/tvm/topi/image/resize.py`
- Updates reference implementation in
`python/tvm/topi/testing/resize_python.py`
- Updates legalize test expected output to reflect the new behavior
---
python/tvm/topi/image/resize.py | 5 +-
python/tvm/topi/testing/resize_python.py | 55 ++++++++++++++++------
.../relax/test_transform_legalize_ops_image.py | 4 +-
3 files changed, 46 insertions(+), 18 deletions(-)
diff --git a/python/tvm/topi/image/resize.py b/python/tvm/topi/image/resize.py
index fdd13ecc01..e8303daa02 100644
--- a/python/tvm/topi/image/resize.py
+++ b/python/tvm/topi/image/resize.py
@@ -592,8 +592,9 @@ def _resize_2d(
height_use_int_div = False
width_use_int_div = False
if method == "nearest_neighbor" and coordinate_transformation_mode ==
"asymmetric":
- height_use_int_div = can_convert_multiply_to_intdiv(image_height,
target_height)
- width_use_int_div = can_convert_multiply_to_intdiv(image_width,
target_width)
+ if rounding_method == "floor" or rounding_method == "":
+ height_use_int_div = can_convert_multiply_to_intdiv(image_height,
target_height)
+ width_use_int_div = can_convert_multiply_to_intdiv(image_width,
target_width)
n, c, y, x, cc, inum, ic = get_2d_indices(indices, layout)
box_idx = box_indices(n) if box_indices is not None else n
diff --git a/python/tvm/topi/testing/resize_python.py
b/python/tvm/topi/testing/resize_python.py
index 5bdc182a05..a63a08bced 100644
--- a/python/tvm/topi/testing/resize_python.py
+++ b/python/tvm/topi/testing/resize_python.py
@@ -41,19 +41,35 @@ def get_inx(x, image_width, target_width,
coordinate_transformation_mode):
return in_x
-def get_index(x, image_width, target_width, coordinate_transformation_mode):
+def get_index(x, image_width, target_width, coordinate_transformation_mode,
rounding_method=""):
"""get and round the nearest index for nearest_neighbor"""
in_x = get_inx(x, image_width, target_width,
coordinate_transformation_mode)
- if coordinate_transformation_mode == "align_corners":
- # round prefer ceil
+
+ effective_rounding_method = rounding_method
+ if not effective_rounding_method:
+ if coordinate_transformation_mode == "align_corners":
+ effective_rounding_method = "round"
+ else:
+ effective_rounding_method = "floor"
+
+ if effective_rounding_method == "floor":
+ out = math.floor(in_x)
+ elif effective_rounding_method == "round":
+ out = round(in_x)
+ elif effective_rounding_method == "round_prefer_floor":
+ out = math.ceil(in_x - 0.5)
+ elif effective_rounding_method == "round_prefer_ceil":
out = math.floor(in_x + 0.5)
+ elif effective_rounding_method == "ceil":
+ out = math.ceil(in_x)
else:
- out = math.floor(in_x)
+ raise ValueError(f"Unknown rounding method: {rounding_method!r}")
+
out = max(min(out, image_width - 1), 0)
- return out
+ return int(out)
-def resize3d_nearest(arr, scale, coordinate_transformation_mode):
+def resize3d_nearest(arr, scale, coordinate_transformation_mode,
rounding_method=""):
"""Populate the array by scale factor"""
d, h, w = arr.shape
out_d, out_h, out_w = [round(i * s) for i, s in zip(arr.shape, scale)]
@@ -61,9 +77,9 @@ def resize3d_nearest(arr, scale,
coordinate_transformation_mode):
for z in range(out_d):
for y in range(out_h):
for x in range(out_w):
- in_z = get_index(z, d, out_d, coordinate_transformation_mode)
- in_y = get_index(y, h, out_h, coordinate_transformation_mode)
- in_x = get_index(x, w, out_w, coordinate_transformation_mode)
+ in_z = get_index(z, d, out_d, coordinate_transformation_mode,
rounding_method)
+ in_y = get_index(y, h, out_h, coordinate_transformation_mode,
rounding_method)
+ in_x = get_index(x, w, out_w, coordinate_transformation_mode,
rounding_method)
out[z, y, x] = arr[in_z, in_y, in_x]
return out
@@ -170,7 +186,11 @@ def resize3d_cubic(data_in, scale,
coordinate_transformation_mode):
def resize3d_ncdhw(
- data, scale, method="nearest_neighbor",
coordinate_transformation_mode="align_corners"
+ data,
+ scale,
+ method="nearest_neighbor",
+ coordinate_transformation_mode="align_corners",
+ rounding_method="",
):
"""reference kernel for 3D image resizing"""
ishape = data.shape
@@ -189,7 +209,7 @@ def resize3d_ncdhw(
for c in range(oshape[1]):
if method == "nearest_neighbor":
output_np[b, c, :, :, :] = resize3d_nearest(
- data[b, c, :, :, :], scale, coordinate_transformation_mode
+ data[b, c, :, :, :], scale,
coordinate_transformation_mode, rounding_method
)
elif method == "linear":
output_np[b, c, :, :, :] = resize3d_linear(
@@ -211,6 +231,7 @@ def resize1d_python(
layout="NCW",
method="nearest_neighbor",
coordinate_transformation_mode="align_corners",
+ rounding_method="",
):
"""Python version of 3D scaling using nearest neighbour"""
@@ -218,7 +239,9 @@ def resize1d_python(
data = data.transpose([0, 2, 1])
data = np.expand_dims(data, axis=[2, 3])
- output_np = resize3d_ncdhw(data, (1, 1) + scale, method,
coordinate_transformation_mode)
+ output_np = resize3d_ncdhw(
+ data, (1, 1) + scale, method, coordinate_transformation_mode,
rounding_method
+ )
output_np = np.squeeze(output_np, axis=2)
output_np = np.squeeze(output_np, axis=2)
@@ -234,6 +257,7 @@ def resize2d_python(
layout="NCHW",
method="nearest_neighbor",
coordinate_transformation_mode="align_corners",
+ rounding_method="",
):
"""Python version of scaling using nearest neighbour"""
@@ -248,7 +272,9 @@ def resize2d_python(
)
data = np.expand_dims(data, axis=2)
- output_np = resize3d_ncdhw(data, (1,) + scale, method,
coordinate_transformation_mode)
+ output_np = resize3d_ncdhw(
+ data, (1,) + scale, method, coordinate_transformation_mode,
rounding_method
+ )
output_np = np.squeeze(output_np, axis=2)
if layout == "NHWC":
@@ -266,13 +292,14 @@ def resize3d_python(
layout="NCDHW",
method="nearest_neighbor",
coordinate_transformation_mode="align_corners",
+ rounding_method="",
):
"""Python version of 3D scaling using nearest neighbour"""
if layout == "NDHWC":
data = data.transpose([0, 4, 1, 2, 3])
- output_np = resize3d_ncdhw(data, scale, method,
coordinate_transformation_mode)
+ output_np = resize3d_ncdhw(data, scale, method,
coordinate_transformation_mode, rounding_method)
if layout == "NDHWC":
output_np = output_np.transpose([0, 2, 3, 4, 1])
diff --git a/tests/python/relax/test_transform_legalize_ops_image.py
b/tests/python/relax/test_transform_legalize_ops_image.py
index b89ace7aa2..19cc290f3b 100644
--- a/tests/python/relax/test_transform_legalize_ops_image.py
+++ b/tests/python/relax/test_transform_legalize_ops_image.py
@@ -45,9 +45,9 @@ def test_image_resize2d():
for i0, i1, i2, i3 in T.grid(T.int64(2), T.int64(16), T.int64(16),
T.int64(3)):
with T.sblock("resize"):
i0_1, i1_1, i2_1, i3_1 = T.axis.remap("SSSS", [i0, i1, i2,
i3])
- T.reads(rxplaceholder[i0_1, T.max(T.min(T.Div(i1_1,
T.int64(2)), T.int64(7)), T.int64(0)), T.max(T.min(T.Div(i2_1, T.int64(2)),
T.int64(7)), T.int64(0)), i3_1])
+ T.reads(rxplaceholder[i0_1, T.int64(0):T.int64(8),
T.int64(0):T.int64(8), i3_1])
T.writes(resize[i0_1, i1_1, i2_1, i3_1])
- resize[i0_1, i1_1, i2_1, i3_1] = rxplaceholder[i0_1,
T.max(T.min(T.Div(i1_1, T.int64(2)), T.int64(7)), T.int64(0)),
T.max(T.min(T.Div(i2_1, T.int64(2)), T.int64(7)), T.int64(0)), i3_1]
+ resize[i0_1, i1_1, i2_1, i3_1] = rxplaceholder[i0_1,
T.max(T.min(T.Cast("int64", T.round(T.float32(0.5) * T.Cast("float32", i1_1))),
T.int64(7)), T.int64(0)), T.max(T.min(T.Cast("int64", T.round(T.float32(0.5) *
T.Cast("float32", i2_1))), T.int64(7)), T.int64(0)), i3_1]
# fmt: on
mod = LegalizeOps()(Resize2D)