siyiweigeHEW opened a new issue, #20232:
URL: https://github.com/apache/tvm/issues/20232

   
   ### Expected behavior
   
   A valid PyTorch model calling `x.split(split_size, dim)` must produce the 
same chunk shapes as PyTorch: chunks of `split_size` elements along `dim`, with 
the last chunk smaller if `D % split_size != 0`. For example, for `x` with 
shape `(10,)`:
   
   - `x.split(6)` → two chunks of shapes `(6,)` and `(4,)` (values `[1..6]` and 
`[7..10]`)
   - `x.split(7)` → `(7,)` and `(3,)`
   - `x.split(3)` → `(3,), (3,), (3,), (1,)`
   
   The model passes `torch.export.export` and runs correctly in PyTorch.
   
   ### Actual behavior
   
   `tvm.relax.frontend.torch.from_exported_program` produces wrong chunk shapes 
whenever the input dimension is not divisible by `split_size` and `split_size > 
D/2` (more generally, whenever `ceil(D / ceil(D / split_size)) != split_size`).
   
   The converter `_split` in 
`python/tvm/relax/frontend/torch/base_fx_graph_translator.py:2316-2327` (mapped 
from `aten.split.Tensor` and `aten.split_with_sizes.default` in 
`exported_program_translator.py:1959-1960`) converts the per-chunk size to a 
**section count**:
   
   ```python
   else:  # int split_size
       n_section = (self.shape_of(x)[dim].value + split_size - 1) // split_size
   return self.block_builder.emit(relax.op.split(x, n_section, dim))
   ```
   
   and passes it to `relax.op.split`'s integer argument. But `relax.op.split`'s 
integer argument is **the number of (equal) sections** (`split_len = ceildiv(D, 
n_section)`, `src/relax/op/tensor/manipulate.cc:1142-1169`), not the per-chunk 
size. For `D=10, split_size=6`, the frontend emits `R.split(x, 
indices_or_sections=2, axis=0)`, which yields two chunks of `ceildiv(10, 2) = 
5` elements — `(5,)` and `(5,)` — instead of PyTorch's `(6,)` and `(4,)`.
   
   For `D=10`:
   
   | `split_size` | PyTorch | TVM (from_exported_program) |
   |---|---|---|
   | 6 | `(6,), (4,)` | `(5,), (5,)` |
   | 7 | `(7,), (3,)` | `(5,), (5,)` |
   | 8 | `(8,), (2,)` | `(5,), (5,)` |
   | 9 | `(9,), (1,)` | `(5,), (5,)` |
   | 3 | `(3,), (3,), (3,), (1,)` | `(3,), (3,), (3,), (1,)` (coincidentally 
matches) |
   
   The bug is not limited to `dim=0`: e.g. `(12, 8)`, `x.split(5, dim=1)` → 
PyTorch `(12,5), (12,3)` vs TVM `(12,4), (12,4)`.
   
   The `list/tuple` form (`x.split([s0, s1, ...])`, 
`aten.split_with_sizes.default`) is handled correctly: cumulative indices `[s0, 
s0+s1, ...]` are passed to `relax.op.split`.
   
   ### Environment
   
   - OS: Linux
   - TVM: v0.24.dev0 (main branch; fuzzed commit `262c6d2e0`; current main 
commit `390af87345` has byte-identical `_split` code — the bug persists)
   - Python: 3.11 (fuzzed) / 3.10 (verification env)
   - torch: 2.6.0
   
   ### Steps to reproduce
   
   ```python
   """Repro: TVM relax torch frontend mishandles x.split(int) with split_size > 
D/2."""
   import numpy as np
   import torch
   import torch.nn as nn
   import tvm
   from tvm import relax
   from tvm.relax.frontend.torch import from_exported_program
   
   
   class SplitModule(nn.Module):
       def __init__(self, split_size):
           super().__init__()
           self.split_size = split_size
   
       def forward(self, x):
           return x.split(self.split_size, dim=0)
   
   
   def run_tvm(x, split_size):
       m = SplitModule(split_size).float().eval()
       exp = torch.export.export(m, (x,))
       mod = from_exported_program(exp)              # <- current frontend, 
_split code above
       ex = relax.build(mod, target="llvm")
       vm = relax.VirtualMachine(ex, tvm.cpu())
       out = vm["main"](tvm.nd.array(x.numpy()))
       return [o.numpy() for o in out]
   
   
   x = torch.arange(10, dtype=torch.float32) + 1     # [1..10]
   refs = x.split(6)                                 # torch: (6,), (4,)
   print("torch:", [r.shape for r in refs], [r.numpy().tolist() for r in refs])
   
   outs = run_tvm(x, 6)
   print("tvm:  ", [o.shape for o in outs], [o.tolist() for o in outs])
   ```
   
   Actual output:
   
   ```
   torch: [torch.Size([6]), torch.Size([4])] [[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 
[7.0, 8.0, 9.0, 10.0]]
   tvm:   [(5,), (5,)] [[1.0, 2.0, 3.0, 4.0, 5.0], [6.0, 7.0, 8.0, 9.0, 10.0]]
   ```
   
   The IR produced by `from_exported_program` for `x.split(6)` on `x: 
R.Tensor((10,))`:
   
   ```
   lv: R.Tuple(R.Tensor((5,), dtype="float32"), R.Tensor((5,), 
dtype="float32")) =
       R.split(x, indices_or_sections=2, axis=0)      # PyTorch wants chunks of 
6: (6,), (4,)
   ```
   
   ### Root cause
   
   In `_split` (`base_fx_graph_translator.py:2316-2327`), the int `split_size` 
branch computes `n_section = ceil(D / split_size)` and passes it as 
`relax.op.split`'s integer `indices_or_sections`, which relax interprets as 
"split into `n_section` equal parts of size `ceil(D / n_section)`". PyTorch's 
`x.split(s)` means "chunks of size `s`". These coincide only when `ceil(D / 
ceil(D / s)) == s` (e.g. divisible sizes, or `D=10, s=3`), so valid models 
silently produce differently-shaped tensors.
   
   Suggested fix: for int `split_size`, pass the cumulative cut positions 
instead of the section count, e.g. `indices = [s, 2*s, ..., (ceil(D/s) - 1) * 
s]` (the same cumulative-index list the `list/tuple` branch already builds).
   
   ### Triage
   
   * needs-triage
   * bug
   * relax
   * frontend/torch
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to