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 7f619733d1 [Fix][Relax][Torch] Align retained expand dimensions by 
trailing rank (#20137)
7f619733d1 is described below

commit 7f619733d130918e0bd63ea6b732663190539806
Author: Akaash Parthasarathy <[email protected]>
AuthorDate: Tue Aug 25 17:13:10 2026 -0700

    [Fix][Relax][Torch] Align retained expand dimensions by trailing rank 
(#20137)
    
    `torch.Tensor.expand` aligns existing input dimensions with the trailing
    dimensions of the requested shape when new leading dimensions are
    introduced. The Torch frontend previously resolved `-1` using the output
    dimension index directly. For an input shaped `(tokens, 3)`,
    `x.expand(2, -1, -1)` thus selected the wrong input dimensions and
    eventually accessed beyond the input rank.
    
    This PR:
    - Accounts for newly introduced leading dimensions when resolving `-1`
    - Aligns retained dimensions against the trailing input rank
    - Rejects `-1` for new leading dimensions, matching PyTorch semantics.=
    - Preserves the FX metadata fallback when the Relax input shape is
    unavailable
---
 .../relax/frontend/torch/base_fx_graph_translator.py | 11 +++++++++--
 .../relax/test_frontend_from_exported_program.py     | 20 ++++++++++++++++++++
 2 files changed, 29 insertions(+), 2 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 85a0d956ca..b0bb14ac95 100644
--- a/python/tvm/relax/frontend/torch/base_fx_graph_translator.py
+++ b/python/tvm/relax/frontend/torch/base_fx_graph_translator.py
@@ -1905,14 +1905,21 @@ class BaseFXGraphImporter(metaclass=abc.ABCMeta):
         x = args[0]
         broadcast_shape = []
         in_shape = self.shape_of(x)
+        input_rank = len(in_shape) if in_shape is not None else None
+        if input_rank is None and hasattr(node.args[0], "meta") and "val" in 
node.args[0].meta:
+            input_rank = len(node.args[0].meta["val"].shape)
+        rank_offset = len(sizes) - input_rank if input_rank is not None else 0
         for idx, i in enumerate(sizes):
             if isinstance(i, int) and i == -1:
+                input_idx = idx - rank_offset
+                if input_idx < 0:
+                    raise ValueError(f"Cannot use -1 in expand for new leading 
dim {idx}")
                 if in_shape is not None:
-                    broadcast_shape.append(in_shape[idx])
+                    broadcast_shape.append(in_shape[input_idx])
                 elif hasattr(node.args[0], "meta") and "val" in 
node.args[0].meta:
                     # Fallback: get shape from FX node metadata (FakeTensor)
                     fake_shape = node.args[0].meta["val"].shape
-                    broadcast_shape.append(fake_shape[idx])
+                    broadcast_shape.append(fake_shape[input_idx])
                 else:
                     raise ValueError(
                         f"Cannot use -1 in expand for dim {idx} when input 
shape is unknown"
diff --git a/tests/python/relax/test_frontend_from_exported_program.py 
b/tests/python/relax/test_frontend_from_exported_program.py
index b2aa982fdf..e9d2ac8b70 100644
--- a/tests/python/relax/test_frontend_from_exported_program.py
+++ b/tests/python/relax/test_frontend_from_exported_program.py
@@ -5529,6 +5529,26 @@ def 
test_derived_input_dimension_without_exported_program_decomposition():
     assert tvm.arith.Analyzer().can_prove_equal(y_shape[1], x_shape[1] * 2)
 
 
+def test_expand_with_new_leading_dimension():
+    class ExpandLeading(torch.nn.Module):
+        def forward(self, x):
+            return x.expand(2, -1, -1)
+
+    tokens = torch.export.Dim("tokens", min=1, max=8)
+    exported_program = export(
+        ExpandLeading(),
+        args=(torch.randn(4, 3),),
+        dynamic_shapes={"x": {0: tokens}},
+    )
+    mod = from_exported_program(exported_program)
+
+    input_shape = mod["main"].params[0].ty.shape.values
+    output_shape = mod["main"].ret_ty.fields[0].shape.values
+    assert tvm.arith.Analyzer().can_prove_equal(output_shape[0], 2)
+    assert tvm.arith.Analyzer().can_prove_equal(output_shape[1], 
input_shape[0])
+    assert tvm.arith.Analyzer().can_prove_equal(output_shape[2], 
input_shape[1])
+
+
 def test_split():
     class Chunk(Module):
         def forward(self, input):

Reply via email to