Skip to content

Wire batch-norm fusion into CpuBackend.preprocess (#24193) - #24193

Merged
JakeStevens merged 4 commits into
pytorch:mainfrom
JakeStevens:export-D126784606
Oct 27, 2026
Merged

JakeStevens merged 4 commits into
pytorch:mainfrom
JakeStevens:export-D126784606

Conversation

@JakeStevens

@JakeStevens JakeStevens commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary:

Run the shared FuseBatchNormWithConvPass and FuseBatchNormWithLinearPass in CpuBackend.preprocess. Unfused _native_batch_norm_legit_no_training already executes through the reference provider, but as a separate kernel with its own serialized weight and stats; folding it into the preceding conv/linear removes that kernel from the delegate. The passes are listed in CPU_PREPROCESS_PASSES (mirroring XNNPACKPassManager.passes) so every lowering flow gets them from one extension point.

Fusion introduces get_attr constants, so preprocess follows with lift_constant_tensor_pass (the native serializer rejects unlifted constants), then drops parameter/buffer/constant inputs left without users. Without that step the delegate serialized the original weights and batch norm stats next to the fused ones.

Authored with Claude Code.

Reviewed By: digantdesai

Differential Revision: D126784606

@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/24193

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

❌ 2 New Failures, 50 Pending

As of commit 63d563f with merge base 5723674 (image):

NEW FAILURES - The following jobs have 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
@meta-codesync

meta-codesync Bot commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

@JakeStevens has exported this pull request. If you are a Meta employee, you can view the originating Diff in D126784606.

@digantdesai digantdesai 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.

Review automatically exported from Phabricator review in Meta.

@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

@meta-codesync meta-codesync Bot changed the title Wire batch-norm fusion into CpuBackend.preprocess Wire batch-norm fusion into CpuBackend.preprocess (#24193) Oct 27, 2026
JakeStevens added a commit to JakeStevens/executorch that referenced this pull request Oct 27, 2026
Summary:

Run the shared `FuseBatchNormWithConvPass` and `FuseBatchNormWithLinearPass` in `CpuBackend.preprocess`. Unfused `_native_batch_norm_legit_no_training` already executes through the reference provider, but as a separate kernel with its own serialized weight and stats; folding it into the preceding conv/linear removes that kernel from the delegate. The passes are listed in `CPU_PREPROCESS_PASSES` (mirroring `XNNPACKPassManager.passes`) so every lowering flow gets them from one extension point.

Fusion introduces `get_attr` constants, so preprocess follows with `lift_constant_tensor_pass` (the native serializer rejects unlifted constants), then drops parameter/buffer/constant inputs left without users. Without that step the delegate serialized the original weights and batch norm stats next to the fused ones.

Authored with Claude Code.

Reviewed By: digantdesai

Differential Revision: D126784606
JakeStevens added a commit to JakeStevens/executorch that referenced this pull request Oct 27, 2026
Summary:

Run the shared `FuseBatchNormWithConvPass` and `FuseBatchNormWithLinearPass` in `CpuBackend.preprocess`. Unfused `_native_batch_norm_legit_no_training` already executes through the reference provider, but as a separate kernel with its own serialized weight and stats; folding it into the preceding conv/linear removes that kernel from the delegate. The passes are listed in `CPU_PREPROCESS_PASSES` (mirroring `XNNPACKPassManager.passes`) so every lowering flow gets them from one extension point.

Fusion introduces `get_attr` constants, so preprocess follows with `lift_constant_tensor_pass` (the native serializer rejects unlifted constants), then drops parameter/buffer/constant inputs left without users. Without that step the delegate serialized the original weights and batch norm stats next to the fused ones.

Authored with Claude Code.

Reviewed By: digantdesai

Differential Revision: D126784606
JakeStevens added a commit to JakeStevens/executorch that referenced this pull request Oct 27, 2026
Summary:

Run the shared `FuseBatchNormWithConvPass` and `FuseBatchNormWithLinearPass` in `CpuBackend.preprocess`. Unfused `_native_batch_norm_legit_no_training` already executes through the reference provider, but as a separate kernel with its own serialized weight and stats; folding it into the preceding conv/linear removes that kernel from the delegate. The passes are listed in `CPU_PREPROCESS_PASSES` (mirroring `XNNPACKPassManager.passes`) so every lowering flow gets them from one extension point.

Fusion introduces `get_attr` constants, so preprocess follows with `lift_constant_tensor_pass` (the native serializer rejects unlifted constants), then drops parameter/buffer/constant inputs left without users. Without that step the delegate serialized the original weights and batch norm stats next to the fused ones.

Authored with Claude Code.

Reviewed By: digantdesai

Differential Revision: D126784606
Summary:

CPU partitioning and delegate I/O required every tensor to be FP32, leaving indices, masks and integer outputs outside the delegate. Accept static uint8, int8, int16, int32, int64 and bool tensors, and validate runtime I/O against the serialized dtype. Existing allocation and ET kernel routes preserve the values without conversion through FP32.

Integer delegation also exposes `index.Tensor`/`index_put`, whose indices are `Tensor?[]`. Native serialization emits a plain tensor list when every index is present, but ET's kernels unbox a list of optional tensors. Mark those Reference routes with `optional_tensor_lists` so the CPU runtime binds optional lists for them; the shared serializer and the other Native engines are unchanged. Add exact runtime regressions for integer precision, indices, mixed outputs, constants and optional index lists. Dynamic shapes/scalars and quantized execution remain separate. The Native schema and storage contract do not change.

Authored with AI assistance using Codex and Claude Code.

Reviewed By: digantdesai

Differential Revision: D123332329
Summary:

Adds `FuseBatchNormWithLinearPass` to `backends/transforms`, the linear counterpart of `FuseBatchNormWithConvPass`. It folds an inference batch norm into the preceding `aten.linear` weight and bias using `torch.nn.utils.fusion.fuse_linear_bn_weights`, so backends can drop the BN without serializing extra weights.

Fusion only happens when it is numerically equivalent: the linear output must be rank 2 (the only case where BN's channel dim is linear's output dim), every weight/bias/running-stat input must be a constant parameter, BN must only be consumed through `getitem(0)`, and training-mode `native_batch_norm` is skipped.

Authored with Claude Code.

Reviewed By: digantdesai

Differential Revision: D125532471
Summary:

Run the shared `FuseBatchNormWithConvPass` and `FuseBatchNormWithLinearPass` in `CpuBackend.preprocess`. Unfused `_native_batch_norm_legit_no_training` already executes through the reference provider, but as a separate kernel with its own serialized weight and stats; folding it into the preceding conv/linear removes that kernel from the delegate. The passes are listed in `CPU_PREPROCESS_PASSES` (mirroring `XNNPACKPassManager.passes`) so every lowering flow gets them from one extension point.

Fusion introduces `get_attr` constants, so preprocess follows with `lift_constant_tensor_pass` (the native serializer rejects unlifted constants), then drops parameter/buffer/constant inputs left without users. Without that step the delegate serialized the original weights and batch norm stats next to the fused ones.

Authored with Claude Code.

Reviewed By: digantdesai

Differential Revision: D126784606
JakeStevens added a commit to JakeStevens/executorch that referenced this pull request Oct 27, 2026
Summary:

Run the shared `FuseBatchNormWithConvPass` and `FuseBatchNormWithLinearPass` in `CpuBackend.preprocess`. Unfused `_native_batch_norm_legit_no_training` already executes through the reference provider, but as a separate kernel with its own serialized weight and stats; folding it into the preceding conv/linear removes that kernel from the delegate. The passes are listed in `CPU_PREPROCESS_PASSES` (mirroring `XNNPACKPassManager.passes`) so every lowering flow gets them from one extension point.

Fusion introduces `get_attr` constants, so preprocess follows with `lift_constant_tensor_pass` (the native serializer rejects unlifted constants), then drops parameter/buffer/constant inputs left without users. Without that step the delegate serialized the original weights and batch norm stats next to the fused ones.

Authored with Claude Code.

Reviewed By: digantdesai

Differential Revision: D126784606
@JakeStevens
JakeStevens force-pushed the export-D126784606 branch 2 times, most recently from 77f8a3b to 42720b9 Compare October 27, 2026 16:08
JakeStevens added a commit to JakeStevens/executorch that referenced this pull request Oct 27, 2026
Summary:

Run the shared `FuseBatchNormWithConvPass` and `FuseBatchNormWithLinearPass` in `CpuBackend.preprocess`. Unfused `_native_batch_norm_legit_no_training` already executes through the reference provider, but as a separate kernel with its own serialized weight and stats; folding it into the preceding conv/linear removes that kernel from the delegate. The passes are listed in `CPU_PREPROCESS_PASSES` (mirroring `XNNPACKPassManager.passes`) so every lowering flow gets them from one extension point.

Fusion introduces `get_attr` constants, so preprocess follows with `lift_constant_tensor_pass` (the native serializer rejects unlifted constants), then drops parameter/buffer/constant inputs left without users. Without that step the delegate serialized the original weights and batch norm stats next to the fused ones.

Authored with Claude Code.

Reviewed By: digantdesai

Differential Revision: D126784606
JakeStevens added a commit to JakeStevens/executorch that referenced this pull request Oct 27, 2026
Summary:

Run the shared `FuseBatchNormWithConvPass` and `FuseBatchNormWithLinearPass` in `CpuBackend.preprocess`. Unfused `_native_batch_norm_legit_no_training` already executes through the reference provider, but as a separate kernel with its own serialized weight and stats; folding it into the preceding conv/linear removes that kernel from the delegate. The passes are listed in `CPU_PREPROCESS_PASSES` (mirroring `XNNPACKPassManager.passes`) so every lowering flow gets them from one extension point.

Fusion introduces `get_attr` constants, so preprocess follows with `lift_constant_tensor_pass` (the native serializer rejects unlifted constants), then drops parameter/buffer/constant inputs left without users. Without that step the delegate serialized the original weights and batch norm stats next to the fused ones.

Authored with Claude Code.

Reviewed By: digantdesai

Differential Revision: D126784606
JakeStevens added a commit to JakeStevens/executorch that referenced this pull request Oct 27, 2026
Summary:

Run the shared `FuseBatchNormWithConvPass` and `FuseBatchNormWithLinearPass` in `CpuBackend.preprocess`. Unfused `_native_batch_norm_legit_no_training` already executes through the reference provider, but as a separate kernel with its own serialized weight and stats; folding it into the preceding conv/linear removes that kernel from the delegate. The passes are listed in `CPU_PREPROCESS_PASSES` (mirroring `XNNPACKPassManager.passes`) so every lowering flow gets them from one extension point.

Fusion introduces `get_attr` constants, so preprocess follows with `lift_constant_tensor_pass` (the native serializer rejects unlifted constants), then drops parameter/buffer/constant inputs left without users. Without that step the delegate serialized the original weights and batch norm stats next to the fused ones.

Authored with Claude Code.

Reviewed By: digantdesai

Differential Revision: D126784606
JakeStevens added a commit to JakeStevens/executorch that referenced this pull request Oct 27, 2026
Summary:

Run the shared `FuseBatchNormWithConvPass` and `FuseBatchNormWithLinearPass` in `CpuBackend.preprocess`. Unfused `_native_batch_norm_legit_no_training` already executes through the reference provider, but as a separate kernel with its own serialized weight and stats; folding it into the preceding conv/linear removes that kernel from the delegate. The passes are listed in `CPU_PREPROCESS_PASSES` (mirroring `XNNPACKPassManager.passes`) so every lowering flow gets them from one extension point.

Fusion introduces `get_attr` constants, so preprocess follows with `lift_constant_tensor_pass` (the native serializer rejects unlifted constants), then drops parameter/buffer/constant inputs left without users. Without that step the delegate serialized the original weights and batch norm stats next to the fused ones.

Authored with Claude Code.

Reviewed By: digantdesai

Differential Revision: D126784606
@JakeStevens
JakeStevens merged commit 5272830 into pytorch:main Oct 27, 2026
212 of 215 checks passed
JakeStevens added a commit that referenced this pull request Oct 27, 2026
`backends/transforms/targets.bzl` defines the
`fuse_batch_norm_with_linear` `python_library` twice (lines 138 and
152). The first copy comes from #24192. The second came in with the
squash of #24193, whose branch picked up the block again when it was
merged with `main`.

Buck refuses to register the same target twice, so any command that
loads `//backends/transforms/...` fails:

```
error: Attempted to register target root//backends/transforms:fuse_batch_norm_with_linear twice
```

This breaks `unittest-buck` on `main` and on open PRs.

This change deletes the second, identical definition. No other targets
in the file are duplicated.

### Test plan
- Checked that `targets.bzl` now defines each target name once.
- CI: `unittest-buck` should get past the `buck2 query` step.
Erik-Lundell pushed a commit to Erik-Lundell/executorch that referenced this pull request Oct 28, 2026
…torch#24193)

Summary:

Run the shared `FuseBatchNormWithConvPass` and
`FuseBatchNormWithLinearPass` in `CpuBackend.preprocess`. Unfused
`_native_batch_norm_legit_no_training` already executes through the
reference provider, but as a separate kernel with its own serialized
weight and stats; folding it into the preceding conv/linear removes that
kernel from the delegate. The passes are listed in
`CPU_PREPROCESS_PASSES` (mirroring `XNNPACKPassManager.passes`) so every
lowering flow gets them from one extension point.

Fusion introduces `get_attr` constants, so preprocess follows with
`lift_constant_tensor_pass` (the native serializer rejects unlifted
constants), then drops parameter/buffer/constant inputs left without
users. Without that step the delegate serialized the original weights
and batch norm stats next to the fused ones.

Authored with Claude Code.

Reviewed By: digantdesai

Differential Revision: D126784606
Erik-Lundell pushed a commit to Erik-Lundell/executorch that referenced this pull request Oct 28, 2026
`backends/transforms/targets.bzl` defines the
`fuse_batch_norm_with_linear` `python_library` twice (lines 138 and
152). The first copy comes from pytorch#24192. The second came in with the
squash of pytorch#24193, whose branch picked up the block again when it was
merged with `main`.

Buck refuses to register the same target twice, so any command that
loads `//backends/transforms/...` fails:

```
error: Attempted to register target root//backends/transforms:fuse_batch_norm_with_linear twice
```

This breaks `unittest-buck` on `main` and on open PRs.

This change deletes the second, identical definition. No other targets
in the file are duplicated.

### Test plan
- Checked that `targets.bzl` now defines each target name once.
- CI: `unittest-buck` should get past the `buck2 query` step.
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. meta-exported

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants