Skip to content

Accept AOTI's compact clones of view buffers in the CUDA weight collector - #23355

Merged
shoumikhin merged 1 commit into
pytorch:mainfrom
shoumikhin:cuda-weight-collector-views
Oct 2, 2026
Merged

shoumikhin merged 1 commit into
pytorch:mainfrom
shoumikhin:cuda-weight-collector-views

Conversation

@shoumikhin

@shoumikhin shoumikhin commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

What is wrong today

When the CUDA backend compiles a model, AOTInductor hands back every constant as a pair: the tensor to save, and a TensorProperties record that describes it (storage size, offset, shape, strides). The CUDA weight collector writes the tensor's storage to the .ptd file and records that metadata so the runtime can rebuild the tensor.

AOTInductor clones every buffer into compact storage first. The clone covers only the bytes the buffer needs and starts at offset 0. But TensorProperties is built from the original buffer.

That breaks when a buffer is a view into a larger tensor:

big = torch.arange(32.0, device="cuda").reshape(8, 4)
self.register_buffer("w", big[2:5])  # 48 bytes, at offset 8 of a 128-byte storage

The clone is 48 bytes at offset 0, while TensorProperties says 128 bytes at offset 8. The collector compares the two, and lowering with CudaPartitioner fails:

RuntimeError: AOTI cloned storage is smaller than its TensorProperties (48 < 128 bytes)

A strided view fails in the bounds check instead, because its TensorProperties has no storage size. A column slice big[:, 1:3] of an 8x8 tensor fails with:

RuntimeError: AOTI view 'w' requires 236 bytes from a 232-byte cloned storage

In both cases the clone holds every byte the buffer needs. Only the metadata describes a different storage.

What this change does

When the tensor is AOTInductor's compact clone of the view (a different storage, with the same shape and strides as TensorProperties), the collector skips the comparison with the original storage size and takes the offset from the clone it writes. Any other tensor goes through the existing path unchanged.

The check that the view fits inside the bytes actually written still runs for every weight, so a truncated clone is still rejected. A compact clone is also checked to span its storage exactly, so a clone with bytes past its view is rejected too.

What was tested

  • New unit tests in test_cuda_partitioner.py: a clone of big[2:5] paired with the view's TensorProperties is written as 48 bytes at offset 0; a clone of the strided big[:, 1:3] is written as its 232-byte storage, byte for byte; a clone with too little storage or with bytes past its view is rejected; a parameter that slices a fused tensor and keeps its storage still records its own offset and storage size; a value with the same shape but other strides is not taken for a clone; and a tensor with another layout keeps the offset its TensorProperties records. The two clone tests fail before this change with the errors above.
  • On a CUDA GPU (Linux aarch64), the module above lowered with CudaPartitioner: before, the error above; after, it exports, and running the .pte with the ExecuTorch runtime gives exactly the eager output.
  • test_cuda_partitioner.py and test_cuda_weight_metadata.py pass with this change.

Copilot AI balanced review requested due to automatic review settings October 2, 2026 07:59
@pytorch-bot

pytorch-bot Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/23355

Note: Links to docs will display an error until the docs builds have been completed.

⏳ No Failures, 2 Pending

As of commit e1704e2 with merge base ecf5c39 (image):
💚 Looks good so far! There are no failures yet. 💚

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 2, 2026

Copilot AI left a comment

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.

Copilot review overview

🟡 Changes recommended

The truncation test must exercise the compact-clone path to protect its remaining bounds check against regressions.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Updates the CUDA weight collector to accept AOTInductor’s compact clones of view buffers while retaining storage-bounds validation.

Changes:

  • Detects matching clone layouts and uses the written tensor’s offset.
  • Adds compact-clone and layout-fallback regression tests.
File Description
backends/​cuda/​tests/​test_cuda_partitioner.py Adds clone, truncation, and offset-fallback tests.
backends/​cuda/​cuda_weight_collector.py Accepts compact clones while preserving view-bounds checks.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread backends/cuda/tests/test_cuda_partitioner.py Outdated
Copilot AI balanced review requested due to automatic review settings October 2, 2026 08:22
@shoumikhin
shoumikhin force-pushed the cuda-weight-collector-views branch from dd3a0d7 to 0103560 Compare October 2, 2026 08:22

Copilot AI left a comment

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.

Copilot review overview

🟢 Approval recommended

The focused change preserves bounds checks and existing shared-weight consistency rules.

Review effort: Balanced
Findings: None

Resolved since last review (1)

Copilot AI balanced review requested due to automatic review settings October 2, 2026 14:02
@shoumikhin
shoumikhin force-pushed the cuda-weight-collector-views branch from 0103560 to 51dcf12 Compare October 2, 2026 14:02

Copilot AI left a comment

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.

Copilot review overview

🟢 Approval recommended

The narrowly scoped change preserves bounds validation and has regression coverage for the affected paths.

Review effort: Balanced
Findings: None

@shoumikhin
shoumikhin force-pushed the cuda-weight-collector-views branch from 51dcf12 to d609fa9 Compare October 2, 2026 14:41
Copilot AI balanced review requested due to automatic review settings October 2, 2026 14:41

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

storages: Dict[str, FileBackedData] = {}

for fqn, (tensor, properties) in weights.items():
is_offgraph_kv = _is_offgraph_kv_fqn(fqn)

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.

Why would offgraph kv be in the weights fqn?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That check isn't new in this change. It came with the off-graph KV lowering (#23292). That pass puts its cache buffers into the program as constants named __et_offgraph_kv_*, so they reach the weight collector together with the real weights. The collector skips them because their data is managed elsewhere. This change only moves that existing line up, so the new check can use it too.

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.

It is for removing kvcache buffer from weight: once we detected is_offgraph_kv we will remove the buffer from ptd to make runtime control it.

getattr(properties, "storage_size", None) or 0
)
if not is_offgraph_kv and storage_nbytes < expected_storage_nbytes:
if (

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.

so basically iiuc if you have a fused weight aot like qkv aoti wants to split them into q k and v and there was some metadata getting trashed in this process that you fix? @shoumikhin

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Close, but it's about views rather than splitting. Take a buffer that is a view of a bigger tensor, like big[2:5]. AOTInductor copies it into a compact clone: 48 bytes, starting at offset 0. But the TensorProperties it reports still describe the original: 128 bytes at offset 8. The collector compared the two and rejected the export, even though the clone holds every byte the view needs. A fused QKV weight split into views is one way to get such a buffer, so yes, that's a real-world case.

The fix uses the clone's own size and offset when it's a compact clone with the same shape and strides. The bounds check still runs, so a truncated clone is still rejected.

)
required_nbytes = _required_view_nbytes(
fqn, sizes, strides, storage_offset, tensor.element_size()
)

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.

can we add a check here that the required_bytes is the same as the tensor.nbytes()?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added, slightly adjusted: a compact clone must now span exactly its storage. Checking against tensor.nbytes() would reject strided views. For example, a column slice like big[:, 1:3] of an 8x8 tensor has 64 bytes of elements, but its clone spans 232 bytes, because the clone keeps the gaps between rows. A new test covers a clone with extra bytes past its view, which is now rejected, and a strided view, which is still accepted.

@shoumikhin
shoumikhin force-pushed the cuda-weight-collector-views branch from d609fa9 to d812bb0 Compare October 2, 2026 19:18
Copilot AI balanced review requested due to automatic review settings October 2, 2026 19:18

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI balanced review requested due to automatic review settings October 2, 2026 21:38
@shoumikhin
shoumikhin force-pushed the cuda-weight-collector-views branch from d812bb0 to 5339a7a Compare October 2, 2026 21:38

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI balanced review requested due to automatic review settings October 2, 2026 22:09
@shoumikhin
shoumikhin force-pushed the cuda-weight-collector-views branch from 5339a7a to 1542b00 Compare October 2, 2026 22:09

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…ctor

AOTInductor (AOTI) clones every buffer into compact storage that starts at
the view, but builds the buffer's TensorProperties from the original. When a
buffer is a view into a larger tensor, the CUDA weight collector judged the
clone by the original storage and rejected the export: a contiguous view
failed the storage size check ("cloned storage is smaller than its
TensorProperties"), and a strided view, whose TensorProperties has no storage
size, failed the bounds check ("requires 236 bytes from a 232-byte cloned
storage"). Yet the clone holds every byte the view needs.

When the value is such a clone (a different storage, with the same shape and
strides as its TensorProperties), skip the storage size check and take the
offset from the clone that is written. The clone must also span its storage
exactly. Any other value takes the existing path. The check that the view
fits inside the written bytes still runs, so a truncated clone is still
rejected.
Copilot AI balanced review requested due to automatic review settings October 2, 2026 22:14
@shoumikhin
shoumikhin force-pushed the cuda-weight-collector-views branch from 1542b00 to e1704e2 Compare October 2, 2026 22:14

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin
shoumikhin merged commit fbf91c2 into pytorch:main Oct 2, 2026
249 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. release notes: backends

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants