Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
7553e6a
Stage 1&2: Python containers + quantize/gemm dispatch/unwrap
negvet Mar 31, 2026
19acc5e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 31, 2026
3f33f40
Merge branch 'main' into hybrid_quantization
negvet Apr 6, 2026
f80f5d0
Enable quantized_model_init
negvet Apr 16, 2026
2185b30
FSDP support
negvet Apr 17, 2026
f22a395
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 17, 2026
c9754e9
Merge branch 'main' into hybrid_quantization
negvet Apr 17, 2026
103fffe
Enable CPU offloading
negvet Apr 22, 2026
16fb371
Activation recomputation
negvet Apr 24, 2026
a50fd63
TP/SP
negvet Apr 24, 2026
2214843
Resolve comments: hybrid uniform list, make_empty try, __repr__, etc.
negvet Apr 24, 2026
88fe467
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 24, 2026
f649cc9
Merge branch 'main' into hybrid_quantization
negvet Apr 29, 2026
4858491
Respect usage
negvet Apr 29, 2026
ef31a9a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 29, 2026
e7136fd
Merge main
negvet May 13, 2026
c7da5b2
Misc minor fixes: comments, tests, etc.
negvet May 20, 2026
a164cd3
Towards MLM integration
negvet May 21, 2026
62e7668
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 21, 2026
4b0d18c
Merge main
negvet Jun 1, 2026
7316516
Resolve comments: improve fsdp/tp/sp tests + amax reduction fix
negvet Jun 3, 2026
5892a74
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 3, 2026
ec7b84c
Enable FSDP2 hybrid protocol for Float8Block tensor
negvet Jun 5, 2026
b99277a
Enable Identity (no-op) quantization
negvet Jun 9, 2026
8cc3332
Bug fixing
negvet Jun 9, 2026
9b444d0
More fixes
negvet Jun 10, 2026
5202da4
Merge main
negvet Jun 10, 2026
9f20d35
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 10, 2026
04965f5
Minor fixes
negvet Jun 11, 2026
4a79a07
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 11, 2026
58d6ad0
Handle transpose-only Float8 in hybrid distopt
negvet Jun 12, 2026
4b9d1f8
Refactor hybrid tensors to own only parent quantizer
negvet Jun 12, 2026
03fd6c0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 15, 2026
5a31bf8
Merge branch 'main' into hybrid_quantization
negvet Jun 15, 2026
e421068
Enable full hybrid nvfp4 recipe (col only nvfp4) + minor fixes
negvet Jun 16, 2026
1c36ae9
Merge branch 'main' into hybrid_quantization
negvet Jun 16, 2026
e3dbd15
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 16, 2026
9d0e7c7
quantization_factory_base/zoo
negvet Jun 17, 2026
cb3ab49
Enable columnwise_source and hybrid recipes
negvet Jun 22, 2026
10aecf0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 22, 2026
965b188
Merge branch 'main' into hybrid_quantization
negvet Jun 22, 2026
cd381ef
Respect quantizer veto for save_original_inp
negvet Jun 23, 2026
06dcdd4
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 23, 2026
4d553d4
IMprove hybrid factory tests
negvet Jun 23, 2026
bd5d752
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 23, 2026
2953245
Fix attention factories and align with env-var setup
negvet Jun 26, 2026
0043a63
Update quantization factory zoo
negvet Jun 29, 2026
4fbfb1a
Merge main
negvet Jun 30, 2026
8c23fed
Resolve comments
negvet Jun 30, 2026
3a8ab22
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 30, 2026
81ffa7e
Add MHA boudary quantization tests
Jul 7, 2026
7aaee06
Merge branch 'main' into hybrid_quantization
negvet Jul 8, 2026
4e54f02
Improve numerical correctness of the tests
negvet Jul 13, 2026
c7e11d2
Resolve comments: fix rowwise_dequantized and improve quantizer valid…
negvet Jul 15, 2026
05f4d1e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 15, 2026
023bbab
Merge branch 'main' into hybrid_quantization
negvet Jul 17, 2026
0dd5a5f
Finish comment resolving: materialize quantizer once, + minor fixes
negvet Jul 17, 2026
77ce9ff
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 17, 2026
d0f604e
Add automatic alignment contract for CustomRecipe
negvet Jul 17, 2026
cf92c3d
Misc fixes: float8 fsdp, column-only float8, checks, etc.
negvet Jul 17, 2026
6fa849a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 17, 2026
495abd5
Hopper fixes
negvet Jul 20, 2026
65fc07f
Merge branch 'main' into hybrid_quantization
negvet Jul 20, 2026
416288b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 20, 2026
a3f5258
More Hopper fixes
negvet Jul 21, 2026
77e0e1c
Guard hybrid partial-master distopt updates
negvet Jul 21, 2026
461daa1
Blackwell CI fixes
negvet Jul 21, 2026
67f0f48
Docs minor update
negvet Jul 21, 2026
a6fe344
Improve fsdp2 and other tests
negvet Jul 22, 2026
04caed2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 22, 2026
a503009
Fix delayed-scaling hybrid FP8 FSDP guard
negvet Jul 22, 2026
8942e70
Resolve comments
negvet Jul 24, 2026
37667d9
Merge main
negvet Jul 24, 2026
d8d3de1
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 24, 2026
0b7c09f
Merge branch 'main' into hybrid_quantization
negvet Jul 27, 2026
7e15ddc
Quantizer config per recipe + keys to manage callables
negvet Jul 29, 2026
51cda18
Merge main
negvet Jul 31, 2026
5ee018d
refactor custom recipe
negvet Jul 31, 2026
650e046
Merge branch 'main' into hybrid_quantization
negvet Jul 31, 2026
aebd392
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 31, 2026
c59bc39
update policy keys and add state.quantizer_config + tests
negvet Jul 31, 2026
4ab0782
Merge hybrid quantization
negvet Jul 31, 2026
2490707
Minor changes
negvet Aug 3, 2026
b924055
Merge hybrid_quantization
negvet Aug 4, 2026
0ac0c05
Renaming
negvet Aug 4, 2026
8d62371
Support mid-training recipe updates in Pytorch modules
negvet Aug 10, 2026
92384cb
Merge main
negvet Aug 11, 2026
4eac680
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 11, 2026
160ec05
CI fixes
negvet Aug 11, 2026
fa85431
Exclude delayed scaling from recipe update
negvet Aug 11, 2026
e33937d
DPA support
negvet Aug 11, 2026
ea6ef5a
Fusible ops and graphs handling
negvet Aug 12, 2026
35ac69d
te.apply_recipe()
negvet Aug 13, 2026
364abe4
Minor fixes
negvet Aug 13, 2026
8e7873f
Handle boundaries before runtime
negvet Aug 19, 2026
328efbe
Propagate recipe flags to per-tensor QParams
negvet Aug 19, 2026
f469a1a
Handle failed autocast activation
negvet Aug 19, 2026
b2beaa1
Fix attention quant config validation
negvet Aug 19, 2026
b56c9a7
Canonicalize FP8 MHA recipe flags
negvet Aug 19, 2026
ee614b5
make sure forward does not change the recipe
negvet Aug 19, 2026
e54ff4c
Merge main
negvet Aug 19, 2026
5ab662b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 19, 2026
5becfd9
improve numerics tests
negvet Aug 19, 2026
1355ff2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 19, 2026
c6d3ed5
add mxfp8 2d config
negvet Aug 19, 2026
b455cd9
CI fixes
negvet Aug 20, 2026
421f41c
Merge main
negvet Sep 9, 2026
8ed5fa2
Minor fixes
negvet Sep 10, 2026
28592f1
Centralize recipe configuration and validation
negvet Sep 10, 2026
fd0adb4
Decide quantizer reuse from the recipe, not a revision counter
negvet Sep 10, 2026
092f00f
Validate recipe entry points before they publish state
negvet Sep 11, 2026
b61e2f4
Freeze the built-in attention recipe path
negvet Sep 11, 2026
f387993
Dispatch apply_recipe on an owner protocol
negvet Sep 11, 2026
ba9b2b7
Tie GroupedLinear's derived state to its runtime
negvet Sep 11, 2026
5fb9af1
Treat restores and resizes as replacements, not updates
negvet Sep 11, 2026
f8d86f1
Reject recipe changes a CUDA graph would ignore
negvet Sep 11, 2026
001597e
Minor fixes
negvet Sep 14, 2026
6fe86f7
Merge branch 'main' into mid_train_recipe_update
negvet Sep 14, 2026
c2946a1
Misc fixes
negvet Sep 14, 2026
e6a841d
Fix ci
negvet Sep 15, 2026
b9cc405
Merge branch 'main' into mid_train_recipe_update
negvet Sep 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/api/common.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Common API

.. autoapiclass:: transformer_engine.common.recipe.Format

.. autoapiclass:: transformer_engine.common.recipe.DelayedScaling(margin=0, fp8_format=Format.HYBRID, amax_history_len=1024, amax_compute_algo="max", scaling_factor_compute_algo=None)
.. autoapiclass:: transformer_engine.common.recipe.DelayedScaling(margin=0, fp8_format=Format.HYBRID, amax_history_len=1024, amax_compute_algo="max", scaling_factor_compute_algo=None, reduce_amax=True, fp8_dpa=False, fp8_mha=False, backward_override=None, amax_compute_algo_key=None, scaling_factor_compute_algo_key=None)

.. autoapiclass:: transformer_engine.common.recipe.MXFP8BlockScaling(fp8_format=Format.E4M3)

Expand All @@ -18,4 +18,6 @@ Common API

.. autoapiclass:: transformer_engine.common.recipe.Float8BlockScaling(fp8_format=Format.E4M3)

.. autoapiclass:: transformer_engine.common.recipe.CustomRecipe(qfactory, fp8_dpa=False, fp8_mha=False)
.. autoapifunction:: transformer_engine.common.recipe.quantizer_factory

.. autoapiclass:: transformer_engine.common.recipe.CustomRecipe(qfactory, fp8_dpa=False, fp8_mha=False, qfactory_key=None)
2 changes: 2 additions & 0 deletions docs/api/pytorch.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ PyTorch

.. autoapiclass:: transformer_engine.pytorch.autocast(enabled=True, calibrating=False, recipe=None, amax_reduction_group=None)

.. autoapifunction:: transformer_engine.pytorch.apply_recipe

.. autoapifunction:: transformer_engine.pytorch.quantized_model_init

.. autoapifunction:: transformer_engine.pytorch.checkpoint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def require_supported_hardware() -> None:
import torch

import transformer_engine.pytorch as te
from transformer_engine.common.recipe import CustomRecipe
from transformer_engine.common.recipe import CustomRecipe, quantizer_factory
from transformer_engine.pytorch.custom_recipes.quantizer_factories import (
mxfp8_factory,
nvfp4_factory,
Expand All @@ -65,9 +65,13 @@ def require_supported_hardware() -> None:
BASE_FACTORY = mxfp8_factory


def quantizer_factory(role: Optional[te.QuantizerRole]):
@quantizer_factory(key=("fine_grained_quantization_example", 1))
def demo_quantizer_factory(role: Optional[te.QuantizerRole]):
"""Return a fresh quantizer for every role, including ``None``.

The key stands in for this factory's behavior: bump its revision whenever the
body changes, so quantizers built from the previous behavior are rebuilt.

``BASE_FACTORY`` makes the factory total: unknown roles, future role values,
and untargeted modules all retain valid MXFP8 behavior.
"""
Expand Down Expand Up @@ -111,7 +115,7 @@ def quantizer_factory(role: Optional[te.QuantizerRole]):
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)
recipe = CustomRecipe(qfactory=demo_quantizer_factory)

with te.autocast(enabled=True, recipe=recipe):
outputs = model(inputs)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,15 @@ TE autocast path:
.. code-block:: python

import transformer_engine.pytorch as te
from transformer_engine.common.recipe import CustomRecipe
from transformer_engine.common.recipe import CustomRecipe, quantizer_factory
from transformer_engine.pytorch.custom_recipes.quantizer_factories import (
mxfp8_factory,
nvfp4_factory,
)


def quantizer_factory(role):
@quantizer_factory(key=("demo_factory", 1))
def demo_factory(role):
if role is not None and role.name == "demo.fc1":
if role.tensor_type == "input":
# wgrad keeps the original BF16 input
Expand Down Expand Up @@ -88,7 +89,7 @@ TE autocast path:
return mxfp8_factory(role) # every other TE module in MXFP8


recipe = CustomRecipe(qfactory=quantizer_factory)
recipe = CustomRecipe(qfactory=demo_factory)

with te.autocast(enabled=True, recipe=recipe):
output = model(inputs)
Expand Down Expand Up @@ -130,7 +131,8 @@ It does not call the factory on every unchanged forward.
# name: str = ""


def quantizer_factory(role: Optional[te.QuantizerRole]):
@quantizer_factory(key=("demo_factory", 1))
def demo_factory(role: Optional[te.QuantizerRole]):
# construct a fresh quantizer on every call
...
# Boundary slots may pass role=None or a role with empty fields, so
Expand All @@ -139,7 +141,7 @@ It does not call the factory on every unchanged forward.


# The factory plugs into the standard TE autocast path:
recipe = CustomRecipe(qfactory=quantizer_factory)
recipe = CustomRecipe(qfactory=demo_factory)

with te.autocast(enabled=True, recipe=recipe):
output = model(inputs)
Expand Down
2 changes: 2 additions & 0 deletions qa/L0_pytorch_unittest/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pip3 install pytest==8.2.1 || error_exit "Failed to install pytest"
NVTE_GROUPED_LINEAR_SINGLE_PARAM=1 python3 -m pytest --tb=auto --junitxml=$XML_LOG_DIR/pytest_test_sanity.xml $TE_PATH/tests/pytorch/test_sanity.py || test_fail "test_sanity.py"
python3 -m pytest --tb=auto --junitxml=$XML_LOG_DIR/pytest_test_recipe.xml $TE_PATH/tests/pytorch/test_recipe.py || test_fail "test_recipe.py"
python3 -m pytest --tb=auto --junitxml=$XML_LOG_DIR/pytest_test_custom_recipe.xml $TE_PATH/tests/pytorch/test_custom_recipe.py || test_fail "test_custom_recipe.py"
python3 -m pytest --tb=auto --junitxml=$XML_LOG_DIR/pytest_test_recipe_update.xml $TE_PATH/tests/pytorch/test_recipe_update.py || test_fail "test_recipe_update.py"
python3 -m pytest --tb=auto --junitxml=$XML_LOG_DIR/pytest_test_hybrid_quantization_fsdp2.xml $TE_PATH/tests/pytorch/test_hybrid_quantization_fsdp2.py || test_fail "test_hybrid_quantization_fsdp2.py"
python3 -m pytest --tb=auto --junitxml=$XML_LOG_DIR/pytest_test_deferred_init.xml $TE_PATH/tests/pytorch/test_deferred_init.py || test_fail "test_deferred_init.py"
PYTORCH_JIT=0 NVTE_TORCH_COMPILE=0 NVTE_ALLOW_NONDETERMINISTIC_ALGO=0 NVTE_FUSED_ATTN=0 python3 -m pytest --tb=auto --junitxml=$XML_LOG_DIR/pytest_test_numerics.xml $TE_PATH/tests/pytorch/test_numerics.py || test_fail "test_numerics.py"
PYTORCH_JIT=0 NVTE_TORCH_COMPILE=0 NVTE_ALLOW_NONDETERMINISTIC_ALGO=0 NVTE_FUSED_ATTN=0 python3 -m pytest --tb=auto --junitxml=$XML_LOG_DIR/pytest_test_cuda_graphs.xml $TE_PATH/tests/pytorch/test_cuda_graphs.py || test_fail "test_cuda_graphs.py"
Expand Down
2 changes: 2 additions & 0 deletions tests/pytorch/distributed/run_numerics_exact.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
Recipe,
QParams,
CustomRecipe,
quantizer_factory,
)
from transformer_engine.pytorch import NVFP4Quantizer
from transformer_engine.pytorch.constants import NVFP4_BLOCK_SCALING_SIZE
Expand Down Expand Up @@ -66,6 +67,7 @@ def get_nvfp4_quantizer_factory():
A factory function that takes a QuantizerRole and returns a quantizer instance
"""

@quantizer_factory(key=("distributed_numerics_exact_nvfp4_reference", 1))
def factory(role):
is_weight = (
role is not None
Expand Down
18 changes: 16 additions & 2 deletions tests/pytorch/hybrid_quantization_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def _make_mxfp8(*, fp8_dtype=te.DType.kFloat8E4M3):
return te.MXFP8Quantizer(fp8_dtype=fp8_dtype)


@recipe.quantizer_factory(key=("test_nvfp4_linear_mxfp8_dpa", 1))
def nvfp4_linear_mxfp8_dpa_test_factory(role):
"""Test-local reference for the experimental NVFP4 Linear + MXFP8 DPA example."""
is_dpa = role is not None and role.module_type == "dpa"
Expand Down Expand Up @@ -211,7 +212,7 @@ def make_role_aware_quantizer(factory, role):
return quantizer


def hybrid_custom_recipe(row_factory, col_factory, grad_factory=None):
def hybrid_custom_recipe(row_factory, col_factory, grad_factory=None, *, qfactory_key):
"""Build a CustomRecipe with hybrid forward and configurable grad quantizers."""
if grad_factory is None:
grad_factory = col_factory
Expand All @@ -227,9 +228,10 @@ def qfactory(role):
return make_role_aware_quantizer(grad_factory, role)
return make_role_aware_quantizer(row_factory, role)

return recipe.CustomRecipe(qfactory=qfactory)
return recipe.CustomRecipe(qfactory=qfactory, qfactory_key=qfactory_key)


@recipe.quantizer_factory(key=("test_hybrid_fp8_current", 1))
def hybrid_fp8_current_qfactory(role):
"""FP8 current scaling in both hybrid directions for forward tensor roles."""
if _is_linear_role(role) and role.tensor_type in _FORWARD_TENSOR_TYPES:
Expand All @@ -240,6 +242,7 @@ def hybrid_fp8_current_qfactory(role):
return current_scaling_factory(role)


@recipe.quantizer_factory(key=("test_hybrid_fp8_current_e5m2_grads", 1))
def hybrid_fp8_current_e5m2_grads_qfactory(role):
"""FP8 current-scaling hybrid with E5M2 for both grad boundary roles."""
if _is_linear_role(role) and role.tensor_type in _FORWARD_TENSOR_TYPES:
Expand All @@ -252,6 +255,7 @@ def hybrid_fp8_current_e5m2_grads_qfactory(role):
return _make_fp8_current()


@recipe.quantizer_factory(key=("test_hybrid_mxfp8", 1))
def hybrid_mxfp8_qfactory(role):
"""MXFP8 in both hybrid directions for forward tensor roles."""
if _is_linear_role(role) and role.tensor_type in _FORWARD_TENSOR_TYPES:
Expand All @@ -262,6 +266,7 @@ def hybrid_mxfp8_qfactory(role):
return mxfp8_factory(role)


@recipe.quantizer_factory(key=("test_hybrid_float8_block", 1))
def hybrid_float8_block_qfactory(role):
"""Float8 block scaling in both hybrid directions for forward roles."""
if _is_linear_role(role) and role.tensor_type in _FORWARD_TENSOR_TYPES:
Expand All @@ -272,6 +277,7 @@ def hybrid_float8_block_qfactory(role):
return float8_block_scaling_factory(role)


@recipe.quantizer_factory(key=("test_hybrid_block_fp8_e4m3", 1))
def hybrid_block_fp8_e4m3_qfactory(role):
"""Hybrid E4M3 Block-FP8 with role-aware 1D/2D block geometry."""
is_linear = _is_linear_role(role)
Expand All @@ -294,6 +300,7 @@ def make_quantizer():
)


@recipe.quantizer_factory(key=("test_hybrid_mixed_mxfp8_fp8", 1))
def hybrid_mixed_mxfp8_fp8_qfactory(role):
"""MXFP8 rowwise plus FP8 current-scaling columnwise."""
if _is_linear_role(role) and role.tensor_type in _FORWARD_TENSOR_TYPES:
Expand All @@ -304,6 +311,7 @@ def hybrid_mixed_mxfp8_fp8_qfactory(role):
return current_scaling_factory(role)


@recipe.quantizer_factory(key=("test_hybrid_fp8_current_identity", 1))
def hybrid_fp8_current_identity_qfactory(role):
"""FP8 current-scaling forward plus Identity backward."""
if _is_linear_role(role) and role.tensor_type in _FORWARD_TENSOR_TYPES:
Expand All @@ -316,6 +324,7 @@ def hybrid_fp8_current_identity_qfactory(role):
return current_scaling_factory(role)


@recipe.quantizer_factory(key=("test_hybrid_mxfp8_identity", 1))
def hybrid_mxfp8_identity_qfactory(role):
"""MXFP8 forward plus Identity backward."""
if _is_linear_role(role) and role.tensor_type in _FORWARD_TENSOR_TYPES:
Expand All @@ -328,11 +337,13 @@ def hybrid_mxfp8_identity_qfactory(role):
return mxfp8_factory(role)


@recipe.quantizer_factory(key=("test_identity", 1))
def identity_qfactory(role): # pylint: disable=unused-argument
"""High-precision passthrough for every quantizer slot."""
return te.IdentityQuantizer()


@recipe.quantizer_factory(key=("test_hybrid_nvfp4", 1))
def hybrid_nvfp4_qfactory(role):
"""NVFP4 in both hybrid directions for forward tensor roles."""
if _is_linear_role(role) and role.tensor_type in _FORWARD_TENSOR_TYPES:
Expand All @@ -343,6 +354,7 @@ def hybrid_nvfp4_qfactory(role):
return nvfp4_factory(role)


@recipe.quantizer_factory(key=("test_hybrid_tp_mxfp8_nvfp4", 1))
def hybrid_tp_mxfp8_nvfp4_qfactory(role):
"""TP/SP MXFP8 rowwise plus NVFP4 columnwise, including boundary roles."""
if _is_linear_role(role) and role.tensor_type in _GRAD_TENSOR_TYPES:
Expand All @@ -353,6 +365,7 @@ def hybrid_tp_mxfp8_nvfp4_qfactory(role):
)


@recipe.quantizer_factory(key=("test_hybrid_fp8_mxfp8", 1))
def hybrid_fp8_mxfp8_qfactory(role):
"""FP8 current-scaling rowwise plus MXFP8 columnwise for CPU-offload tests."""
if _is_linear_role(role) and role.tensor_type in _FORWARD_TENSOR_TYPES:
Expand All @@ -365,6 +378,7 @@ def hybrid_fp8_mxfp8_qfactory(role):
return _make_fp8_current()


@recipe.quantizer_factory(key=("test_hybrid_mxfp8_nvfp4", 1))
def hybrid_mxfp8_nvfp4_qfactory(role):
"""MXFP8 rowwise plus NVFP4 columnwise for CPU-offload tests."""
if _is_linear_role(role) and role.tensor_type in _FORWARD_TENSOR_TYPES:
Expand Down
32 changes: 32 additions & 0 deletions tests/pytorch/nvfp4/test_nvfp4_module_exact.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,15 @@ def _default_quantizer():
with_rht=with_rht,
)

@recipe.quantizer_factory(
key=(
"nvfp4_module_exact_reference",
1,
("with_rht", with_rht),
("with_2d_quantization", with_2d_quantization),
("row_scaled", row_scaled),
)
)
def factory(role):
if role is None:
return _default_quantizer()
Expand Down Expand Up @@ -137,6 +146,29 @@ def factory(role):
return factory


def test_nvfp4_reference_factory_semantic_keys(monkeypatch):
"""Factory keys are stable, capture all behavior, and require no factory probe."""

def unexpected_factory_invocation(*_args, **_kwargs):
pytest.fail("Creating semantic recipe configuration invoked the quantizer factory")

monkeypatch.setattr(reference_nvfp4, "NVFP4QuantizerRef", unexpected_factory_invocation)
flags = (
(False, False, False),
(False, False, False),
(True, False, False),
(False, True, False),
(False, False, True),
)
factories = [get_nvfp4_quantizer_factory(*values) for values in flags]
configs = [recipe.CustomRecipe(qfactory=factory).quantizer_config() for factory in factories]

assert factories[0].qfactory_key == factories[1].qfactory_key
assert configs[0] == configs[1]
assert len({factory.qfactory_key for factory in factories[1:]}) == 4
assert len(set(configs[1:])) == 4


def reset_rng_states():
seed = 1234
torch.manual_seed(seed)
Expand Down
9 changes: 9 additions & 0 deletions tests/pytorch/test_backward_override.py
Original file line number Diff line number Diff line change
Expand Up @@ -1905,6 +1905,15 @@ def test_operation_fuser_rebuilds_userbuffers_fusion_on_backward_override_switch
)
assert not any(isinstance(op, UserbuffersForwardLinear) for op, _ in fuser._forward_ops)

current_recipe["value"] = quant_recipe
fuser.maybe_fuse_ops(
is_grad_enabled=True,
recipe=quant_recipe,
input_=x,
extra_inputs=extra_inputs,
)
assert any(isinstance(op, UserbuffersForwardLinear) for op, _ in fuser._forward_ops)


@pytest.mark.parametrize("recipe_name", _quantized_numerics_recipe_list)
@pytest.mark.parametrize("dtype", _core_dtypes, ids=str)
Expand Down
50 changes: 50 additions & 0 deletions tests/pytorch/test_cuda_graphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,56 @@ def test_make_graphed_callables_with_kwargs(
assert_all_equal(outputs, graph_outputs)


@pytest.mark.skipif(not fp8_available, reason="FP8 is not available")
def test_graphed_callable_rejects_changed_quantization_recipe() -> None:
"""Accept replays under an equal configuration and reject changed ones."""
model_config = model_configs["small"]
dtype = torch.bfloat16
captured_recipe = recipe.Float8CurrentScaling()
equal_recipe = copy.deepcopy(captured_recipe)
model = Linear(
model_config.hidden_size,
model_config.hidden_size,
device="cuda",
params_dtype=dtype,
)
for param in model.parameters():
param.grad = torch.empty_like(param)
model = make_graphed_callables(
model,
(generate_data(model_config, dtype, warmup=True),),
enabled=True,
recipe=captured_recipe,
)

def replay():
output = model(generate_data(model_config, dtype))
output.backward(generate_data(model_config, dtype, requires_grad=False))

try:
# An equal but independent recipe replays, in this scope and a later one.
for _ in range(2):
with autocast(enabled=True, recipe=equal_recipe):
for _ in range(2):
replay()

# Mutating and reusing the caller-owned recipe must not mutate the
# immutable semantic configuration saved at capture time.
captured_recipe.fp8_quant_fwd_inp = recipe.QParams(amax_epsilon=0.25)
with autocast(enabled=True, recipe=captured_recipe):
with pytest.raises(RuntimeError, match="Recapture the graph with the new recipe"):
model(generate_data(model_config, dtype))

# A manager reset cannot make a different recipe look like the captured
# one: the check compares configurations, not a recyclable counter.
FP8GlobalStateManager.reset()
with autocast(enabled=True, recipe=recipe.Float8CurrentScaling(use_power_2_scales=True)):
with pytest.raises(RuntimeError, match="Recapture the graph with the new recipe"):
model(generate_data(model_config, dtype))
finally:
reset_graphs(model)


def test_make_graphed_callables_returns_owned_parameter_grads() -> None:
"""Parameter grads returned from graph replay must not alias static graph buffers."""
reset_rng_states()
Expand Down
Loading
Loading