From 9a793e7ceecbb31806bc607fc86b25418f482037 Mon Sep 17 00:00:00 2001 From: tqchen Date: Thu, 26 Feb 2026 16:59:34 +0000 Subject: [PATCH] [BugFix][TOPI] Fix resize accuracy issue with non-floor rounding `can_convert_multiply_to_intdiv` was enabled unconditionally for `nearest_neighbor` + `asymmetric` mode, even when `rounding_method` is not `floor`. This converted the mapped coordinate via integer division (equivalent to floor) regardless of the requested rounding, producing wrong results whenever the target size is an exact integer multiple of the source size. Fix: guard the `can_convert_multiply_to_intdiv` optimisation so it only activates when `rounding_method` is `"floor"` or `""` (which defaults to `"floor"` for asymmetric mode). Also update the Python reference implementation (`resize_python.py`) to accept and thread a `rounding_method` parameter through all public entry points (`get_index`, `resize3d_nearest`, `resize3d_ncdhw`, `resize1d_python`, `resize2d_python`, `resize3d_python`), so the reference matches the corrected TVM behaviour. Add `tests/python/te/test_topi_resize2d.py` with regression tests that exercise the buggy path (non-floor rounding + asymmetric + integer scale factor) as well as sanity checks for floor rounding and non-integer scale factors. Fixes: https://github.com/apache/tvm/pull/16137 --- python/tvm/topi/image/resize.py | 5 +-- python/tvm/topi/testing/resize_python.py | 45 +++++++++++++++++------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/python/tvm/topi/image/resize.py b/python/tvm/topi/image/resize.py index fdd13ecc014b..e8303daa0256 100644 --- a/python/tvm/topi/image/resize.py +++ b/python/tvm/topi/image/resize.py @@ -592,8 +592,9 @@ def _cast_output(value, data_dtype="float32", out_dtype=None): 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 5bdc182a056b..e74fe8f775b8 100644 --- a/python/tvm/topi/testing/resize_python.py +++ b/python/tvm/topi/testing/resize_python.py @@ -41,19 +41,29 @@ 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 + if rounding_method == "" or rounding_method == "floor": + if coordinate_transformation_mode == "align_corners": + out = math.floor(in_x + 0.5) + else: + out = math.floor(in_x) + elif rounding_method == "round": out = math.floor(in_x + 0.5) + elif rounding_method == "round_prefer_floor": + out = math.ceil(in_x - 0.5) + elif rounding_method == "round_prefer_ceil": + out = math.floor(in_x + 0.5) + elif rounding_method == "ceil": + out = math.ceil(in_x) else: out = math.floor(in_x) out = max(min(out, image_width - 1), 0) return 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 +71,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 +180,11 @@ def _get_patch(zint, yint, xint): 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 +203,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 +225,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 +233,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 +251,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 +266,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 +286,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])