Skip to content

Commit 157469a

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 157469a

2 files changed

Lines changed: 70 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: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2917,6 +2917,70 @@ 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+
(
2928+
torch.randint(-5, 5, (2, 3, 4), dtype=torch.int32),
2929+
None,
2930+
False,
2931+
torch.float32,
2932+
),
2933+
# mean.dim: reduce along specific dimensions, int32 input → float32.
2934+
(torch.randint(0, 10, (3, 4), dtype=torch.int32), 1, False, torch.float32),
2935+
(torch.randint(0, 10, (3, 4), dtype=torch.int32), 0, True, torch.float32),
2936+
(
2937+
torch.randint(-5, 5, (2, 3, 4), dtype=torch.int32),
2938+
[0, 2],
2939+
False,
2940+
torch.float32,
2941+
),
2942+
(
2943+
torch.randint(-5, 5, (2, 3, 4), dtype=torch.int32),
2944+
[0, 2],
2945+
True,
2946+
torch.float32,
2947+
),
2948+
# dtype-agnostic: root cause is independent of the specific int operand
2949+
# dtype or the specific float target dtype.
2950+
(torch.randint(0, 10, (3, 4), dtype=torch.int64), 1, False, torch.float32),
2951+
(torch.randint(0, 10, (3, 4), dtype=torch.int32), None, False, torch.float16),
2952+
],
2953+
)
2954+
async def test_mean_dtype_kwarg_integer(
2955+
x: Tensor,
2956+
dim: int | list[int] | None,
2957+
keepdim: bool,
2958+
target_dtype: torch.dtype,
2959+
dynamic: bool,
2960+
) -> None:
2961+
"""Regression: torch.mean(x, dtype=...) on an integer tensor must promote the
2962+
operand to the requested dtype before averaging, not crash at conversion time.
2963+
2964+
Covers both aten.mean.default (dim=None) and aten.mean.dim (dim=...) with an
2965+
explicit float dtype kwarg on an integer operand — the operand must be cast to
2966+
the output element type before reduce_mean, mirroring sum's dtype handling.
2967+
Parametrized across int32/int64 operands and float32/float16 targets since the
2968+
root cause is dtype-agnostic (the cast was simply never attempted)."""
2969+
2970+
class MeanDtypeModel(nn.Module):
2971+
def __init__(self) -> None:
2972+
super().__init__()
2973+
2974+
def forward(self, x: Tensor) -> Tensor:
2975+
if dim is None:
2976+
return torch.mean(x, dtype=target_dtype)
2977+
return torch.mean(x, dim=dim, keepdim=keepdim, dtype=target_dtype)
2978+
2979+
model = MeanDtypeModel().eval()
2980+
dynamic_shapes = {"x": _all_dims_dynamic(x)} if dynamic else None
2981+
await validate_numerical_output(model=model, x=x, dynamic_shapes=dynamic_shapes)
2982+
2983+
29202984
@pytest.mark.parametrize("dynamic", [False, True])
29212985
@pytest.mark.parametrize("x", [torch.rand(2, 2)])
29222986
@pytest.mark.parametrize("y", [torch.rand(2, 2)])

0 commit comments

Comments
 (0)