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 bc8947b7dd [Fix][Relax][Frontend][Torch] Honor the `dtype` argument of 
`aten.mean` (`torch.Tensor.mean` / `torch.mean`) (#20241)
bc8947b7dd is described below

commit bc8947b7ddd02f14680e0b3d6ea3ea7cb93f3acd
Author: HuEnwei <[email protected]>
AuthorDate: Wed Sep 2 09:52:55 2026 +0800

    [Fix][Relax][Frontend][Torch] Honor the `dtype` argument of `aten.mean` 
(`torch.Tensor.mean` / `torch.mean`) (#20241)
    
    Fixes: #20230
    
    ## Summary
    
    PyTorch's `Tensor.mean` (and the equivalent `torch.mean`) accepts an
    optional keyword-only `dtype` argument that controls **both** the
    accumulation type and the output type. `torch.export` preserves it on
    the
    `aten.mean.dim` / `aten.mean.default` nodes, but the `_mean` converter
    in
    `base_fx_graph_translator.py` never reads `node.kwargs["dtype"]`, so the
    argument is silently dropped and the output keeps the input dtype. For
    example, `x.mean(dim=1, dtype=torch.float64)` on an fp32 `x` returns
    `float32` instead of `float64`, and in the fp16→fp32 direction the
    values
    are also accumulated at the wrong (lower) precision.
    
    This PR makes `_mean` honor `dtype` by casting the input to the
    requested
    dtype before reducing — the same pattern `_sum` already uses.
    
    ## Root cause
    
    `_mean`
    (`python/tvm/relax/frontend/torch/base_fx_graph_translator.py:1646`)
    only reads `dim` and `keepdim`:
    
    ```python
    def _mean(self, node: fx.Node) -> relax.Var:
        args = self.retrieve_args(node)
        x = args[0]
        dim = args[1] if len(node.args) > 1 else node.kwargs.get("dim", None)
        keepdim = args[2] if len(node.args) > 2 else node.kwargs.get("keepdim", 
False)
        return self.block_builder.emit(relax.op.mean(x, dim, keepdims=keepdim))
    ```
    
    `relax.op.mean` (`python/tvm/relax/op/statistical.py:54`) has no
    `out_dtype` parameter, so the requested dtype cannot be propagated.
    `torch.export` does preserve the `dtype` kwarg on the node:
    
    ```
    %mean : call_function[target=torch.ops.aten.mean.dim](args = (%x, [1]), 
kwargs = {dtype: torch.float64})
    ```
    
    The same converter is registered for `mean.dim` / `mean.default`
    (exported-program path) and for `mean` (fx-trace path), so both entry
    points are affected.
    
    ## Fix
    
    When `dtype` is present on the node, cast the input to the requested
    dtype
    before emitting the mean, mirroring the existing `_sum` handling:
    
    ```python
    dtype = node.kwargs.get("dtype", None)
    if dtype is not None:
        x = self.block_builder.emit(
            relax.op.astype(x, self._convert_data_type(dtype, self.env))
        )
    return self.block_builder.emit(relax.op.mean(x, dim, keepdims=keepdim))
    ```
    
    Casting the input and then reducing is exactly PyTorch's documented
    semantics: the `dtype` argument controls the accumulation type (the
    input
    is converted to `dtype` before the reduction), so no precision is lost
    between the cast and the mean.
    
    ## Validation
    
    ### In-tree regression test
    
    Extended `test_mean` in `tests/python/relax/test_frontend_from_fx.py`
    with
    `MeanDtype`, an fx-traced module calling `input.mean(-1,
    dtype=torch.float64)`, and its expected IR: an `R.astype` to `"float64"`
    followed by `R.mean` producing a `float64` output. The produced module
    is
    checked with `tvm.ir.assert_structural_equal` via the file's
    `verify_model`
    helper.
    
    ### Differential test
    
    The prove_hum differential harness (`1复现_torch_mean.py`, converting
    `torch.export` output with `from_exported_program` and comparing against
    native PyTorch 2.10.0) was run on a runnable build of this change — 15
    cases:
    
    - **Baseline (7)**: global / single-dim / multi-dim / keepdim /
      negative-axis `mean` without `dtype` — unchanged, all match native
      PyTorch (dtype, shape, values).
    - **Same-dtype control (1)**: `mean(dtype=fp32)` on fp32 — matches.
    - **`dtype != input` (6)**: `fp32→fp64`, `fp64→fp32`, `fp16→fp32`,
      `fp32→fp16`, including global and multi-dim + keepdim variants.
    
    All 6 `dtype != input` cases now produce the requested output dtype in
    every direction (pre-fix, all 6 returned the input dtype). Values match
    to
    native PyTorch within fp16/fp32 rounding precision. Both converter entry
    paths — `from_exported_program` and `from_fx` — were verified.
    
    ## Files changed
    
    - `python/tvm/relax/frontend/torch/base_fx_graph_translator.py` —
    `_mean`
      reads `node.kwargs["dtype"]` and casts the input with
      `relax.op.astype` before emitting `relax.op.mean`.
    - `tests/python/relax/test_frontend_from_fx.py` — add `MeanDtype` /
      `ExpectedDtype` coverage to `test_mean`.
---
 .../tvm/relax/frontend/torch/base_fx_graph_translator.py |  5 +++++
 tests/python/relax/test_frontend_from_fx.py              | 16 ++++++++++++++++
 2 files changed, 21 insertions(+)

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 2afbd009e7..f5f8afd441 100644
--- a/python/tvm/relax/frontend/torch/base_fx_graph_translator.py
+++ b/python/tvm/relax/frontend/torch/base_fx_graph_translator.py
@@ -1648,6 +1648,11 @@ class BaseFXGraphImporter(metaclass=abc.ABCMeta):
         x = args[0]
         dim = args[1] if len(node.args) > 1 else node.kwargs.get("dim", None)
         keepdim = args[2] if len(node.args) > 2 else 
node.kwargs.get("keepdim", False)
+        dtype = node.kwargs.get("dtype", None)
+        if dtype is not None:
+            x = self.block_builder.emit(
+                relax.op.astype(x, self._convert_data_type(dtype, self.env))
+            )
         return self.block_builder.emit(relax.op.mean(x, dim, keepdims=keepdim))
 
     def _median(self, node: fx.Node) -> relax.Var:
diff --git a/tests/python/relax/test_frontend_from_fx.py 
b/tests/python/relax/test_frontend_from_fx.py
index 1d086cf043..0759aab1ee 100644
--- a/tests/python/relax/test_frontend_from_fx.py
+++ b/tests/python/relax/test_frontend_from_fx.py
@@ -5070,8 +5070,24 @@ def test_mean():
                 R.output(gv)
             return gv
 
+    class MeanDtype(Module):
+        def forward(self, input):
+            return input.mean(-1, dtype=torch.float64)
+
+    @I.ir_module
+    class ExpectedDtype:
+        @R.function
+        def main(inp_0: R.Tensor((256, 256), dtype="float32")) -> 
R.Tensor((256,), dtype="float64"):
+            with R.dataflow():
+                lv: R.Tensor((256, 256), dtype="float64") = R.astype(inp_0, 
dtype="float64")
+                lv1: R.Tensor((256,), dtype="float64") = R.mean(lv, axis=[-1], 
keepdims=False)
+                gv: R.Tensor((256,), dtype="float64") = lv1
+                R.output(gv)
+            return gv
+
     verify_model(Mean(), [([256, 256], "float32")], {}, Expected1)
     verify_model(MeanKeepDim(), [([256, 256], "float32")], {}, Expected2)
+    verify_model(MeanDtype(), [([256, 256], "float32")], {}, ExpectedDtype)
 
 
 def test_cat():

Reply via email to