Skip to content

[PyTorch] Support dynamic recipe updates during training - #3343

Open
negvet wants to merge 121 commits into
NVIDIA:mainfrom
negvet:mid_train_recipe_update
Open

negvet wants to merge 121 commits into
NVIDIA:mainfrom
negvet:mid_train_recipe_update

Conversation

@negvet

@negvet negvet commented Aug 11, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Enables recipe updates during training, either lazily through te.autocast() or atomically across a model with te.apply_recipe().

Diff:

  • Library: +2,016 / −794 / net +1,222
  • Tests: +5,829 / −274 / net +5,555

TODO - callable compatibility:
Callable factories and algorithms now require explicit semantic metadata, supplied through a decorator or recipe key argument. Consider whether unkeyed callables should remain supported and, if so, what safe fallback identity should be used.

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

  • Identifies recipe changes through semantic configuration keys.
  • Builds and validates complete forward/backward runtimes before publishing them.
  • Invalidates cached quantized weights when their recipe changes.
  • Supports role-aware CustomRecipe factories and composed MHA/DPA boundaries.
  • Rejects unsupported transitions before modifying active state.
  • Preserves existing numerics for unchanged supported configurations.

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

negvet and others added 30 commits March 31, 2026 14:51
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
Signed-off-by: Evgeny <etsykunov@nvidia.com>
@negvet

negvet commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

/te-ci pytorch L1

Signed-off-by: Evgeny <etsykunov@nvidia.com>
@negvet

negvet commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

/te-ci pytorch L1

@negvet
negvet marked this pull request as ready for review September 15, 2026 13:09
@greptile-apps

greptile-apps Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 3/5

The PR is not safe to merge until legacy unkeyed CustomRecipe factories remain activatable and custom policies are checked against the hardware capabilities their actual quantizers require.

Findings

  1. P1 Unkeyed factories no longer activate ▶
  2. P1 Identity recipes require FP8 ▶

Summary

This PR introduces semantic recipe configurations and transactional forward/backward runtime construction so PyTorch quantization recipes can be updated lazily or model-wide. It also propagates role-aware recipe state through linear, grouped-linear, attention, CUDA-graph, and fusible-operation paths.

  • Adds te.apply_recipe() and per-module runtime planning/activation.
  • Rebuilds quantizers and invalidates cached quantized weights when semantic recipe configuration changes.
  • Adds stable keys for callable algorithms and custom quantizer factories.
  • Extends MHA/DPA boundary roles and grouped-quantization validation.
  • Adds extensive recipe-update, attention, graph, distributed, and numerics coverage.
  • The new callable-key and blanket CustomRecipe hardware checks introduce two compatibility failures.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Recipe requested by autocast or apply_recipe] --> B[Build semantic recipe configuration]
  B --> C[Resolve module quantizer roles]
  C --> D[Construct forward and backward candidate states]
  D --> E[Validate candidate runtime]
  E -->|Failure| F[Keep active runtime unchanged]
  E -->|Success| G[Publish candidate runtime]
  G --> H[Clear quantized weight workspaces]
  H --> I[Execute with updated quantizers]
Loading

Reviews (1) · Last reviewed commit: "Merge branch 'main' into mid_train_recip..."

Comment on lines +168 to +180
if key is None:
qfactory = getattr(recipe, "qfactory", None)
name = getattr(qfactory, "__qualname__", None) or repr(qfactory)
raise ValueError(
f"CustomRecipe requires a semantic qfactory key for {name}. The key stands in for"
" the factory's behavior, because callable identity is not stable across processes,"
" ranks or checkpoints. Attach one where the factory is defined:\n"
' @quantizer_factory(key=("my_factory", 1))\n'
" def my_factory(role): ...\n"
"or pass it with the recipe: CustomRecipe(qfactory=my_factory,"
' qfactory_key=("my_factory", 1)).\n'
"Bump the revision whenever the factory's behavior changes, so quantizers built from"
" the previous behavior are rebuilt instead of silently reused."

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.

P1 Unkeyed factories no longer activate

Existing callers can construct CustomRecipe(qfactory=callable) without a semantic key, but the first enabled autocast, calibration, or model initialization now calls quantizer_config() and raises here before invoking the factory. This breaks the previously supported callable-factory contract and prevents existing unkeyed recipes from running unless callers add new metadata.

Knowledge Base Used: PyTorch FP8 and low-precision quantization

Comment on lines +287 to +290
else:
# Covers DelayedScaling, Float8CurrentScaling and CustomRecipe, whose
# quantizers are chosen by a factory this check cannot inspect.
recipe_supported, unsupported_reason = check_fp8_support()

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.

P1 Identity recipes require FP8

If a CUDA environment can run Transformer Engine but fails the generic FP8 availability check, an identity-only CustomRecipe is rejected here before its factory is inspected. The documented high_precision_factory returns only IdentityQuantizers and performs no FP8 work, so this blanket gate prevents a supported high-precision policy from running even though it does not need FP8 kernels.

Knowledge Base Used: PyTorch FP8 and low-precision quantization

@zhongbozhu zhongbozhu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why do we even need to dynamically change recipe during training?

@timmoon10 timmoon10 mentioned this pull request Oct 20, 2026
4 of 9 tasks

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 might conflict with #3717.

Comment on lines +661 to +664
raise RuntimeError(
"Mid-training recipe updates are not supported for fusible operations. "
"Recreate the fusible operation or operation pipeline with the new recipe."
)

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.

Why doesn't this work? Can't we just let the fuser re-fuse the ops if the recipe changes?

# cannot expose a requested recipe globally.
for module, update in plans:
module._apply_recipe_update(update) # pylint: disable=protected-access
FP8GlobalStateManager.quantization_state.fp8_recipe = recipe

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 attr is set when entering te.autocast, so we don't expect this to be read if the function is called outside te.autocast. Consider replacing the default recipe instead, or keep the current API where the user has to pass the recipe into te.autocast.

# owner implementing the pair can participate.
plans = []
for fqn, module in model.named_modules():
planner = getattr(module, "_plan_recipe_update", 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.

I see we have this scheme so that we can process all the modules before actually applying the recipe change, which means we only apply the recipe change after all the modules have had some kind of validation. It adds a lot of complexity though, so skeptical if it is worth it.

Comment on lines +968 to +969
self._declared_output_quantizer_role = _declared_output_quantizer_role
self._declared_grad_input_quantizer_role = _declared_grad_input_quantizer_role

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.

I think these are redundant with the existing logic for _output_quantizer_role and _grad_input_quantizer_role. If I understand, this is intended to allow MHA to change the output/grad input quantizer and detect recipe changes. However, I don't think there's value in having separate paths for users to set output quantizers and MHA to set output quantizers, they should use the same infrastructure.

Comment on lines +245 to +248
# Cached semantic configuration. Lazily populated by ``quantizer_config``
# and invalidated together with the cached repr on recipe mutation.
_cached_quantizer_config: Any = None
_has_cached_quantizer_config: bool = False

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.

The quantizer config infrastructure seems overengineered and redundant. #3717 uses the string representation to detect recipe changes in the op fuser.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants