Skip to content

fix(megatron): ROCm-safe attention_backend + Mamba/SFT E2E, dead-code & CI cleanup#870

Merged
Xiaoming-AMD merged 15 commits into
mainfrom
cicd/coverage-runtime-e2e-fixes
Jul 16, 2026
Merged

fix(megatron): ROCm-safe attention_backend + Mamba/SFT E2E, dead-code & CI cleanup#870
Xiaoming-AMD merged 15 commits into
mainfrom
cicd/coverage-runtime-e2e-fixes

Conversation

@WangLingxun

@WangLingxun WangLingxun commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • fix(megatron): coerce attention_backend enum args + make it ROCm-safe. Primus builds Megatron's arg namespace straight from YAML/CLI, bypassing argparse type coercion, so enum args like attention_backend arrived as plain str and lost every downstream == AttnBackend.member check — --attention_backend fused was a silent no-op. MegatronArgBuilder now coerces enum args through Megatron's own argparse converter. A ROCm-gated before_train patch also lets the selected backend win over the image's baked NVTE_*_ATTN (ROCm images bake NVTE_FLASH_ATTN=0, which makes stock megatron's auto/explicit-backend assertion crash): auto respects the baked FLASH=0, explicit backends force their combination.
  • test(megatron): add Mamba/Hybrid and native SFT+LoRA E2E cases. test_mamba_370M / test_zebra_llama_1B_hybrid cover the MambaStack/HybridStack auto attention path; test_qwen2_sft_lora is the first posttrain (MegatronSFTTrainer) + peft/* E2E, using a tiny offline config/fixture. The conversion hook (01_convert_checkpoints.py) neutralizes NVTE_*_ATTN around AutoBridge.import_ckpt() since it runs against Bridge's bundled megatron that never sees the patch.
  • chore(megatron): remove dead code — unreachable Muon optimizer patch + vendored impl, and superseded te_gemm_patch_wgrad / te_group_gemm_patch_wgrad.
  • ci(dependabot): monthly, major-only GitHub Actions updates to cut PR churn (security updates unaffected).

…narrowing

- Runtime summary now reads step timing live from the Actions API instead of
  hand-wrapped start/end timers, so untracked stages (e.g. a ~19min git
  clone) show up automatically instead of staying invisible.
- coverage_summary.py's classify() now separates "counts toward a total"
  from "gets its own row": a bare file with no sub-package of its own (e.g.
  core/base_module.py) folds into its group's total without faking a
  sub-package row. junit_summary's Time column now matches runtime's h/m/s.
- select_tests.py no longer narrows unit tests (the full suite is only ~5min,
  so narrowing risked under-selection for little gain), and now treats a
  bare examples/<file> (e.g. run_pretrain.sh, which MaxText's E2E runs
  directly) as blast-radius-unknown instead of silently ignoring it.
Neither patch runs in any other case in this file: SDMA param all-gather only
activates behind ENABLE_SDMA_ALLGATHER=1 (env, not a --arg) + a distributed
optimizer, and fused residual+RMSNorm needs use_turbo_rms_norm +
PRIMUS_FUSED_RESIDUAL_NORM_V2. Confirmed on real MI300X hardware with
coverage instrumentation that both patches install and actually execute
(not just get registered and skipped), at ~60s added runtime.

Also looked at two bigger targets and ruled both out for now, for different
reasons:
- Muon optimizer: third_party/Emerging-Optimizers is already a populated
  submodule (just needs PYTHONPATH, not a new dependency as first assumed),
  and the vendor import chain works. But coverage shows the dispatch wrapper
  in muon_optimizer_patches.py installs and then never actually runs during
  training, so --optimizer muon isn't exercising the muon path yet -- needs
  tracing why megatron.training.training's optimizer setup isn't calling
  through the patched reference before this is worth an E2E case.
- Legacy ZeroBubble PP scheduler (core/pipeline_parallel/zerobubble/): has
  genuinely bit-rotted since being disabled -- it now throws
  "SchedNodeRuntime.prepare() got an unexpected keyword argument
  'force_all_reduce'", a real incompatibility with the current megatron-core
  version that needs its own fix, not a config tweak.
The fast-exit path only flushed stdout/stderr before os._exit(0), which
skips atexit and silently drops any process coverage data that hadn't
been saved yet. Route through the existing flush_before_hard_exit()
(handles both the output flush and coverage.save()) instead of a
partial inline reimplementation.
…m-safe

Primus patches Megatron's parse_args to return a namespace built straight
from YAML/CLI, bypassing argparse's type coercion. Enum-typed args such as
attention_backend therefore arrived as plain strings and lost every
downstream `== AttnBackend.member` comparison in
LanguageModule._set_attention_backend(), so `--attention_backend fused`
(and friends) were silently no-ops.

- MegatronArgBuilder now coerces enum-typed overrides through Megatron's own
  argparse `type` converter (reusing the parser's definitions, no hand-kept
  mapping), so attention_backend resolves to the real AttnBackend enum.
- Add a before_train patch (ROCm-gated) that reconciles attention_backend
  with the image's baked NVTE_*_ATTN. Stock megatron asserts the vars are
  unset-or-equal to what the backend wants and crashes on ROCm images that
  intentionally bake NVTE_FLASH_ATTN=0 (TE flash attn is unavailable on
  ROCm). The patch instead lets the selected backend win: `auto` enables
  only the backends the platform hasn't disabled (respecting the baked
  FLASH=0), and an explicit backend forces exactly its combination,
  overriding the baked defaults.

Unit-tested the coercion (mocked and against the real Megatron parser) and
the patched reconciliation.
Verified on a16-19 (MI300X x8).

- test_mamba_370M / test_zebra_llama_1B_hybrid: cover the MambaStack and
  HybridStack code paths, which (unlike the GPT configs) go through
  megatron-core's own attention probe on the default `auto` backend -- now
  ROCm-safe via the attention_backend patch, so no NVTE workaround is needed.
- test_qwen2_sft_lora: first E2E case for the posttrain suite
  (MegatronSFTTrainer) and primus/backends/megatron/peft/*.py. Uses a
  dedicated tiny config (reusing sft_trainer.yaml + qwen2.5_7B.yaml, pointing
  tokenizer_model at a real-but-tiny Qwen2 checkpoint so the runner hook's
  HF->Megatron conversion matches the model shape) plus a local jsonl SFT
  fixture, so the test is fast and offline. Pins the unfused backend via
  --attention_backend (its head_dim=2 is below the fused CK kernel minimum).
- 01_convert_checkpoints.py: a general posttrain fix, not test-only -- the
  conversion hook runs in a separate subprocess against Megatron-Bridge's
  bundled Megatron-LM, which never sees the attention_backend patch. Since
  the conversion computes no attention, neutralize (unset + restore) the
  NVTE_*_ATTN vars around AutoBridge.import_ckpt() to get past stock
  megatron's `auto` assertion on ROCm's baked NVTE_FLASH_ATTN=0.
…entation

Vendored Megatron-LM's setup_model_and_optimizer() dispatches to its own
get_megatron_muon_optimizer whenever "muon" is in config.optimizer, before
Primus's patched get_megatron_optimizer ever runs. muon_optimizer_patches.py
only patches the latter, so it can never be reached, and the backported
moun.py / moun_optimizer_config.py / layer_wise_optimizer.py behind it is
unreachable too. Remove the patch, its unit test, and that implementation.
--optimizer muon continues to work via Megatron's native path.

Keep the remaining muon_* fields in trainer_base.yaml (drop only
muon_weight_decay / muon_weight_decay_method, which nothing reads):
Megatron-LM's native get_megatron_optimizer_config() opportunistically
copies any args attr matching an AdamOptimizerConfig field name, and reads
these; since most have no CLI flag, this yaml is the only way to tune Muon's
internals via Primus config. Also drop the now-stale muon references in the
patch-explorer skill doc and the coverage summary notes.
…h_wgrad

Superseded by te_wgrad_store.py's WeightGradStore-based approach (introduced
in the same wgrad-split migration that should have retired these but didn't).
No file imports either module or the classes they define
(_LinearWithWGradSplit / _GroupedLinearWithWGradSplit), and there is no
dedicated unit test for them.
Cut PR churn: run the GitHub Actions version-update check monthly instead of
weekly, and ignore minor/patch bumps so only major version updates open a PR.
Dependabot security updates are unaffected and still land regardless.
@WangLingxun
WangLingxun force-pushed the cicd/coverage-runtime-e2e-fixes branch from 7d72911 to 0d7f5b1 Compare July 13, 2026 09:13
Wrap an argparse add_argument() call that exceeded the 110-char line limit,
so `pre-commit run black --all-files` (the code-lint CI job) passes. Formatting
only; no behavior change.
@WangLingxun
WangLingxun marked this pull request as ready for review July 13, 2026 09:40
Add a first-class Mamba2 130M pretrain example (model config + MI300X/MI355X
example yamls) and an E2E case wiring it into the megatron suite -- the only
E2E covering the megatron_bridge backend (trainer, load_recipe_config,
MegatronBridgeArgBuilder, bridge patches). The test pins a tiny shape via
extra_args so it is independent of the example yaml's sizes; seq_length is
left to the yaml since the recipe feeds it to both model and dataset and
Bridge asserts they match. Verified on MI300X.

coverage_summary.py: rename the "(top-level)" row to "primus (top-level)" with
an accurate note (loose primus/ modules folded by path depth), wrap NOTES with
<br> so a long note can't stretch the column, move the tier dot to the
module-name column, and drop the stale megatron_bridge "no E2E" note.

Also trims the Mamba/Hybrid/SFT-LoRA E2E comments to the code-relevant
rationale, and sets the new attention_backend_patches files' copyright to 2026.
@WangLingxun
WangLingxun force-pushed the cicd/coverage-runtime-e2e-fixes branch from 2f056f4 to cad4eef Compare July 14, 2026 07:06
Add a PRIORITY map (lower sorts first) so important groups lead the table
regardless of coverage; ties keep coverage-descending order. core (Primus
core, imported by every run) ranks highest. Ordering only: no target or gate.
@WangLingxun
WangLingxun force-pushed the cicd/coverage-runtime-e2e-fixes branch from dd53264 to edc07be Compare July 15, 2026 05:14
@Xiaoming-AMD
Xiaoming-AMD merged commit 6bad695 into main Jul 16, 2026
4 of 5 checks passed
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