siyiweigeHEW opened a new pull request, #20239:
URL: https://github.com/apache/tvm/pull/20239
# [Relax][Frontend][Torch] Fix `torch.round(x, decimals)` via
`from_exported_program` and negative-decimals rounding
Fixes: #20231
## Summary
`torch.export` lowers `torch.round(x, decimals)` (any explicit `decimals`,
including `decimals=0`) to `aten.round.decimals`, while plain
`torch.round(x)`
lowers to `aten.round.default`. The Relax Torch frontend registered only
`round.default` in the exported-program convert map, so **any** explicit
`decimals` made `from_exported_program` fail outright with
`"AssertionError: Unsupported function types ['round.decimals']"`.
In addition, the `decimals != 0` path in `BaseFXGraphImporter._round` always
scaled by `round(x * 10**decimals) / 10**decimals`. For **negative**
`decimals`
this multiplies by `0.1 / 0.01 / ...`, which loses the exact power-of-10
scale
and, in float64, breaks e.g. `torch.round(torch.tensor(25.0, dtype=float64),
decimals=-1)` (`25 * 0.1 == 2.5000000000000004` rounds up to `30` instead of
the
correct `20`).
This PR registers `round.decimals` in the exported-program convert map and
makes
the `decimals != 0` scaling use an exact integer power of 10: multiply for
positive decimals, **divide** for negative ones.
> Note: the round-half-to-even (ties-to-even) semantics themselves are
already
> provided on latest by upstream #19367 / #19368 (`tir.round` → `nearbyint`
> across all backends); they are **not** changed by this PR. The fixes here
are
> the `round.decimals` dispatch gap and the negative-decimals scale
precision.
## Root cause
1. **`from_exported_program` rejects `torch.round(x, decimals)`.** In
`exported_program_translator.py`,
`ExportedProgramImporter.create_convert_map`
maps `"round.default": self._round` but no `round.decimals` entry. Since
`torch.export` always emits `aten.round.decimals` when `decimals` is
passed
explicitly — even `decimals=0` — every such call hits the
`"Unsupported function types ['round.decimals']"` assert in dispatch.
2. **Negative decimals round incorrectly.** `_round` computed
`scale = relax.const(10**decimals, dtype)` and emitted
`divide(round(multiply(arg, scale)), scale)` for every non-zero
`decimals`.
For `decimals = -1` the scale is `0.1`; multiplying by a non-integer
power of 10 is inexact in floating point, so
`torch.round(torch.tensor([25.0], dtype=torch.float64), decimals=-1)`
produced `30` instead of `20`. (`from_fx` shares the same `_round`.)
## Fix
- `exported_program_translator.py` — add `"round.decimals": self._round,`
right after `"round.default": self._round,` in
`ExportedProgramImporter.create_convert_map`, so any explicit-`decimals`
`torch.round` converts through the existing `_round`.
- `base_fx_graph_translator.py` — in `BaseFXGraphImporter._round`, keep the
`decimals == 0` fast path, and branch the `decimals != 0` scale:
- `decimals > 0`: `divide(round(multiply(arg, 10**d)), 10**d)` (unchanged).
- `decimals < 0`: `multiply(round(divide(arg, 10**-d)), 10**-d)` — divide
by
the exact integer power of 10 and multiply back, avoiding the inexact
`× 0.1` path.
## Validation
### In-tree regression tests (added)
- `test_round_decimals` in
`tests/python/relax/test_frontend_from_exported_program.py`
— runs `verify_model_numerically` (Relax vs PyTorch) for `decimals in (0,
1, -1, -2)`
over a value set that exercises ties-to-even half values
(`0.5, 1.5, 2.5, 4.5, -0.5, -2.5`) and the negative-decimals path
(`25.0, 125.0, 165.0` → `20, 120, 160` at `decimals=-1`, and `2.25 → 2.2`).
Before the fix, `decimals=0` alone fails to import with
`"Unsupported function types ['round.decimals']"`.
- `test_round_decimals` in `tests/python/relax/test_frontend_from_fx.py` —
same
values through `from_fx`, asserting TVM output matches `torch.round` for
the
same decimals set (this path already dispatched to `_round`, but produced
the
wrong negative-decimals result before the fix).
### Differential test
`verify_patch.py` was run on the locked pre-#19368 build (rounds half values
away from zero). The real fix code is monkey-patched in; the ties-to-even
inner
round is reproduced with `te.nearbyint` to stand in for latest
`relax.op.round`
semantics (#19368):
| stage | export + fx × decimals {0,1,2,3,-1,-2,-3} | matched | rejected |
diff elements |
|-------|--------------------------------------------|---------|----------|---------------|
| Part 0 — before fix | `export(decimals=-1)` | import fails:
`AssertionError: Unsupported function types ['round.decimals']` |
| | `fx(decimals=-1)` | `[30,130,170]` vs torch
`[20,120,160]` |
| Part A — fix, ties-away inner round (locked build) | 14 | 0 | 22 (all
half-value ties — the #19368 ties-to-even gap, unrelated to this PR) |
| Part B — fix + ties-to-even inner round (= latest) | 28 | 0 | 0 |
Part B matches PyTorch for **all** 28 combinations — both frontends
(`from_exported_program`, `from_fx`) × 7 `decimals` × `float32`/`float64` —
including the previously-failing `round(25, -1) == 20` and
`round(2.25, 1) == 2.2` cases.
Run:
```bash
export PATH=/home/shenqingchao/miniconda3/envs/tvm23/bin:$PATH
export
PYTHONPATH=/tmp/tvmffi019:/data/shenqingchao/enwei/familyfuzz/tvm/python
export TVM_LIBRARY_PATH=/data/shenqingchao/enwei/familyfuzz/tvm/build
python results/TVM/deepseek-v4-flash/prove_hum/torch_round/verify_patch.py
```
## Files changed
- `python/tvm/relax/frontend/torch/base_fx_graph_translator.py` — `_round`:
negative `decimals` now divide by the exact integer power of 10
(`round(x / 10^|d|) * 10^|d|`) instead of multiplying by `10**decimals`
(`× 0.1`).
- `python/tvm/relax/frontend/torch/exported_program_translator.py` — register
`round.decimals` in the exported-program convert map.
- `tests/python/relax/test_frontend_from_exported_program.py` — add
`test_round_decimals`.
- `tests/python/relax/test_frontend_from_fx.py` — add `test_round_decimals`.
--
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]