Skip to content

Commit af8ef07

Browse files
committed
Cast mean operand to output dtype before reduce_mean
torch.mean(x, dtype=...) on an integer tensor crashed during conversion (ValueError: mean[0]: dtype si32 vs f32): replace_mean_default and replace_mean_dim never honored the dtype kwarg, so reduce_mean ran on the int operand and produced a result whose type mismatched the node's declared float output. Cast the operand to the node's output element type before reducing, mirroring replace_sum_dim_intlist. Adds integer-operand coverage for aten.mean.default and aten.mean.dim across float32/float16 targets.
1 parent 698f11a commit af8ef07

2 files changed

Lines changed: 55 additions & 0 deletions

File tree

coreai_torch/_aten_to_core.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2247,6 +2247,9 @@ def replace_mean_default(
22472247
) -> Value:
22482248
"""Computes global mean across all dimensions, returning a scalar tensor."""
22492249
x = _get_operand(values_map, node, 0)
2250+
target_type = get_output_element_type_from_node(node)
2251+
if x.type.element_type != target_type:
2252+
x = coreai.cast(x, target_type)
22502253
all_dims = list(range(x.type.rank))
22512254
return coreai.shrink_dims(coreai.reduce_mean(x, all_dims), all_dims)
22522255

@@ -2256,6 +2259,9 @@ def replace_mean_dim(
22562259
) -> Value:
22572260
"""Computes mean along specified dimensions."""
22582261
x, axes = _get_operands(values_map, node, [0, 1])
2262+
target_type = get_output_element_type_from_node(node)
2263+
if x.type.element_type != target_type:
2264+
x = coreai.cast(x, target_type)
22592265
keepdim = len(node.args) >= 3 and bool(node.args[2])
22602266
result = coreai.reduce_mean(x, axes)
22612267
return result if keepdim else coreai.shrink_dims(result, axes)

tests/ops/test_ops.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2917,6 +2917,55 @@ def forward(self, x: Tensor) -> Tensor:
29172917
await validate_numerical_output(model=model, x=x, dynamic_shapes=dynamic_shapes)
29182918

29192919

2920+
@pytest.mark.parametrize("dynamic", [False, True])
2921+
@pytest.mark.parametrize(
2922+
"x,dim,keepdim,target_dtype",
2923+
[
2924+
# mean.default: global mean (dim=None), int32 input → float32 output.
2925+
# torch promotes the operand to the requested dtype BEFORE averaging.
2926+
(torch.randint(0, 10, (3, 4), dtype=torch.int32), None, False, torch.float32),
2927+
(torch.randint(-5, 5, (2, 3, 4), dtype=torch.int32), None, False, torch.float32),
2928+
# mean.dim: reduce along specific dimensions, int32 input → float32.
2929+
(torch.randint(0, 10, (3, 4), dtype=torch.int32), 1, False, torch.float32),
2930+
(torch.randint(0, 10, (3, 4), dtype=torch.int32), 0, True, torch.float32),
2931+
(torch.randint(-5, 5, (2, 3, 4), dtype=torch.int32), [0, 2], False, torch.float32),
2932+
(torch.randint(-5, 5, (2, 3, 4), dtype=torch.int32), [0, 2], True, torch.float32),
2933+
# dtype-agnostic: root cause is independent of the specific int operand
2934+
# dtype or the specific float target dtype.
2935+
(torch.randint(0, 10, (3, 4), dtype=torch.int64), 1, False, torch.float32),
2936+
(torch.randint(0, 10, (3, 4), dtype=torch.int32), None, False, torch.float16),
2937+
],
2938+
)
2939+
async def test_mean_dtype_kwarg_integer(
2940+
x: Tensor,
2941+
dim: int | list[int] | None,
2942+
keepdim: bool,
2943+
target_dtype: torch.dtype,
2944+
dynamic: bool,
2945+
) -> None:
2946+
"""Regression: torch.mean(x, dtype=...) on an integer tensor must promote the
2947+
operand to the requested dtype before averaging, not crash at conversion time.
2948+
2949+
Covers both aten.mean.default (dim=None) and aten.mean.dim (dim=...) with an
2950+
explicit float dtype kwarg on an integer operand — the operand must be cast to
2951+
the output element type before reduce_mean, mirroring sum's dtype handling.
2952+
Parametrized across int32/int64 operands and float32/float16 targets since the
2953+
root cause is dtype-agnostic (the cast was simply never attempted)."""
2954+
2955+
class MeanDtypeModel(nn.Module):
2956+
def __init__(self) -> None:
2957+
super().__init__()
2958+
2959+
def forward(self, x: Tensor) -> Tensor:
2960+
if dim is None:
2961+
return torch.mean(x, dtype=target_dtype)
2962+
return torch.mean(x, dim=dim, keepdim=keepdim, dtype=target_dtype)
2963+
2964+
model = MeanDtypeModel().eval()
2965+
dynamic_shapes = {"x": _all_dims_dynamic(x)} if dynamic else None
2966+
await validate_numerical_output(model=model, x=x, dynamic_shapes=dynamic_shapes)
2967+
2968+
29202969
@pytest.mark.parametrize("dynamic", [False, True])
29212970
@pytest.mark.parametrize("x", [torch.rand(2, 2)])
29222971
@pytest.mark.parametrize("y", [torch.rand(2, 2)])

0 commit comments

Comments
 (0)