[qat] feat: MXFP4 weight-only QAT for MLite MoE models - #127
Merged
Conversation
Extend weight-only QAT to 3D GroupedLinear expert weights, add ImplConfig.qat + apply_qat_to_chunks in qwen3_moe protocol, and map HF loads onto parametrized masters via _canonical_state_key. CPU tests cover expert STE, skip lists, and DAPO three-arm impl_cfg resolution.
…m-weight train) apply_qat_to_chunks parametrizes linear weights in build_model, renaming mod.weight -> mod.parametrizations.weight.original, which runs before the runtime's load_hf_weights. _copy_loaded_state matched loaded '..weight' keys against state_dict via substring, which no longer hits the parametrized name, so quantized-layer masters were silently never loaded (random-weight training). Fix: canonicalize parametrized state keys (strip .parametrizations.<attr>.original back to .<attr>) when resolving loaded tensors, so '..linear.weight' lands on '..linear.parametrizations.weight.original'. Quantizer buffers (.amax) untouched; amax is recomputed from the real weight on first forward, and dist_opt resyncs its fp32 master post-load via reload_model_params, so apply-before-load is safe. CPU regression: 7 new tests (all four formats int8/int4/fp8_e4m3/mxfp4) prove the master receives real weights, plus a guard that the naive substring path misses the parametrized key. Full qat suite 46 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend PR#127 with QuantizerB prepare/export_hf_checkpoint for INT4_W4A16_G128 and NVFP4_W4A16 (ModelOpt 0.43 preset), qwen3_moe protocol/checkpoint QAT (MoE 3D expert fake-quant), and HF master-weight load canonicalization. CPU: 55 unit tests (qat_unit + qat_recipe_unit + qat_qwen3_moe_unit).
Co-authored-by: OpenAI Codex <codex@openai.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
Training-time MXFP4 fake-quant, the checkpoint serializer and the quantizer the rollout actually runs (ModelOpt MXFP4QTensor.quantize, reached from verl utils/modelopt/qat_weight_exporter.py) were three different encodings: implementation E8M0 scale exponent rule E2M1 ties training fake-quant floor(log2(amax)) - 2 (OCP Alg. 1) to-even ckpt serializer ceil(log2(amax / 6)) to-even rollout (ModelOpt) ceil(max(log2(amax / 6), -127)) down The OCP Alg. 1 form picks a scale 2x too small whenever the block amax mantissa exceeds 1.5 -- 29% of blocks of a standard gaussian tensor, 42% at weight-like scales -- and then saturates the block maximum to 6.0, clipping ~2.3% of all elements. ModelOpt never saturates. On randn[512, 4096] that was 8.07% of elements different, sum|d|/sum|w| = 2.19%. QAT was therefore learning to compensate a distortion deployment does not produce. Consolidate the two rules into primitive/quantization/mxfp4.py as the single source of truth (mx_shared_scale_exponent + e2m1_round_index) and have qat.py import them instead of keeping a second "equivalent" copy. Both are now bit-identical to ModelOpt on gaussian / weight-scale / heavy-tail / extreme (1e4, 1e-4, all-zero, bf16 max, exact E2M1 midpoints) tensors. Add test_mxfp4_modelopt_parity_unit.py, which locks both paths against a verbatim transcription of the ModelOpt kernel (vendored so the test runs without nvidia-modelopt, cross-checked against the real package when it is importable). Two tests in test_qat_unit.py asserted the old rules and were what let the mismatch ship; they now assert the ModelOpt contract. In particular the block maximum is no longer clipped, so its gradient is no longer zeroed by the STE.
ISEEKYAN
force-pushed
the
qat-clean-review
branch
from
July 27, 2026 16:32
b80c467 to
e3264ed
Compare
…ted routing Receiving routed_experts proves that rollout routes arrived, but it does not prove that replay overrode a routing decision. A silently inactive replay hook could otherwise emit the same configuration, metric keys, and shapes. Count the behavior directly. RouterReplay accumulates replayed forward calls, routing rows, and changed rows; RouterReplayDriver emits one R3_REPLAY_EVIDENCE line with calls/rows/changed/changed_frac. The counters distinguish replay not running, replay running without changing a route while actor and rollout still agree, and replay substituting routing. The first is a loud R3_REPLAY_VOID error, the second emits R3_REPLAY_WARN, and the third emits direct evidence. Tests cover the counters, mask-limited partial replay, record mode, and both void paths.
ISEEKYAN
force-pushed
the
qat-clean-review
branch
from
July 27, 2026 16:39
e3264ed to
0b3b398
Compare
flameagainst
pushed a commit
to verl-project/Megatron-LM
that referenced
this pull request
Jul 28, 2026
Mirror of ISEEKYAN#127 at d60ec48, rebased onto verl-project lite. Content is byte-identical to that PR: 41 files, +4502/-384.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MXFP4 weight-only QAT for Megatron Lite MoE models
Scope
This PR aligns MXFP4 weight-only QAT in Megatron Lite with the quantization
contract used by rollout and composes the path into Qwen3 MoE, Qwen3.5,
DeepSeek V4, GLM-5, and Kimi K2.
differentiable MXFP4 fake quantization.
...weightnames onto the surviving...parametrizations.weight.originalmaster instead of silently droppingtensors.
rollout.
shared checkpoint-key canonicalization contract.
excluded, and GLM-5 route and replay-mask packing share its contiguous CP layout.
routing-map load statistics do not describe replay dispatch.
The quantization primitive remains model-agnostic and contains no model-name
allowlist. The verified delivery surface described here is MXFP4.
MXFP4 numerical alignment
Before this change, training fake quantization, checkpoint serialization, and
rollout used different scale and tie-rounding rules. The old training rule
selected a scale 2x too small whenever a block maximum's mantissa exceeded
1.5, clipping the largest value in each affected block.
Measured over 99,090,432 real Qwen3-30B-A3B weight elements
(3,096,576 blocks), the old rule produced a different scale for 43.07% of
blocks and a different decoded value for 12.38% of elements.
The implementation now shares one MXFP4 scale-selection and E2M1-rounding
contract between training fake quantization and serialization, with an
independent ModelOpt transcription as the reference. On the same real-weight
sample:
This is weight-level parity. Training and rollout still use different GEMM
implementations, so it is not an end-to-end equality claim.
Five-model QAT and R3 wiring
The R3 correction prevents training-only MTP routers from consuming rollout
route entries. With MTP disabled, replay still attaches to every decoder layer;
with MTP enabled, the attachment count remains the decoder-layer count rather
than decoder plus MTP layers.
Four-arm DAPO experiment
All arms were planned for 30 steps but were truncated by a four-hour scheduler
limit: BF16 ended at step 21, QAT-off at step 20, QAT-on at step 22, and R3 at
step 24. These are early-window results.
QAT-off and QAT-on used byte-identical rollout settings; their only QAT
difference was training-side enablement. The rollout path was vLLM
compressed-tensors plus
verl.utils.qat.vllm_patch.Curves
The images live on a non-merge asset branch; this PR contains no PNG files.
Measured results
probs_diffmeanQAT-on reduced the last-step train/rollout probability discrepancy by about
38% versus QAT-off (0.0166 versus 0.0267), but remained about 2.8x
BF16 (0.00598). The result is therefore that MXFP4 QAT reduces the discrepancy,
not that it eliminates it.
MXFP4 rollout cost about 30% less per generated token than BF16 in this
window. QAT-off and QAT-on differed by only 0.4% after token normalization
(0.04072 versus 0.04090 seconds per token).
Validation was measured only at global steps 4, 9, 14, and 19. The observed
within-run noise floor was about ±0.1, so these points do not support ranking
the arms by validation score.
Two observations remain unexplained and no mechanism is claimed:
validation series trended downward.
probs_diffgap growing from about 11% to 44% over steps 13-22.Known limitations and follow-ups
routing; replaying MTP or speculative routes has not been validated.
Correctness relies on the corresponding
r3_replay_maskrows being false;zero is not a sentinel expert ID.
here, not an end-to-end training/rollout parity test or a committed full-model
test artifact.
changed=0emits a warning rather than failing, and replay counters are notexported as training metrics.
MLite skill registry is still missing.
Usage
experimental/lite/examples/verl/QAT.mddocuments the MXFP4 training contract, apply-before-optimizer ordering,
checkpoint mapping, safe exclusions, packed snapshots, and the exporter
boundary.
experimental/lite/examples/verl/scripts/run_qwen3moe_mxfp4_qat.shprovides the four experiment modes and keeps the QAT delta separate from
ordinary training parameters.
Author: Yan Bai
Coverage matrix
MXFP4 export now covers all five models. For Qwen3.5, GLM-5, and Kimi K2, the exporter validates the resync target, preserves embeddings, lm_head, and router gates, and emits primitive-identical packed weights plus UE8M0 scales.