Skip to content

Commit df4214c

Browse files
HJSangclaude
andcommitted
Check megatron.core, and let the fallback take the default group as None
megatron-energon installs a bare megatron namespace without megatron.core, which find_spec("megatron") counted as Megatron. The fallback also took group.rank() on a None group, which compute_log_probs callers pass for the default group; dist.get_rank(None) gives torch.distributed's meaning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 6f4a1bb commit df4214c

2 files changed

Lines changed: 42 additions & 7 deletions

File tree

‎miles/backends/training_utils/loss/hub/math_utils.py‎

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,19 @@
1818
from miles.backends.training_utils.parallel import get_parallel_state
1919

2020
_LOG_RATIO_EXP_CLAMP = 20.0
21-
# A pip install for the FSDP or torchtitan backend has no Megatron; a broken Megatron install
22-
# still fails loudly at its import instead of silently switching implementations.
23-
_MEGATRON_INSTALLED = importlib.util.find_spec("megatron") is not None
21+
22+
23+
def _megatron_core_installed() -> bool:
24+
"""A pip install for the FSDP or torchtitan backend has no Megatron. ``megatron.core`` holds the fused
25+
kernel: megatron-energon alone installs a bare ``megatron`` namespace. A broken Megatron install still
26+
fails loudly at its import instead of silently switching implementations."""
27+
try:
28+
return importlib.util.find_spec("megatron.core") is not None
29+
except ModuleNotFoundError: # no megatron package at all
30+
return False
31+
32+
33+
_MEGATRON_INSTALLED = _megatron_core_installed()
2434

2535

2636
def _safe_clamp_log_ratio(log_ratio: torch.Tensor) -> torch.Tensor:
@@ -320,20 +330,21 @@ def compute_log_probs(
320330
class _VocabParallelCrossEntropy(torch.autograd.Function):
321331
"""Megatron's ``fused_vocab_parallel_cross_entropy`` math in plain torch, for installs without Megatron.
322332
323-
Logits are ``[seq, batch, vocab / tp]``, split over ``group`` along the vocabulary. Unlike
333+
Logits are ``[seq, batch, vocab / tp]``, split over ``group`` (None: the default group, as in
334+
``torch.distributed``) along the vocabulary. Unlike
324335
Megatron's, it leaves its input untouched and returns the gradient in the input's dtype.
325336
"""
326337

327338
@staticmethod
328-
def forward(ctx, logits: torch.Tensor, target: torch.Tensor, group: dist.ProcessGroup) -> torch.Tensor:
339+
def forward(ctx, logits: torch.Tensor, target: torch.Tensor, group: dist.ProcessGroup | None) -> torch.Tensor:
329340
ctx.input_dtype = logits.dtype
330341
logits = logits.float()
331342
logits_max = logits.max(dim=-1)[0]
332343
dist.all_reduce(logits_max, op=dist.ReduceOp.MAX, group=group)
333344
logits = logits - logits_max.unsqueeze(-1)
334345

335346
partition_vocab_size = logits.size(-1)
336-
vocab_start = group.rank() * partition_vocab_size
347+
vocab_start = dist.get_rank(group) * partition_vocab_size
337348
target_mask = (target < vocab_start) | (target >= vocab_start + partition_vocab_size)
338349
masked_target = (target - vocab_start).masked_fill(target_mask, 0)
339350
predicted_logits = logits.gather(-1, masked_target.unsqueeze(-1)).squeeze(-1).masked_fill(target_mask, 0.0)
@@ -357,7 +368,7 @@ def backward(ctx, grad_output: torch.Tensor):
357368
return (grad_input * grad_output.unsqueeze(-1)).to(ctx.input_dtype), None, None
358369

359370

360-
def vocab_parallel_cross_entropy(logits: torch.Tensor, target: torch.Tensor, group: dist.ProcessGroup):
371+
def vocab_parallel_cross_entropy(logits: torch.Tensor, target: torch.Tensor, group: dist.ProcessGroup | None):
361372
return _VocabParallelCrossEntropy.apply(logits, target, group)
362373

363374

‎tests/fast/backends/training_utils/loss_hub/test_vocab_parallel_cross_entropy.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ def _check_compute_log_probs_without_megatron(group: dist.ProcessGroup) -> None:
8080
expected = torch.log_softmax(logits, dim=-1).gather(-1, tokens.unsqueeze(-1))
8181

8282
torch.testing.assert_close(compute_log_probs(logits, tokens, group), expected, atol=1e-6, rtol=1e-6)
83+
# None is the default group, as in torch.distributed and the callers that pass it
84+
torch.testing.assert_close(compute_log_probs(logits, tokens, None), expected, atol=1e-6, rtol=1e-6)
8385

8486

8587
def _run_checks(rank: int, world_size: int, port: int) -> None:
@@ -99,3 +101,25 @@ def _run_checks(rank: int, world_size: int, port: int) -> None:
99101
def test_vocab_parallel_cross_entropy(world_size: int) -> None:
100102
"""One process group per tensor-parallel size: spawning is what costs, so every check shares it."""
101103
run_multiprocess(_run_checks, world_size=world_size)
104+
105+
106+
def _find_spec_seeing(found: dict[str, bool]):
107+
def find_spec(name: str):
108+
if name.split(".")[0] not in found: # importing the parent package fails, as find_spec does
109+
raise ModuleNotFoundError(name)
110+
return object() if found.get(name, False) else None
111+
112+
return find_spec
113+
114+
115+
@pytest.mark.parametrize(
116+
"found,installed",
117+
[
118+
({"megatron": True, "megatron.core": True}, True),
119+
({"megatron": True}, False), # megatron-energon's bare namespace, no megatron.core
120+
({}, False), # no megatron package at all
121+
],
122+
)
123+
def test_megatron_counts_as_installed_only_with_megatron_core(monkeypatch, found, installed) -> None:
124+
monkeypatch.setattr(math_utils.importlib.util, "find_spec", _find_spec_seeing(found))
125+
assert math_utils._megatron_core_installed() is installed

0 commit comments

Comments
 (0)