Skip to content

Commit a0e2e9d

Browse files
committed
fix(batch-invariant): full-reduction mean returned the sum
Under batch-invariant mode, `x.mean()` and `torch.mean(x)` reach the `aten::mean.dim` override (`mean_batch_invariant`) with an empty dim list. The multi-dim branch divided the sum by the product over that empty list (1), so every full-reduction mean returned the sum; `dim=None` raised. Autograd was unaffected (the `mean` derivative divides by numel itself), but every forward value of a full mean was wrong. Treat an empty or None dim as all dimensions, as aten::mean does. Signed-off-by: wdykas <wdykas@nvidia.com>
1 parent 4e23fd6 commit a0e2e9d

2 files changed

Lines changed: 27 additions & 0 deletions

File tree

‎megatron/core/transformer/custom_layers/batch_invariant_kernels.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,9 @@ def _log_softmax_batch_invariant(input, dim, _half_to_float):
582582
def mean_batch_invariant(input, dim, keepdim=False, dtype: torch.dtype | None = None):
583583
"""Batch-invariant replacement for `aten::mean.dim` over one or more dimensions."""
584584
assert dtype is None or dtype == torch.float32, f"unsupported dtype: {dtype}"
585+
# A full reduction (`x.mean()`, `torch.mean(x)`) arrives with an empty dim list.
586+
if dim is None or len(dim) == 0:
587+
dim = list(range(input.ndim))
585588
if len(dim) == 1:
586589
return mean_dim(input, dim[0], keepdim=keepdim)
587590
else:

‎tests/unit_tests/transformer/test_te_layers_batch_invariant.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from megatron.core.transformer.custom_layers.batch_invariant_kernels import (
2020
HAVE_DEEPGEMM_BF16,
2121
assert_te_supports_batch_invariant_attention,
22+
mean_batch_invariant,
2223
set_batch_invariant_mode,
2324
te_supports_batch_invariant_attention,
2425
)
@@ -65,6 +66,29 @@ def test_batch_invariant_rejects_incompatible_te(monkeypatch):
6566
assert_te_supports_batch_invariant_attention()
6667

6768

69+
@pytest.mark.parametrize("dim", ([], (), None))
70+
@pytest.mark.parametrize("keepdim", (False, True))
71+
def test_mean_batch_invariant_full_reduction(dim, keepdim):
72+
"""An empty (or None) dim list reduces over every dimension, as aten::mean does."""
73+
x = torch.arange(24, dtype=torch.float32).reshape(2, 3, 4)
74+
out = mean_batch_invariant(x, dim, keepdim=keepdim)
75+
torch.testing.assert_close(out, x.mean(dim=(0, 1, 2), keepdim=keepdim))
76+
77+
78+
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA is required")
79+
def test_full_mean_under_batch_invariant_mode():
80+
"""`x.mean()` / `torch.mean(x)` dispatch to aten::mean.dim with dim=[]."""
81+
x = torch.randn(4, 8, device="cuda", requires_grad=True)
82+
ref = x.detach().sum() / x.numel()
83+
with set_batch_invariant_mode(True):
84+
out = x.mean()
85+
out_fn = torch.mean(x)
86+
out.backward()
87+
torch.testing.assert_close(out.detach(), ref)
88+
torch.testing.assert_close(out_fn.detach(), ref)
89+
torch.testing.assert_close(x.grad, torch.full_like(x, 1.0 / x.numel()))
90+
91+
6892
# ============================================================================
6993
# Batch-Invariant test helpers
7094
# ============================================================================

0 commit comments

Comments
 (0)