Skip to content

dist_ckpt: add --stream-ckpt-dequant to fix OOM on large FP8/MXFP8 loads with --fp8-param-gather - #4451

Merged
asolergi-nv merged 21 commits into
NVIDIA:mainfrom
asolergi-nv:fp8_gather_load
Jul 28, 2026
Merged

dist_ckpt: add --stream-ckpt-dequant to fix OOM on large FP8/MXFP8 loads with --fp8-param-gather#4451
asolergi-nv merged 21 commits into
NVIDIA:mainfrom
asolergi-nv:fp8_gather_load

Conversation

@asolergi-nv

@asolergi-nv asolergi-nv commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

What does this PR do ?

Problem

Loading a distributed checkpoint on a large model trained with --fp8-param-gather (or --fp4-param-gather) can OOM on GPU during the load itself — not during training. Root cause: force_all_tensors_to_non_fp8 in megatron/core/dist_checkpointing/serialization.py walks the entire sharded state dict before the checkpoint read starts and, for every QuantizedTensor destination (Float8 / MXFP8 / blockwise FP8 / NVFP4), allocates a fresh BF16 scratch tensor of the same shape. For an N-element quantized weight set that's a transient 2·N bytes of GPU memory sitting idle until the read finishes — and for large models, 2·N exceeds what's left on device.

A secondary OOM was surfaced by the first fix: _unwrap_pyt_sharded_tensor used ten[0] to strip prepended singleton axes, which on MXFP8Tensor falls through to QuantizedTensor.__torch_dispatch__ and dequantizes the entire tensor to BF16 just to select the leading index — re-creating the same OOM per shard after a successful load.

Solution

Move the dequantize from an upfront bulk pass into PyTorch DCP's per-tensor LoadPlanner hooks. MCoreLoadPlanner.resolve_tensor now detects QuantizedTensor destinations, allocates a single BF16 scratch per tensor, returns it as the load destination, and commit_tensor copy-quantizes it back into the original quantized tensor. Only one scratch is live at any moment — peak transient GPU overhead drops from 2·sum(numel) to ~2·max(numel). Delayed-scaling amax_history pollution is prevented via a per-tensor snapshot/restore around the quantize-on-copy.

The _unwrap_pyt_sharded_tensor OOM is fixed by using view() (which all TE quantized tensor classes implement natively without dequantizing) instead of ten[0]/squeeze.

Default behaviour is unchanged. The streaming path is gated behind a new opt-in flag --stream-ckpt-dequant (default False); without it, the load path is byte-identical to today. A warning is emitted when --fp8-param-gatheror --fp4-param-gather is used without --stream-ckpt-dequant and a checkpoint load is configured, pointing users at the escape hatch.

Tests

New file tests/unit_tests/dist_checkpointing/test_stream_ckpt_dequant.py:

  • test_fp8_save_load_content_equivalence — FP8 round-trip with streaming on and off; asserts loaded values are bit-equivalent within FP8 quantization tolerance.
  • test_fp8_amax_history_not_polluted — seeds a sentinel into the delayed-scaling quantizer's amax, runs a streaming load, asserts the amax is exactly restored (no amax_history pollution).
  • test_mxfp8_save_load_content_equivalence — MXFP8 round-trip both paths; incidentally exercises the _unwrap_pyt_sharded_tensor view fix (without it this test OOMs on ten[0] dequantize fallback).
  • test_plain_tensor_untouched_by_streaming_path — plain BF16 tensors must round-trip losslessly under both paths; guards against the streaming branch breaking non-quantized loads.
  • test_default_is_off — asserts the new stream_ckpt_dequant default is False on both TorchDistLoadShardedStrategy and MCoreLoadPlanner, preserving legacy behaviour.
  • test_planner_state_cleanup_after_load — asserts the planner's per-tensor tracking dict is empty after a streaming load completes, confirming scratch tensors are GC-eligible and no state leaks.
  • test_streaming_flag_forwards_through_fpsl_wrapper — asserts FullyParallelLoadStrategyWrapper.stream_ckpt_dequant forwards correctly from the wrapped base strategy.
  • test_nvfp4_save_load_content_equivalence — NVFP4 round-trip both paths; skipped on non-Blackwell hardware. Validates that the streaming path works for FP4 destinations via QuantizedTensor dispatch.

Issue tracking

For PRs from open-source community contributors:

  • New features: a linked issue is required. Please open a feature request and reference it here before submitting the PR.
  • Small updates (bug fixes, minor improvements): a linked issue is recommended and will accelerate the PR review process.

Linked issue:

Contribution process

Pre-checks

  • I have added relevant unit tests
  • I have added relevant functional tests
  • I have added proper typing to my code Typing guidelines
  • I have added relevant documentation
  • I have run the autoformatter.sh on my PR

Code review

Feel free to message or comment the @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"

  1. When your PR is ready, click Ready for Review.
  2. An oncall reviewer is auto-assigned and expert reviewers are notified based on your changes.
    • Some PRs may jump straight to step 2. This is determined by .github/CODEOWNERS.

⚠️ Only mark as ready once merge-conflicts are resolved and the CI is passing.
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, the Final Review label 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 Approved label is applied automatically.

Merge

Any member of mcore-engineers will be able to merge your PR.

For MRs into `dev` branch The proposed review process for `dev` branch is under active discussion.

MRs are mergable after one approval by either eharper@nvidia.com or zijiey@nvidia.com.

@copy-pr-bot

copy-pr-bot Bot commented Apr 23, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@asolergi-nv
asolergi-nv marked this pull request as ready for review April 23, 2026 17:43
@asolergi-nv
asolergi-nv requested review from a team as code owners April 23, 2026 17:43
@svcnvidia-nemo-ci svcnvidia-nemo-ci added this to the Core 0.16 milestone Apr 23, 2026
@svcnvidia-nemo-ci
svcnvidia-nemo-ci requested a review from a team April 23, 2026 17:44
@asolergi-nv

Copy link
Copy Markdown
Contributor Author

/ok to test add6edc

@asolergi-nv

Copy link
Copy Markdown
Contributor Author

/ok to test 088b812

@asolergi-nv

Copy link
Copy Markdown
Contributor Author

/claude review

Comment on lines +364 to +365
for _ in range(mcore_sh_ten.prepend_axis_num):
assert ten.size(0) == 1

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.

Bug: since ten is no longer mutated inside the loop, ten.size(0) is checked repeatedly instead of validating each prepended axis. If prepend_axis_num > 1 and a non-leading prepended axis isn't actually 1, the assertion silently passes and view produces a wrong shape.

The original code worked because ten = ten[0] peeled off axis 0 each iteration, so the next ten.size(0) checked the next axis. With the view-based approach the loop variable needs to advance the axis index:

Suggested change
for _ in range(mcore_sh_ten.prepend_axis_num):
assert ten.size(0) == 1
for i in range(mcore_sh_ten.prepend_axis_num):
assert ten.size(i) == 1

@asolergi-nv

Copy link
Copy Markdown
Contributor Author

/ok to test a13ba55

@santhnm2

santhnm2 commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

@asolergi-nv High-level question: why do we want to gate this feature behind the --stream-ckpt-dequant flag? Can we not always enable it? Or at least default it to True?

@sidsingh-nvidia

Copy link
Copy Markdown
Contributor

This has been validated in megatron-inference by @santhnm2 . We are able to run ultra on 4 GB200s in 8-bit precision.

@copy-pr-bot

copy-pr-bot Bot commented Apr 29, 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.

@asolergi-nv

Copy link
Copy Markdown
Contributor Author

/ok to test 1964c9b

Resolve conflict in megatron/training/arguments.py: keep the branch's
tensorwise stream_ckpt_dequant safety check and drop the FlashInfer
inference-backend import validation, which main removed in NVIDIA#5791
("Prevent FlashInfer sampling from running with CUDA graphs"). The
FlashInfer argument itself is unchanged; only the relocated runtime
validation block is dropped.

Signed-off-by: Antoni-Joan Solergibert <asolergibert@nvidia.com>
@asolergi-nv
asolergi-nv enabled auto-merge July 27, 2026 11:16
@asolergi-nv

Copy link
Copy Markdown
Contributor Author

/ok to test 89e4b96

@svcnvidia-nemo-ci

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started!

You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/30273509109

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 27, 2026
@asolergi-nv
asolergi-nv added this pull request to the merge queue Jul 27, 2026
@svcnvidia-nemo-ci

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started!

You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/30286225199

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 27, 2026
@svcnvidia-nemo-ci

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started!

You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/30295594142

@svcnvidia-nemo-ci

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started!

You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/30299352591

@svcnvidia-nemo-ci

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started!

You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/30306309809

@svcnvidia-nemo-ci

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started!

You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/30311011972

@svcnvidia-nemo-ci

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started!

You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/30335393420

@svcnvidia-nemo-ci

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started!

You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/30337947510

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

Labels

Approved All necessary approvals have been made complexity: medium Run functional tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants