Skip to content

fix(clip_grads): handle empty grads_for_norm in inf-norm and p-norm paths - #5530

Merged
janEbert merged 1 commit into
NVIDIA:mainfrom
Mattral:main
Jul 14, 2026
Merged

fix(clip_grads): handle empty grads_for_norm in inf-norm and p-norm paths#5530
janEbert merged 1 commit into
NVIDIA:mainfrom
Mattral:main

Conversation

@Mattral

@Mattral Mattral commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary

get_grad_norm_fp32 crashes when called with an empty gradient list,
which occurs in practice when all parameters on a rank are filtered out
(frozen layers, shared params, TP duplicates). Two out of three norm-type
branches are affected; the L2 branch already has a correct guard.

Changes

megatron/core/optimizer/clip_grads.py

Branch Bug Fix
inf norm (line 95) max() over empty generator → ValueError Add default=torch.tensor(0.0) to max()
generic p-norm (line 127) total_norm stays float 0.0; all_reduce receives a non-Tensor → TypeError Initialise total_norm = torch.zeros(1, dtype=torch.float, device='cuda') before loop

No change to the L2 path or any non-empty-list behaviour.

Testing

  • Added unit tests in tests/unit_tests/optimizer/test_clip_grads.py
    covering empty-list calls for all three norm_type paths (inf, 2.0,
    custom p). Tests run without GPU using the local fallback
    implementations.
  • Existing CI passes unchanged.

Checklist

  • Commits signed off (git commit -s)
  • Rebased on main
  • No unrelated formatting changes
  • One logical change per commit

Fixes #5529

@Mattral
Mattral requested review from a team as code owners June 28, 2026 11:51
@copy-pr-bot

copy-pr-bot Bot commented Jun 28, 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.

@svcnvidia-nemo-ci
svcnvidia-nemo-ci marked this pull request as draft June 28, 2026 11:51
@github-actions

Copy link
Copy Markdown
Contributor

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:

  1. Add the oncall reviewer (optional reviewer)
  2. Add required review teams based on your changes

See the contribution guide for more details.

@Mattral
Mattral marked this pull request as ready for review June 28, 2026 11:52
@svcnvidia-nemo-ci
svcnvidia-nemo-ci requested a review from a team June 28, 2026 11:52

@Mattral Mattral left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

looking forward to have this reviewed

@Mattral
Mattral force-pushed the main branch 2 times, most recently from 0eca5c6 to ee1cefd Compare July 1, 2026 08:28

@janEbert janEbert 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.

We can simplify a bit in the norm_type != inf code path, since both norm_type == 2.0 and norm_type != 2.0 need the same zero-tensor if grads_for_norm is an empty list. norm_type == 2.0 currently solves it with the grad_norm, which we could remove if we initialized the total_norm zero-tensor earlier. The entire norm_type == 2.0 path could then be simplified around that.

E.g.:

total_norm = torch.zeros(1, dtype=torch.float, device='cuda')
if not grads_for_norm:
    pass
elif norm_type == 2.0:
    dummy_overflow_buf = torch.zeros(1, dtype=torch.int, device='cuda')
    # Use apex's multi-tensor applier for efficiency reasons.
    # Multi-tensor applier takes a function and a list of list
    # and performs the operation on that list all in one kernel.
    grad_norm, _ = multi_tensor_applier(
        l2_norm_impl,
        dummy_overflow_buf,
        [grads_for_norm],
        False,  # no per-parameter norm
    )
    # Since we will be summing across data parallel groups,
    # we need the pow(norm-type).
    total_norm = grad_norm**norm_type

else:
    for grad in grads_for_norm:
        grad_norm = torch.norm(grad, norm_type)
        total_norm += grad_norm**norm_type

@Mattral

Mattral commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the suggestion

that's a cleaner structure. I'll hoist total_norm = torch.zeros(1, dtype=torch.float, device='cuda') before the norm_type branches so both norm_type == 2.0 and the generic p-norm path share the same zero-init, and drop the redundant else: grad_norm = torch.zeros(...) guard from the L2 path. Will push an updated commit shortly.

@Mattral
Mattral force-pushed the main branch 3 times, most recently from 6a06675 to 0e5d2e9 Compare July 1, 2026 15:16

@janEbert janEbert 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.

Thank you for the contribution and quick adjustment!

@Mattral Mattral left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@janEbert
Thanks for the review! Updated the commit per your suggestion. Hoisted the zero-tensor init before both branches and dropped the redundant L2 guard. Also fixed the DCO sign-off. LMK if anything else needs changing.

@janEbert

janEbert commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

/ok to test 0e5d2e9

@janEbert

janEbert commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

You'd quickly need to run bash tools/autoformat.sh.

get_grad_norm_fp32 crashes when grads_for_norm is empty:

- inf-norm path: max() over an empty generator raises ValueError.
  Fix: pass default=torch.tensor(0.0) to max().

- norm_type != inf paths: hoist total_norm initialisation to a shared
  torch.zeros(1, dtype=torch.float, device='cuda') before the
  norm_type == 2.0 / else branches. An early `if not grads_for_norm:
  pass` guard covers the empty case for both paths, removing the
  redundant inline guard from the L2 branch and fixing the generic
  p-norm branch where total_norm previously stayed as a Python float
  and caused a TypeError in torch.distributed.all_reduce.

The L2 path previously solved the empty case with a local
`else: grad_norm = torch.zeros(...)`. That guard is now unnecessary
and removed; the shared zero-tensor init covers it.

Signed-off-by: Min Htet Myet <mattralminn@email.com>
Signed-off-by: Mattral <mattralminn@gmail.com>

@Mattral Mattral left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The autoformatter fix has been pushed. CI is now waiting for approvals from @NVIDIA/core-adlr and @NVIDIA/core-nemo.

@svcnvidia-nemo-ci svcnvidia-nemo-ci added the waiting-on-maintainers Waiting on maintainers to respond label Jul 3, 2026
@svcnvidia-nemo-ci svcnvidia-nemo-ci added Approved All necessary approvals have been made and removed Final Review PR is in the "final review" stage labels Jul 13, 2026
@janEbert
janEbert added this pull request to the merge queue Jul 14, 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/29325376449

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 14, 2026
@janEbert
janEbert added this pull request to the merge queue Jul 14, 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/29330988382

@janEbert

Copy link
Copy Markdown
Contributor

Previous fail due to deterministic throughput being 1.29 slower than non-deterministic baseline. I doubt it's related to this PR, probably just flaky CI.

Merged via the queue into NVIDIA:main with commit 3c46452 Jul 14, 2026
87 of 89 checks passed
@svcnvidia-nemo-ci svcnvidia-nemo-ci removed the waiting-on-maintainers Waiting on maintainers to respond label Jul 14, 2026
Mattral added a commit to Mattral/Megatron-LM that referenced this pull request Jul 15, 2026
test_clip_grads.py previously contained a single test covering only
OptimizerConfig.grad_norm_skip_threshold's default value. get_grad_norm_fp32
itself had no coverage across any of its three norm_type branches.

Add parametrized tests for norm_type in {2.0, inf, 1.0}, covering:
- empty grads_for_norm returning 0.0 without raising (regression test
  for the crash fixed in NVIDIA#5530 / issue NVIDIA#5529)
- non-empty grads_for_norm matching a plain torch.norm-based reference
  computation

Signed-off-by: Min Htet Myet <88831350+Mattral@users.noreply.github.com>
chochowski pushed a commit to chochowski/Megatron-LM that referenced this pull request Jul 20, 2026
…aths (NVIDIA#5530)

Signed-off-by: Min Htet Myet <mattralminn@email.com>
Signed-off-by: Mattral <mattralminn@gmail.com>
Signed-off-by: mchochowski <mchochowski@nvidia.com>
terminator123 pushed a commit to 021ai/Megatron-LM that referenced this pull request Aug 3, 2026
…aths (NVIDIA#5530)

Signed-off-by: Min Htet Myet <mattralminn@email.com>
Signed-off-by: Mattral <mattralminn@gmail.com>
svcnvidia-nemo-ci pushed a commit to dimapihtar/Megatron-LM that referenced this pull request Aug 4, 2026
…aths (NVIDIA#5530)

Signed-off-by: Min Htet Myet <mattralminn@email.com>
Signed-off-by: Mattral <mattralminn@gmail.com>
Signed-off-by: Dmytro Pykhtar <dpykhtar@nvidia.com>
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 community-request complexity: low

Projects

None yet

Development

Successfully merging this pull request may close these issues.

get_grad_norm_fp32 crashes on empty gradient list with inf norm or custom p-norm

8 participants