Skip to content

[PyTorch] Add FusedAdam.reload_master_param - #3825

Draft
CarlosGomes98 wants to merge 1 commit into
NVIDIA:mainfrom
CarlosGomes98:fix/reload-master-params
Draft

CarlosGomes98 wants to merge 1 commit into
NVIDIA:mainfrom
CarlosGomes98:fix/reload-master-params

Conversation

@CarlosGomes98

Copy link
Copy Markdown
Contributor

Add a method that replaces a parameter and its FP32 master weights without resetting Adam history, for use after loading model weights without optimizer state. It accepts full FP32 values when store_param_remainders is enabled, writing the BF16 high bits with the same rounding convention as AdamFunctorMasterParamRemainder, and supports quantized and DTensor parameters.

The method rejects tensors the optimizer does not own (e.g. param.data), DTensor masters whose mesh, placements or shape differ from the parameter, non-FP32 values and mismatched local shapes, all before mutating any state.

Description

Please include a brief summary of the changes, relevant motivation and context.

Fixes # (issue)

Type of change

  • Documentation change (change only to the documentation, either a fix or a new content)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Infra/Build change
  • Code refactoring

Changes

Please list the changes introduced in this PR:

  • Change A
  • Change B

Checklist:

  • I have read and followed the contributing guidelines
  • The functionality is complete
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Add a method that replaces a parameter and its FP32 master weights without
resetting Adam history, for use after loading model weights without
optimizer state. It accepts full FP32 values when store_param_remainders is
enabled, writing the BF16 high bits with the same rounding convention as
AdamFunctorMasterParamRemainder, and supports quantized and DTensor
parameters.

The method rejects tensors the optimizer does not own (e.g. param.data),
DTensor masters whose mesh, placements or shape differ from the parameter,
non-FP32 values and mismatched local shapes, all before mutating any state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Carlos Gomes <cgomes@nvidia.com>
@github-actions github-actions Bot added the community-contribution PRs from external contributor outside the core maintainers, representing community-driven work. label Nov 6, 2026

@timmoon10 timmoon10 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Overall this is a good idea. The optimizer state for master weights can get weird and esoteric (especially the BF16 remainders trick) and it's not reasonable for users to interact with it directly. With some minor tweaks, this API is very straightforward.

self.set_scaled_state(param, "master_param", master)

@torch.no_grad()
def reload_master_param(self, param: torch.nn.Parameter, master_param: torch.Tensor) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is fine functionally, but I think the following signature would be cleaner:

def update_param(self, param: torch.nn.Parameter, values: torch.Tensor) -> None:

The docstring makes you think you're getting rid of param and using master_param for the rest of the training run, when you're actually just updating values in param and in the optimizer state master weights.

Comment on lines +485 to +494
"""Replace a parameter and its master weights without resetting Adam history.

Use this after loading model weights without loading optimizer state. Unlike
:meth:`set_scaled_state`, this method accepts full FP32 values even when
``store_param_remainders`` is enabled. It updates the model parameter as well,
including the high bits needed to reconstruct the master from its remainders.
Moment buffers are initialized if absent; existing moments and step counters
are preserved. Distributed callers must synchronize updated parameter shards.
Call this outside CUDA graph capture. The configured master-weight dtype still
determines storage precision when remainders are disabled.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a very Claude-y docstring, which is more showing off its debugging history than providing useful guidance for users.

In fact, it seems we could get across everything important with just:

Suggested change
"""Replace a parameter and its master weights without resetting Adam history.
Use this after loading model weights without loading optimizer state. Unlike
:meth:`set_scaled_state`, this method accepts full FP32 values even when
``store_param_remainders`` is enabled. It updates the model parameter as well,
including the high bits needed to reconstruct the master from its remainders.
Moment buffers are initialized if absent; existing moments and step counters
are preserved. Distributed callers must synchronize updated parameter shards.
Call this outside CUDA graph capture. The configured master-weight dtype still
determines storage precision when remainders are disabled.
"""Update parameter values and optimizer state for master weights.

I prefer implementing functions with a clear idea of what they are supposed to do, rather than prescribing when you're supposed to call them. Also, store_param_remainders is an internal implementation detail.

Comment thread docs/api/pytorch.rst
----------

.. autoapiclass:: transformer_engine.pytorch.optimizers.FusedAdam
:members: reload_master_param, get_unscaled_state, set_scaled_state

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we want to treat get_unscaled_state/set_scaled_state as part of the user-facing API? I imagine it as an internal helper function that expert users like Mcore might use. How much does Mcore depend on it as a stable interface?

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

community-contribution PRs from external contributor outside the core maintainers, representing community-driven work.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants