Skip to content

[dev] lite: Sync Megatron Lite experimental updates for deepseek v4 - #6397

Merged
ISEEKYAN merged 11 commits into
NVIDIA:devfrom
ISEEKYAN:sync/fork-main-to-nvidia-dev
Aug 31, 2026
Merged

[dev] lite: Sync Megatron Lite experimental updates for deepseek v4#6397
ISEEKYAN merged 11 commits into
NVIDIA:devfrom
ISEEKYAN:sync/fork-main-to-nvidia-dev

Conversation

@ISEEKYAN

Copy link
Copy Markdown
Contributor

Summary

This PR syncs the current experimental/** Megatron Lite tree from ISEEKYAN/Megatron-LM:main onto NVIDIA dev in one PR with two reviewable commits.

  • NVIDIA base: 43124b60c26bb233bc9ba0cf75b2f637d0387c14
  • Fork source: 4145ea889e7035e2e6d9e34c709a87eb04e8ebb1
  • Consolidated sync commit: 3833103b4ec7cb64f7f1109d317247f340ca6c6e
  • DS4 QAT HF-load follow-up: 737381c95c4982f67423b351781869b2b436a764
  • Scope: 89 files, 14,983 additions, 2,367 deletions
  • Boundary: every changed file is under experimental/lite/**

Full diff: dev...ISEEKYAN:Megatron-LM:sync/fork-main-to-nvidia-dev

Consolidated diff: ISEEKYAN@3833103

DS4 QAT HF-load diff: ISEEKYAN@737381c

The source and target histories have diverged substantially, so retaining the original commits would also retain thousands of unrelated fork commits. The sync is therefore intentionally squashed; the original implementation notes remain linked below.

Diff contents

QAT and router replay

  • Add weight-only QAT primitives for INT8, INT4, FP8 E4M3, and MXFP4.
  • Wire QAT checkpoint naming, load/export, and model construction across Qwen3 MoE, Qwen3.5, DeepSeek-v4, GLM5, and Kimi-K2.
  • Add R3 router replay masks, replay evidence, and model-wide replay contracts.
  • Add the real MXFP4 QAT veRL launch recipe and configuration.

Dynamic context parallelism

  • Add the runtime-owned dynamic CP plugin and scheduling state.
  • Preserve jagged batches, broadcast metadata, loss normalization, metrics, pipeline scheduling, and router replay across dynamically selected CP groups.
  • Add CPU contract coverage for mixed CP plans and runtime sidecars.

Checkpoint and weight streaming

  • Stream Hugging Face checkpoint loading through the primitive boundary.
  • Keep export and actor-to-rollout weight synchronization device-resident and bounded.
  • Add bounded TP/EP/FSDP/PP gathering and streaming HF shard/index output.
  • Add QAT-aware and persistent-buffer checkpoint contracts.
  • Exclude parametrization-owned QAT auxiliary state from DS4's dynamic HF load map while preserving the logical master weight.

Model and training compatibility

  • Add GLM5.2 IndexShare DSA scheduling and checkpoint mappings.
  • Extend DeepSeek-v4, GLM5, Kimi-K2, Qwen3.5, and Qwen3 MoE protocols and checkpoint paths.
  • Restore chunkwise GDN context-parallel behavior and Qwen3.5 replicated attention-head support.
  • Fix cross-entropy fusion behind data-parallel wrappers and the MLite loss-hook reference cycle.

veRL integration and validation

  • Update MLite veRL engine configuration, QAT launch paths, SFT scripts, metrics, and bounded online export.
  • Expand the local validation harness and organize focused unit/smoke coverage.

Original source notes

Credits

  • @shyoshyo — DeepSeek-v4 Hugging Face save support, gradient-checkpointing fix, and THD label shifting (0968605a4f, 476fda588d, 710b0a454e).
  • @Meirtz — DeepSeek-v4 MTP auxiliary-loss gradient scaling (0cf711bcaa).

Validation

  • Syntax compilation for every changed Python file: passed.
  • git diff --check: passed.
  • Exact tree comparison against fork source for experimental/**: passed.
  • Focused dynamic CP, QAT, router replay, checkpoint streaming, model wiring, and veRL config tests: 114 passed, 14 skipped.
    • One skip requires CUDA.
    • Thirteen skips require the optional veRL dependency.
  • Focused Hugging Face load-boundary and streaming-load tests for LM for long sequence (e.g. - BigBird) support into Megatron-LM #165: 23 passed.

Known source-test gaps

No code changes were added while preparing this sync. Two existing fork tests are stale relative to the synced implementation:

  • DeepSeek-v4 cases in test_all_model_qat_r3_contracts.py stub the Core CSA namespace without the newer csa_kernels module.
  • test_glm52_checkpoint_mapping_skips_shared_indexer_without_te still calls the removed private _load_attention helper instead of the current Glm5WeightSpec mapping contract.

These are reported as source-tree test gaps rather than being silently fixed in this sync PR.

@copy-pr-bot

copy-pr-bot Bot commented Aug 10, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@ISEEKYAN
ISEEKYAN marked this pull request as ready for review August 10, 2026 10:43
@ISEEKYAN
ISEEKYAN requested review from a team as code owners August 10, 2026 10:43
Signed-off-by: Yan Bai <bayan@nvidia.com>
Signed-off-by: Yan Bai <bayan@nvidia.com>
Signed-off-by: Yan Bai <bayan@nvidia.com>
@ISEEKYAN
ISEEKYAN force-pushed the sync/fork-main-to-nvidia-dev branch from 737381c to 58bcc78 Compare August 31, 2026 13:23
pytest requires `pytestmark` to be a Mark or a list of them; a tuple raises
`TypeError: got (MarkDecorator(...), MarkDecorator(...)) instead of Mark` during
collection, which fails the whole run before any test executes.

Observed on one 8xH100 node with `tests/run_tests.sh` against this branch:
878 tests collected, 2 errors -- both this. Every other file in the tree that
carries multiple marks already uses a list.

Signed-off-by: Yan Bai <bayan@nvidia.com>
@ISEEKYAN
ISEEKYAN force-pushed the sync/fork-main-to-nvidia-dev branch 2 times, most recently from f610632 to 5ee3345 Compare August 31, 2026 13:46
`experimental/lite/examples/` and Megatron-LM's top-level `examples/` are both
regular packages, so whichever appears first on `sys.path` wins. The harness set
`PYTHONPATH=REPO_ROOT:LITE_ROOT`, which was harmless while lite lived in a
lite-only fork whose root holds nothing but README.md and experimental/. Inside
the full tree the root package wins and lite's own examples become unreachable:

    ModuleNotFoundError: No module named 'examples.bench'
    (tests/unit/runtime/backends/bridge/test_packed_batch_bridge.py)

The worker is launched as `[sys.executable, worker, *args]`, i.e. a script, so
`sys.path[0]` is the harness directory and the working directory never
participates in resolution. Changing the subprocess cwd therefore does not help;
the PYTHONPATH order is what decides.

Verified with `importlib.util.find_spec` on one 8xH100 node with the lite root
first:

    megatron.core -> <root>/megatron/core/__init__.py
    megatron.lite -> <lite>/megatron/lite/__init__.py
    tests         -> <root>/tests/__init__.py

`megatron` is unaffected because neither directory carries an `__init__.py`, so
both are implicit namespace packages and merge. `tests` is unaffected because
lite's `tests/` has no `__init__.py`, so the root's regular package still wins.
Only `examples` changes hands, which is the intent.

Signed-off-by: Yan Bai <bayan@nvidia.com>
@ISEEKYAN
ISEEKYAN force-pushed the sync/fork-main-to-nvidia-dev branch from 5ee3345 to db83120 Compare August 31, 2026 13:49
Signed-off-by: Yan Bai <bayan@nvidia.com>
The merge kept dev's `add_cross_entropy_fusion`, which reads the flag with a
plain `getattr(model, "cross_entropy_fusion", False)`, and dropped the fork's
`_resolve_cross_entropy_fusion`, which walks the `.module` chain. Megatron's
`_BaseDataParallel` keeps the wrapped module in `self.module` and does not proxy
unknown attributes, so once the runtime wraps each chunk the plain getattr
misses the flag and silently takes the unfused branch -- materialising the full
`[tokens, vocab/tp]` logits in fp32 while every config dump still reports
`cross_entropy_fusion=True`.

Also updates two contract tests that pin surfaces dev has since moved:

- `test_lite_csa_imports_core_csa_kernel_namespace` matched the literal
  `...experimental_attention_variant.csa_kernels import`, which no longer exists
  after dev's CSA restructure (1c44a57); it now pins the `csa_utils` package.
- `test_attention_public_api_is_narrow` gained `MagiAttentionConfig` and
  `MagiDotProductAttention`, the two names MagiAttention (NVIDIA#6478) exports. Both
  are genuine public surface, unlike the internal helpers the same test asserts
  stay unexported.

Signed-off-by: Yan Bai <bayan@nvidia.com>
`test_gqa_magi_branch_uses_dispatched_positions_and_runtime` builds a
`GQAttention` through `__new__` and assigns the attributes the forward pass
needs. It came from upstream NVIDIA#6478, where `GQAttention` has no `_replicate_kv`;
lite's copy does, because it replicates KV heads when
`num_key_value_heads < tp_size`, and every forward path reads the flag. After
the merge the stub is missing it:

    AttributeError: 'GQAttention' object has no attribute '_replicate_kv'

Set it to False, matching the test's 4 query / 2 KV head configuration, which is
what the real `__init__` would compute. No assertion is relaxed.

Signed-off-by: Yan Bai <bayan@nvidia.com>
`_test_harness/markers.py` treats a missing `gpus` marker as "this is a CPU
test", so all five CUDA tests in this file were scheduled into the `gpus=0` cpu
suite, where `torch.cuda.is_available()` is false and they skipped.
`pytest_worker.py` counts any skip as a failure and `runner.py` stops at the
first non-PASS suite, so the cpu suite reported FAIL with `736 passed, 0 failed,
5 skipped` and every GPU suite after it never ran at all.

Two of the five call `_require_two_ranks()` and assert
`dist.get_world_size() == 2`, so they need `gpus(2)`; `_gpus_marker` reads
`next(item.iter_markers("gpus"))`, closest-first, so a per-test marker overrides
the module-level one. The sibling NCCL test in the fsdp2 file already uses the
same pattern.

Signed-off-by: Yan Bai <bayan@nvidia.com>
@ISEEKYAN
ISEEKYAN force-pushed the sync/fork-main-to-nvidia-dev branch from 5fe9601 to a850af5 Compare August 31, 2026 14:48
@ISEEKYAN

Copy link
Copy Markdown
Contributor Author

Brings experimental/lite on dev up to the state of the downstream fork and
makes dev the branch Megatron Lite is developed on, rather than a periodic
export target. Supersedes the earlier content of this PR, which was a
DeepSeek-V4-focused sync from 2026-08-10.

Only experimental/lite is touched: git diff <head> dev restricted to
everything outside that subtree is empty.

Why now

dev's own copy of lite does not currently import against dev's own
megatron.core. The CSA restructure in 1c44a57 (#6372) moved the helpers into
megatron/core/transformer/experimental_attention_variant/csa_utils/ and deleted
the old modules, but experimental/lite/.../attention/csa.py was never updated.
Running the lite test suite on unmodified dev today:

overall=FAIL exit_code=1 reason=collection_failed
E ImportError: cannot import name 'csa_cp_layout_kernels'
  from 'megatron.core.transformer.experimental_attention_variant'
  (3 modules affected)

This PR fixes that as part of the sync.

How the merge was done

The two trees last agreed at c8239f9 (#5862, 2026-07-20). Merging from the
older common ancestor produces ~284 spurious conflicts, because most of what
dev gained since then originated from the fork's own earlier syncs
(#5427/#5577/#5694/#5682/#5862). Using c8239f9 as the merge base narrows it to
25 real conflicts, all inside experimental/lite.

dev-only work since that point is preserved rather than overwritten,
specifically MagiAttention (#6478), the CP layout refactor (#6387), the standard
local validation harness (#5888), and the main-first DSA migration (#6020).
Eleven files that exist only on dev are carried through unchanged.

What arrives from the fork

  • DeepSeek-V4: QAT export, resync, MXFP4/block-FP8 quantisation, and the
    R3 router-replay contract.
  • Qwen3.5: dense model support, TP head replication, checkpoint/export fixes.
  • FSDP2: meta-device initialisation, offload/checkpoint continuity, and removal
    of full-size FP32 temporaries from the optimizer step.
  • MoE: moe_token_dispatcher_type in place of the use_deepep boolean.
  • Runtime: dynamic CP scheduling, streamed HF weight load/export, and the
    weight-sync fast path.
  • Roughly thirty additional unit and contract tests.

Conflict resolution worth reviewing closely

Six source files were changed on both sides. Two of them had a defect in the
first pass, both caught by the suite and fixed:

  • model/protocol_utils.py kept dev's add_cross_entropy_fusion, which reads
    the flag with getattr(model, "cross_entropy_fusion", False), and dropped the
    fork's _resolve_cross_entropy_fusion, which walks the .module chain.
    _BaseDataParallel does not proxy unknown attributes, so once the runtime
    wraps each chunk the flag is missed and the model silently takes the unfused
    branch -- materialising the full [tokens, vocab/tp] logits in fp32 while
    every config dump still reports cross_entropy_fusion=True.
  • pack_r3_replay_mask ignored the upstream per-token mask, producing masks of
    the right length with the wrong positions set.

The other four (gqa.py, csa.py, glm5/lite/model.py,
runtime/backends/mlite/runtime.py) keep both sides: dev's MagiAttention
wiring alongside the fork's dispatcher, LoRA, KV-replication and THD changes.

Test harness

Two harness changes were needed for the suite to run inside the full repository
rather than the lite-only fork:

  • PYTHONPATH now puts the lite root ahead of the repository root. Both
    experimental/lite/examples/ and the repository's top-level examples/ are
    regular packages, so the first on sys.path wins; with the repository root
    first, import examples.bench resolved to Megatron's examples/. The worker
    is launched as a script, so sys.path[0] is the harness directory and the
    working directory never participates -- the PYTHONPATH order is what decides.
    megatron is unaffected (both are namespace packages and merge); tests is
    unaffected (lite's has no __init__.py, so the root's regular package wins).
  • tests/unit/primitive/ckpt/test_hf_weights_gpu.py was missing its gpus
    marker, so its five CUDA tests were scheduled into the gpus=0 cpu suite and
    skipped; since a skip counts as a failure, the run stopped there and no GPU
    suite executed.

Validation

One 8xH100 node, experimental/lite/tests/run_tests.sh with no arguments
(standard profile), this branch and unmodified dev in the same container.

suite=cpu                                 PASS  736 passed / 0 failed / 0 skipped / 1 xfailed
suite=1gpu-csa-thd-cp                     PASS  3
suite=1gpu-fsdp2-offload-gpu              PASS  2
suite=1gpu-fsdp2-unit                     PASS  2
suite=1gpu-glm5-lite-static               PASS  3 + 4
suite=1gpu-hf-weights-gpu                 PASS  3
suite=1gpu-hf-weights-streaming           PASS  1
suite=1gpu-qwen-lite-forward-smoke        PASS  2
suite=1gpu-qwen35-hf-numeric-roundtrip    skipped: needs the FLA / GatedDeltaNet stack

Nothing fails. The one non-PASS suite is an optional-dependency skip:
test_qwen35_hf_numeric_roundtrip_smoke.py calls pytest.importorskip("fla"),
and the container used here does not carry the FLA / GatedDeltaNet stack. That
file is one of the dev-only tests this PR carries through unchanged. Because a
skip counts as a failure the run stops there, so the suites after it were not
reached in this run.

A second run with the FLA stack present (FLA 0.5.0) confirms the file still does
not execute, for a different and equally environmental reason: its ground-truth
check is gated on QWEN35_HF_DIR pointing at a real Qwen3.5 HF checkpoint,
which was not available. So this file has not been exercised here either way,
and the suites ordered after it remain unrun. Both are environment gaps rather
than anything this PR changes -- the file is byte-identical to dev.

Unmodified dev cannot get past collection at all, so there is no per-suite
baseline to compare against; the csa_utils ImportError above is the reason.

@ISEEKYAN

Copy link
Copy Markdown
Contributor Author

/ok to test a850af5

`tools/check_copyright.py` requires every file to open with the current-year
NVIDIA header, and CI applies it to the files a PR touches. Four Python files
in this change did not:

- `verl_mlite/config/ref/__init__.py` carried the header on `dev` and lost it in
  the merge -- the fork's copy of this file is empty. Restored.
- `tests/unit/model/test_deepseek_v4_hf_export_dtype.py` is new and had none.
- `primitive/kernels/dsa_kernels.py` still had the older
  `Copyright (c) 2025, NVIDIA CORPORATION.` form; bumped to the current wording.
- `examples/verl/scripts/validate_deepseek_v4_dapo.py` starts with a shebang.
  The header goes above it, matching `docs/add_copyright_header.py`, because the
  check tests `content.startswith(...)`.

Shell and YAML files touched here open with a shebang or a plain comment on
`dev` as well and are left as they are.

Signed-off-by: Yan Bai <bayan@nvidia.com>
Keeps the branch current with the base. dev's only new commit
(d59d4b9, paged stash with TE whole-MoE) does not touch
experimental/lite, so this is a fast-forward of the non-lite tree.

Signed-off-by: Yan Bai <bayan@nvidia.com>
@ISEEKYAN

Copy link
Copy Markdown
Contributor Author

/ok to test 3888693

@ISEEKYAN
ISEEKYAN added this pull request to the merge queue Aug 31, 2026
Merged via the queue into NVIDIA:dev with commit 5e12dce Aug 31, 2026
90 checks passed
@ISEEKYAN
ISEEKYAN deleted the sync/fork-main-to-nvidia-dev branch August 31, 2026 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants