Skip to content

[torchlib] Add missing dtype parameter to aten_mean_dim - #2885

Merged
Justin Chu (justinchuby) merged 2 commits into
microsoft:mainfrom
linusjuni:fix/aten-mean-dim-dtype
Apr 10, 2026
Merged

[torchlib] Add missing dtype parameter to aten_mean_dim#2885
Justin Chu (justinchuby) merged 2 commits into
microsoft:mainfrom
linusjuni:fix/aten-mean-dim-dtype

Conversation

@linusjuni

Copy link
Copy Markdown
Contributor

Fixes #2884

aten_mean_dim and aten_mean_dim_complex are missing the dtype keyword argument from their signatures, even though the ATen schema documents it (ScalarType? dtype=None). This causes a TypeError when PyTorch lowers aten::mean.dim with an explicit dtype - which happens for any model using GlobalAveragePooling2D (Keras/PyTorch).

  • Add dtype: int = -1 to aten_mean_dim, with op.Cast when dtype is specified
  • Add dtype: int = -1 to aten_mean_dim_complex, raising NotImplementedError for complex tensors

Follows the same pattern used by aten_sum_dim_IntList and aten_sum_dim_IntList_complex.

The ATen schema for mean.dim documents dtype as an optional parameter,
but aten_mean_dim and aten_mean_dim_complex did not accept it. This
causes a TypeError when PyTorch lowers mean.dim with an explicit dtype
(e.g. from GlobalAveragePooling2D in Keras).

Add dtype: int = -1 to both functions, following the same pattern used
by aten_sum_dim_IntList.

Fixes microsoft#2884
@linusjuni

Copy link
Copy Markdown
Contributor Author

Hey! This is my first contribution to onnxscript. We ran into this while exporting Keras models to ONNX at work - GlobalAveragePooling2D lowers through aten::mean.dim with an explicit dtype, which hits the missing parameter. Happy to adjust anything if needed😄

@justinchuby Justin Chu (justinchuby) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@justinchuby Justin Chu (justinchuby) added the module: torchlib Related to the torch/aten function lib in development label Apr 10, 2026
@justinchuby
Justin Chu (justinchuby) enabled auto-merge (squash) April 10, 2026 15:16
@codecov

codecov Bot commented Apr 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 33.33333% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.03%. Comparing base (1ef0ec9) to head (abcd2b6).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
onnxscript/function_libs/torch_lib/ops/core.py 33.33% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2885      +/-   ##
==========================================
- Coverage   72.04%   72.03%   -0.01%     
==========================================
  Files         239      239              
  Lines       29305    29309       +4     
  Branches     2880     2882       +2     
==========================================
  Hits        21112    21112              
- Misses       7216     7218       +2     
- Partials      977      979       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@justinchuby

Copy link
Copy Markdown
Collaborator

Linus Juni (@linusjuni) could you fix lint?

@linusjuni

Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

@justinchuby
Justin Chu (justinchuby) enabled auto-merge (squash) April 10, 2026 15:55
@justinchuby
Justin Chu (justinchuby) merged commit 12234f8 into microsoft:main Apr 10, 2026
28 of 32 checks passed
Justin Chu (justinchuby) pushed a commit that referenced this pull request Apr 17, 2026
Fixes #2884

`aten_mean_dim` and `aten_mean_dim_complex` are missing the `dtype`
keyword argument from their signatures, even though the ATen schema
documents it (`ScalarType? dtype=None`). This causes a `TypeError` when
PyTorch lowers `aten::mean.dim` with an explicit `dtype` - which happens
for any model using `GlobalAveragePooling2D` (Keras/PyTorch).

- Add `dtype: int = -1` to `aten_mean_dim`, with `op.Cast` when dtype is
specified
- Add `dtype: int = -1` to `aten_mean_dim_complex`, raising
`NotImplementedError` for complex tensors

Follows the same pattern used by `aten_sum_dim_IntList` and
`aten_sum_dim_IntList_complex`.
Justin Chu (justinchuby) pushed a commit that referenced this pull request Aug 24, 2026
)

