[Main] Numerical fix for moe single grouped weight with fp8 fp4 primary weight and grad norm spikes - #5487
Conversation
|
This PR has been automatically converted to draft because all PRs must start as drafts. When you are ready for review, click Ready for Review to begin the review process. This will:
See the contribution guide for more details. |
20f6379 to
9df8f4e
Compare
|
/ok to test 9df8f4e |
9df8f4e to
7973f73
Compare
| buffer_type=BufferType.PARAM, | ||
| ) | ||
| modify_underlying_storage(param, new_param_data) | ||
| if not is_grouped_tensor(param): |
There was a problem hiding this comment.
This is a total mess...why are there so many if conditions that all seem to be doing similar things?
There was a problem hiding this comment.
explained in the updated description section
| # For MXFP8 param: | ||
| # we only need to map bf16 weights (layernorm, embedding, etc) to the buffer. | ||
| if not self.ddp_config.reuse_grad_buf_for_mxfp8_param_ag or not is_mxfp8tensor(param): | ||
| # This branch remaps the parameter storage into persistent DDP param_data buffer. |
There was a problem hiding this comment.
I dislike how much low-precision logic is seeping into megatron/core/distributed/param_and_grad_buffer.py
| elif is_float8tensor(param): | ||
| # NVFP4 packs two FP4 values per byte, so param_data uses | ||
| # packed-byte offsets instead of logical element offsets. | ||
| new_param_data = self._get( |
There was a problem hiding this comment.
What's the difference between this block and the block starting at line 1263?
There was a problem hiding this comment.
is_float8tensor checks for non-grouped parameters of moe, while the other section is for grouped tensor, more details updated in the description section
|
Can you please summarize what the fixes are in the description for this PR? |
| # In TE2, is_float8tensor() checks QuantizedTensor, including MXFP8. | ||
| # NVFP4 is handled by the branch above. | ||
| elif is_float8tensor(param): | ||
| # NVFP4 packs two FP4 values per byte, so param_data uses |
There was a problem hiding this comment.
Why are you talking about NVFP4 in the FP8 branch?
There was a problem hiding this comment.
Because every code block has this following selector, which was already there before I made this PR. It's because FP4 now packs two elements in one byte, so we need some special handling. I didn't change this code block, just adding more comments about it.
(
nvfp4_packed_param_start_index
if self.has_nvfp4_params
else param_start_index
),
| modify_underlying_storage(param, new_param_data) | ||
| # Plain torch param: replace param.data with DDP buffer view. | ||
| else: | ||
| # NVFP4 packs two FP4 values per byte, so param_data uses |
There was a problem hiding this comment.
When do we get into this else branch?
There was a problem hiding this comment.
It's part of the non-grouepd tensor path: so it's discrete weight for moe.
The else here means that each weight is still a high precision buffer in bf16, so it means that we are not quantizing in the optimizer, but we only quantize the weight in forward pass.
|
🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/29852112748 |
|
🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/29875881010 |
|
🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/29890584304 |
|
🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/29909530628 |
|
🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/29958286941 |
|
🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/29967254482 |
|
🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/29973194772 |
…uffer distopt (cherry picked from commit 0689947, PR NVIDIA#5470 on dev; adapted for main) - Compact decoupled LayerWise layout becomes the DEFAULT: use_layer_wise_param_layout flips to False on both DistributedDataParallelConfig and OptimizerConfig, and the CLI flag becomes the opt-IN --use-layer-wise-param-layout (was --no-...). Matches what merged on dev, so the two branches agree on user-visible behaviour. - Per-buffer DistOpt gets a single source of truth: instead of a separate _ParamAndGradBuffer.use_distributed_optimizer attribute threaded into _ParamAndGradBucketGroup, bake the decision into the buffer's own ddp_config via dataclasses.replace(). Bucket groups inherit it through buffer.ddp_config, so the use_distributed_optimizer= constructor parameter and the _param_to_buffer_distopt map in partition_buckets both go away. Main-only adaptations vs the dev commit: - kept main's is_grouped_mxfp8tensor() widening in the shared-buffer guard (NVIDIA#5487), - dropped this branch's extra 'route differing non-fp8 buckets into their own groups' tail in partition_buckets, which dev replaced with the merged-consistency assert, - refreshed the two remaining --no-use-layer-wise-param-layout mentions in validate_args and training.py to the new opt-in spelling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Pingtian Li <pingtianl@nvidia.com>
…ry weight and grad norm spikes (NVIDIA#5487) Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com> Signed-off-by: zhongboz <zhongboz@nvidia.com>
…ry weight and grad norm spikes (NVIDIA#5487) Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com> Signed-off-by: zhongboz <zhongboz@nvidia.com>
…ry weight and grad norm spikes (NVIDIA#5487) Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com> Signed-off-by: zhongboz <zhongboz@nvidia.com> Signed-off-by: Dmytro Pykhtar <dpykhtar@nvidia.com>
What does this PR do ?
Mirrors: #5464
TODOs:
Unit tests with numerical checks passed, pending E2E validation.
test_single_grouped_mxfp8_train_eval_train_matches_train_onlyis a newly introduced test targeting to test thereuse_grad_buff_for_mxfp8_param_agrigorously, like adding checks fortrain-eval-trainswitches.Unit test coverage matrix:
bf16=Truefp8=Nonefp4=Nonegradient_accumulation_fusion=Falsebf16=Truefp8=Nonefp4=Nonegradient_accumulation_fusion=Truebf16=Truefp8="e4m3"fp8_recipe="mxfp8"fp8_param_gather=Falsereuse_grad_buf_for_mxfp8_param_ag=Falsegradient_accumulation_fusion=Falsebf16=Truefp8="e4m3"fp8_recipe="mxfp8"fp8_param_gather=Falsereuse_grad_buf_for_mxfp8_param_ag=Falsegradient_accumulation_fusion=Truebf16=Truefp8="e4m3"fp8_recipe="mxfp8"fp8_param_gather=Truereuse_grad_buf_for_mxfp8_param_ag=Truegradient_accumulation_fusion=Falsebf16=Truefp8="e4m3"fp8_recipe="mxfp8"fp8_param_gather=Truereuse_grad_buf_for_mxfp8_param_ag=Truegradient_accumulation_fusion=Truebf16=Truefp4="e2m1"fp4_recipe="nvfp4"fp4_param_gather=Falsegradient_accumulation_fusion=Falsebf16=Truefp4="e2m1"fp4_recipe="nvfp4"fp4_param_gather=Falsegradient_accumulation_fusion=Truebf16=Truefp4="e2m1"fp4_recipe="nvfp4"fp4_param_gather=Truegradient_accumulation_fusion=Falsebf16=Truefp4="e2m1"fp4_recipe="nvfp4"fp4_param_gather=Truegradient_accumulation_fusion=TrueEnv: 1 x gb200 node, 4 GPUs, the unit test only uses 2 parallel ranks.
Command:
What does this PR fix?
This PR fixes two correctness issues exposed by combining TE single grouped MoE weights with low-precision primary parameters and Megatron’s distributed optimizer.
1. Incomplete DDP Parameter Remapping
Current
mainprimarily handles the traditional MoE layout where every expert weight is an independent tensor. Withmoe_single_grouped_weight, all local expert weights are represented by one contiguous TEGroupedTensor:This layout is more efficient for grouped GEMM, but it cannot be handled as a regular
torch.TensororList[QuantizedTensor]. The DDP remapping logic must consider both the grouped layout and the primary-weight recipe:The complete remapping rules are:
Without this handling, DDP could update
param_datawhile TE continued reading stale grouped storage, causing silent loss-curve divergence.2. MXFP8 Reused-Buffer Gradient Pollution
With
--reuse-grad-buf-for-mxfp8-param-ag, parameter AG and gradient accumulation share storage:After eval, checkpoint preparation, or another forced parameter sync,
param_gather_dispatchedcould remainTrue. The next training step staged BF16 master weights into the shared buffer, but the forward pre-hook incorrectly treated parameter AG as already completed.That skipped
_post_param_sync(), including the required cleanup:Backward then accumulated gradients into a buffer still containing parameter values, producing grad-norm spikes.
The PR resets parameter-sync dispatch state whenever master weights are staged. This forces the next forward to complete AG, update TE’s MXFP8 storage, and zero the shared buffer before backward.
Together, these changes make single grouped MoE weights work correctly across BF16, MXFP8, and NVFP4 primary-weight configurations while preserving the existing discrete-expert behavior.
Issue tracking
For PRs from open-source community contributors:
Linked issue:
Contribution process
Pre-checks
Code review
Feel free to message or comment @NVIDIA/mcore-oncall to help accelerate your merge into main. The less complex your PR is, the faster it will be approved and merged!
All PRs start as draft. If you open a non-draft PR, it will be automatically converted to draft.
Step 1: Mark PR as "Ready for Review"
.github/CODEOWNERS.Final Review might get declined if these requirements are not fulfilled.
Step 2: Final Review
For PRs that change
megatron/core, once all expert reviewers have approved, theFinal Reviewlabel is applied automatically and final reviewers are assigned.For PRs outside
megatron/core, this step is skipped.Step 3: Approved
Once all required reviewers have approved, the
Approvedlabel is applied automatically.Merge
Any member of mcore-engineers will be able to merge your PR.