Skip to content

[PyTorch][torch.compile] Add TensorProto mechanism#3153

Open
pggPL wants to merge 16 commits into
NVIDIA:mainfrom
pggPL:tensor_proto_mechanism
Open

[PyTorch][torch.compile] Add TensorProto mechanism#3153
pggPL wants to merge 16 commits into
NVIDIA:mainfrom
pggPL:tensor_proto_mechanism

Conversation

@pggPL

@pggPL pggPL commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR introduces TensorProto — a data-free prototype of a tensor (or quantized tensor) that captures everything needed to reason about and rebuild a tensor without holding any storage: its logical shape/dtype and, for quantized tensors, the value-opaque quantizer defining the layout.

The key property is that TensorProto.create_tensor() materializes a quantized tensor purely in Python (via Quantizer.alloc_tensors + the storage's __tensor_unflatten__), so it traces under torch.compile(fullgraph=True) with no graph break — unlike make_empty, which goes through the opaque C++ tex.create_empty_quantized_tensor. This is the foundation for writing torch.library custom-op fake implementations of quantized ops.

This builds on the value-opaque quantizer work (so a TensorProto is itself safe to treat as a compile-time constant).

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

  • dynamo.py: Add TensorProto dataclass (shape, dtype, quantizer, requires_grad, device) with is_quantized, inner_names(), create_metadata() and create_tensor(), plus a to_tensor_proto() helper that builds a proto from a plain torch.Tensor or a QuantizedTensorStorage/QuantizedTensor.
  • quantized_tensor.py:
    • Add the PyTorch wrapper-subclass flatten protocol (__tensor_flatten__ / __tensor_unflatten__) to QuantizedTensorStorage, driven by a per-class _FLATTEN_TENSOR_BUFFERS declaration of (attribute_name, constructor_kwarg) pairs.
    • Add a _STORAGE_REGISTRY (populated via __init_subclass__) so __tensor_unflatten__ can resolve a concrete storage/wrapper class from its qualname inside an FX graph.
    • Add pure-Python, traceable allocation hooks to Quantizer: alloc_tensors, create_metadata, and the opt-in overrides _describe_buffers, _storage_scalars, _resolve_storage_cls.
  • Quantizers: Implement the allocation hooks for Float8CurrentScalingQuantizer, MXFP8Quantizer and Float8BlockQuantizer.
  • Storage classes: Declare _FLATTEN_TENSOR_BUFFERS for Float8TensorStorage, MXFP8TensorStorage and Float8BlockwiseQTensorStorage.
  • ops/basic/basic_linear.py: Add allocation-free _functional_forward_fake / _functional_backward_fake that operate on TensorProto and return output/gradient protos, as a basis for custom-op fake impls (single-device only; TP/SP shape effects not yet modeled).
  • Tests: Add tests/pytorch/test_tensor_proto.py (CPU smoke tests for _describe_buffers/alloc_tensors/create_metadata, flatten round-trip, and to_tensor_proto) and torch.compile fullgraph tests in test_torch_compile.py.

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

@pggPL pggPL requested a review from ksivaman as a code owner June 29, 2026 09:39
@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces TensorProto — a data-free prototype of a tensor or quantized tensor — as infrastructure for torch.library custom-op fake implementations that trace under torch.compile(fullgraph=True). It also adds the PyTorch subclass flatten/unflatten protocol to QuantizedTensorStorage and pure-Python traceable allocation hooks to Quantizer.

  • TensorProto (dynamo/tensor_proto.py): new dataclass capturing shape, dtype, and an optional value-opaque quantizer; create_tensor() materializes a quantized tensor via alloc_tensors + __tensor_unflatten__ entirely in Python, enabling fullgraph tracing.
  • QuantizedTensorStorage flatten protocol (quantized_tensor.py): adds _STORAGE_REGISTRY (via __init_subclass__), _FLATTEN_TENSOR_BUFFERS on all four storage classes, and a shared __tensor_flatten__ / __tensor_unflatten__ implementation.
  • Fake linear impls (module/linear.py): allocation-free shape-only twins returning TensorProto descriptors; not yet registered as custom-op fake impls (follow-up PR), plus correctness fix capturing requires_grad into args rather than reading from live tensors under compile.

Confidence Score: 5/5

Safe to merge. Core allocation and flatten/unflatten paths are exercised by comprehensive CPU smoke tests including fullgraph compile checks.

Structural parity tests validate that Python-allocated tensors match C++ make_empty in buffer layout and kernel output. The only outstanding items are a private PyTorch API import and implicit stride forwarding via **kwargs, both non-blocking style issues. The fake forward/backward impls are intentionally incomplete and cannot be called incorrectly in the current state.

transformer_engine/pytorch/dynamo/tensor_proto.py (private API import) and transformer_engine/pytorch/quantized_tensor.py (tensor_unflatten stride forwarding) are worth a second look before the follow-up custom-op registration PR lands.

Important Files Changed

Filename Overview
transformer_engine/pytorch/dynamo/tensor_proto.py New TensorProto dataclass with create_tensor/inner_names/create_metadata; uses private torch._prims_common.make_contiguous_strides_for where the tests use the public meta-device alternative.
transformer_engine/pytorch/quantized_tensor.py Adds _STORAGE_REGISTRY, init_subclass registration, tensor_flatten/tensor_unflatten protocol, and Quantizer allocation primitives; stride forwarding via **kwargs is implicit.
transformer_engine/pytorch/module/linear.py Adds _linear_forward_impl_fake/_linear_backward_impl_fake, fixes requires_grad capture, and extends weight alias dedup; fake impls not yet registered.
transformer_engine/pytorch/tensor/nvfp4_tensor.py Adds _storage_metadata and _describe_buffers for NVFP4Quantizer; uses type(self).convert_shape_for_fp4 to avoid torch.compile guard issues.
transformer_engine/pytorch/tensor/storage/nvfp4_tensor_storage.py Declares _FLATTEN_TENSOR_BUFFERS for all six NVFP4 buffers including amax buffers.
tests/pytorch/test_torch_compile.py Comprehensive tests for allocation primitives, flatten/unflatten roundtrip, structural and functional parity with C++ make_empty, and TensorProto API.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User as User / torch.compile
    participant TP as TensorProto
    participant Q as Quantizer
    participant SR as _STORAGE_REGISTRY
    participant S as QuantizedTensorStorage

    User->>TP: TensorProto(shape, dtype, quantizer)
    TP->>Q: copy() quantizer
    User->>TP: create_tensor()
    TP->>Q: create_metadata(shape, dtype)
    Q-->>TP: "ctx {cls: qualname, nontensor_kwargs}"
    TP->>Q: alloc_tensors(shape, device)
    Q->>Q: _describe_buffers(shape)
    Q-->>TP: "{attr: torch.empty(...)}"
    TP->>TP: inner_names() ordered attr list
    TP->>SR: _STORAGE_REGISTRY[ctx[cls]]
    SR-->>TP: storage_cls
    TP->>S: storage_cls.__tensor_unflatten__(inner, ctx, shape, stride)
    S-->>User: QuantizedTensor / QuantizedTensorStorage
    User->>S: __tensor_flatten__()
    S->>S: _FLATTEN_TENSOR_BUFFERS present attrs
    S->>S: _flatten_nontensor_kwargs() via get_metadata()
    S-->>User: (names, ctx)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User as User / torch.compile
    participant TP as TensorProto
    participant Q as Quantizer
    participant SR as _STORAGE_REGISTRY
    participant S as QuantizedTensorStorage

    User->>TP: TensorProto(shape, dtype, quantizer)
    TP->>Q: copy() quantizer
    User->>TP: create_tensor()
    TP->>Q: create_metadata(shape, dtype)
    Q-->>TP: "ctx {cls: qualname, nontensor_kwargs}"
    TP->>Q: alloc_tensors(shape, device)
    Q->>Q: _describe_buffers(shape)
    Q-->>TP: "{attr: torch.empty(...)}"
    TP->>TP: inner_names() ordered attr list
    TP->>SR: _STORAGE_REGISTRY[ctx[cls]]
    SR-->>TP: storage_cls
    TP->>S: storage_cls.__tensor_unflatten__(inner, ctx, shape, stride)
    S-->>User: QuantizedTensor / QuantizedTensorStorage
    User->>S: __tensor_flatten__()
    S->>S: _FLATTEN_TENSOR_BUFFERS present attrs
    S->>S: _flatten_nontensor_kwargs() via get_metadata()
    S-->>User: (names, ctx)
Loading

Reviews (13): Last reviewed commit: "Merge branch 'main' of https://github.co..." | Re-trigger Greptile

Comment thread transformer_engine/pytorch/tensor/mxfp8_tensor.py Outdated
Comment thread transformer_engine/pytorch/dynamo/tensor_proto.py Outdated
@pggPL pggPL force-pushed the tensor_proto_mechanism branch 8 times, most recently from 9e78a6c to 50c11cd Compare June 29, 2026 13:46
Comment thread transformer_engine/pytorch/tensor/nvfp4_tensor.py Outdated
pggPL and others added 5 commits July 7, 2026 11:54
Squashed PR #8 (tensor_proto_mechanism) onto the rebased base. Adds TensorProto
(pure-Python, torch.compile-traceable quantized-tensor allocation via
Quantizer.alloc_tensors + storage __tensor_flatten__/__tensor_unflatten__),
Linear fake fwd/bwd impls for the custom-op path, and tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
The cached FP8 weight is the same tensor returned as new_weight_workspace (cache miss) or passed in as weight_workspace (cache hit). A custom op may not return a tensor that aliases an input or another return, so mark those slots and reconstruct wt_save in _linear_setup_ctx instead of saving it twice. Mirrored in the fake impl so the saved-slot layout matches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
NVFP4Quantizer._describe_buffers grouped each amax right after its scale (per-usage), diverging from NVFP4TensorStorage._FLATTEN_TENSOR_BUFFERS (amax buffers last). The order is functionally irrelevant (buffers are consumed by name in alloc_tensors and reordered in TensorProto.inner_names), but aligning it makes describe/flatten agree and fixes test_to_tensor_proto_quantized[nvfp4].

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…upport

- TensorProto.inner_names now raises if the quantizer describes buffer(s) absent
  from the storage's _FLATTEN_TENSOR_BUFFERS, instead of silently appending them.
- Gate the nvfp4 proto-quantizer param on nvfp4_available so it skips on hardware
  without NVFP4 support rather than failing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…escribe_buffers

Access NVFP4Quantizer @staticmethods (convert_shape_for_fp4, get_columnwise_shape)
via the class instead of the instance. Under torch.compile, instance access of a
@staticmethod on a value-opaque object crashes Dynamo guard generation with
"'function' object has no attribute '__func__'" (pytorch/pytorch#182741).
Temporary workaround until the PyTorch-side fix lands.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
@pggPL pggPL force-pushed the tensor_proto_mechanism branch from ff48e52 to e36cf6d Compare July 7, 2026 10:04
Comment thread transformer_engine/pytorch/tensor/nvfp4_tensor.py
pggPL added 10 commits July 13, 2026 10:47
The union is intentional: fields may carry bare QuantizedTensorStorage
objects (internal-quantizer optimization), and the annotation is
introspected in the follow-up custom-op PR to build the op schema with
flatten/unflatten slots. Also note the size()/.shape asymmetry and how
TensorProto handles it.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Make .shape valid on bare storages (derived from size()), so Tensor,
QuantizedTensor, bare storage and TensorProto all expose the same
attribute. Wrapper subclasses defer to the native TensorBase.shape.
Simplifies the shape fallback in to_tensor_proto.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Address review: build the same quantized tensor via make_empty (C++,
tex.create_empty_quantized_tensor) and via the Python primitives
(_describe_buffers + create_metadata + alloc_tensors +
__tensor_unflatten__) and check structural parity (class, buffer set,
per-buffer shape/dtype/device, flatten context) and functional parity
(the real quantize kernel writes bit-identical results into both,
dequantize matches), across quantizer families x rowwise/columnwise
x wrapper/internal.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Address review: the change stands on its own as a correctness fix;
drop the detailed (and imprecise) fake-impl/cudagraph justification.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Address review: wt_save is known non-None past the first branch, so
'X is not None and wt_save is X' reduces to 'wt_save is X'.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Address review: replace the local _contiguous_stride helper with the
torch one (stable at this path since v1.13); it also matches the ATen
contiguous-stride convention for zero-size dims and handles SymInts.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Address review: a silent no-op diverges from the real object's
behavior (plain torch.Tensor has no update_usage), which is exactly
the class of fake/real mismatches the proto is meant to avoid.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Address review: after the QuantizedTensorStorage.shape property the
storage and plain-tensor paths differed only in getattr fallbacks
(dtype/_dtype, _quantizer), which work uniformly for all input kinds;
drop the isinstance branch and the local import it needed.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Address review: the saved_weight slot is unconditionally aliased to
the weight parameter in forward, so it is never None in backward.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Comment on lines +89 to +90
# machine-read when the args bag becomes a torch.compile custom op (follow-up
# PR): such fields get flatten/unflatten slots so a bare storage can cross the

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Note: We will update this comment as part of the custom op PR.

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