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 eeb1d61c83 [Fix][Relax][Torch] Materialize runtime scalar shape values 
(#20138)
eeb1d61c83 is described below

commit eeb1d61c83a970fab10af32cdc2c800613c022ff
Author: Akaash Parthasarathy <[email protected]>
AuthorDate: Sat Aug 29 21:20:26 2026 -0700

    [Fix][Relax][Torch] Materialize runtime scalar shape values (#20138)
    
    Materialize runtime integer scalars from `torch.export` as Relax
    symbolic values and convert them back to rank-zero tensors for fill
    operations. Preserve `full`/`full_like` dtype semantics, support direct
    `fill`/`fill_` without decomposition, and lower symbolic shape
    comparisons as real predicates. Runtime scalar extraction now also
    accepts statically one-element integer tensors at higher ranks and
    signed/unsigned integer dtypes, preserving negative, unsigned, and
    64-bit values. Python scalar fills are materialized in the destination
    dtype across `full`, `full_like`, `fill`, `fill_`, `masked_fill`, and
    `masked_fill_`, avoiding 32-bit narrowing or precision loss. Symbolic
    boolean fills, negation, floor division, and modulo are also supported.
---
 .../frontend/torch/base_fx_graph_translator.py     | 113 ++++--
 .../frontend/torch/exported_program_translator.py  |  25 +-
 tests/python/relax/test_frontend_dynamo.py         |   2 +-
 .../relax/test_frontend_from_exported_program.py   | 400 ++++++++++++++++++++-
 4 files changed, 499 insertions(+), 41 deletions(-)

diff --git a/python/tvm/relax/frontend/torch/base_fx_graph_translator.py 
b/python/tvm/relax/frontend/torch/base_fx_graph_translator.py
index b0bb14ac95..d600987cdd 100644
--- a/python/tvm/relax/frontend/torch/base_fx_graph_translator.py
+++ b/python/tvm/relax/frontend/torch/base_fx_graph_translator.py
@@ -2501,15 +2501,35 @@ class BaseFXGraphImporter(metaclass=abc.ABCMeta):
     def _fill(self, node: fx.Node) -> relax.Var:
         args = self.retrieve_args(node)
         x = args[0]
-        dtype = x.ty.dtype
-        value = args[1] if isinstance(args[1], relax.Expr) else 
relax.const(args[1], dtype)
+        dtype = str(x.ty.dtype)
+        value = self._convert_scalar_fill_value(args[1], dtype)
         return self.block_builder.emit(relax.op.full(x.ty.shape, value, dtype))
 
+    def _prim_value_to_scalar_tensor(self, value: relax.Expr, dtype: str) -> 
relax.Var:
+        """Materialize an integer or boolean primitive value as a rank-zero 
tensor."""
+        if not value.ty.matches_code(DataTypeCode.INT, DataTypeCode.UINT, 
DataTypeCode.BOOL):
+            raise TypeError(f"Cannot materialize primitive value of dtype 
{value.ty} as a tensor")
+        shape_value = value if str(value.ty) == "int64" else 
value.astype("int64")
+        value_tensor = self.block_builder.emit(
+            relax.op.shape_to_tensor(relax.ShapeExpr([shape_value]))
+        )
+        if dtype != "int64":
+            value_tensor = 
self.block_builder.emit(relax.op.astype(value_tensor, dtype))
+        return self.block_builder.emit(relax.op.squeeze(value_tensor, 
axis=[0]))
+
+    def _convert_scalar_fill_value(self, value, dtype: str) -> relax.Expr:
+        """Convert a PyTorch scalar fill value to a rank-zero Relax tensor."""
+        if isinstance(getattr(value, "ty", None), PrimType):
+            return self._prim_value_to_scalar_tensor(value, dtype)
+        if isinstance(value, relax.Expr):
+            return value
+        return relax.const(value, dtype)
+
     def _inplace_fill(self, node: fx.Node) -> relax.Var:
         args = self.retrieve_args(node)
         x = args[0]
-        dtype = x.ty.dtype.dtype
-        value = args[1] if isinstance(args[1], relax.Expr) else 
relax.const(args[1], dtype)
+        dtype = str(x.ty.dtype)
+        value = self._convert_scalar_fill_value(args[1], dtype)
         filled = self.block_builder.emit(relax.op.full(x.ty.shape, value, 
dtype))
         self.env[node.args[0]] = filled
         return filled
@@ -2519,10 +2539,23 @@ class BaseFXGraphImporter(metaclass=abc.ABCMeta):
 
         args = self.retrieve_args(node)
         size = relax.ShapeExpr(args[0] if isinstance(args[0], list | tuple) 
else (args[0],))
-        dtype = self._convert_data_type(
-            node.kwargs.get("dtype", torch.get_default_dtype()), self.env
-        )
-        value = args[1] if isinstance(args[1], relax.expr.Constant) else 
relax.const(args[1], dtype)
+        torch_dtype = node.kwargs.get("dtype")
+        if torch_dtype is None:
+            output_meta = node.meta.get("val")
+            if output_meta is None:
+                output_meta = node.meta.get("tensor_meta")
+            torch_dtype = getattr(output_meta, "dtype", None)
+        if torch_dtype is None:
+            if isinstance(args[1], bool):
+                torch_dtype = "bool"
+            elif isinstance(args[1], int):
+                torch_dtype = "int64"
+            elif isinstance(getattr(args[1], "ty", None), PrimType):
+                torch_dtype = args[1].ty.dtype
+            else:
+                torch_dtype = torch.get_default_dtype()
+        dtype = self._convert_data_type(torch_dtype, self.env)
+        value = self._convert_scalar_fill_value(args[1], dtype)
         return self.block_builder.emit(
             relax.op.full(
                 size,
@@ -2532,15 +2565,28 @@ class BaseFXGraphImporter(metaclass=abc.ABCMeta):
         )
 
     def _full_like(self, node: fx.Node) -> relax.Var:
-        x = self.env[node.args[0]]
-        value = node.args[1]
-        fill_value = relax.const(value)
-
-        x_dtype = x.ty.dtype.dtype
-        fill_dtype = None
-        if isinstance(value, int | float) and (math.isinf(value) or 
math.isnan(value)):
+        args = self.retrieve_args(node)
+        x = args[0]
+        value = args[1]
+        x_dtype = str(x.ty.dtype)
+        torch_dtype = node.kwargs.get("dtype")
+        dtype = self._convert_data_type(x_dtype if torch_dtype is None else 
torch_dtype, self.env)
+        fill_dtype = dtype if dtype != x_dtype else None
+        if (
+            fill_dtype is None
+            and isinstance(value, int | float)
+            and (math.isinf(value) or math.isnan(value))
+        ):
             if not ("float" in x_dtype or "bfloat16" in x_dtype):
                 fill_dtype = "float32"
+                dtype = fill_dtype
+
+        if isinstance(getattr(value, "ty", None), PrimType):
+            fill_value = self._prim_value_to_scalar_tensor(value, dtype)
+        elif isinstance(value, relax.Expr):
+            fill_value = value
+        else:
+            fill_value = relax.const(value, dtype)
 
         return self.block_builder.emit(relax.op.full_like(x, fill_value, 
dtype=fill_dtype))
 
@@ -2551,16 +2597,14 @@ class BaseFXGraphImporter(metaclass=abc.ABCMeta):
         return self.block_builder.emit(relax.op.take(x, index, dim))
 
     def _inplace_masked_fill(self, node: fx.Node) -> relax.Var:
-        x = self.env[node.args[0]]
-        mask = self.env[node.args[1]]
-        value = node.args[2]
-        rx_value = relax.const(value)
-
-        x_dtype = x.ty.dtype.dtype
+        args = self.retrieve_args(node)
+        x, mask, value = args[:3]
+        x_dtype = str(x.ty.dtype)
         fill_dtype = None
         if isinstance(value, int | float) and (math.isinf(value) or 
math.isnan(value)):
             if not ("float" in x_dtype or "bfloat16" in x_dtype):
                 fill_dtype = "float32"
+        rx_value = self._convert_scalar_fill_value(value, fill_dtype or 
x_dtype)
 
         values = self.block_builder.emit(relax.op.full_like(x, rx_value, 
dtype=fill_dtype))
 
@@ -2596,16 +2640,14 @@ class BaseFXGraphImporter(metaclass=abc.ABCMeta):
         )
 
     def _masked_fill(self, node: fx.Node) -> relax.Var:
-        x = self.env[node.args[0]]
-        mask = self.env[node.args[1]]
-        value = node.args[2]
-        rx_value = relax.const(value)
-
-        x_dtype = x.ty.dtype.dtype
+        args = self.retrieve_args(node)
+        x, mask, value = args[:3]
+        x_dtype = str(x.ty.dtype)
         fill_dtype = None
         if isinstance(value, int | float) and (math.isinf(value) or 
math.isnan(value)):
             if not ("float" in x_dtype or "bfloat16" in x_dtype):
                 fill_dtype = "float32"
+        rx_value = self._convert_scalar_fill_value(value, fill_dtype or 
x_dtype)
 
         values = self.block_builder.emit(relax.op.full_like(x, rx_value, 
dtype=fill_dtype))
 
@@ -2807,6 +2849,23 @@ class BaseFXGraphImporter(metaclass=abc.ABCMeta):
 
     def _item(self, node: fx.Node) -> relax.Var:
         x = self.env[node.args[0]]
+        shape = self.shape_of(x)
+        dtype = x.ty.dtype
+        analyzer = tvm.arith.Analyzer()
+        has_single_element = shape is not None and all(
+            analyzer.can_prove_equal(dim, 1) for dim in shape
+        )
+        if has_single_element and dtype.matches_code(DataTypeCode.INT, 
DataTypeCode.UINT):
+            scalar = x
+            if str(dtype) != "int64":
+                scalar = self.block_builder.emit(relax.op.astype(scalar, 
"int64"))
+            scalar = self.block_builder.emit(relax.op.reshape(scalar, [1]))
+            shape_value = 
self.block_builder.emit(relax.op.tensor_to_shape(scalar))
+            dim = tirx.Var(f"{node.name}_dim", "int64")
+            self.block_builder.match_cast(shape_value, relax.ShapeType([dim]))
+            return dim
+        if shape is not None and len(shape) == 0:
+            return x
         return self.block_builder.emit(relax.op.take(x, relax.const(0, 
"int64"), axis=0))
 
     def _sym_size_int(self, node: fx.Node) -> relax.Expr:
diff --git a/python/tvm/relax/frontend/torch/exported_program_translator.py 
b/python/tvm/relax/frontend/torch/exported_program_translator.py
index 0e69074af6..ced0aa7b28 100644
--- a/python/tvm/relax/frontend/torch/exported_program_translator.py
+++ b/python/tvm/relax/frontend/torch/exported_program_translator.py
@@ -1481,8 +1481,12 @@ class ExportedProgramImporter(BaseFXGraphImporter):
 
     ########## Symbolic Shape Constraints ##########
 
-    def _symbolic_comparison(self, _: fx.Node) -> relax.Expr:
-        return self.block_builder.emit(relax.const(True, dtype="bool"))
+    def _symbolic_comparison(self, intrinsic_op: Callable) -> Callable:
+        def convert(node: fx.Node) -> relax.Expr:
+            lhs, rhs = self.retrieve_args(node)
+            return self.block_builder.emit(relax.prim_value(intrinsic_op(lhs, 
rhs)))
+
+        return convert
 
     ########## Higher-Order Ops ##########
 
@@ -1754,6 +1758,7 @@ class ExportedProgramImporter(BaseFXGraphImporter):
             "logical_xor.default": self._logical_xor,
             "log_softmax.int": self._log_softmax,
             "_log_softmax.default": self._log_softmax,
+            "neg": lambda node: operator.neg(self.retrieve_args(node)[0]),
             "neg.default": self._unary_op(relax.op.negative),
             "pad.default": self._pad,
             "constant_pad_nd.default": self._constant_pad_nd,
@@ -1792,6 +1797,7 @@ class ExportedProgramImporter(BaseFXGraphImporter):
             "triu.default": self._tril_triu(relax.op.triu),
             "trunc.default": self._unary_op(relax.op.trunc),
             # binary
+            "add": self._binary_op(relax.op.add, operator.add),
             "add.Tensor": self._binary_op(relax.op.add, operator.add),
             "add.Scalar": self._binary_op(relax.op.add, operator.add),
             "add_.Tensor": self._binary_op(relax.op.add, operator.add),
@@ -1810,6 +1816,7 @@ class ExportedProgramImporter(BaseFXGraphImporter):
             "eq.Scalar": self._binary_op(relax.op.equal, operator.eq),
             "eq.Tensor": self._binary_op(relax.op.equal, operator.eq),
             "floor_divide.default": self._binary_op(relax.op.floor_divide, 
operator.floordiv),
+            "floordiv": self._binary_op(relax.op.floor_divide, 
operator.floordiv),
             "fmod.Scalar": self._fmod,
             "fmod.Tensor": self._fmod,
             "logaddexp.default": self._binary_op(relax.op.log_add_exp, 
torch.logaddexp),
@@ -1835,6 +1842,7 @@ class ExportedProgramImporter(BaseFXGraphImporter):
             "minimum.default": self._binary_op(relax.op.minimum, 
torch.minimum),
             "remainder.Tensor": self._binary_op(relax.op.floor_mod, 
operator.mod),
             "remainder.Scalar": self._binary_op(relax.op.floor_mod, 
operator.mod),
+            "mod": self._binary_op(relax.op.floor_mod, operator.mod),
             "mul": self._binary_op(relax.op.multiply, operator.mul),
             "mul.Tensor": self._binary_op(relax.op.multiply, operator.mul),
             "mul.Scalar": self._binary_op(relax.op.multiply, operator.mul),
@@ -1847,6 +1855,7 @@ class ExportedProgramImporter(BaseFXGraphImporter):
             "pow.Scalar": self._binary_op(relax.op.power, operator.pow),
             "pow.Tensor_Scalar": self._pow,
             "pow.Tensor_Tensor": self._binary_op(relax.op.power, operator.pow),
+            "sub": self._binary_op(relax.op.subtract, operator.sub),
             "sub.Tensor": self._binary_op(relax.op.subtract, operator.sub),
             "sub.Scalar": self._binary_op(relax.op.subtract, operator.sub),
             "__and__.Tensor": self._binary_op(relax.op.bitwise_and, 
operator.and_),
@@ -2031,13 +2040,15 @@ class ExportedProgramImporter(BaseFXGraphImporter):
             "item.default": self._item,
             "sym_size.int": self._sym_size_int,
             "_local_scalar_dense.default": self._item,
-            # symbolic shape constraints (no-ops for compilation)
+            # symbolic shape operations and constraints
             "sym_constrain_range_for_size.default": lambda node: 
self.env[node.args[0]],
             "_assert_scalar.default": lambda node: self.env[node.args[0]],
-            "ge": self._symbolic_comparison,
-            "le": self._symbolic_comparison,
-            "gt": self._symbolic_comparison,
-            "lt": self._symbolic_comparison,
+            "ge": self._symbolic_comparison(operator.ge),
+            "le": self._symbolic_comparison(operator.le),
+            "gt": self._symbolic_comparison(operator.gt),
+            "lt": self._symbolic_comparison(operator.lt),
+            "eq": self._symbolic_comparison(operator.eq),
+            "ne": self._symbolic_comparison(operator.ne),
             # higher-order ops
             "cond": self._cond,
         }
diff --git a/tests/python/relax/test_frontend_dynamo.py 
b/tests/python/relax/test_frontend_dynamo.py
index 72e05a5649..e795b0881f 100644
--- a/tests/python/relax/test_frontend_dynamo.py
+++ b/tests/python/relax/test_frontend_dynamo.py
@@ -504,7 +504,7 @@ def test_masked_fill():
         ) -> R.Tensor((256, 256), dtype="float32"):
             with R.dataflow():
                 lv: R.Tensor((256, 256), dtype="float32") = R.full_like(
-                    inp_1, R.const(0, "int32"), dtype=None
+                    inp_1, R.const(0.0, "float32"), dtype=None
                 )
                 lv1: R.Tensor((256, 256), dtype="float32") = R.where(inp_0, 
lv, inp_1)
                 gv: R.Tensor((256, 256), dtype="float32") = lv1
diff --git a/tests/python/relax/test_frontend_from_exported_program.py 
b/tests/python/relax/test_frontend_from_exported_program.py
index e9d2ac8b70..7dc3c73564 100644
--- a/tests/python/relax/test_frontend_from_exported_program.py
+++ b/tests/python/relax/test_frontend_from_exported_program.py
@@ -5549,6 +5549,265 @@ def test_expand_with_new_leading_dimension():
     assert tvm.arith.Analyzer().can_prove_equal(output_shape[2], 
input_shape[1])
 
 
+def test_dynamic_scalar_item_in_shape_operations():
+    class DynamicShapeOps(torch.nn.Module):
+        def forward(self, x):
+            lengths = torch.full(
+                (x.shape[0],),
+                x.shape[1],
+                device=x.device,
+                dtype=torch.int64,
+            )
+            max_len = lengths.max().item()
+            positions = torch.arange(max_len, device=x.device)
+            mask = positions.unsqueeze(0).expand(x.shape[0], -1) == 
lengths.unsqueeze(1)
+            filled = torch.full(
+                (x.shape[0], x.shape[1]),
+                x.shape[1],
+                device=x.device,
+                dtype=torch.int64,
+            )
+            shifted = torch.arange(x.shape[1] + 1, device=x.device)
+            shortened = torch.full_like(lengths, x.shape[1] - 1)
+            return mask, filled, shifted, shortened
+
+    example_args = (torch.randn(1, 4, 3, dtype=torch.float32),)
+    tokens = torch.export.Dim("tokens", min=1, max=8)
+    exported_program = export(
+        DynamicShapeOps(),
+        args=example_args,
+        dynamic_shapes={"x": {1: tokens}},
+    )
+    mod = from_exported_program(exported_program)
+
+    script = mod.script()
+    assert "R.tensor_to_shape" in script
+    assert "R.shape_to_tensor" in script
+
+    executable = relax.build(mod, tvm.target.Target("llvm"))
+    vm = relax.VirtualMachine(executable, tvm.cpu())
+    for token_count in (4, 6):
+        torch_input = torch.randn(1, token_count, 3)
+        expected = DynamicShapeOps()(torch_input)
+        actual = vm["main"](tvm.runtime.tensor(torch_input.numpy()))
+        for actual_value, expected_value in zip(actual, expected):
+            np.testing.assert_array_equal(actual_value.numpy(), 
expected_value.numpy())
+
+
[email protected](not env.has_llvm(), reason="need llvm")
[email protected](
+    ("item_dtype", "item_shape"),
+    [
+        (torch.int8, ()),
+        (torch.uint8, ()),
+        (torch.int16, ()),
+        (torch.int32, ()),
+        (torch.int64, ()),
+        (torch.int64, (1,)),
+        (torch.int64, (1, 1)),
+    ],
+)
+def test_dynamic_scalar_item_single_element_integer_dtypes(item_dtype, 
item_shape):
+    class DynamicItem(torch.nn.Module):
+        def forward(self, x):
+            lengths = torch.full(
+                (x.shape[0],),
+                x.shape[1],
+                device=x.device,
+                dtype=item_dtype,
+            )
+            value = lengths.max().reshape(item_shape).item()
+            return torch.arange(value, device=x.device), torch.full_like(
+                x, value, dtype=torch.int64
+            )
+
+    rows = torch.export.Dim("rows", min=1, max=8)
+    columns = torch.export.Dim("columns", min=1, max=8)
+    example_args = (torch.randn(3, 4, dtype=torch.float32),)
+    exported_program = export(
+        DynamicItem(),
+        args=example_args,
+        dynamic_shapes={"x": {0: rows, 1: columns}},
+    )
+    mod = from_exported_program(exported_program, run_ep_decomposition=False)
+    executable = relax.build(mod, tvm.target.Target("llvm"))
+    vm = relax.VirtualMachine(executable, tvm.cpu())
+
+    for shape in ((3, 4), (5, 2)):
+        torch_input = torch.randn(shape, dtype=torch.float32)
+        expected = DynamicItem()(torch_input)
+        actual = vm["main"](tvm.runtime.tensor(torch_input.numpy()))
+        for actual_value, expected_value in zip(actual, expected):
+            np.testing.assert_array_equal(actual_value.numpy(), 
expected_value.numpy())
+
+
[email protected](not env.has_llvm(), reason="need llvm")
+def test_runtime_scalar_item_integer_value_ranges():
+    class RuntimeItems(torch.nn.Module):
+        def forward(self, x, signed, unsigned, wide):
+            return (
+                torch.full_like(x, signed.item(), dtype=torch.int64),
+                torch.full_like(x, unsigned.item(), dtype=torch.int64),
+                torch.full_like(x, wide.item(), dtype=torch.int64),
+            )
+
+    example_args = (
+        torch.randn(2, 3, dtype=torch.float32),
+        torch.tensor([[-3]], dtype=torch.int8),
+        torch.tensor([200], dtype=torch.uint8),
+        torch.tensor(1 << 40, dtype=torch.int64),
+    )
+    exported_program = export(RuntimeItems(), args=example_args)
+    mod = from_exported_program(exported_program, run_ep_decomposition=False)
+    executable = relax.build(mod, tvm.target.Target("llvm"))
+    vm = relax.VirtualMachine(executable, tvm.cpu())
+
+    expected = RuntimeItems()(*example_args)
+    actual = vm["main"](*(tvm.runtime.tensor(arg.numpy()) for arg in 
example_args))
+    for actual_value, expected_value in zip(actual, expected):
+        np.testing.assert_array_equal(actual_value.numpy(), 
expected_value.numpy())
+
+
[email protected](not env.has_llvm(), reason="need llvm")
+def test_dynamic_scalar_fill_operations():
+    class DynamicFills(torch.nn.Module):
+        def forward(self, x):
+            inferred_dtype = torch.full((x.shape[0],), x.shape[1], 
device=x.device)
+            explicit_dtype = torch.full_like(x, x.shape[0], 
dtype=torch.float64)
+            filled = torch.fill(x, x.shape[1])
+            filled_inplace = x.clone()
+            filled_inplace.fill_(x.shape[0])
+            return inferred_dtype, explicit_dtype, filled, filled_inplace
+
+    example_args = (torch.randn(3, 4, dtype=torch.float32),)
+    rows = torch.export.Dim("rows", min=1, max=8)
+    columns = torch.export.Dim("columns", min=1, max=8)
+    exported_program = export(
+        DynamicFills(),
+        args=example_args,
+        dynamic_shapes={"x": {0: rows, 1: columns}},
+    )
+    mod = from_exported_program(exported_program, run_ep_decomposition=False)
+    executable = relax.build(mod, tvm.target.Target("llvm"))
+    vm = relax.VirtualMachine(executable, tvm.cpu())
+
+    for shape in ((3, 4), (5, 2)):
+        torch_input = torch.randn(shape, dtype=torch.float32)
+        expected = DynamicFills()(torch_input)
+        actual = vm["main"](tvm.runtime.tensor(torch_input.numpy()))
+        actual_arrays = [value.numpy() for value in actual]
+
+        assert actual_arrays[0].dtype == np.dtype("int64")
+        assert actual_arrays[1].dtype == np.dtype("float64")
+        for actual_value, expected_value in zip(actual_arrays, expected):
+            assert actual_value.dtype == expected_value.numpy().dtype
+            np.testing.assert_array_equal(actual_value, expected_value.numpy())
+
+
[email protected](not env.has_llvm(), reason="need llvm")
+def test_dynamic_boolean_fill_operations():
+    class DynamicBooleanFills(torch.nn.Module):
+        def forward(self, x):
+            value = x.shape[0] == x.shape[1]
+            base = x > 0
+            filled = torch.fill(base, value)
+            filled_inplace = base.clone()
+            filled_inplace.fill_(value)
+            created = torch.full(
+                (x.shape[0], x.shape[1]),
+                value,
+                device=x.device,
+                dtype=torch.bool,
+            )
+            like = torch.full_like(x, value, dtype=torch.bool)
+            return created, like, filled, filled_inplace
+
+    rows = torch.export.Dim("rows", min=1, max=8)
+    columns = torch.export.Dim("columns", min=1, max=8)
+    example_args = (torch.randn(3, 4, dtype=torch.float32),)
+    exported_program = export(
+        DynamicBooleanFills(),
+        args=example_args,
+        dynamic_shapes={"x": {0: rows, 1: columns}},
+    )
+    mod = from_exported_program(exported_program, run_ep_decomposition=False)
+    executable = relax.build(mod, tvm.target.Target("llvm"))
+    vm = relax.VirtualMachine(executable, tvm.cpu())
+
+    for shape in ((3, 3), (3, 4)):
+        torch_input = torch.randn(shape, dtype=torch.float32)
+        expected = DynamicBooleanFills()(torch_input)
+        actual = vm["main"](tvm.runtime.tensor(torch_input.numpy()))
+        for actual_value, expected_value in zip(actual, expected):
+            np.testing.assert_array_equal(actual_value.numpy(), 
expected_value.numpy())
+
+
[email protected](not env.has_llvm(), reason="need llvm")
+def test_dynamic_scalar_arithmetic():
+    class DynamicScalarArithmetic(torch.nn.Module):
+        def forward(self, x):
+            lengths = torch.full(
+                (x.shape[0],),
+                x.shape[1],
+                device=x.device,
+                dtype=torch.int64,
+            )
+            value = lengths.max().item()
+            return (
+                torch.full_like(x, -value, dtype=torch.int64),
+                torch.arange(value // 2, device=x.device),
+                torch.arange(value % 3, device=x.device),
+            )
+
+    rows = torch.export.Dim("rows", min=1, max=8)
+    columns = torch.export.Dim("columns", min=1, max=8)
+    example_args = (torch.randn(3, 4, dtype=torch.float32),)
+    exported_program = export(
+        DynamicScalarArithmetic(),
+        args=example_args,
+        dynamic_shapes={"x": {0: rows, 1: columns}},
+    )
+    mod = from_exported_program(exported_program, run_ep_decomposition=False)
+    executable = relax.build(mod, tvm.target.Target("llvm"))
+    vm = relax.VirtualMachine(executable, tvm.cpu())
+
+    for shape in ((3, 4), (5, 2)):
+        torch_input = torch.randn(shape, dtype=torch.float32)
+        expected = DynamicScalarArithmetic()(torch_input)
+        actual = vm["main"](tvm.runtime.tensor(torch_input.numpy()))
+        for actual_value, expected_value in zip(actual, expected):
+            np.testing.assert_array_equal(actual_value.numpy(), 
expected_value.numpy())
+
+
[email protected](not env.has_llvm(), reason="need llvm")
[email protected](
+    ("fill_value", "dtype"),
+    [
+        (1 << 40, torch.int64),
+        (1.0 + 2**-40, torch.float64),
+    ],
+)
+def test_full_and_full_like_python_fill_value_uses_explicit_dtype(fill_value, 
dtype):
+    class FullLike(torch.nn.Module):
+        def forward(self, x):
+            return (
+                torch.full(x.shape, fill_value, dtype=dtype),
+                torch.full_like(x, fill_value, dtype=dtype),
+            )
+
+    example_args = (torch.randn(2, 3, dtype=torch.float32),)
+    exported_program = export(FullLike(), args=example_args)
+    mod = from_exported_program(exported_program, run_ep_decomposition=False)
+    executable = relax.build(mod, tvm.target.Target("llvm"))
+    vm = relax.VirtualMachine(executable, tvm.cpu())
+
+    expected = FullLike()(*example_args)
+    actual = vm["main"](tvm.runtime.tensor(example_args[0].numpy()))
+    for actual_value, expected_value in zip(actual, expected):
+        assert actual_value.numpy().dtype == expected_value.numpy().dtype
+        np.testing.assert_array_equal(actual_value.numpy(), 
expected_value.numpy())
+
+
 def test_split():
     class Chunk(Module):
         def forward(self, input):
@@ -6072,6 +6331,71 @@ def test_masked_fill_inplace():
     verify_model(Masked_Fill_Inplace(), example_args, {}, Expected)
 
 
[email protected](not env.has_llvm(), reason="need llvm")
+def test_dynamic_scalar_masked_fill_operations():
+    class DynamicMaskedFills(torch.nn.Module):
+        def forward(self, x):
+            mask = x > 0
+            filled = x.masked_fill(mask, x.shape[0])
+            filled_inplace = x.clone()
+            filled_inplace.masked_fill_(mask, x.shape[1])
+            return filled, filled_inplace
+
+    rows = torch.export.Dim("rows", min=1, max=8)
+    columns = torch.export.Dim("columns", min=1, max=8)
+    example_args = (torch.randn(3, 4, dtype=torch.float32),)
+    exported_program = export(
+        DynamicMaskedFills(),
+        args=example_args,
+        dynamic_shapes={"x": {0: rows, 1: columns}},
+    )
+    mod = from_exported_program(exported_program, run_ep_decomposition=False)
+    executable = relax.build(mod, tvm.target.Target("llvm"))
+    vm = relax.VirtualMachine(executable, tvm.cpu())
+
+    for shape in ((3, 4), (5, 2)):
+        torch_input = torch.randn(shape, dtype=torch.float32)
+        expected = DynamicMaskedFills()(torch_input)
+        actual = vm["main"](tvm.runtime.tensor(torch_input.numpy()))
+        for actual_value, expected_value in zip(actual, expected):
+            np.testing.assert_array_equal(actual_value.numpy(), 
expected_value.numpy())
+
+
[email protected](not env.has_llvm(), reason="need llvm")
[email protected](
+    ("fill_value", "dtype"),
+    [
+        (1 << 40, torch.int64),
+        (1.0 + 2**-40, torch.float64),
+    ],
+)
+def test_fill_and_masked_fill_python_value_uses_input_dtype(fill_value, dtype):
+    class MaskedFills(torch.nn.Module):
+        def forward(self, x, mask):
+            filled_all = torch.fill(x, fill_value)
+            filled_all_inplace = x.clone()
+            filled_all_inplace.fill_(fill_value)
+            filled = x.masked_fill(mask, fill_value)
+            filled_inplace = x.clone()
+            filled_inplace.masked_fill_(mask, fill_value)
+            return filled_all, filled_all_inplace, filled, filled_inplace
+
+    example_args = (
+        torch.arange(6, dtype=dtype).reshape(2, 3),
+        torch.tensor([[True, False, True], [False, True, False]]),
+    )
+    exported_program = export(MaskedFills(), args=example_args)
+    mod = from_exported_program(exported_program, run_ep_decomposition=False)
+    executable = relax.build(mod, tvm.target.Target("llvm"))
+    vm = relax.VirtualMachine(executable, tvm.cpu())
+
+    expected = MaskedFills()(*example_args)
+    actual = vm["main"](*(tvm.runtime.tensor(arg.numpy()) for arg in 
example_args))
+    for actual_value, expected_value in zip(actual, expected):
+        assert actual_value.numpy().dtype == expected_value.numpy().dtype
+        np.testing.assert_array_equal(actual_value.numpy(), 
expected_value.numpy())
+
+
 def test_masked_select():
     class MaskedSelect(Module):
         def forward(self, data: torch.Tensor, mask: torch.Tensor):
@@ -6094,8 +6418,8 @@ def test_masked_select():
                 )
                 lv4: R.Tensor((u0,), dtype="int64") = R.squeeze(lv3, axis=[0])
                 lv5: R.Tensor((u0,), dtype="float32") = R.take(lv, lv4, 
axis=0, mode="fast")
-                lv6: R.Tensor((), dtype="bool") = R.const(True, "bool")
-                lv7: R.Tensor((), dtype="bool") = R.const(True, "bool")
+                lv6: T.bool = u0 >= 0
+                lv7: T.bool = u0 <= 6
                 gv: R.Tuple(R.Tensor((u0,), dtype="float32")) = (lv5,)
                 R.output(gv)
             return gv
@@ -6430,7 +6754,9 @@ def test_ones_like():
             R.Tensor((128, 128), dtype="float32")
         ):
             with R.dataflow():
-                lv: R.Tensor((128, 128), dtype="float32") = R.full_like(input, 
R.const(1, "int32"))
+                lv: R.Tensor((128, 128), dtype="float32") = R.full_like(
+                    input, R.const(1.0, "float32")
+                )
                 gv: R.Tuple(R.Tensor((128, 128), dtype="float32")) = (lv,)
                 R.output(gv)
             return gv
@@ -6452,7 +6778,9 @@ def test_zero_inplace():
             R.Tensor((128, 128), dtype="float32")
         ):
             with R.dataflow():
-                lv: R.Tensor((128, 128), dtype="float32") = R.full_like(input, 
R.const(0, "int32"))
+                lv: R.Tensor((128, 128), dtype="float32") = R.full_like(
+                    input, R.const(0.0, "float32")
+                )
                 gv: R.Tuple(R.Tensor((128, 128), dtype="float32")) = (lv,)
                 R.output(gv)
             return gv
@@ -6498,7 +6826,9 @@ def test_zeros_like():
             R.Tensor((128, 128), dtype="float32")
         ):
             with R.dataflow():
-                lv: R.Tensor((128, 128), dtype="float32") = R.full_like(input, 
R.const(0, "int32"))
+                lv: R.Tensor((128, 128), dtype="float32") = R.full_like(
+                    input, R.const(0.0, "float32")
+                )
                 gv: R.Tuple(R.Tensor((128, 128), dtype="float32")) = (lv,)
                 R.output(gv)
             return gv
