siyiweigeHEW opened a new issue, #20231:
URL: https://github.com/apache/tvm/issues/20231
### Expected behavior
PyTorch's `torch.round` follows round-half-to-even (banker's rounding):
```python
>>> torch.round(torch.tensor([0.5, 1.5, 2.5, -0.5, -2.5]))
tensor([0., 2., 2., -0., -2.])
```
`decimals` is an official parameter (positive = round to decimal places,
negative = round to tens/hundreds, e.g. `torch.round(torch.tensor(25.),
decimals=-1) == tensor(20.)`).
A valid PyTorch model using `torch.round` — with or without `decimals` —
should be convertible by `tvm.relax.frontend.torch.from_exported_program` /
`from_fx` and produce results identical to native PyTorch.
### Actual behavior
`tvm.relax.frontend.torch` mishandles `torch.round` in three ways:
**1. Half values are rounded away from zero, not to even
(`from_exported_program`, `aten.round.default`).**
`_round`
(python/tvm/relax/frontend/torch/base_fx_graph_translator.py:396-409) delegates
`decimals == 0` to `relax.op.round`, which lowers to `topi.round`
(python/tvm/topi/math.py:450, `te.round`) → `tir.round` → `llvm.round`, i.e.
**round-half-away-from-zero**. Plain `torch.round(tensor([0.5, 2.5, -2.5,
4.5]))` returns `[1, 3, -3, 5]` instead of `[0, 2, -2, 4]` (max diff 1.0). This
also affects the ONNX frontend `onnx.Round` (shared `relax.op.round`), which
violates the ONNX Round spec ("In case of halves, the rule is to round them to
the nearest even integer").
**2. Any explicit `decimals` argument makes the whole model fail to import
(`from_exported_program`).**
`torch.export` lowers `torch.round(x, decimals=k)` (even `decimals=0`) to
the ATen op `aten.round.decimals`, but the exported-program dispatch map
registers only `"round.default"`
(python/tvm/relax/frontend/torch/exported_program_translator.py:1240).
Conversion aborts with:
```
AssertionError: Unsupported function types ['round.decimals']
```
So the `decimals != 0` branch in `_round` is unreachable on the recommended
export path.
**3. Via `from_fx`, the `decimals` path computes wrong values.**
`_round` uses `round(x * 10**decimals) / 10**decimals` with `scale =
10**decimals`. Because the inner `relax.op.round` is ties-away-from-zero, and
the multiply/divide is numerically sensitive in float64:
```python
torch.round(torch.tensor([25., 125., 165.]), decimals=-1) # -> [20., 120.,
160.]
# TVM (from_fx) -> [30.,
130., 170.] max|diff|=10
torch.round(torch.tensor(2.25), decimals=1) # -> 2.2 TVM
-> 2.3
torch.round(torch.tensor(250.), decimals=-2) # -> 200. TVM
-> 300. max|diff|=100
```
(Note: even after switching the inner round to ties-to-even, the float64
case `25. * 0.1 = 2.5000000000000004` rounds up to `30` instead of `20`, so the
`x*10^d / 10^d` transform itself is not a correct implementation of `round(x,
decimals)`.)
### Environment
- OS: Linux
- TVM: v0.24.dev0 (commit `176c77388`, branch main)
- Python: 3.11
- torch: 2.10.0
- onnx: 1.20.1 / onnxruntime: 1.24.1 (for the ONNX-side observation)
### Steps to reproduce
```python
"""Repro: TVM relax torch frontend torch.round defects."""
import warnings; warnings.filterwarnings("ignore")
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, from_fx
def run_tvm(mod, x):
ex = relax.build(mod, target="llvm")
vm = relax.VirtualMachine(ex, tvm.cpu())
out = vm["main"](x.numpy())
return out.numpy() if hasattr(out, "numpy") else [o.numpy() for o in
out][0]
# Case 1: plain torch.round(x) on half values (from_exported_program)
class M1(nn.Module):
def forward(self, t):
return torch.round(t)
x = torch.tensor([-2.5, -0.5, 0.5, 2.5, 4.5], dtype=torch.float32)
exp = torch.export.export(M1().eval(), (x,))
print("Case1 ref:", torch.round(x).numpy().tolist())
print("Case1 tvm:", run_tvm(from_exported_program(exp), x).tolist())
# Case 2: torch.round(x, decimals=...) fails to import
(from_exported_program)
class M2(nn.Module):
def forward(self, t):
return torch.round(t, decimals=-1)
exp2 = torch.export.export(M2().eval(), (x,))
print("Case2 exported op:", [n.target for n in exp2.graph.nodes if "round"
in str(n.target)])
try:
from_exported_program(exp2)
print("Case2 tvm: OK")
except Exception as e:
print(f"Case2 tvm: {type(e).__name__}: {e}")
# Case 3: decimals path wrong results (from_fx)
class M3(nn.Module):
def forward(self, t):
return torch.round(t, decimals=-1)
x3 = torch.tensor([25.0, 125.0, 165.0], dtype=torch.float32)
gm = torch.fx.symbolic_trace(M3().eval())
mod3 = from_fx(gm, [(list(x3.shape), "float32")])
print("Case3 ref:", torch.round(x3, decimals=-1).numpy().tolist())
print("Case3 tvm:", run_tvm(mod3, x3).tolist())
```
Actual output:
```
Case1 ref: [-2.0, -0.0, 0.0, 2.0, 4.0]
Case1 tvm: [-3.0, -1.0, 1.0, 3.0, 5.0] # half values rounded away from
zero
Case2 exported op: [aten.round.decimals]
Case2 tvm: AssertionError: Unsupported function types ['round.decimals']
Case3 ref: [20.0, 120.0, 160.0]
Case3 tvm: [30.0, 130.0, 170.0] # error = 10^decimals
```
### Notes
- The root-cause rounding mode (`topi.round`/`tir.round` being
ties-away-from-zero) is already fixed upstream for the ONNX path by #19367
("[BugFix][ONNX] Fix Round op to use ties-to-even", topi.round →
`te.nearbyint`) and #19368 (aligning `tir.round` across backends). The tested
commit predates both fixes.
- The **torch-frontend** defects above are not addressed by those fixes:
`aten.round.decimals` is still missing from the dispatch map on current `main`
(only `"round.default"` is registered), and the `_round` `decimals` branch is
unchanged. The float64 precision issue of the `round(x*10^d)/10^d` transform
also remains once the inner rounding is corrected.
- Impact includes silent wrong results (Case 1, Case 3) and a hard import
failure (Case 2) for valid, common PyTorch models (`torch.round` is frequently
used in quantization-style preprocessing, including with negative `decimals`).
### 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]