Skip to content

Serialize only a view constant's own bytes in XNNPACK and Vulkan - #24208

Open
pytorchbot wants to merge 1 commit into
release/1.6from
cherry-pick-24181-by-pytorch_bot_bot_
Open

pytorchbot wants to merge 1 commit into
release/1.6from
cherry-pick-24181-by-pytorch_bot_bot_

Conversation

@pytorchbot

Copy link
Copy Markdown
Collaborator

What is wrong today

A constant tensor can be a view into a larger tensor. For example:

self.register_buffer("weight", torch.randn(3, 8, 8)[1])   # starts 64 floats into its storage
...
return F.linear(x, self.weight)

Constant folding can also leave a weight as a view, like one chunk of the packed in_proj_weight of nn.MultiheadAttention in cross-attention.

The XNNPACK and Vulkan serializers copy the bytes of the whole underlying storage, starting at its first byte. For a view that starts later in the storage, the delegate gets the wrong weights, and the .pte gives wrong results with no error. In the model above, the largest XNNPACK error was about 6 to 8. For a view that starts at the beginning, the values are right, but the file carries the whole parent.

XNNPACK per-channel scales are read the same way. They also skip the contiguous() step that weights get, so a transposed groupwise scale is written in the wrong order.

What this change does

  • Before reading a weight's bytes, both serializers now clone it if its storage is larger than the tensor. The ExecuTorch emitter already uses this rule for weights that are views (exir/tensor.py, exir/emit/_emitter.py).
  • XNNPACK per-channel scales are small, so they are now always copied into contiguous storage of their own.
  • The Vulkan path that serializes raw tensors (only tests use it today) now does the same.

What was tested

On macOS arm64. Each of these fails before this change and passes after:

  • test_fp32_linear_view_weight (XNNPACK): a linear whose weight is a view.
  • test_qd8_linear_transposed_scale (XNNPACK): a groupwise int4 linear with a transposed scale.
  • test_view_constant_serializes_only_its_own_bytes (Vulkan graph builder): checks the bytes of a view constant, no GPU needed.
  • test_serialize_vulkan_binary (existing): now also serializes a view and a transposed view.

The default backends/xnnpack/test run passes. It skips test_linear.py, so that file was run directly: all tests pass except test_linear_qd8_{f32,f16}_per_channel_int4, which fail the same way without this change. A per-channel scale replaced by an equal view was also checked by hand: off by about 4e4 before, exact after.

Not in this change

_tensor_to_bytes in exir/_serialize/_named_data_store.py has a similar problem: for a non-contiguous tensor, such as a channels_last slice of a bigger tensor, it writes the whole storage. XNNPACK and Vulkan pass it plain bytes, so this change does not depend on it. It is left for a separate fix.

cc @SS-JIA @manuelcandales @digantdesai @cbilgin

)

## What is wrong today

A constant tensor can be a view into a larger tensor. For example:

```python
self.register_buffer("weight", torch.randn(3, 8, 8)[1])   # starts 64 floats into its storage
...
return F.linear(x, self.weight)
```

Constant folding can also leave a weight as a view, like one chunk of
the packed `in_proj_weight` of `nn.MultiheadAttention` in
cross-attention.

The XNNPACK and Vulkan serializers copy the bytes of the whole
underlying storage, starting at its first byte. For a view that starts
later in the storage, the delegate gets the wrong weights, and the .pte
gives wrong results with no error. In the model above, the largest
XNNPACK error was about 6 to 8. For a view that starts at the beginning,
the values are right, but the file carries the whole parent.

XNNPACK per-channel scales are read the same way. They also skip the
`contiguous()` step that weights get, so a transposed groupwise scale is
written in the wrong order.

## What this change does

- Before reading a weight's bytes, both serializers now clone it if its
storage is larger than the tensor. The ExecuTorch emitter already uses
this rule for weights that are views (`exir/tensor.py`,
`exir/emit/_emitter.py`).
- XNNPACK per-channel scales are small, so they are now always copied
into contiguous storage of their own.
- The Vulkan path that serializes raw tensors (only tests use it today)
now does the same.

## What was tested

On macOS arm64. Each of these fails before this change and passes after:
- `test_fp32_linear_view_weight` (XNNPACK): a linear whose weight is a
view.
- `test_qd8_linear_transposed_scale` (XNNPACK): a groupwise int4 linear
with a transposed scale.
- `test_view_constant_serializes_only_its_own_bytes` (Vulkan graph
builder): checks the bytes of a view constant, no GPU needed.
- `test_serialize_vulkan_binary` (existing): now also serializes a view
and a transposed view.

The default `backends/xnnpack/test` run passes. It skips
`test_linear.py`, so that file was run directly: all tests pass except
`test_linear_qd8_{f32,f16}_per_channel_int4`, which fail the same way
without this change. A per-channel scale replaced by an equal view was
also checked by hand: off by about 4e4 before, exact after.

## Not in this change

`_tensor_to_bytes` in `exir/_serialize/_named_data_store.py` has a
similar problem: for a non-contiguous tensor, such as a channels_last
slice of a bigger tensor, it writes the whole storage. XNNPACK and
Vulkan pass it plain bytes, so this change does not depend on it. It is
left for a separate fix.

cc @SS-JIA @manuelcandales @digantdesai @cbilgin

(cherry picked from commit b3bd402)
@pytorch-bot pytorch-bot Bot added the module: vulkan Issues related to the Vulkan delegate and code under backends/vulkan/ label Oct 27, 2026
@pytorch-bot

pytorch-bot Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

🔗 Helpful Links

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

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

❌ 1 New Failure

As of commit 8f7115f with merge base 4db778e (image):

NEW FAILURE - The following job has failed:

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 27, 2026

This branch was successfully deployed

1 active deployment
cadence — 8f7115f0 Deployed Oct 27, 2026 by pytorchbot via hifi-op-test / hifi4 #34878
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. module: vulkan Issues related to the Vulkan delegate and code under backends/vulkan/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants