Skip to content

[Fix][Relax][ONNX] Import Resize with empty scales tensor and symbolic sizes - #20406

Open
LiRunGuo wants to merge 1 commit into
apache:mainfrom
LiRunGuo:fix/onnx-resize-empty-scales-symbolic-sizes
Open

LiRunGuo wants to merge 1 commit into
apache:mainfrom
LiRunGuo:fix/onnx-resize-empty-scales-symbolic-sizes

Conversation

@LiRunGuo

Copy link
Copy Markdown

Models exported from PyTorch with F.interpolate(x, size=...) currently fail to import through from_onnx, and the failure comes from how the Resize converter unpacks its optional inputs rather than from the resize itself. There are two separate triggers.

At opset 12 and below, the PyTorch exporter does not omit roi and scales when sizes is used; it passes them as empty constant tensors. The converter only treated a missing input as "not provided", so it saw both scales and sizes and failed the Only one of scales and sizes can be provided in Resize assertion. The ONNX spec treats an empty tensor here the same as an omitted input, so this PR maps zero-element constants to None before the check.

At opset 13 and above, when the model has a dynamic batch dimension, the exporter builds sizes as Concat(Shape(x)[:2], [H_out, W_out]). That reaches the converter as a ShapeExpr whose leading entries are symbolic, and int(val.value) raised AttributeError: 'Var' object has no attribute 'value'. Only the spatial entries are used, and relax.op.image.resize2d/resize3d already accept symbolic output extents, so the converter now keeps non-constant entries as PrimExprs instead of forcing them to Python ints. As a side effect, resizing to another tensor's runtime height and width (sizes taken from a symbolic Shape) also imports.

A minimal reproduction:

import io, torch, torch.nn.functional as F, onnx
from tvm.relax.frontend.onnx import from_onnx

class M(torch.nn.Module):
    def forward(self, x):
        return F.interpolate(x, size=(8, 8), mode="nearest")

for opset, dynamic_axes in [(11, {}), (13, {"x": {0: "N"}})]:
    buf = io.BytesIO()
    torch.onnx.export(M(), torch.randn(1, 3, 4, 4), buf, opset_version=opset,
                      input_names=["x"], dynamic_axes=dynamic_axes, dynamo=False)
    from_onnx(onnx.load_from_string(buf.getvalue()))
# opset 11: AssertionError: Only one of scales and sizes can be provided in Resize.
# opset 13: AttributeError: 'Var' object has no attribute 'value'

The new tests cover the empty-scales pattern with static and symbolic batch, sizes computed from Shape(x) with a symbolic batch, and spatial sizes taken from a second input with symbolic height and width. Each checks the imported output shape and compares against onnxruntime; all four fail without this change. I also checked PyTorch exports of nearest and bilinear F.interpolate at opsets 11, 13 and 17 with batch sizes 1 and 3, which match onnxruntime to within 2.4e-7.

…c sizes

Two input-unpacking paths in the Resize converter rejected graphs that
PyTorch emits for `F.interpolate(size=...)`:

- At opset <= 12 the exporter passes `roi` and `scales` as empty constant
  tensors rather than omitting them, so the "only one of scales and sizes"
  assertion fired even though scales was effectively absent. Treat a
  zero-element constant as a missing input.
- With a dynamic batch dimension the `sizes` input is built as
  Concat(Shape(x)[:2], spatial) and reaches the converter as a ShapeExpr
  whose leading entries are symbolic, so `int(val.value)` raised
  AttributeError on a tir.Var. Only the spatial entries are used, and the
  relax resize ops accept symbolic extents there, so keep non-constant
  entries as PrimExprs instead of forcing them to int.

Add tests for both patterns, including spatial sizes taken from another
input's symbolic shape.
@yongwww

yongwww commented Sep 22, 2026

Copy link
Copy Markdown
Member

@tvm-bot rerun

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants