From 070df8bdc44b8951116940ec05ef14c016579b77 Mon Sep 17 00:00:00 2001 From: Jake Stevens Date: Tue, 27 Oct 2026 14:09:16 -0700 Subject: [PATCH] Accept empty tensor bindings in CPUBackend execute (#24195) Summary: Pull Request resolved: https://github.com/pytorch/executorch/pull/24195 The CPU delegate rejected any binding whose strides/dim-order were not canonical-contiguous, including empty (0-byte) tensors whose layout is meaningless: PyTorch does not canonicalize strides for 0-size dims, so e.g. a (0,0,0,0,0) input arrives with dim-order != identity and fails with 0x12 (InvalidArgument). Skip the contiguity check for 0-byte bindings; the input/output memcpys already skip empty buffers, and providers handle 0-size execution. Reviewed By: digantdesai Differential Revision: D126930703 --- backends/cpu/runtime/CPUBackend.cpp | 23 ++++++++++++++--------- backends/cpu/test/test_runtime.py | 11 +++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/backends/cpu/runtime/CPUBackend.cpp b/backends/cpu/runtime/CPUBackend.cpp index 963bf3e7f13..3ff1396c71d 100644 --- a/backends/cpu/runtime/CPUBackend.cpp +++ b/backends/cpu/runtime/CPUBackend.cpp @@ -183,15 +183,20 @@ class CPUExecution { InvalidArgument, "CPU static shape/dtype/layout/storage mismatch: %s", method_->graph.value(id).name.c_str()); - int64_t stride = 1; - for (size_t reverse = expected.sizes.size(); reverse > 0; --reverse) { - const size_t dim = reverse - 1; - ET_CHECK_OR_RETURN_ERROR( - tensor.dim_order()[dim] == dim && tensor.strides()[dim] == stride, - InvalidArgument, - "CPU binding must be contiguous: %s", - method_->graph.value(id).name.c_str()); - stride *= expected.sizes[dim]; + // Empty bindings carry no data, so their strides and dim order are + // meaningless (PyTorch does not canonicalize them); skip the contiguity + // check rather than rejecting valid empty tensors. + if (tensor.nbytes() != 0) { + int64_t stride = 1; + for (size_t reverse = expected.sizes.size(); reverse > 0; --reverse) { + const size_t dim = reverse - 1; + ET_CHECK_OR_RETURN_ERROR( + tensor.dim_order()[dim] == dim && tensor.strides()[dim] == stride, + InvalidArgument, + "CPU binding must be contiguous: %s", + method_->graph.value(id).name.c_str()); + stride *= expected.sizes[dim]; + } } } // EValue exposes the logical span, but no backing capacity or readable diff --git a/backends/cpu/test/test_runtime.py b/backends/cpu/test/test_runtime.py index b3d1df05982..4e821d76eec 100644 --- a/backends/cpu/test/test_runtime.py +++ b/backends/cpu/test/test_runtime.py @@ -95,6 +95,17 @@ def forward(self, x, mask): for output, expected in zip(actual, model(x, current), strict=True): torch.testing.assert_close(output, expected, rtol=0, atol=0) + def test_empty_tensors_execute(self): + class Add(torch.nn.Module): + def forward(self, x): + return x + 1 + + model = Add() + x = torch.empty(2, 0, 3) + with self._lower(model, (x,)) as runtime: + actual = runtime.forward((x,))[0] + torch.testing.assert_close(actual, model(x)) + def test_neg_view_copy_routes_to_neg(self): class NegView(torch.nn.Module): def forward(self, x):