@@ -8692,7 +9022,7 @@ def test_cond_shape_predicate():
             s77 = T.int64()
             R.func_attr({"tir_var_lower_bound": {"s77": 1}})
             cls = expected
-            gv: R.Tensor((), dtype="bool") = R.const(True, "bool")
+            gv: T.bool = s77 > 4
             if gv:
                 gv1: R.Tensor((s77, 4), dtype="float32") = 
cls.cond_true_branch_0(x)
                 cond_result: R.Tensor((s77, 4), dtype="float32") = gv1
@@ -8712,6 +9042,64 @@ def test_cond_shape_predicate():
     )
 
 
[email protected](not env.has_llvm(), reason="need llvm")
+def test_cond_shape_equality_predicate():
+    class CondShapeEqualityModel(Module):
+        def forward(self, x):
+            def true_fn(x):
+                return x + 1.0
+
+            def false_fn(x):
+                return x - 1.0
+
+            return torch.cond(x.shape[0] == x.shape[1], true_fn, false_fn, 
(x,))
+
+    rows = torch.export.Dim("rows", min=1, max=8)
+    columns = torch.export.Dim("columns", min=1, max=8)
+    exported_program = export(
+        CondShapeEqualityModel(),
+        args=(torch.zeros(3, 3),),
+        dynamic_shapes={"x": {0: rows, 1: columns}},
+    )
+    mod = from_exported_program(exported_program)
+    executable = relax.build(mod, tvm.target.Target("llvm"))
+    vm = relax.VirtualMachine(executable, tvm.cpu())
+
+    for shape, expected_value in (((3, 3), 1.0), ((2, 3), -1.0)):
+        torch_input = torch.zeros(shape, dtype=torch.float32)
+        actual = vm["main"](tvm.runtime.tensor(torch_input.numpy()))[0]
+        np.testing.assert_array_equal(actual.numpy(), np.full(shape, 
expected_value, "float32"))
+
+
[email protected](not env.has_llvm(), reason="need llvm")
+def test_cond_shape_inequality_predicate():
+    class CondShapeInequalityModel(Module):
+        def forward(self, x):
+            def true_fn(x):
+                return x + 1.0
+
+            def false_fn(x):
+                return x - 1.0
+
+            return torch.cond(x.shape[0] != x.shape[1], true_fn, false_fn, 
(x,))
+
+    rows = torch.export.Dim("rows", min=1, max=8)
+    columns = torch.export.Dim("columns", min=1, max=8)
+    exported_program = export(
+        CondShapeInequalityModel(),
+        args=(torch.zeros(2, 3),),
+        dynamic_shapes={"x": {0: rows, 1: columns}},
+    )
+    mod = from_exported_program(exported_program)
+    executable = relax.build(mod, tvm.target.Target("llvm"))
+    vm = relax.VirtualMachine(executable, tvm.cpu())
+
+    for shape, expected_value in (((2, 3), 1.0), ((3, 3), -1.0)):
+        torch_input = torch.zeros(shape, dtype=torch.float32)
+        actual = vm["main"](tvm.runtime.tensor(torch_input.numpy()))[0]
+        np.testing.assert_array_equal(actual.numpy(), np.full(shape, 
expected_value, "float32"))
+
+
 def test_cond_tuple_output():
     """Cond where both branches return a tuple."""
 

Reply via email to