From b2f4bb09b0730cfcd5adb0fa74ab93a4a9a07b76 Mon Sep 17 00:00:00 2001 From: AxelNoun <150222552+AxelNoun@users.noreply.github.com> Date: Tue, 6 Oct 2026 16:13:17 +0200 Subject: [PATCH 1/2] Raise InternalError for non-CPU constant storage in the emitter _tensor_spec_to_evalue reads a constant's bytes by casting spec.storage.data_ptr() to a host pointer. For CUDA storage that is a device address, so to_executorch() segfaults with no Python exception (#21545). Check the storage device first and raise an InternalError that names the device and asks the user to move the constants to CPU. --- exir/emit/_emitter.py | 14 ++++++++++++++ exir/emit/test/test_emit.py | 9 +++++++++ 2 files changed, 23 insertions(+) diff --git a/exir/emit/_emitter.py b/exir/emit/_emitter.py index d8db9145232..5dedff77cc7 100644 --- a/exir/emit/_emitter.py +++ b/exir/emit/_emitter.py @@ -467,6 +467,20 @@ def _tensor_spec_to_evalue( ctypes.c_char * typing.cast(torch.UntypedStorage, spec.storage).nbytes() ) + # data_ptr() is dereferenced from the host below; for non-CPU storage + # (e.g. CUDA) that read can segfault instead of raising (#21545). + storage_device = typing.cast(torch.UntypedStorage, spec.storage).device + if spec.allocated_memory != 0 and storage_device.type != "cpu": + raise InternalError( + self._emit_node_specific_error( + self.node, + f"Constant tensor storage is on device '{storage_device}', " + "but to_executorch() can only serialize constants from CPU " + "memory. Move parameters, buffers and tensor constants to " + "CPU (e.g. model.cpu()) before export.", + ) + ) + buffer_data = ( bytes( ctypes.cast( diff --git a/exir/emit/test/test_emit.py b/exir/emit/test/test_emit.py index 78480d3b55c..c52dbf32276 100644 --- a/exir/emit/test/test_emit.py +++ b/exir/emit/test/test_emit.py @@ -1833,6 +1833,15 @@ def forward(self, x): self.assertEqual(len(program.constant_buffer[1].storage), 4) self.assertEqual(len(program.constant_buffer[2].storage), 4) + def test_emit_non_cpu_constant_raises(self) -> None: + # Like CUDA storage, "meta" storage is not host-readable, but needs no + # accelerator. Unguarded, it already fails with a wrapped "NULL pointer + # access" InternalError, so match on the device name. + model = torch.nn.Linear(4, 3).to("meta") + edge = to_edge(export(model, (torch.ones(2, 4, device="meta"),), strict=True)) + with self.assertRaisesRegex(InternalError, "on device 'meta'"): + edge.to_executorch() + def test_non_persistent_buffer(self) -> None: class NonPersistentBuffer(nn.Module): def __init__(self): From 7ee070b8f1adcdcff30eb08378d9a276f8fd22bd Mon Sep 17 00:00:00 2001 From: AxelNoun <150222552+AxelNoun@users.noreply.github.com> Date: Tue, 27 Oct 2026 18:02:13 +0100 Subject: [PATCH 2/2] Mention delegation in the non-CPU constant error message Constants consumed by a delegate do not reach this check, so telling every user to move the model to CPU is wrong for flows that delegate to a device backend. Co-Authored-By: Claude Opus 5.5 --- exir/emit/_emitter.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/exir/emit/_emitter.py b/exir/emit/_emitter.py index 5dedff77cc7..a4751e5497f 100644 --- a/exir/emit/_emitter.py +++ b/exir/emit/_emitter.py @@ -475,9 +475,10 @@ def _tensor_spec_to_evalue( self._emit_node_specific_error( self.node, f"Constant tensor storage is on device '{storage_device}', " - "but to_executorch() can only serialize constants from CPU " - "memory. Move parameters, buffers and tensor constants to " - "CPU (e.g. model.cpu()) before export.", + "but constants serialized into the program must be in CPU " + "memory. Move them to CPU before export (e.g. model.cpu()), " + "or delegate the ops that use them to a backend that " + "accepts device tensors.", ) )