# Honor the dtype argument of aten::mean when exporting without dim

Fixes #3008

## Summary

The no dim overload of `aten::mean` declares `dtype` in its schema
(`mean(Tensor self, *, ScalarType? dtype=None)`), but `aten_mean` in
`onnxscript/function_libs/torch_lib/ops/core.py` did not accept the
argument. Exporting `torch.mean(x, dtype=torch.float64)` for a float32
input succeeded silently, emitted only `ReduceMean -> Squeeze`, declared
a FLOAT output, and accumulated in float32. For the input `[[1e8, 1.0,
-1e8]]` PyTorch returns `0.3333333333333333` as float64 while the
exported model returned `0.0` as float32.

## Changes

- `aten_mean` is now `trace_only=True`, takes `dtype: int = -1`, and
when a dtype is given casts `self` before `ReduceMean`. The no dtype
path is unchanged (`ReduceMean -> Squeeze`).
- `aten_mean_complex` takes the same argument and raises
`NotImplementedError` when it is supplied, matching
`aten_mean_dim_complex` and `aten_sum_complex`.
- New `ops.aten.mean.dtype` OpInfo in
`tests/function_libs/torch_lib/extra_opinfo.py`
(`sample_inputs_mean_dtype`) registered against `core_ops.aten_mean` in
`ops_test_data.py`. It yields `make_tensor` samples of shapes `(5, 5)`,
`(5,)` and `()` plus the precision sensitive tensor `[[1e8, 1.0,
-1e8]]`, all with `dtype=torch.float64`.

## Why the cast happens before the reduction

`aten_mean_dim` (#2885) and `aten_sum` cast the reduced result after the
reduction. That is not sufficient here: PyTorch accumulates in the
requested dtype, so for `[1e8, 1.0, -1e8]` the float32 mean is `0.0`
(the `1.0` is lost when added to `1e8`) while the float64 mean is `1/3`.
Casting after `ReduceMean` would produce a DOUBLE tensor holding `0.0`,
which still mismatches PyTorch. Casting the input first reproduces
PyTorch semantics. The new test includes this sample specifically so
that a cast after reduction implementation cannot pass by accident.

## Verification

Reporter's script (`torch.onnx.export(..., dynamo=True)` of
`torch.mean(x, dtype=torch.float64)` with float32 input) on pristine
main at a39c0a5:

```
torch eager result: 0.3333333333333333 dtype: torch.float64
ONNX output elem_type: 1 (FLOAT), expected 11 (DOUBLE)
ORT result: 0.0 dtype: float32
```

With this change:

```
torch eager result: 0.3333333333333333 dtype: torch.float64
ONNX output elem_type: 11 (DOUBLE)
ORT result: 0.3333333333333333 dtype: float64
```

`pytest tests/function_libs/torch_lib/ops_test.py -k mean`:

| State | Result |
| --- | --- |
| Pristine main, without the new test | 8 passed, 48 skipped, 8 xfailed,
60 subtests passed |
| New test with the source change stashed | 4 failed, 10 passed, 48
skipped, 8 xfailed, 60 subtests passed. All four `ops_aten_mean_dtype`
samples fail with `TypeInferenceError: Inferred elem type differs from
existing elem type: (1) vs (11)` |
| New test with a cast placed after the reduction | 1 failed, 8 passed,
50 skipped, 8 xfailed, 63 subtests passed. Only the `[[1e8, 1.0, -1e8]]`
sample fails: `Expected 0.3333333333333333 but got 0.0` |
| New test with this change | 8 passed, 50 skipped, 8 xfailed, 64
subtests passed |

The two additional skips in the fixed state are the function proto
validity checks, which skip for traced functions.

`ruff check` and `ruff format --check` (ruff 0.15.1, the lintrunner
pinned version) pass on the three changed files.

Environment: Python 3.10, torch 2.13.0 (CPU), onnx 1.22.0, onnxruntime
1.23.2, macOS arm64.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

module: torchlib Related to the torch/aten function lib in development

2 participants