Skip to content

feat(model): add GLM-5.3-Flash support - #6044

Open
HollowMan6 wants to merge 11 commits into
NVIDIA-NeMo:mainfrom
HollowMan6:glm53flash
Open

HollowMan6 wants to merge 11 commits into
NVIDIA-NeMo:mainfrom
HollowMan6:glm53flash

Conversation

@HollowMan6

@HollowMan6 HollowMan6 commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Pending on:

What does this PR do ?

Add HF ↔ Megatron support for GLM-5.3-Flash (model_type glm5_next, Glm5NextForConditionalGeneration): a unified bridge, provider, and VLM wrapper for its hybrid KDA/DSA + MoE + mHC language model with a replicated HF vision tower, plus the shared conversion, PEFT, and packed-sequence pieces it needs.

Changelog

GLM-5.3-Flash (src/megatron/bridge/models/glm5next/)

  • Add GLM53FlashBridge, GLM53FlashModelProvider, and GLM53FlashModel, registered for Glm5NextForConditionalGeneration with model_type="glm5_next" and exported from megatron.bridge.models.
  • Build hybrid_layer_pattern from the HF layer_types / mlp_layer_types: each HF decoder layer becomes two physical layers (KDA K or DSA D attention at 2N, dense - or MoE E FFN at 2N+1), with moe_layer_freq derived per physical module and num_hidden_layers recovered from the pattern on export.
  • Configure MLA NoPE geometry (qk_pos_emb_head_dim=0, kv_channels=v_head_dim), the kpool DSA indexer (FP8 kpool; index_kpool / index_kpool_always_select_tail read from the HF config), KDA two-stage low-rank gates with gate_lower_bound, mHC multi-stream hyper-connections with fp32 mappings, sigmoid routing with expert bias and an always-on shared expert (moe_shared_expert_gate=False), grouped GEMM with the HybridEP flex dispatcher, and a one-layer DE MTP using the repeated-layer spec.
  • Weight mappings: fused KDA in_proj / conv1d built from separate HF Q/K/V tensors with section-wise TP sharding (_ColumnParallelConcatMapping), MLA projections and indexer weights, dense and MoE FFN (grouped-GEMM and sequential expert layouts), mHC hc_{attn,ffn}_{fn,base,scale}, and the MTP eh_proj split into e_proj / h_proj (_EhProjSplitMapping, import only).
  • Dequantize blockwise FP8 checkpoint weights on import; keep Megatron init for the MTP inner-layer hyper-connection params, which have no HF source.
  • VLM wrapper: replicated HF Glm5NextVisionModel on pre-process ranks, image/video features scattered into the embeddings before THD packing and the sequence-parallel scatter, text-only fallback when no pixel inputs are given, and freeze_{language_model,vision_model,vision_projection} options.

Hybrid providers

  • Add HybridMLAModelProvider (HybridModelProvider + MLATransformerConfig). With pipeline_model_parallel_size > 1 and DSA layers in the pattern, it inserts balanced | separators only before full-indexer DSA layers so IndexShare groups never cross a stage, validates user-provided separators, and rejects num_layers_in_first/last_pipeline_stage.
  • Add scatter_embedding_sequence_parallel to HybridModelProvider (default True, matching HybridModel) and pass it through; the VLM provider sets it to False so vision features are injected before the SP scatter.

Shared conversion infrastructure

  • Add HCAlphaMapping, mapping an HF [3] hc_*_scale tensor to Megatron's alpha_pre / alpha_post / alpha_res (index 0 exports all three), and migrate DeepSeekV4Bridge from its private _HCAlphaMapping / _HCAlphaSecondaryMapping to it with equivalent import/export behavior.
  • Add InitOnlyMapping for Megatron params with no HF counterpart (import keeps init, export is a no-op).
  • Add MegatronModelBridge._prefix_mapping_registry to re-key a text-model registry under a VLM prefix while keeping nested _tp_mapping names in sync.

PEFT

  • Split fused KDA in_proj LoRA linear_out weights by Q/K/V section sizes when kda_two_stage_gates is set, instead of the GQA-style split_qkv_weights path, so KDA LoRA adapters export correctly.

Packed sequences

  • Move preprocess_packed_seqs from models/qwen_vl/modelling_qwen3_vl/utils.py to training/utils/packed_seq_utils.py (shared by Qwen3-VL and GLM-5.3-Flash) and add use_fp8_padding, which aligns each sequence to lcm(16, align) and the packed total to align * 128.

Tests

  • tests/unit_tests/models/glm5next/test_glm53flash_mapping.py: provider precision contract and multimodal config copy, language_model. prefixing of text mappings, HCAlphaMapping import/export and wildcard resolution, section-wise KDA TP sharding round-trips (TP 1 to 16).
  • tests/unit_tests/models/qwen_vl/modelling_qwen3_vl/test_utils.py: packed-sequence alignment and gradient flow with and without FP8 padding.
  • tests/unit_tests/models/test_model_bridge_lora.py: KDA Q/K/V LoRA linear_out split.

GitHub Actions CI

See the CI section in the Contributing doc for how to trigger the CI. A Nvidia developer will need to approve and trigger the CI for external contributors.

Before your PR is "Ready for review"

Pre checks:

  • Make sure you read and followed Contributor guidelines
  • Did you write any new necessary tests?
  • Did you add or update any necessary documentation? (no model page yet; see follow-ups below)
  • Does the PR affect components that are optional to install? (Ex: Numba, Pynini, Apex etc)
    • Reviewer: Does the PR have correct import guards for all optional libraries?

Glm5NextForConditionalGeneration is imported behind a try/except that falls back to the class name, Glm5NextModel / Glm5NextVisionModel are imported lazily inside GLM53FlashModel.__init__, and the new unit tests use pytest.importorskip on the GLM-5.3-Flash config module.

Additional Information

  • No linked issue.
  • Dependency: transformers. The glm5_next model family ships in transformers v5.17.0; pyproject.toml currently pins transformers<=5.15.0, so on the pinned version the bridge only resolves by class name, the VLM wrapper cannot be instantiated, and the new unit tests are skipped. The pin bump should go in a separate build: PR per the contributing guide.
  • preprocess_packed_seqs moved without a compatibility re-export from its old Qwen3-VL module.
  • Follow-ups not in this PR: docs/models/glm/ page and README / docs model tables, recipes and examples, verification card, and a functional HF ↔ Megatron round-trip test.

@copy-pr-bot

copy-pr-bot Bot commented Sep 9, 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.

@kevalmorabia97

Copy link
Copy Markdown
Contributor

Bug: the MTP final_layernorm is imported from the main norm instead of the MTP layer's shared_head.norm

In glm53flash_bridge.py#L727-L730 the mapping is:

AutoMapping(
    f"{mtp_mg}.final_layernorm.weight",
    "model.language_model.norm.weight",
),

The GLM-5.3-Flash checkpoint has its own model.language_model.layers.45.shared_head.norm.weight, which this mapping never reads:

  • On import, the MTP head's norm is silently replaced with the decoder's final norm. That hurts MTP / speculative-decoding acceptance.
  • On export, the main norm is written back out, so the checkpoint doesn't round-trip.

The GLM-4.5 bridge maps the same tensor from shared_head.norm (glm45_bridge.py#L295-L297).

Suggested fix:

                     AutoMapping(
                         f"{mtp_mg}.final_layernorm.weight",
-                        "model.language_model.norm.weight",
+                        f"{mtp_hf}.shared_head.norm.weight",
                     ),

How we found it: we round-tripped a tiny GLM-5.3-Flash through this bridge. It had 4 HF layers built from the real config, with random weights and every norm randomized so no two were equal. We imported it, then exported it with ModelOpt's Megatron→HF exporter (NVIDIA/Model-Optimizer#2539).

  • With the current mapping: the exported layers.4.shared_head.norm.weight equalled the source model.language_model.norm.weight, not the source shared_head.norm. It was the only one of 197 unquantized tensors that didn't round-trip bit-exactly.
  • With the fix above: all 197 round-trip bit-exactly.

This was on the 26.10.rc2 container with Megatron-LM #7054 / #7521 / #7522 and transformers==5.17.0.

@HollowMan6

Copy link
Copy Markdown
Contributor Author

Thank you @kevalmorabia97, this is now fixed in e6f7cf2

erictang000 added a commit to erictang000/SkyRL that referenced this pull request Sep 28, 2026
…ash code

@HollowMan6 wrote NVIDIA/Megatron-LM#7054/#7521/#7522 (mcore_ext), NVIDIA-NeMo/Megatron-Bridge#6044
(the upstream counterpart of glm5_next) and vllm-project/vllm#56327; @hershg wrote
vllm-project/vllm#56718. Name Megatron-Bridge#6044 in the removal plan as the PR that retires
glm5_next.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
erictang000 added a commit to NovaSky-AI/SkyRL that referenced this pull request Sep 28, 2026
…MoE, full fine-tuning and LoRA sync to vLLM (merge_lora=false) (#2179)

## What

Adds GLM-5.3-Flash (HF `glm5_next`, `Glm5NextForConditionalGeneration`)
to the Megatron backend, for both full fine-tuning and LoRA training
with adapter-only weight sync to vLLM (`merge_lora=false`).

The architecture is new to both upstreams: 45 layers of 3:1 KDA linear
attention / NoPE-MLA DeepSeek sparse attention with a k-pool compressed
indexer, a 288-expert sigmoid MoE with one shared expert (dense MLP in
the first layer), clamped SwiGLU, and manifold-constrained
hyper-connections (mHC) on every block. It ships as a VL checkpoint, and
SkyRL trains the language model only (`language_model_only=True`).

No dependency pins change: this runs on `main`'s vLLM 0.30.0,
megatron-core and megatron-bridge. What the pinned upstreams don't have
yet is vendored under `patches/`.

**Credit:** most of the vendored code comes from @HollowMan6's open
upstream PRs: NVIDIA/Megatron-LM#7054, #7521 and #7522,
NVIDIA-NeMo/Megatron-Bridge#6044 (the upstream home for `glm5_next/`),
and vllm-project/vllm#56327. The MLA prefill LoRA fix is @hershg's
vllm-project/vllm#56718. Each vendored section credits them in its
module docstring.

## Components

### `patches/megatron/mcore_ext/`: megatron-core pieces (vendored from
open Megatron-LM PRs)

| module | upstream | what |
|---|---|---|
| `kda.py` | NVIDIA/Megatron-LM#7054 | `KimiDeltaAttention`: TE linears,
depthwise causal convs, fp32 `A_log`/`dt_bias`, fla `chunk_kda`, gated
output norm. TP over heads, packed THD via `cu_seqlens`. |
| `hyper_connection.py` | #7521 | megatron-core's
`HyperConnectionModule` with GLM's standard-RMSNorm input norm. |
| `mhc_transformer_layer.py` | #7521 | `HyperConnectionTransformerLayer`
with **MoE** sub-layers (upstream's layer rejects MoE). |
| `dsa_kpool.py` | #7522 | The k-pool DSA indexer kernels, copied
verbatim. |

### `patches/megatron/glm5_next/`: the Megatron-Bridge model

- `provider.py`: `Glm5NextModelProvider`, with the KDA, mHC and k-pool
fields.
- `layer_specs.py`: per-layer KDA-or-DSA × dense-or-MoE mHC specs.
- `dsa.py`: `Glm5NextDSAIndexer` (k-pool gate/ape and gate score) and
`Glm5NextDSAttention`.
- The pinned `DSAttention.forward` does its own token-level top-k and
never calls the indexer's `forward_with_scores`.
- So `Glm5NextDSAttention` swaps that step for `fused_qk_topk_kpool`,
and raises if the pooled selection doesn't run exactly once.
- `bridge.py`: `Glm5NextBridge` maps all 3050 language-model tensors
1:1, including the mHC scale mappings and the k-pool compression
parameters.

**`patches/megatron/README.md` is the removal plan.** For each vendored
module and runtime patch it gives:
- the upstream change that retires it;
- how to check whether a new pin already contains it;
- every touchpoint outside `patches/` that has to change with it;
- the tests to run.

It's linked from `AGENTS.md` for megatron-core / megatron-bridge bumps.

### `patches/vllm/patch_glm5next_lora_packing.py`: vLLM LoRA for
GLM-5.3-Flash

Mostly a backport of vllm-project/vllm#56327 and #56718, applied in
every vLLM worker before model init:
- the Glm5Next `packed_modules_mapping`;
- `replicated_shard_ids` honored for KDA's duplicated `f_a`/`g_a`
LoRA-B;
- a `.contiguous()` guard in `add_shrink`;
- the `kv_b_proj` adapter on MLA's absorbed decode path, which decode
would otherwise drop entirely;
- the `kv_b_proj` adapter on MLA's MHA prefill path and prefix-cached
context chunks (#56718).
- The attention impl keeps a pre-LoRA reference to `kv_b_proj`. So every
prompt longer than `reorder_batch_threshold` (128 tokens at TP4/TP8) was
prefilled without the adapter.
- The patch swaps in a LoRA-applying proxy for the duration of
`forward_mha`, rather than rewriting five method signatures.

### Other SkyRL changes

- `MegatronWorker.init_configs`: mHC models downgrade SkyRL's default
`recompute_granularity="full"` to selective. megatron-core rejects mHC
under full recompute, and its suggested `'mhc'` selective mode needs
recompute managers the vendored layer doesn't thread.
- `model_bridges.py` registers the bridge.
- `SKYRL_VLLM_START_PORT` makes the vLLM base port overridable, for
hosts where port 8000 is taken.
- `CUDA_HOME` is forwarded to Ray actors, since TileLang JIT picks its
toolkit from it.
- Non-JSON data-plane responses are logged at warning level with the
body.
- Tests:
- GLM-5.3-Flash rows in `gpu_ci/megatron/test_megatron_models.py`: the
4-layer slice, and the same slice past `index_topk`.
- `glm-5.3-flash-4layer_h100_tp2_ep4_adapter` in
`gpu_ci/megatron/test_megatron_lora_models.py`: #2275's zero → perturb →
publish LoRA roundtrip, with GLM's trainer and vLLM target modules.
  - A GLM LoRA case in full-trainer checkpointing.
- **Tests for `patches/` code now mirror its layout**, so they're found
and deleted with the patch:
- CPU:
`tests/backends/skyrl_train/patches/megatron/mcore_ext/test_dsa_kpool_math.py`.
Now marked `megatron`; before, it never ran in CI.
- GPU: `tests/backends/skyrl_train/gpu/gpu_ci/patches/megatron/`, with
`mcore_ext/test_dsa_kpool.py`, `mcore_ext/test_modules_vs_hf.py`
(KDA/mHC vs HF), `test_dsa_index_share_recompute.py` and
`test_shared_expert_lora_tp.py`.
- The FSDP GPU job ignores `gpu_ci/patches/megatron`, as it already does
`gpu_ci/megatron`.
- Examples: `examples/train/glm5_3_flash/`.
- `run_gsm8k_glm5p3_flash_lora_1node.sh`: a single-node GSM8K LoRA smoke
run.
- `run_dapo_glm5p3_flash_lora_sync_2node.sh`: a 2-node DAPO 2k/8k LoRA
recipe with `merge_lora=false`, and R3 behind `ENABLE_ROUTING_REPLAY`.
- `run_dapo_glm5p3_flash_fullft_sync_8node.sh`: the 8-node full
fine-tuning recipe validated below, with R3 on by default.
- Docs: `supported_models.mdx`, the Megatron backend agent doc, fixed
CPU test commands in `AGENTS.md` / `testing.md`, and the
`tests/.../patches/` convention in `testing.md`.

## Validation

Megatron vs vLLM logprob parity on the 4-layer slice
(`eatang/GLM-5.3-Flash-4layer`), TP2 EP4, threshold 0.1:

| row | Megatron vs vLLM | vLLM pre/post weight sync |
|---|---|---|
| `glm-5.3-flash-4layer_h100_tp2_ep4` | 0.065 | 0.254 |
| `..._kpool_beyond_topk` (sequences past `index_topk`=2048) | 0.053 |
0.348 |

The k-pool row measured 0.059 before the `DSAttention` top-k swap, when
the pooled indexer wasn't actually used in training.

LoRA roundtrip (`glm-5.3-flash-4layer_h100_tp2_ep4_adapter`,
`merge_lora=false`, LoRA-B std 0.008):

| phase | diff | check |
|---|---|---|
| zero adapter: vLLM vs Megatron | 0.068 | < 0.1 |
| perturbed trainer vs stale sampler | 0.155 | > 0.1 |
| synced adapter: vLLM vs Megatron | 0.062 | < 0.1 |

Before the #56718 backport, the synced-adapter diff grew with the
adapter (0.18 at std 0.01). That fits vLLM prefilling without the
`kv_b_proj` adapter.

Also passing:
- `test_modules_vs_hf.py`: mHC matches HF to 4e-9; KDA on packed
sequences matches the per-sequence HF reference.
- `test_dsa_kpool.py` and `test_dsa_kpool_math.py`.

Full model, DAPO 2k/8k recipe, 8×8 B200, TP4 / DP16. Held-out AIME-2024
`avg_score` (30 problems × 12 samples; step-0 evals across runs spread
about ±0.03):

| step | 0 | 5 | 10 | 15 | 20 | 25 | 30 | 35 |
|---|---|---|---|---|---|---|---|---|
| **full fine-tuning, R3** (EP32 × ETP2, LR 1e-6) | 0.072 | 0.300 |
0.494 | 0.572 | 0.617 | 0.672 | **0.711** | |
| LoRA `merge_lora=false`, R3 (EP16) | 0.083 | 0.183 | 0.511 | 0.622 |
0.639 | **0.656** | 0.478 | 0.261 |
| LoRA `merge_lora=false`, R3 off (EP16) | 0.106 | | | | | 0.483 | | |

- **Full fine-tuning:** every step syncs the full ~599 GiB of weights to
vLLM, in about 45 s.
- The rollout/train logprob gap stayed at 0.007-0.010 for all 30 steps
while every weight changed, so vLLM always served the trained weights.
- Held-out AIME was still rising at step 30. Entropy held at 0.18-0.25
and response length stayed steady, with none of the LoRA run's late
overfitting.
- Steps took 15-21 min. At EP32 × ETP1 the backward's DSA softmax
recompute ran out of memory at step 7; ETP2 halves the per-GPU expert
params and grads.

- **R3 off:** policy entropy collapsed to about 0.02 early on, and the
run learned slowly. The same thing happens on this cluster with the
earlier fork-based megatron-core / vLLM 0.28 stack, at both DP16 and
DP4.
- **R3 on:** the rollout/train logprob gap is about 4× smaller (0.009 vs
0.039 at step 1) and entropy stays up. Held-out AIME peaks at 0.656 at
step 25. For comparison, an earlier fork-based-stack run on B300 without
R3 reached 0.561 at step 25.
- Past step 25 training reward keeps rising, but held-out accuracy falls
and entropy climbs to about 0.77. That's overfitting.
- For longer runs, stop around step 25, or add entropy control / a lower
LR, and use `ckpt_interval=5`.
- This is why the DAPO recipe documents R3 as recommended.

## Known limitations

- The LoRA DAPO runs above predate the #56718 backport, so their
rollouts prefilled prompts over 128 tokens without the `kv_b_proj`
adapter. That may account for some of the LoRA run's late collapse. Full
fine-tuning is unaffected.
- `f_b_proj` / `g_b_proj` stay out of both LoRA target lists.
- KDA has no context parallelism and no inference cache.
- mHC requires PP=1 and doesn't support selective
`layernorm`/`mlp`/`mhc` recompute.
- Cross-layer DSA index sharing raises. The 4-layer slice and the full
checkpoint use only `"full"` indexers.
- `patches/megatron/patch_mla_thd_v_pad.py` isn't applied by anything.
This PR doesn't change that; the README flags it for a decision.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Eric Tang <erictang000@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kevalmorabia97

Copy link
Copy Markdown
Contributor

Two training-loop failures with GLM-5.3-Flash (e6f7cf28), found while testing QAD; both reproduce with plain distillation (no quantization)

Setup:

  • nvcr.io/nvidian/nemo:26.10.rc2 plus this PR at e6f7cf28, Megatron-LM #7054 / #7521 / #7522, and transformers==5.17.0.
  • A tiny random GLM-5.3-Flash built from the real config: 4 HF layers (3 KDA + 1 DSA), hidden 512, 8 routed experts, hc_mult=4, plus the MTP layer.
  • One GPU, TP=PP=EP=1, driven by the Megatron-Bridge distillation API (megatron.bridge.training.distill.distill) through ModelOpt's examples/megatron_bridge/distill.py.
  • Settings: --use_mock_data --seq_length 64 --mbs 1 --gbs 4 --train_iters 3, with --teacher_hf_path and --student_hf_path both pointing at the tiny model.

Iteration 1 trains fine. Loss is finite and grad norm 1.92. Then:

1. Saving a checkpoint with the distributed optimizer asserts

use_distributed_optimizer=True, ckpt_format="torch_dist", overlap_grad_reduce / overlap_param_gather. Fails at the first save:

megatron/core/optimizer/distrib_optimizer.py:1979, in sharded_param_state_dp_reshardable
    assert tensors[key].shape == (gbuf_local_end - gbuf_local_start,), (
AssertionError: (torch.Size([49152]), 0, 79298560)
  • The only 49,152-element parameters in the model are the mHC hyper_connection.mapping_proj.weight tensors (24 × 2048), in the decoder layers and in the MTP layer. The optimizer state for one of them is being matched against the whole 79,298,560-element grad-buffer range.
  • The provider keeps mHC in FP32, so this may be the mixed-dtype buffer handling around #7521.
  • dist_ckpt_optim_fully_reshardable=True fails at the same point: RuntimeError: The size of tensor a (79298560) must match the size of tensor b (49152).

2. With the non-distributed optimizer, iteration 2 asserts in the grad buffer

With use_distributed_optimizer=False (in both the DDP and optimizer configs), the checkpoint saves, but the next step fails:

megatron/core/distributed/param_and_grad_buffer.py:338, in reset   (from DistributedDataParallel.zero_grad_buffer)
    assert len(self.per_param_grad_ready_counts) == len(self.params)

That looks like some parameters never receive a gradient in iteration 1. I haven't isolated which ones. The DSA indexer, whose training goes through its own loss, is a candidate.

Both happen without any quantization. Running the same job with a plain BF16 student, no ModelOpt quantizers involved, gives the identical assertion (torch.Size([49152]), 0, 79298560) at the first save.

Workaround we used: one iteration, --no_async_save, and the non-distributed optimizer. That was enough to validate a train → save → export round-trip, but not a real multi-step training run.

@HollowMan6

Copy link
Copy Markdown
Contributor Author

@kevalmorabia97 For 1, you should be able to fix it by patching this dev commit to main NVIDIA/Megatron-LM@ceaa6c5, for 2, that's a good catch and now it should be fixed with NVIDIA/Megatron-LM@bfb1773

@HollowMan6
HollowMan6 force-pushed the glm53flash branch 2 times, most recently from e353ca3 to 2a13240 Compare October 5, 2026 19:12
@cuichenx cuichenx added the blocked Work cannot move forward until an external dependency is cleared label Oct 8, 2026
@HollowMan6
HollowMan6 marked this pull request as ready for review October 10, 2026 08:44
Copilot AI balanced review requested due to automatic review settings October 10, 2026 08:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

kevalmorabia97
kevalmorabia97 previously approved these changes Oct 26, 2026

@kevalmorabia97 kevalmorabia97 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM from the Model Optimizer side. Tested 22e09a8b with NVIDIA/Megatron-LM#7521 + NVIDIA/Megatron-LM#7054 in a NeMo 26.10 container on GB300: ModelOpt GPTQ PTQ, distillation with save + resume, and quantized HF export on a 4-layer GLM-5.3-Flash, plus full-size GLM-5.3-Flash QAD on 32 nodes (EP32 x CP4, packed SFT) with distributed-optimizer save + resume, matching the loss and grad norms of our earlier full PTQ + QAD + vLLM eval study on older heads of this stack.

GLM-5.3-Flash support in Model Optimizer (NVIDIA/Model-Optimizer#2539) is waiting on this PR, so we'd appreciate a review and merge once the Megatron-LM PRs land. Thanks @HollowMan6!

kevalmorabia97 added a commit to NVIDIA/Model-Optimizer that referenced this pull request Oct 28, 2026
…and QAD (#2915)

### What does this PR do?

[2/2], after #2917 (date-prefixed announcement filenames, merged).

Type of change: documentation

**Executive summary**

- **IQ2_S + NVFP4 matches FP8 with no training.** It uses IQ2_S on the
experts' gate and up projections and NVFP4 on their down projections,
3.21 bits per expert weight. The model is ~155 GB, against 328 GB for
FP8 (2.1x smaller), and its six-benchmark mean is within 0.1 points of
FP8.
- **IQ2_S is the smallest option.** At 2.56 bits per weight on all
expert weights, the model is ~130 GB (2.5x smaller), and its mean is 2.3
points below FP8. Most of the loss is runaway generation on long tasks.
- **QAD fixes long-context runaways.** 300 iterations on IQ2_S brought
AA-LCR from 70.0 to 74.0 (FP8 74.9) and cut answers hitting the token
budget from 6.9% to 0.6%. The mean gap shrank from 2.3 to 1.4 points.
- **We recommend the PTQ recipes.** QAD cost ~4,500 GPU-hours, didn't
restore knowledge recall, and IQ2_S + NVFP4 does better without
training. Both recipes still trail FP8 by ~2–3 points on AA-Omniscience.
Evaluations served BF16 fake-quant exports; IQ2_S can also be converted
to an INT8 weight-only checkpoint that vLLM serves at 86 instead of 148
GiB of weights per GPU (#2903).

Adds an announcement page (blog) for the GLM-5.3-Flash IQ2 study, with a
results figure in the Qwen3.6 announcement's style, a card on the
announcements index and a Latest News entry in the README. It presents
two PTQ recipes as alternatives:

| | FP8 checkpoint | IQ2_S | IQ2_S + NVFP4 |
|---|---|---|---|
| Routed experts | FP8 | IQ2_S | IQ2_S gate/up, NVFP4 down |
| Routed-expert bits per weight | 8 | 2.56 | 3.21 |
| Model size | 328 GB | ~130 GB | ~155 GB |
| Mean delta vs FP8, six benchmarks | – | −2.3 | −0.1 |

It also covers knowledge recall (HLE, AA-Omniscience), RULER-1M, QAD on
the IQ2_S model (300 iterations, −1.4, with worse recall), the cost, and
the GLM-specific reproduction steps.

**Blog, not a full tutorial.** QAD did not become the recipe: IQ2_S +
NVFP4 reaches FP8 with PTQ alone. So the page points to the
Qwen3.6-35B-A3B tutorial for the general flow and lists only GLM's
differences: the recipes, `--no_moe_grouped_gemm`, EP16 PTQ and
fake-quant export, long-reasoning calibration, eval settings, and the
QAD flags.

**Baseline:** every delta is against the FP8 release
(`zai-org/GLM-5.3-Flash`), not the BF16 checkpoint. A note near the top
says so.

**Sizes:** computed from each tensor's bits per weight using the FP8
checkpoint's safetensors headers, since evaluation used BF16 fake-quant
exports. The post also covers serving IQ2_S as INT8 weight-only
(`convert_ggml_to_int8.py`, #2903 / #2946): accuracy, weight memory and
vLLM throughput with the Humming kernels. IQ2_S + NVFP4 has no such path
yet.

**Dependencies:** none for merging this docs-only change. Reproducing
the experiments needed:
- ModelOpt: #2873 (merged; GGML PTQ, QAD and fake-quant export for large
MoE/VLM models) and #2539 (GLM-5.3-Flash support for Megatron-Bridge PTQ
and HF export, plus both IQ2_S recipes).
- Megatron-Bridge: NVIDIA-NeMo/Megatron-Bridge#6044 (GLM-5.3-Flash
bridge).
- Megatron-LM: #7521 (mHC precision controls) and #7522 (KPool DSA
indexer), both merged, and the open #7054 (KDA hybrid layers), plus two
training fixes for QAD: ceaa6c5d (#5835, distributed-optimizer save/load
for mixed fp32/bf16 param groups) and bfb17738 (#7522, freeze the DSA
indexer when `dsa_indexer_loss_coeff` is 0).
- transformers >= 5.16.1 (`glm5_next`), nemo:26.10 base image, vLLM >=
0.30 for evaluation.

The recipes stay in #2539: they use Megatron-Bridge module names, and
`examples/hf_ptq` would neither apply GPTQ to the fused HF routed
experts nor match the dense-MLP and NVFP4-down patterns, so they cannot
be used without the Megatron-Bridge support.

**Prefix-caching check (done):** our evals serve vLLM 0.31 with prefix
caching on, which on vLLM < 0.31 corrupts GLM's long-context cache
(vllm#57477). An A/B on FP8 AA-LCR (8 runs each) gives 74.88 ± 0.87 with
caching on and 76.12 ± 0.83 with it off: about one standard error apart,
so the 0.31 fix holds and the published numbers stand.

### Usage

N/A (documentation)

### Testing

- Parsed the new page with docutils: no errors or warnings.
- Did not run a full Sphinx build.

### Before your PR is "*Ready for review*"

- Is this change backward compatible?: N/A
- If you copied code from any other sources or added a new PIP
dependency, did you follow guidance in `CONTRIBUTING.md`: N/A
- Did you write any new necessary tests?: N/A
- Did you update
[Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?:
N/A
- Did you get Claude approval on this PR?: ❌

### Additional Information

Dated 2026-10-28, the publish date. The Reproducing section points at
Megatron-LM main (which now has #7521) with #7054 merged in, and at
Megatron-Bridge #6044; it will be updated once both merge.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@HollowMan6
HollowMan6 force-pushed the glm53flash branch 2 times, most recently from d778045 to 14efd6c Compare October 29, 2026 00:15
@kevalmorabia97

Copy link
Copy Markdown
Contributor

@cuichenx can we bump Megatron-LM submodule to include NVIDIA/Megatron-LM#7054 to unblock this PR?

@cuichenx

Copy link
Copy Markdown
Contributor

@cuichenx can we bump Megatron-LM submodule to include NVIDIA/Megatron-LM#7054 to unblock this PR?

yep, monitoring #6717

@HollowMan6

Copy link
Copy Markdown
Contributor Author

Rebased! Would you mind triggering the CI? Thanks!

cc: @kevalmorabia97 @cuichenx

@kevalmorabia97 kevalmorabia97 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the rebase! Two things before CI:

  1. The branch is based on 5bc5076, which predates #6717. The PR's 3rdparty/Megatron-LM is still a296523 (without NVIDIA/Megatron-LM#7054), so CI on the head commit won't have KDA. Could you rebase onto current main? It's conflict-free.
  2. tests/unit_tests/models/qwen_vl/modelling_qwen3_vl/test_utils.py:49 still imports preprocess_packed_seqs from modelling_qwen3_vl.utils, which this PR removes. The module then fails to import, including the new test_packed_sequence_alignment_and_gradients. Importing it from megatron.bridge.training.utils.packed_seq_utils fixes it. With that change, the glm5next, qwen3_vl, LoRA, deepseek, hybrid and packed-seq unit tests pass on main + this PR with Megatron-LM 5de526f2.

Code-wise the commits are unchanged from my last review; happy to approve once these are in.

Signed-off-by: Hollow Man <hollowman@opensuse.org>
Signed-off-by: Hollow Man <hollowman@opensuse.org>
Signed-off-by: Hollow Man <hollowman@opensuse.org>
Signed-off-by: Hollow Man <hollowman@opensuse.org>
Signed-off-by: Hollow Man <hollowman@opensuse.org>
Signed-off-by: Hollow Man <hollowman@opensuse.org>
Signed-off-by: Hollow Man <hollowman@opensuse.org>
Signed-off-by: Hollow Man <hollowman@opensuse.org>
Signed-off-by: Hollow Man <hollowman@opensuse.org>
tensor-parallel correctness fixes for GLM5.3-Flash LoRA,
both exercised by the DSA (absorbed MLA) attention path whose
`linear_kv_up_proj` (HF `kv_b_proj`) is a LoRA target.

split q/k/v in TP-rank order
  `_split_qkv_linear_out_weight` previously split the concatenated
  linear-out weight as a flat `[Q|K|V]` block, which is only correct at
  tp_size=1. Under TP the column-parallel weight is laid out per-TP-rank
  as `[rank0_Q|rank0_K|rank0_V | rank1_Q|rank1_K|rank1_V | ...]`, so the
  naive three-way split interleaved shards and produced wrong q/k/v
  projections at export time. Reshape to `[tp_size, per_rank_Q+K+V, ...]`
  and split the per-rank section so each of q/k/v gathers its shards
  across TP ranks in the correct order. Adds a parametrized unit test
  (tp_size in {1,2,4,8}) asserting the gathered output matches the
  reference exactly (rtol=0, atol=0).

use autograd-aware all_gather for the effective-weight path
  `LoRAMerge.merge` is invoked from the `LoRALinear.weight` property,
  which is read as a live effective weight by the absorbed MLA forward
  (e.g. `AbsorbedMLASelfAttention._get_kv_up_weights` feeds
  `q_absorbed = einsum(q, k_up_weight)`). The prior `dist.all_gather` is
  a c10d collective that is not differentiable, so gradients from the
  effective-weight path were dropped at the TP gather instead of being
  summed back to the sharded LoRA A/B on each rank. Switch to
  `torch.distributed.nn.functional.all_gather`, whose backward sums
  contributions across TP ranks, restoring the LoRA gradient for the
  effective-weight path. Behaviour is unchanged for the non-LoRA and
  non-TP cases.

extend test_model_bridge_lora.py with TP-order split coverage for
both the split path and the streaming export path.

Signed-off-by: Hollow Man <hollowman@opensuse.org>
The GLM-5.3-Flash change moved preprocess_packed_seqs out of
modelling_qwen3_vl.utils into training.utils.packed_seq_utils, but the
qwen3_vl utils test still imported it from the old module. The whole
test file then failed to import, so none of its tests ran, including
the new test_packed_sequence_alignment_and_gradients.

Signed-off-by: Hollow Man <hollowman@opensuse.org>
@HollowMan6

Copy link
Copy Markdown
Contributor Author

Thank you @kevalmorabia97! Sorry I forgot to update my local main branch, now it's rebased and the test case is fixed.

@kevalmorabia97 kevalmorabia97 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks! Both are in: the branch is on main with Megatron-LM 5de526f2, and the 10 earlier commits are unchanged. On e342d93, the glm5next, qwen3_vl, LoRA bridge, deepseek, hybrid, packed-seq and peft utils unit tests pass (618 passed). The only failures are Transformer Engine kernel errors from running on Ada GPUs locally. LGTM.

@kevalmorabia97

Copy link
Copy Markdown
Contributor

/ok to test e342d93

@github-actions

Copy link
Copy Markdown
Contributor

Automatic Claude reviews have been retired. To request a pull-request review, post a comment containing:

/review

Add model=claude to use a Claude reviewer (the default is model=codex). mode=light|strict selects the review depth; for example, /review model=claude mode=strict. Comment /review help for all options.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:model Model implementations and HF bridge logic blocked Work cannot move forward until an external dependency is cleared breaking-change Public behavior or API compatibility changes community-request feature New capabilities, enhancements, or enablement work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants