Skip to content

feat(moe): shared MoE expert-routing stats (capture/reduce/scalars + router hook + CLI)#15

Open
ilsa001 wants to merge 5 commits into
sirl-stablefrom
feat/expert-stats-moe
Open

feat(moe): shared MoE expert-routing stats (capture/reduce/scalars + router hook + CLI)#15
ilsa001 wants to merge 5 commits into
sirl-stablefrom
feat/expert-stats-moe

Conversation

@ilsa001

@ilsa001 ilsa001 commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a framework-agnostic MoE expert-routing stats module to the shared vendor, consumed by BOTH mcore-trainer (SFT/CPT/DPO) and slime-trainer (RL) — single source of truth for capture/reduce/scalars; each engine owns its own logging sink.

Changes (+516, 3 files)

  • megatron/core/transformer/moe/expert_stats.py (new): configure/is_enabled, save_expert_stats (1-based layer guard, skips MTP/out-of-range rows), increment_step_count, reduce_snapshot_and_clear (SUM over PP → TP+CP → DP-without-CP, so CP is counted exactly once), collect_scalar_metrics, render_and_dump (lazy matplotlib — not a hard megatron dep), resolve_output_dir, register_extra_observer (v2 per-source hook).
  • TopKRouter.routing(): flag-gated capture hook. Stock-router callers (mcore) get it automatically; full-override subclasses (slime SiRLTopKRouter) call save_expert_stats themselves.
  • arguments.py: _add_expert_stats_args (--expert-stats-{log-interval,heatmap-interval,output-dir,per-layer-logging}), named so structured submit passthrough discovers the expert_stats group.

Safety

  • Disabled by default (--expert-stats-log-interval 0): the hook is a single bool short-circuit; zero behavior change when off. Dense models never build a router → never fires.
  • Env kill-switch MEGATRON_DISABLE_EXPERT_STATS=1.

Tests

py_compile (3 files) + 15 CPU unit tests: negative-interval raise, env kill-switch, 1-based layer guard (rejects 0 and >num_layers, writes last layer), reduce group selection (asserts with_context_parallel=False, no CP double-count), cadence, scalars (CV/std/max/min/entropy/per-layer), output-dir resolution, clear-on-all-ranks, non-logging-rank → None.
GPU/distributed smoke (real collective, multi-rank) to follow.

Downstream / review gate

Foundational change. Engine PRs depend on this landing + a vendor stable tag:

  • scitix/slime-trainer#… (RL glue)
  • scitix/mcore-trainer#… (SFT/CPT/DPO glue)

Per the vendor-stable-branch contract: merge here → tag scitix-megatron-sirl-stable-YYYY.MM.DD.N → engines bump gitlink to the tag.
Reviewers: @ilsa001 (integration lead).

🤖 Generated with Claude Code

tracker["tokens_per_expert"][idx] += routing_map.float().sum(dim=0) # [E]
tracker["weight_per_expert"][idx] += probs.detach().sum(dim=0) # [E]

for fn in _EXTRA_OBSERVERS:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

保险起见这里也加上 no_grad 吧

if "tokens_per_expert" not in tracker:
device = routing_map.device
tracker["tokens_per_expert"] = torch.zeros(
num_layers, num_experts, device=device, dtype=torch.float32

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

这里应该是 int64 吧,torch 累加自动 int64


with torch.no_grad():
idx = layer_number - 1
tracker["tokens_per_expert"][idx] += routing_map.float().sum(dim=0) # [E]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

int64 后不用转型了

for key in ("tokens_per_expert", "weight_per_expert"):
values = tracker[key]
# PP: different stages hold different layers (idle stages contribute 0).
dist.all_reduce(values, group=parallel_state.get_pipeline_model_parallel_group())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

这里如果有 pp 只有 dense的情况下,会有 pp 不掉用 save_expert_stats 的,这里会不会 hang 住?

ilsa001 and others added 5 commits July 3, 2026 06:45
…router hook + CLI)

Adds a framework-agnostic expert-routing stats module consumed by BOTH
mcore-trainer (SFT/CPT/DPO) and slime-trainer (RL):
- megatron/core/transformer/moe/expert_stats.py: configure/is_enabled,
  save_expert_stats (1-based layer guard), increment_step_count,
  reduce_snapshot_and_clear (PP -> TP+CP -> DP-without-CP; CP counted once),
  collect_scalar_metrics, render_and_dump (lazy matplotlib, no hard dep),
  resolve_output_dir, register_extra_observer.
- TopKRouter.routing(): flag-gated capture hook (stock-router callers get it
  automatically; full-override subclasses call save_expert_stats themselves).
- arguments.py: _add_expert_stats_args (--expert-stats-*), discoverable by
  slime submit's structured megatron.* passthrough.

Disabled by default (--expert-stats-log-interval 0). Verified: py_compile +
15 CPU unit tests (validation, 1-based guard, reduce group selection incl.
with_context_parallel=False, cadence, scalars, path resolution, clear-on-all-ranks).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In-tree coverage for the shared expert_stats module (addresses the review
note that the 15 CPU tests were not committed):
- TestExpertStatsLogic (CPU): configure negative-raise + env kill-switch,
  1-based layer guard, save accumulate/noop, heatmap cadence, output-dir
  resolution, collect_scalar_metrics (CV/std/max/min/entropy). 9 tests,
  verified passing via the megatron pytest harness on CPU.
- TestExpertStatsDistributed (CUDA-gated, model-parallel via Utils):
  reduce_snapshot_and_clear clears + normalizes; real TopKRouter.routing()
  hook fires only on the grad-enabled training forward, excluded under
  no_grad/eval and when disabled.

The strong multi-topology single-count / CP-no-double-count assertion stays
in the external GPU smoke (needs a controlled world size).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirrors the cosmos/slime feishu workflow so new comments on Megatron-LM
fork issues are pushed to Feishu. Untrusted event fields pass via env (no
shell injection), jq builds the JSON payload, and it skips gracefully
until the FEISHU_WEBHOOK_URL secret is set.

Runner: [self-hosted, hisys-megatron-runner].

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Synchronous helper returning the rendered MoE-layer token-count heatmap (RGB
array or PNG-path fallback) so trainers can log it into their own sink (e.g.
swanlab.Image) — megatron owns rendering, the trainer owns the sink. The async
local-PNG path (render_and_dump) is unchanged. Unit test added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
slime/sglang runtimes ship a stubbed `IPython` module that lacks the
attributes matplotlib's interactive-framework probe reads (get_ipython /
version_info, varying by matplotlib version), raising AttributeError
mid-render so the expert-stats heatmap silently fell back to 'skipped'.

Force the non-interactive Agg backend and hide IPython from sys.modules for
the duration of the render (restore afterward) so matplotlib never probes it.
No-op on trainers with a real IPython (mcore SFT already rendered fine).

Verified: 32-GPU UBio-A3B-160E RL smoke now produces heatmap PNGs +
swanlab.Image (slime) in addition to the JSONL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ilsa001
ilsa001 force-pushed the feat/expert-stats-moe branch from 10602f1 to cd698ee Compare July 2, 2026 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants