This is an automated email from the ASF dual-hosted git repository.

tlopex 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 e6c6cc8785 [Fix][Relax][ONNX] Support Hardmax over a symbolic axis 
extent (#20416)
e6c6cc8785 is described below

commit e6c6cc8785091ad10c18b495a1dbdac6cdb8fc51
Author: Gawr Gura 0721 <[email protected]>
AuthorDate: Fri Sep 25 11:46:01 2026 +0800

    [Fix][Relax][ONNX] Support Hardmax over a symbolic axis extent (#20416)
    
    `Hardmax` is lowered to `argmax` followed by `one_hot`, but
    `relax.op.one_hot` takes its depth as a static integer attribute. When
    the reduced axis has a symbolic extent, the importer passed a `tir.Var`
    as the depth and failed with `TypeError: Mismatched type on argument #3
    when calling relax.op.one_hot ... Expected int but got ir.Var`. For
    opset 12 and below the input is first flattened to 2-D, so the reduced
    extent is the product of the trailing dimensions and fails the same way
    (as a `prim.Mul`) whenever any of them is symbolic, for example
    `Hardmax(axis=1)` on an `[N, 3, W]` input.
    
    This keeps the `one_hot` lowering whenever the extent is static, so the
    IR for existing models is unchanged. For a symbolic extent it compares
    the `keepdims` argmax index against an `arange` over the axis, broadcast
    along it, and casts the result to the input dtype. `argmax` returns the
    first maximum, so ties still resolve to the first index as the ONNX spec
    requires.
    
    The tests cover opsets 11 and 13 with the reduced axis symbolic, a
    flattened extent that is a product involving a symbolic dim, a static
    axis on an otherwise symbolic input, and inputs with ties, all compared
    against onnxruntime. Six of the seven fail without this change; the
    remaining one checks the unchanged static-extent path.
---
 python/tvm/relax/frontend/onnx/onnx_frontend.py |  8 ++++++++
 tests/python/relax/test_frontend_onnx.py        | 13 +++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/python/tvm/relax/frontend/onnx/onnx_frontend.py 
b/python/tvm/relax/frontend/onnx/onnx_frontend.py
index a3e6140381..c078073c33 100644
--- a/python/tvm/relax/frontend/onnx/onnx_frontend.py
+++ b/python/tvm/relax/frontend/onnx/onnx_frontend.py
@@ -1080,6 +1080,14 @@ class Hardmax(OnnxOpConverter):
             data = bb.normalize(data)
         normalized_axis, axis_extent = _get_axis_extent(data, axis, "Hardmax")
         dtype = data.ty.dtype
+        if not isinstance(axis_extent, int):
+            # one_hot needs a static depth. For a symbolic extent, mark the 
first
+            # maximum by comparing its index against a broadcast iota instead.
+            argmax = relax.op.argmax(data, axis=normalized_axis, keepdims=True)
+            iota_shape = [1] * data.ty.ndim
+            iota_shape[normalized_axis] = axis_extent
+            iota = relax.op.reshape(relax.op.arange(0, axis_extent, 
dtype="int64"), iota_shape)
+            return relax.op.astype(relax.op.equal(iota, argmax), dtype)
         argmax = relax.op.argmax(data, axis=normalized_axis)
         on_value = relax.prim_value(tvm.tirx.const(1.0, dtype))
         off_value = relax.prim_value(tvm.tirx.const(0.0, dtype))
diff --git a/tests/python/relax/test_frontend_onnx.py 
b/tests/python/relax/test_frontend_onnx.py
index abef4b7cf5..ec8bc6daed 100644
--- a/tests/python/relax/test_frontend_onnx.py
+++ b/tests/python/relax/test_frontend_onnx.py
@@ -1466,6 +1466,19 @@ def test_hardmax_ir():
     tvm.ir.assert_structural_equal(tvm_model, Expected)
 
 
[email protected]("opset", [11, 13])
[email protected]("axis", [0, 1, -1])
[email protected]("symbolic", [False, True], ids=["static", "symbolic"])
+def test_hardmax(opset, axis, symbolic):
+    shape = [2, 3, 4]
+    model = make_unary_model(
+        "Hardmax", ["N", "C", "W"] if symbolic else shape, attrs={"axis": axis}
+    )
+    # Few distinct values, so rows tie and the first maximum must win.
+    x = rg.integers(0, 3, size=shape).astype("float32")
+    check_correctness(model, inputs={"x": x}, opset=opset)
+
+
 def test_legacy_softmax_family_opset11_axis_semantics():
     def verify_legacy_softmax_family_axis_ir(op_name: str, expected, 
axis_attr: int | None = None):
         attrs = {} if axis_attr is None else {"axis": axis_attr}

Reply via email to