-
Notifications
You must be signed in to change notification settings - Fork 805
[PyTorch] Document heterogeneous quantization recipes #3336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
negvet
wants to merge
3
commits into
NVIDIA:main
Choose a base branch
from
negvet:fine_grained_recipe_docs
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
docs/examples/heterogeneous_quantization/heterogeneous_quantization.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| .. | ||
| Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
|
|
||
| See LICENSE for license information. | ||
|
|
||
| .. _fine-grained-quantization-tutorial: | ||
| .. _heterogeneous-quantization-tutorial: | ||
|
|
||
| Building a heterogeneous quantization recipe | ||
| ============================================== | ||
|
|
||
| This tutorial demonstrates factory composition, module and name targeting, | ||
| the general three-format GEMM mapping, and high-precision directions. See | ||
| :doc:`Heterogeneous quantization recipes | ||
| <../../features/low_precision_training/heterogeneous_quantization/heterogeneous_quantization>` | ||
| for the API concepts and direction mapping. | ||
|
|
||
| Constructing a factory | ||
| ---------------------- | ||
|
|
||
| A robust factory follows four construction rules: | ||
|
|
||
| * Return a quantizer for every role. Return ``IdentityQuantizer`` for an | ||
| intentional high-precision slot; do not return ``None``. | ||
| * Constructing a fresh quantizer for every call is recommended. | ||
| ``HybridQuantizer`` owns and configures its rowwise and columnwise children. | ||
| * A module-level function is the most portable factory definition, especially | ||
| when a launcher or checkpointing setup needs to import or pickle it. | ||
| * Treat role strings as selectors, not a fixed enumeration. Preserve a base | ||
| factory fallback for roles the factory does not recognize. | ||
|
|
||
| For example, compose a TE-native factory by keeping one named ``linear`` | ||
| module in high precision, using NVFP4 for every ``grouped_linear`` role, and | ||
| retaining MXFP8 as the global fallback: | ||
|
|
||
| .. tabs:: | ||
|
|
||
| .. tab:: PyTorch | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| from typing import Optional | ||
|
|
||
| import transformer_engine.pytorch as te | ||
| from transformer_engine.pytorch.custom_recipes.quantizer_factories import ( | ||
| mxfp8_factory, | ||
| nvfp4_factory, | ||
| ) | ||
|
|
||
| def my_factory(role: Optional[te.QuantizerRole]): | ||
| if role is not None: | ||
| if role.module_type == "linear" and role.name == "decoder.39.fc2": | ||
| return te.IdentityQuantizer() | ||
| if role.module_type == "grouped_linear": | ||
| return nvfp4_factory(role) | ||
| return mxfp8_factory(role) | ||
|
|
||
| Runnable example | ||
| ---------------- | ||
|
|
||
| The following synthetic example demonstrates base-factory composition, the | ||
| general three-format mapping, high-precision directions, and module/name | ||
| targeting. It uses only TE-native quantizers. MXFP8 and NVFP4 execution requires | ||
| supported hardware and software. | ||
|
|
||
| .. tabs:: | ||
|
|
||
| .. tab:: PyTorch | ||
|
|
||
| .. raw:: html | ||
|
|
||
| <div style="background: #f0f4f8; border-left: 3px solid #5c7cfa; padding: 6px 12px; font-size: 13px; color: #495057; margin-bottom: 0; border-radius: 4px 4px 0 0;"> | ||
| Requires SM100 (Blackwell) or later | ||
| </div> | ||
|
|
||
| .. literalinclude:: pytorch_heterogeneous_quantization_example.py | ||
| :language: python | ||
| :start-after: # START_HETEROGENEOUS_QUANTIZATION_EXAMPLE | ||
| :end-before: # END_HETEROGENEOUS_QUANTIZATION_EXAMPLE | ||
|
|
||
| Run it from the repository root after installing TE: | ||
|
|
||
| .. code-block:: bash | ||
|
|
||
| python docs/examples/heterogeneous_quantization/pytorch_heterogeneous_quantization_example.py | ||
|
|
||
| Recipe starting points | ||
| ---------------------- | ||
|
|
||
| The runnable example above is deliberately synthetic: it demonstrates the | ||
| expressiveness of the API, not a recommended training recipe. | ||
|
|
||
| The TE-native base factories in | ||
| ``transformer_engine/pytorch/custom_recipes/quantizer_factories.py`` construct | ||
| standard TE quantizers. They can be used directly as factory fallbacks or | ||
| composed as children of a ``HybridQuantizer``. | ||
|
|
||
| More specialized starting points are available in | ||
| ``transformer_engine/pytorch/custom_recipes/quantizer_factory_zoo.py``. Some | ||
| zoo factories encode externally described recipe structures or have specific | ||
| motivating evidence. They remain illustrative examples rather than official, | ||
| broadly validated defaults; read each factory's rationale before adapting it. | ||
|
|
||
| Validating and optimizing a recipe | ||
| ---------------------------------- | ||
|
|
||
| A factory can describe assignments beyond current optimized kernel coverage. | ||
| Before adopting an assignment for a workload: | ||
|
|
||
| * Confirm that the module, GEMM layout and shape, software version, and GPU can | ||
| execute it. | ||
| * Check whether the selected module path has an appropriate optimized kernel | ||
| and integration. | ||
| * Validate accuracy and convergence on the target model and distributed setup. | ||
| * Benchmark throughput and memory on the target workload. | ||
|
|
||
| Fine-grained assignments provide a way to explore an accuracy/performance | ||
| slider by varying precision and quantization logic per GEMM. The recipes that | ||
| can be realized efficiently are constrained by available kernels and | ||
| integrations. Accuracy and convergence experiments can run on functionally | ||
| executable, non-optimized paths before dedicated kernels are available. |
130 changes: 130 additions & 0 deletions
130
docs/examples/heterogeneous_quantization/pytorch_heterogeneous_quantization_example.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| #!/usr/bin/env python3 | ||
| # Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # | ||
| # See LICENSE for license information. | ||
|
|
||
| """Runnable heterogeneous quantization recipe example. | ||
|
|
||
| The factory assigns one precision to each ``demo.fc1`` Linear GEMM: | ||
|
|
||
| * fprop: ``weight.row(MXFP8) x input.row(MXFP8)`` | ||
| * dgrad: ``weight.col(NVFP4) x grad_output.row(NVFP4)`` | ||
| * wgrad: ``input.col(original BF16) x grad_output.col(original BF16)`` | ||
|
|
||
| ``demo.fc2`` runs every GEMM in high precision. ``demo.output`` is not | ||
| special-cased and therefore exercises the MXFP8 base-factory fallback. | ||
|
|
||
| Run from the Transformer Engine repository root:: | ||
|
|
||
| python docs/examples/heterogeneous_quantization/\ | ||
| pytorch_heterogeneous_quantization_example.py | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import torch | ||
| import transformer_engine.pytorch as te | ||
|
|
||
|
|
||
| def require_supported_hardware() -> None: | ||
| """Fail early with TE's reason when either required format is unavailable.""" | ||
|
|
||
| if not torch.cuda.is_available(): | ||
| raise SystemExit("This example requires a CUDA-capable NVIDIA GPU.") | ||
|
|
||
| failures = [] | ||
| for name, check in ( | ||
| ("MXFP8", te.is_mxfp8_available), | ||
| ("NVFP4", te.is_nvfp4_available), | ||
| ): | ||
| available, reason = check(return_reason=True) | ||
| if not available: | ||
| failures.append(f"{name}: {reason}") | ||
| if failures: | ||
| raise SystemExit("Required formats are unavailable: " + "; ".join(failures)) | ||
|
|
||
|
|
||
| require_supported_hardware() | ||
|
|
||
| # START_HETEROGENEOUS_QUANTIZATION_EXAMPLE | ||
|
|
||
| from typing import Optional | ||
|
|
||
| import torch | ||
|
|
||
| import transformer_engine.pytorch as te | ||
| from transformer_engine.common.recipe import CustomRecipe | ||
| from transformer_engine.pytorch.custom_recipes.quantizer_factories import ( | ||
| mxfp8_factory, | ||
| nvfp4_factory, | ||
| ) | ||
|
|
||
|
|
||
| THREE_FORMAT_MODULE = "demo.fc1" | ||
| HIGH_PRECISION_MODULE = "demo.fc2" | ||
| BASE_FACTORY = mxfp8_factory | ||
|
|
||
|
|
||
| def quantizer_factory(role: Optional[te.QuantizerRole]): | ||
| """Return a fresh quantizer for every role, including ``None``. | ||
|
|
||
| ``BASE_FACTORY`` makes the factory total: unknown roles, future role values, | ||
| and untargeted modules all retain valid MXFP8 behavior. | ||
| """ | ||
|
|
||
| if role is not None and role.name == THREE_FORMAT_MODULE: | ||
| # Constructing fresh child quantizers for every call is recommended. | ||
| if role.tensor_type == "input": | ||
| # Wgrad retains the original BF16 input. | ||
| return te.HybridQuantizer( | ||
| rowwise_quantizer=mxfp8_factory(role), | ||
| columnwise_quantizer=te.IdentityQuantizer(), | ||
| columnwise_source="original", | ||
| ) | ||
| if role.tensor_type == "weight": | ||
| # Dgrad uses NVFP4 quantized from the dequantized MXFP8 fprop weight. | ||
| return te.HybridQuantizer( | ||
| rowwise_quantizer=mxfp8_factory(role), | ||
| columnwise_quantizer=nvfp4_factory(role), | ||
| columnwise_source="rowwise_dequantized", | ||
| ) | ||
| if role.tensor_type == "grad_output": | ||
| # Dgrad uses NVFP4 while wgrad retains the original BF16 gradient. | ||
| return te.HybridQuantizer( | ||
| rowwise_quantizer=nvfp4_factory(role), | ||
| columnwise_quantizer=te.IdentityQuantizer(), | ||
| columnwise_source="original", | ||
| ) | ||
|
|
||
| if role is not None and role.name == HIGH_PRECISION_MODULE: | ||
| return te.IdentityQuantizer() | ||
|
|
||
| return BASE_FACTORY(role) | ||
|
|
||
|
|
||
| linear_options = {"bias": False, "params_dtype": torch.bfloat16, "device": "cuda"} | ||
| model = torch.nn.Sequential( | ||
| te.Linear(128, 256, name=THREE_FORMAT_MODULE, **linear_options), | ||
| torch.nn.GELU(), | ||
| te.Linear(256, 256, name=HIGH_PRECISION_MODULE, **linear_options), | ||
| torch.nn.GELU(), | ||
| te.Linear(256, 128, name="demo.output", **linear_options), | ||
| ) | ||
| inputs = torch.randn(64, 128, device="cuda", dtype=torch.bfloat16, requires_grad=True) | ||
| recipe = CustomRecipe(qfactory=quantizer_factory) | ||
|
|
||
| with te.autocast(enabled=True, recipe=recipe): | ||
| outputs = model(inputs) | ||
|
|
||
| loss = outputs.float().square().mean() | ||
| loss.backward() | ||
|
|
||
| # END_HETEROGENEOUS_QUANTIZATION_EXAMPLE | ||
|
|
||
| gradients = [inputs.grad, *(parameter.grad for parameter in model.parameters())] | ||
| assert all(gradient is not None for gradient in gradients) | ||
| assert all(torch.isfinite(gradient).all() for gradient in gradients) | ||
|
|
||
| print(f"GPU: {torch.cuda.get_device_name()}") | ||
| print(f"TE Linear names: {[model[index].name for index in (0, 2, 4)]}") | ||
| print(f"loss: {loss.item():.6f}; forward and backward completed") |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is too long - we can have something before #START_FINE_GRAINED_QUANTIZATION_EXAMPLE or in different files like model building and require_supported_hardware() . BTW see how we deal with support hardware in boxes in the other recipes docs.