Skip to content

Commit 11640ea

Browse files
committed
[runtime] Let Method.execute borrow outputs
Module.forward and the native method take clone_outputs, but the public Method wrapper of executorch.runtime always copied every output, so a caller of the recommended API could not avoid that copy. Method.execute now takes clone_outputs, with the same default and meaning as the bindings: False returns outputs that share the method's memory and may change on the next execution.
1 parent 8c88fdc commit 11640ea

2 files changed

Lines changed: 28 additions & 5 deletions

File tree

‎runtime/__init__.py‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,16 +136,20 @@ class Method:
136136
def __init__(self, method: ExecuTorchMethod) -> None:
137137
self._method = method
138138

139-
def execute(self, inputs: Sequence[Any]) -> Sequence[Any]:
139+
def execute(
140+
self, inputs: Sequence[Any], clone_outputs: bool = True
141+
) -> Sequence[Any]:
140142
"""Executes the method with the given inputs.
141143
142144
Args:
143145
inputs: A sequence of input values, typically torch.Tensor objects.
146+
clone_outputs: If False, the outputs share the method's memory and
147+
a later execution may overwrite them, which avoids a copy.
144148
145149
Returns:
146150
A list of output values, typically torch.Tensor objects.
147151
"""
148-
return self._method(inputs)
152+
return self._method(inputs, clone_outputs=clone_outputs)
149153

150154
@property
151155
def metadata(self) -> MethodMeta:
@@ -188,9 +192,11 @@ def load_method(self, name: str) -> Optional[Method]:
188192
program placed them. A method whose buffers are all on the host shares
189193
one set of arenas with every other host-only method of this program,
190194
sized to the largest of them, so two such methods overwrite each
191-
other's intermediate values. Outputs are copied out on every call, so
192-
that sharing does not change what execute returns. A method with any
193-
buffer placed on an accelerator gets its own arenas instead, claimed
195+
other's intermediate values. By default execute copies its outputs out,
196+
so that sharing does not change what it returns. With
197+
``clone_outputs=False`` the outputs share that memory, so the next
198+
execution of any method of this program may overwrite them. A method
199+
with any buffer placed on an accelerator gets its own arenas instead, claimed
194200
when that method is first loaded and owned by that method until the
195201
method itself is released. This program caches every method it loads,
196202
so in normal use those arenas live as long as the program does. A

‎runtime/test/test_runtime.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,23 @@ def test_smoke(self):
5252
outputs = method.execute(inputs)
5353
self.assertTrue(torch.allclose(outputs[0], inputs[0] + inputs[1]))
5454

55+
def test_execute_can_borrow_outputs(self):
56+
ep, inputs = create_program(ModuleAdd())
57+
method = Runtime.get().load_program(ep.buffer).load_method("forward")
58+
59+
def address(output):
60+
return torch.from_dlpack(output).data_ptr()
61+
62+
# Borrowed outputs are views of the method's own memory, so each
63+
# execution writes the same place. Cloned outputs are copies.
64+
borrowed = [method.execute(inputs, clone_outputs=False)[0] for _ in range(2)]
65+
self.assertEqual(address(borrowed[0]), address(borrowed[1]))
66+
cloned = [method.execute(inputs)[0] for _ in range(2)]
67+
self.assertNotEqual(address(cloned[0]), address(cloned[1]))
68+
torch.testing.assert_close(
69+
torch.from_dlpack(borrowed[1]), inputs[0] + inputs[1]
70+
)
71+
5572
def test_module_with_multiple_method_names(self):
5673
ep, inputs = create_program(ModuleMulti())
5774
runtime = Runtime.get()

0 commit comments

Comments
 (0)