Skip to content

Support 3-D convolution in the portable convolution kernel - #24385

Open
shoumikhin wants to merge 25 commits into
pytorch:mainfrom
shoumikhin:portable-conv3d
Open

shoumikhin wants to merge 25 commits into
pytorch:mainfrom
shoumikhin:portable-conv3d

Conversation

@shoumikhin

@shoumikhin shoumikhin commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

What is wrong today

A model with torch.nn.Conv3d lowers without an error but fails at run time on CPU, for example with XNNPACK:

Check failed (in.dim() == 3 || in.dim() == 4): Expect input tensor to be 3-D or 4-D, but got, 5.

XNNPACK does not take 3-D or bfloat16 convolutions, so they run on the portable convolution kernel, which only supported 1-D and 2-D. This blocks vision encoders with a Conv3d patch embedding, such as Qwen2-VL and Qwen2.5-VL. It is the failure in #11704.

What this change does

The portable convolution kernel now runs 3-D convolutions, regular and transposed. 1-D and 2-D convolutions run as 3-D ones with extra dims of size 1 and add up their terms in the same order as before, so their results stay the same (except that, with fused multiply-add, a result of -0 can now be +0, as in PyTorch). The input and output may now have different dim orders, because torch.export gives a Conv3d with a channels-last input a contiguous output. The backend test suite no longer skips 3-D convolutions.

It also fixes older bugs in the argument checks, which affect 1-D and 2-D too:

  • A bias of the wrong length was read past its end (missing parentheses), and a Bool bias crashed the kernel.
  • groups = 0 divided by zero, and a huge groups could overflow in the bias check.
  • A huge padding or dilation, or an output size too large for the 32-bit tensor sizes, let a call that should fail run. Stride, padding and dilation must now fit in 32 bits.
  • Like PyTorch, the checks now reject a kernel dim of 0, a regular convolution's kernel larger than the padded input, and an empty spatial dim unless the batch or channel dim is empty too.

Testing

  • New tests cover grouped regular and transposed 3-D convolutions with contiguous, channels-last and mixed layouts, and the checks above. All but one fail without this change; that one checks that an empty batch or channel dim still works.
  • The portable kernel tests pass with AddressSanitizer and UndefinedBehaviorSanitizer on Linux Arm64.
  • The Cortex-M conv3d test, now with several channels, a 2x3x3 kernel, stride and padding, no longer expects a failure and passes in CI on the Arm simulator.
  • A Conv3d patch embedding lowered with XNNPACK, and random convolutions on the portable kernel, match eager PyTorch in float32 and bfloat16.
  • Speed against main on Arm64 and x86-64: regular 1-D and 2-D convolutions are 1.5 to 13 times faster with GCC and with Clang at -Os, and 18 percent faster to 11 percent slower with Clang at -O2 and -O3. Transposed ones are 5 percent faster to 17 percent slower.
  • Code size for a Cortex-M55 (Arm GNU toolchain 15.2): op_convolution.o grows from 32,484 to 33,263 bytes at -Os and from 48,477 to 61,457 at -O3. kernel_ops_util.o, which holds the argument checks, grows from 15,746 to 17,743 bytes at -Os and from 25,932 to 28,979 at -O3.

Notes

@shoumikhin shoumikhin added the release notes: ops & kernels Changes to the opset and any new / changed kernel implementations label Oct 31, 2026
@shoumikhin
shoumikhin requested a review from rascani as a code owner October 31, 2026 13:41
@shoumikhin shoumikhin added the release notes: ops & kernels Changes to the opset and any new / changed kernel implementations label Oct 31, 2026
Copilot AI balanced review requested due to automatic review settings October 31, 2026 13:41
@pytorch-bot

pytorch-bot Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

🔗 Helpful Links

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

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

⏳ No Failures, 189 Pending

As of commit 33241ab with merge base e68a824 (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 31, 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 was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@shoumikhin

Copy link
Copy Markdown
Contributor Author

@claude /pr-review

@claude

claude Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Claude finished @shoumikhin's task in 32m 34s —— View job


PR Review: #24385

Needs Discussion: the PR removes two helpers from a public util target, multiplies by groups before validating it, turns a loud XNNPACK int8 Conv3d failure into silent zeros with no tracking issue, leaves gaps in the 3-D tests, and hasn't measured its code size on an MCU toolchain.

Detailed review

Summary

The PR sends 1-D, 2-D and the new 3-D convolutions through a single 3-D implementation in the portable convolution.out kernel. Along the way it fixes the bias-length check and rejects groups < 1. The reviewers found no indexing bugs in the new loops. The problems are a public-helper removal, an overflow caused by check ordering, an untracked int8 regression, two test gaps, and an MCU size cost nobody has measured.

Code Quality

  • Known int8 regression has no tracking. Accepting 5-D inputs (kernels/portable/cpu/util/kernel_ops_util.cpp:421-424) means a static int8 Conv3d lowered with XNNPACK no longer fails with an error. Per the Notes, the XNNPACK quantize and dequantize partitions next to the undelegated Conv3d now return zeros, and a silent wrong result is worse than the old error. Please file an issue, link it here, and mention the change in the release note (the PR is labeled release notes: ops & kernels). Alternatively, decide whether this PR should wait for the XNNPACK fix.
  • groups is used before it is validated. The corrected bias check computes groups * weight.size(1) on every transposed call with a bias (kernel_ops_util.cpp:431-436). The new groups > 0 check and the weight.size(0) >= groups bound only run later (kernel_ops_util.cpp:454-469). A malformed program with transposed=true, a bias and groups near INT64_MAX overflows int64_t at that multiply. That is undefined behavior, and UBSan flags it, even though a later check rejects the call. Moving the bias block below the groups checks bounds groups to [1, weight.size(0)] first. Fix this →
  • Input smaller than the dilated kernel (existed before this PR, now also reachable through depth). For a non-transposed convolution with stride ≥ 2, _kernel_output_size_helper (kernel_ops_util.cpp:180-182) truncates a negative numerator toward zero and returns 1. For example, input depth 2, kernel depth 3, stride 2 and padding 0 produce an output depth of 1 from two of the three taps, where PyTorch raises an error. check_avg_pool1d_args guards against this (kernel_ops_util.cpp:305-312), but check_convolution_args doesn't. A follow-up PR is fine. Fix this →

Testing

  • No NDHWC kernel test. check_convolution_args now accepts 5-D channels-last tensors (runtime/core/exec_aten/util/dim_order_util.h:78), and the kernel indexes them by t.strides(). Both 3-D tests (kernels/test/op_convolution_test.cpp:1185-1237) use contiguous tensors only.
    • The Cortex-M conv3d case (backends/cortex_m/test/ops/test_conv.py:226-233) is NDHWC, but it has one input and one output channel, so its strides address memory exactly like contiguous ones.
    • The 2-D tests run both layouts (op_convolution_test.cpp:156, 1157).
    • tf.channels_last_like (runtime/core/exec_aten/testing_util/tensor_factory.h:894) and get_channels_last_data (op_convolution_test.cpp:801) only handle 4-D. tf.make_channels_last takes 5-D sizes (tensor_factory.h:867-873), so the 3-D tests only need their data in NDHWC order. Fix this →
  • The 3-D rejection checks have no positive control. The three ET_EXPECT_KERNEL_FAILURE(context_, conv_3d(...)) checks (op_convolution_test.cpp:670-672) also pass on main, which rejects every 5-D input at the rank check. So they don't show that the four-element arrays are what gets rejected. No 3-D test passes a single stride, padding or dilation value either, which leaves the broadcast described in the comment at lines 645-646 untested. A passing call conv_3d(one, zero, one), checked against tf.full({1, 4, 2, 2, 2}, 16), covers both. Fix this →

Backward Compatibility

  • Helpers removed from a public target. The PR deletes get_unsqueezed_sizes and get_unsqueezed_dim_order (main kernels/portable/cpu/util/kernel_ops_util.h:53-63 and kernel_ops_util.cpp:167-199).
    • The kernel_ops_util target has visibility = ["PUBLIC"] (kernels/portable/cpu/util/targets.bzl:210, 223) and is already used outside the portable kernels (Cadence, Cortex-M, optimized).
    • No in-tree callers remain, but internal and out-of-tree callers can't be ruled out, and the Notes say add forward portable Conv3d support for contiguous 5D inputs in aten:convolution.out #19977 was reverted for breaking internal jobs.
    • 3-D support doesn't need this removal. Keep the helpers in this PR and remove them in a separate one, so reverting one change doesn't undo the other. Fix this →

Performance

  • 1-D and 2-D users pay for the 3-D loops, and nobody has measured the cost on an MCU. Every 1-D and 2-D convolution now runs the 3-D loop nest.
    • The description's own numbers, all from 64-bit hosts:
      • Regular convolutions are up to 10% slower with Clang at -O2/-O3.
      • Transposed convolutions are up to 12% slower with GCC at -Os.
      • Kernel code is up to 14% larger at -Os and 55% larger at -O2/-O3.
    • MCU builds link this kernel (backends/cortex_m/test/build_test_runner.sh:48).
    • CI won't catch the growth: test-arm-cortex-m-size-test (.github/workflows/pull.yml:705) measures size_test, which links no ops (test/CMakeLists.txt:48-56).
    • Please post the -Os size change of op_convolution.o when built with arm-none-eabi, or say that the trade-off is accepted.

Recommendation

Needs Discussion

The reviewers found no blocking bug in the kernel. Three decisions are needed before merge:

The groups ordering fix and the two tests are small changes.

  • Gather context (diff, commits, CI results)
  • Run three independent reviewers
  • Consolidate findings
  • Fact-check the consolidated findings
  • Write and post the review

Copilot AI balanced review requested due to automatic review settings October 31, 2026 19:06

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 insufficient remaining quota. Try requesting a review at a lower effort level.

Copilot AI balanced review requested due to automatic review settings October 31, 2026 20:23

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 insufficient remaining quota. Try requesting a review at a lower effort level.

@shoumikhin

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Pushed:

  • groups is now checked before the bias length check uses it.
  • get_unsqueezed_sizes and get_unsqueezed_dim_order are back, unchanged.
  • The 3-D tests also run with channels-last tensors. The 3-D rejection checks now start with a call that must succeed, with single values that are not the defaults.
  • A kernel larger than the padded input is now rejected, like in PyTorch, with a test.
  • Built for a Cortex-M55 with the Arm GNU toolchain at -Os, op_convolution.o grows from 32,484 to 33,392 bytes. Keeping the transposed range helpers out of line brought the growth down from 14 to 3 percent. Speed was measured only on Arm64 and x86-64 hosts, not on an MCU. The numbers are in the description.
  • The silent wrong values from a static int8 Conv3d with XNNPACK are fixed in Keep ops above 6 dims out of XNNPACK and fix quantized ops it leaves out #24380. The description now says this change depends on it.

@shoumikhin

Copy link
Copy Markdown
Contributor Author

@claude /pr-review

@claude

claude Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Claude finished @shoumikhin's task in 35m 37s —— View job


PR Review: #24385

Approve once #24380 lands: I found no bugs in the new 3-D loops, and the remaining items don't block: an overflow that gets past the new kernel-size check, an unchecked bias dtype, and two tests that check less than they appear to.

Detailed review

Summary

This PR sends 1-D, 2-D and new 3-D convolutions in portable convolution.out through one 3-D implementation and tightens the argument checks. I found no bugs in the new loops or in the 1-D/2-D bit-for-bit claim. The problems:

  • an overflow lets a huge dilation past the new kernel-size check;
  • a bias dtype check is missing, which predates this PR;
  • two tests check less than they appear to.

Code Quality

  • A huge dilation gets past the new kernel-size check (kernels/portable/cpu/util/kernel_ops_util.cpp:479-482).
    • Stride, padding and dilation have only lower bounds: param_array_is_valid (line 46), and padding_is_valid defaults to enforce_half_kernel = false (kernel_ops_util.h:45).
    • Example: a 1-D convolution with an input of 3, a kernel of 3 and dilation 2^62. In practice dilation * (kernel_size - 1) + 1 wraps to a negative number, so the check passes.
    • _kernel_output_size_helper (line 213) also wraps. Line 246 then narrows the negative size into the 32-bit SizesType as 3, and the call succeeds when it should be rejected.
    • The PR fixes this same kind of signed overflow for groups. Since the check is new, fix it in this PR: bound the parameters in check_convolution_args, for example to INT32_MAX. That covers this check and the output-size math. Fix this →
  • The bias dtype is never checked. This predates the PR, so a follow-up PR is fine.
    • check_convolution_args compares dtypes only for in, weight and out (line 442).
    • For a bias outside REALHBF16, such as Bool, get_load_to_compute_fn in convolution_out (kernels/portable/cpu/op_convolution.cpp:497-500) fails the context and returns nullptr.
    • convolution_wrapper runs anyway, and conv3d_impl calls the null load_bias (op_convolution.cpp:276-278; 183-185 when transposed). A malformed program crashes instead of getting InvalidArgument.
    • Fix: an isRealHBF16Type check in the bias block at kernel_ops_util.cpp:511-520. Fix this →

Testing

  • The INT64_MAX case in InvalidGroups can't catch the old check order in CI (kernels/test/op_convolution_test.cpp:733-751).
    • With the old order, groups * weight.size(1) = INT64_MAX * 4 wraps to −4 in practice. The call is still rejected and the test passes.
    • The Debug unittest job in .github/workflows/pull.yml does build with -fsanitize=address,undefined (CMakeLists.txt:148). Nothing sets -fno-sanitize-recover or UBSAN_OPTIONS=halt_on_error=1, so UBSan only logs the overflow.
    • Fix: say in the comment that this case needs a UBSan build that halts on errors. Fix this →
  • The Cortex-M conv3d case can't catch indexing mistakes (backends/cortex_m/test/ops/test_conv.py:226-233).
    • CortexMConv3D(1, 1, 1) is a 1×1×1 kernel on one channel, with constant weights of 2.0 (line 86). The output is just twice the input.
    • It uses no padding, stride, dilation or extra kernel taps. With one channel, channels_last_3d addresses memory the same way as contiguous.
    • The PR description cites this case passing on the Arm simulator. That is the only 32-bit run of the new 3-D code the PR reports.
    • Fix: use several channels, a kernel larger than 1 in each dim, stride, padding and non-constant weights. For example, CortexMConv3D(2, 4, (2, 3, 3), stride=(1, 2, 2), padding=1) without the fill_(2.0), on a (2, 2, 4, 6, 6) input. Fix this →

Recommendation

Approve. Merge only after #24380 lands on main, or together with it. This branch is based on aed328d and does not contain #24380. By the PR's own Notes, landing this alone turns the current error for a static int8 Conv3d under XNNPACK into wrong values. None of the findings above block.

Review steps
  • Gather context (diff, commits): 9 commits on top of aed328d, 5 files, +467/−245.
  • Run three independent reviewers.
  • Consolidate findings: 9 candidates. Six were reported by two or three reviewers, and three by one.
  • Fact-check the consolidated findings: 5 kept, 4 dropped. The merge-order finding moved into the Recommendation. Dropped:
    • the ET_NOINLINE speed trade-off, already measured and accepted;
    • 3-D test gaps, covered by the shared 1-D/2-D tests;
    • a faster Qwen2-VL path, out of scope;
    • PR-description style.
  • Write and post the review.

Not done:

Copilot AI balanced review requested due to automatic review settings October 31, 2026 23:08

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 insufficient remaining quota. Try requesting a review at a lower effort level.

Copilot AI balanced review requested due to automatic review settings November 1, 2026 00:10

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.

🟡 Changes recommended

Negative transposed-convolution output sizes can narrow to valid depths and silently produce incorrect results, while tensors without stored strides can trigger invalid memory access or crashes.

2 open issues

Review effort: Balanced

for (const auto d : c10::irange(t.dim())) {
const ssize_t view_d = d < 2 ? d : d + added_dims;
sizes[view_d] = t.size(d);
strides[view_d] = t.strides()[d];

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.

Thanks. Tensors are required to have strides: the TensorImpl constructor documents that strides must contain one entry per dim. The program loader and make_tensor_ptr both fill them in. Many portable kernels already read strides directly, including the shared broadcast and reduction helpers, so a tensor without strides would fail in those too. So I kept this kernel reading strides directly, like those kernels do.

Copilot AI balanced review requested due to automatic review settings November 1, 2026 00: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.

🔵 Needs a closer look

Maintainers must validate the internal jobs that caused the prior Conv3d revert and coordinate landing with still-open #24380 to prevent incorrect quantized XNNPACK outputs.

1 open issue
1 resolved since last review

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings November 1, 2026 00:27

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.

🔵 Needs a closer look

Internal-job validation remains unavailable after the prior Conv3d regression-driven revert, and merge ordering with #24380 must be coordinated to prevent silent quantized XNNPACK output corruption.

1 open issue

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings November 1, 2026 03:00
The conv3d case ran a 1x1x1 kernel with constant weights on one channel,
so its output was just twice the input. It could not catch an indexing
mistake, and with one channel its channels-last input had the same
layout as a contiguous one. It now has two input and four output
channels, a 2x3x3 kernel, stride (1, 2, 2), padding 1 and the default
random weights, on a channels-last input.
PyTorch's CPU conv3d returns a contiguous output even for a
channels_last_3d input, so the exported program gave the convolution a
channels-last input and a contiguous output. The portable kernel needs
both in the same layout, so the run on the Arm simulator failed. With
one channel the two layouts were the same, which is why the old case
passed. The C++ tests cover the kernel's channels-last 3-D path.
The 32-bit bound on stride, padding and dilation left two gaps:

- An output size above 2^31 - 1 still fit int64_t, but was then cut to
  a 32-bit tensor size. Padding 2^31 - 1 on an input of 3 with a kernel
  of 1 gives 2^32 + 1, which became 1, so a call that should fail ran.
- An empty spatial dim made the transposed output size math compute
  0 - 1 in size_t. On 32-bit targets that overflowed int64_t.

PyTorch rejects empty spatial dims, and now check_convolution_args does
too. It also computes each output size and rejects one that does not
fit in the 32-bit tensor size. The tests cover both.
A transposed convolution with a large padding can have a negative output
size. For example, an input of 3, a kernel of 1 and padding 2^31 - 1
give 5 - 2^32. The output size check only had an upper bound, and the
size became a positive 5 when cut to 32 bits, so the call ran. PyTorch
rejects it as too small. Now each output size must be at least 1.

The test now covers that case instead of a stride above the 32-bit
bound, which the output size check catches too, so it is renamed to
HugePaddingAndDilation.
With a kernel of 3, dilation 2^62 gave a negative output size once the
bound was removed, so the new check on output sizes rejected the call
too, and the test passed without the bound. With a kernel of 5 the
dilated kernel size wraps to exactly 1 and the output size to a valid 3,
so only the bound rejects the call.
PyTorch's CPU conv3d returns a contiguous output even for a
channels_last_3d input, so an exported Conv3d with a channels-last input
gives the kernel a channels-last input and a contiguous output. The
kernel rejected that pair, so such a model failed at run time.

The kernel reads the input, weight and output through their own
strides, and check_convolution_args already limits the input, the output
and a non-transposed weight to the contiguous or the channels-last dim
order, so the input and output do not need to match. Drop that check.
Both 3-D tests now also run a channels-last input and weight with a
contiguous output, and the Cortex-M conv3d test uses a channels_last_3d
input again.
check_convolution_args read the kernel size from the weight but never
checked it. With a kernel dim of 0, the dilated kernel size became
1 - dilation, so even the kernel size check passed, and an input of 3
gave an output of 4 that no kernel tap ever wrote. PyTorch rejects a
kernel size of 0. Check it with kernel_size_is_valid, like the pooling
checks do.
The new check for empty spatial dims also rejected inputs whose batch
or channel dim is empty, which main and PyTorch accept and turn into an
empty output. Allow those again, like PyTorch. The transposed output
size math now subtracts 1 from the input size in int64_t instead of
size_t, so an empty spatial dim cannot make it wrap on 32-bit targets.
With a channels_last_3d input the case now ran on the Arm simulator but
gave wrong values. The Arm test runner writes an input in its logical
order unless its dim order is NHWC or the runner's own 5-D order, so a
channels_last_3d input reached the program in the wrong byte order. The
kernel handles a channels-last input with a contiguous output, as the
C++ 3-D tests show, so give the Cortex-M case a contiguous input, with a
comment that says why.
The kernel size check fixes 1-D and 2-D convolutions as well, but its
test used only a 5-D input, which main rejected at the rank check
anyway. Add a 1-D case: an input of 2 with a kernel of 3 and stride 2,
which main ran from two of the three kernel taps.
Say why the bias length check comes after the groups checks: they keep
groups * weight.size(1) from overflowing. Before this, only a test
comment said so. Also fix three comments that were hard to read or not
quite right: the output size math can round a too-large kernel up to 1,
but only when the kernel is too large by less than the stride; the
range helper comment is reworded; and the depth loop comment now says
the loop runs once for 1-D and 2-D inputs.
Three cases had no test, so the code that handles them could break
without any test failing:

- An empty spatial dim with an empty channel dim, which PyTorch
  accepts. Only the empty batch case was tested.
- A transposed convolution with a huge stride or padding, where the
  64-bit output size math would wrap around to a valid size. Only the
  dilation part of the 32-bit bound was tested.
- A channels-last 5-D input with a contiguous weight and a contiguous
  output, which is what export gives when only the input is
  channels-last. The mixed layout calls used a channels-last weight.
The backend test suite skipped every model with a 5-D convolution,
because the portable kernel could not run one. Now it can, so remove
the skip, and the suite's Conv3d and ConvTranspose3d cases run in every
flow.
Copilot AI balanced review requested due to automatic review settings November 2, 2026 17: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 was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@shoumikhin

Copy link
Copy Markdown
Contributor Author

@claude /pr-review

@claude

claude Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Claude finished @shoumikhin's task in 25m 40s —— View job


Approve: the three reviewers found no bugs in the 3-D kernel, the argument checks or the tests, so what remains is to merge after #24380 and to check the Conv3d results of this head's backend test jobs, which are still running and pass even when cases fail.

Detailed review

Summary

This PR runs 1-D, 2-D and 3-D convolutions in the portable convolution.out kernel through one set of 3-D loops, tightens check_convolution_args, and lets the backend test suite run 5-D convolutions. No issues were found in this round; the two remaining merge conditions are under Recommendation.

Recommendation

Approve

  • Merge after Keep ops above 6 dims out of XNNPACK and fix quantized ops it leaves out #24380 (XNNPACK static int8 Conv3d) or together with it, as the PR notes say. I couldn't check whether it has landed.
  • With _graph_has_unsupported_patterns removed, backends/test/suite/runner.py:166-173 skips only on UNSUPPORTED_PORTABLE_OPS, so 5-D convolutions now run in every backend flow, not only the portable one. This head's backend test jobs, including the ciflow/nightly ones, are still running or queued. They also pass when cases fail: .ci/scripts/test_backend.sh:165-167 passes pytest's exit code to the summary script, and generate_markdown (backends/test/suite/generate_markdown_summary_json.py:118) never reads it. Before merging, check each job's report for new Conv3d or ConvTranspose3d failures.
Review steps
  • Gather context: 25 commits, 6 files, +717/−277, based directly on the origin/main tip e68a824
  • Run three independent reviewers: none found a bug in the 3-D loops or the argument checks; 13 minor items reported
  • Consolidate findings: 7 distinct items after merging duplicates
  • Fact-check: 5 dropped, 2 moved to the Recommendation
  • Write and post the review

Moved to the Recommendation

Dropped

  • The channelless case in EmptyBatchOrChannelWithEmptySpatialDim (kernels/test/op_convolution_test.cpp:739-752) differing from PyTorch or failing in ATen mode: with padding 1 the padded input fits the kernel, and PyTorch's calc_output_size also sets the output channels to 0 when the input has none, so it gives {1, 0, 2} too.
  • A transposed convolution with an empty batch and an output spatial size of 0 being rejected, where PyTorch returns an empty tensor: output_size_is_valid already rejects it on main.
  • The output size being computed twice, and the leftover output_size_is_valid call at kernels/portable/cpu/op_convolution.cpp:472-476: the argument check needs its own 64-bit copy of the size, and the leftover call is harmless. Dropped in earlier rounds too.
  • Missing 3-D, mixed-layout, batch, tile, stride and dtype test cases: these paths share code with the tested ones, and the kernel reads each tensor through its own strides.
  • No issue link in the Cortex-M test comment about the Arm runner byte-order problem: raised two rounds ago.

Not done

This branch is being deployed

1 in progress deployment
cadence — 33241abd Deployed Nov 2, 2026 by pytorch-bot[bot] via hifi1s-op-test / hifi1s #35982
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ciflow/nightly CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. release notes: ops & kernels Changes to the opset and any new / changed kernel implementations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants