Skip to content

Commit 3aadad6

Browse files
committed
Copy constants that feed a device delegate into device memory
A constant that feeds a delegate on a device, such as a buffer the partitioner leaves outside the delegate, broke device memory planning. - Without skip_h2d_for_method_inputs, its _h2d_copy cloned the constant's spec, constant flag included, so memory planning skipped the copy's output and emission failed. - With it, the constant was treated as a method input: its placeholder was tagged with the device while its data stays in the program on the host, so the delegate got a host pointer labeled as device memory. A copy's output is now never a constant, and skip_h2d_for_method_inputs applies only to placeholders that are not constants.
1 parent 6368414 commit 3aadad6

2 files changed

Lines changed: 46 additions & 1 deletion

File tree

‎exir/passes/propagate_device_pass.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ def _clone_spec_with_device(
143143
"""Create a copy of a TensorSpec with a different device."""
144144
new_spec = copy.copy(spec)
145145
new_spec.init_mem_planning_fields()
146+
# A copy's output is planned memory, even when it copies a constant.
147+
new_spec.const = False
146148
_set_device_on_spec(new_spec, device_type, device_index)
147149
return new_spec
148150

@@ -214,7 +216,13 @@ def _insert_h2d_copies(
214216
if not isinstance(arg_spec, TensorSpec):
215217
continue
216218

217-
if self.skip_h2d_for_method_inputs and self._is_placeholder(arg):
219+
# A constant is a placeholder too, but its data is in the program on
220+
# the host, so it still needs a copy like any other host tensor.
221+
if (
222+
self.skip_h2d_for_method_inputs
223+
and self._is_placeholder(arg)
224+
and not arg_spec.const
225+
):
218226
# TODO(gasoonjia): support skip_h2d_for_method_inputs for
219227
# multiple-user placeholder inputs.
220228
if len(arg.users) != 1:

‎exir/tests/test_propagate_device_pass.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,6 +1265,43 @@ def forward(self, a, b):
12651265
"CPU placeholder",
12661266
)
12671267

1268+
def test_constant_feeding_device_delegate_is_copied(self):
1269+
"""A constant feeding a device delegate gets an _h2d_copy into planned device
1270+
memory, with or without skip_h2d_for_method_inputs: its data lives in the
1271+
program on the host, so it is not a method input the caller can place."""
1272+
1273+
class Model(torch.nn.Module):
1274+
def __init__(self):
1275+
super().__init__()
1276+
self.register_buffer("offset", torch.randn(2, 2))
1277+
1278+
def forward(self, a):
1279+
return torch.add(a, self.offset)
1280+
1281+
model = Model()
1282+
inputs = (torch.randn(2, 2),)
1283+
for skip in (False, True):
1284+
et_config = ExecutorchBackendConfig(
1285+
emit_stacktrace=False,
1286+
propagate_device_config=PropagateDeviceConfig(
1287+
skip_h2d_for_method_inputs=skip
1288+
),
1289+
enable_non_cpu_memory_planning=True,
1290+
)
1291+
for pipeline, program, gm in self._get_executorch_program(
1292+
model, inputs, DeviceAwarePartitioner("cuda:0"), et_config
1293+
):
1294+
with self.subTest(pipeline=pipeline, skip=skip):
1295+
nodes = _collect_device_copy_nodes(gm)
1296+
# Input a is copied only when the caller cannot place it.
1297+
self.assertEqual(len(nodes.h2d_nodes), 1 if skip else 2)
1298+
cuda_ph, cpu_ph = self._collect_placeholders_by_device(gm)
1299+
self.assertEqual(len(cuda_ph), int(skip))
1300+
self.assertTrue(all(not n.meta["spec"].const for n in cuda_ph))
1301+
copies = [n.meta["spec"] for n in nodes.h2d_nodes]
1302+
self.assertTrue(all(not spec.const for spec in copies))
1303+
self.assertTrue(all(spec.mem_id is not None for spec in copies))
1304+
12681305
def test_tensorspec_repr_includes_device(self):
12691306
spec = TensorSpec(dtype=torch.float32, shape=torch.Size([2, 3]))
12701307
repr_str = repr(spec)

0 commit comments

Comments
 (0)