-
Notifications
You must be signed in to change notification settings - Fork 773
[PyTorch][torch.compile] Make quantizers opaque value objects #3152
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
Merged
Merged
Changes from all commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
f3401df
[PyTorch] Make tensorless quantizers opaque value objects for torch.c…
pggPL c4ad54c
[PyTorch] Drop quantizer value registry; reconstruct via __fx_repr__ …
pggPL a06324b
[PyTorch] Split dynamo.py into a dynamo/ package
pggPL ea5b396
[PyTorch] Raise in quantizer __fx_repr__ when a process group is stored
pggPL aa65e34
[PyTorch] Cover NVFP4 in quantizer value-object test
pggPL e1b1db6
Reject a value quantizer that carries an amax reduction group in __eq…
pggPL 8c33d0e
Recognize value-opaque quantizers via a class flag
pggPL 945f62d
Address review: narrow opaque-type except, add fullgraph test, fix nv…
pggPL e3c8f43
Restore NVFP4 rht_matrix on value-key rebuild; assert quantize round-…
pggPL 3f68621
Enforce process-group rejection in _value_key, not __fx_repr__; add test
pggPL 32d1768
Strengthen fullgraph test: quantize/dequantize via a custom op, not p…
pggPL 28bde9e
Clarify comments: rht_matrix_random_sign_mask_t derivation; why the o…
pggPL 2c3c5df
Reword opaque-flag comment: self-contained, no Linear reference
pggPL 826f271
Cover is_opaque_value_type with the import-safety guard too
pggPL 613c545
Stamp value-opaque flag only after successful registration
pggPL 9db604f
Drop verbose comments around value-opaque flag stamping
pggPL 3011dfd
Narrow value process-group check to amax_reduction_group
pggPL fe5e5db
Shorten amax_reduction_group check comment
pggPL f6b6d78
Merge remote-tracking branch 'upstream/main' into make_qunatizers_opaque
pggPL 6f66c3e
Drop trivial value-equality boilerplate from quantizer test
pggPL 6fb6a0d
Address review comments: qualname registry, import and comment cleanups
pggPL f9c0e18
Derive quantizer value fields from class annotations
pggPL dd96956
Drop redundant annotation-exclusion comments
pggPL 447a4e1
Shorten _is_value_quantizer comment
pggPL 02a8fc9
Fold annotation walk into _value_fields, trim comments
pggPL 5f55a51
Simplify qualname-registry comment
pggPL 02afbdc
Reword qualname-registry comment for outside readers
pggPL 1590931
Fix review findings: pickle compat, subclass opt-in, cached value fields
pggPL 43a4083
Validate value-field annotations by resolved type, not annotation text
pggPL 10994b7
Drop the amax_reduction_group fixup from the generic rebuilder
pggPL f4ddb51
Merge remote-tracking branch 'upstream/main' into make_qunatizers_opaque
pggPL ba520ca
Enable post-RHT amax in the NVFP4 value-object test factory
pggPL 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
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,12 @@ | ||
| # Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # | ||
| # See LICENSE for license information. | ||
|
|
||
| """torch.compile glue for Transformer Engine.""" | ||
|
|
||
| from .quantizer_opaque import register_value_opaque_quantizer, is_value_opaque_quantizer | ||
|
|
||
| __all__ = [ | ||
| "register_value_opaque_quantizer", | ||
| "is_value_opaque_quantizer", | ||
| ] |
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,134 @@ | ||
| # Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # | ||
| # See LICENSE for license information. | ||
|
|
||
| """Value-opaque quantizers for torch.compile.""" | ||
|
|
||
| from __future__ import annotations | ||
| import enum | ||
| from typing import Any, Dict, Tuple, get_type_hints | ||
|
|
||
| from ..constants import DType | ||
|
|
||
|
|
||
| # Qualnames of the registered quantizer classes. The set holds strings rather | ||
| # than the classes themselves so that ``is_value_opaque_quantizer`` can be | ||
| # called inside a ``torch.compile``'d function without a graph break: Dynamo | ||
| # can evaluate ``type(q).__qualname__ in <set of strings>``, but not set | ||
| # membership of a class registered as an opaque type. | ||
| _VALUE_OPAQUE_QUALNAMES: set = set() | ||
|
|
||
|
|
||
| def is_value_opaque_quantizer(quantizer: Any) -> bool: | ||
| """Whether *quantizer*'s class is registered as a torch.compile value-opaque | ||
| type.""" | ||
| return type(quantizer).__qualname__ in _VALUE_OPAQUE_QUALNAMES | ||
|
|
||
|
|
||
| def _rebuild_quantizer(cls: type, items: Tuple[Tuple[str, Any], ...]) -> Any: | ||
| """Rebuild a tensorless quantizer of type *cls* from its value items. | ||
|
|
||
| Referenced by the ``__fx_repr__`` emitted for value-opaque quantizers; the | ||
| generated FX code calls this to materialize the quantizer constant. | ||
|
|
||
| Only the value fields (plus derived state via ``_rebuild_derived_state``) | ||
| are restored. Non-value attributes such as the deprecated | ||
| ``amax_reduction_group`` are deliberately absent on the rebuilt quantizer, | ||
| so accessing them fails loudly unless set explicitly. | ||
| """ | ||
| # Bypass ``__init__`` and restore the value attributes directly: the value | ||
| # items already capture every value-defining field (including derived ones), | ||
| # and the constructors have heterogeneous signatures / side effects. | ||
|
pggPL marked this conversation as resolved.
|
||
| obj = cls.__new__(cls) | ||
| for name, value in items: | ||
| if name == "dtype": | ||
| value = DType.cast(value) | ||
| object.__setattr__(obj, name, value) | ||
| # Restore non-value derived state that ``__init__`` would normally build but | ||
| # that cannot live in the value key (e.g. NVFP4's ``rht_matrix`` tensor). | ||
| finalize = getattr(obj, "_rebuild_derived_state", None) | ||
|
pggPL marked this conversation as resolved.
|
||
| if finalize is not None: | ||
| finalize() | ||
| return obj | ||
|
|
||
|
|
||
| def _quantizer_fx_repr(self: Any) -> Tuple[str, Dict[str, Any]]: | ||
| """``__fx_repr__`` for value-opaque quantizers (attached at registration). | ||
|
|
||
| Returns an evaluable expression that rebuilds the quantizer via | ||
| :func:`_rebuild_quantizer`, capturing both the helper and the quantizer | ||
| class itself in the FX globals so codegen can resolve them with no global | ||
| registry and no qualname collisions. | ||
|
|
||
| Raises ``TypeError`` (via :meth:`Quantizer._value_key`) if the quantizer | ||
| stores a process group (e.g. a non-``None`` deprecated | ||
| ``amax_reduction_group``): live distributed state must never be baked into | ||
| the graph as a constant. Pass the reduction group per quantize call instead | ||
| of storing it on the quantizer. | ||
| """ | ||
| cls = type(self) | ||
| items = self._value_key()[1] | ||
| return ( | ||
| f"_rebuild_quantizer({cls.__name__}, {items!r})", | ||
| {"_rebuild_quantizer": _rebuild_quantizer, cls.__name__: cls}, | ||
| ) | ||
|
|
||
|
|
||
| def register_value_opaque_quantizer(cls: type) -> None: | ||
| """Register a tensorless quantizer class as a torch.compile value opaque type. | ||
|
|
||
| This is the opt-in point for value semantics: it derives the value fields | ||
| from the class annotations and stores them on the class (enabling | ||
| config-based ``__eq__`` / ``__hash__``, see | ||
| :class:`transformer_engine.pytorch.quantized_tensor.Quantizer`), attaches | ||
| ``__fx_repr__`` and registers the class with | ||
| ``torch._library.opaque_object``. Safe to call on any PyTorch build: on | ||
| versions without the opaque-object API the value semantics still apply, | ||
| only the torch.compile specialization is skipped. | ||
|
|
||
| Only plain value types (``int``/``bool``/``float``/``str`` and enums) may | ||
| be annotated: anything else (derived tensors, process groups, containers) | ||
| cannot be hashed into the value key or rebuilt from its repr, so it must | ||
| be left unannotated and rebuilt in ``_rebuild_derived_state`` instead. | ||
| This runs once per class at import time, not in any hot path, so resolving | ||
| the annotation strings to real types is affordable. | ||
| """ | ||
| fields = cls._annotated_fields() | ||
| resolved = get_type_hints(cls) | ||
| for name in fields: | ||
| typ = resolved[name] | ||
| if typ not in (int, bool, float, str) and not ( | ||
| isinstance(typ, type) and issubclass(typ, enum.Enum) | ||
| ): | ||
| raise TypeError( | ||
| f"{cls.__name__} cannot be a torch.compile value quantizer: " | ||
| f"annotated field {name!r} ({typ!r}) is not a plain value type " | ||
| "(int/bool/float/str/enum). Remove the annotation and rebuild " | ||
| "the field in ``_rebuild_derived_state`` instead." | ||
| ) | ||
| cls._value_field_names = tuple(fields) | ||
| # ``register_opaque_type`` requires ``__fx_repr__`` to already exist on the | ||
| # class, so attach it before registering. | ||
| if "__fx_repr__" not in cls.__dict__: | ||
| cls.__fx_repr__ = _quantizer_fx_repr | ||
|
pggPL marked this conversation as resolved.
ptrendx marked this conversation as resolved.
|
||
|
|
||
| try: | ||
| from torch._library.opaque_object import ( # pylint: disable=import-outside-toplevel | ||
| register_opaque_type, | ||
| is_opaque_value_type, | ||
| ) | ||
| except (ImportError, AttributeError): | ||
| # Older PyTorch without the opaque-object API: eager value semantics | ||
| # still work; torch.compile specialization on the quantizer does not. | ||
| return | ||
|
|
||
| try: | ||
| if not is_opaque_value_type(cls): | ||
| register_opaque_type(cls, typ="value") | ||
| except (RuntimeError, TypeError): | ||
| # Keep TE importable: neither the opaque-type query nor the registration | ||
| # must crash the import, e.g. on PyTorch versions with only partial / | ||
| # experimental opaque-object support. | ||
| return | ||
|
|
||
| _VALUE_OPAQUE_QUALNAMES.add(cls.__qualname__) | ||
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.
Uh oh!
There was an error while loading. Please reload this page.