Skip to content

Commit d65a186

Browse files
committed
Keep torch.Tensor outputs for calls without tensor inputs
The announced rule said that a call without tensor inputs, such as get_attribute(), a bundled program check, or a method that takes no inputs, would later return torch.Tensor only if torch was already imported. So its type would depend on whether unrelated code imported torch first, and the warning told users to rely on that import order. Such calls now keep returning torch.Tensor outputs whenever torch can be imported, now and in the future release, which is what they do today. They no longer warn, since their outputs will not change. Calls whose tensor inputs are not torch.Tensor still warn, as before. The warning, the README, the API reference and the Method.execute docstring describe the new rule.
1 parent 8d2bde0 commit d65a186

5 files changed

Lines changed: 53 additions & 37 deletions

File tree

‎docs/source/runtime-python-api-reference.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Output types
4848

4949
Importing ``executorch.runtime`` does not import PyTorch, unless the setting below asks for ``torch``. When PyTorch is installed, ``Method.execute`` returns ``torch.Tensor`` outputs, and the first call that returns one imports ``torch``. Without PyTorch, or when importing it raises an ``ImportError``, it returns ``ExecuTorchResult`` outputs. Any other error from importing PyTorch is raised by every call that returns a tensor; set the setting below to ``executorch`` to run without PyTorch.
5050

51-
A future release will make the outputs follow the tensor inputs of each call. A call with any ``torch.Tensor`` input will return ``torch.Tensor`` outputs. A call whose tensor inputs are all NumPy arrays, other buffers, DLPack objects, or ``ExecuTorchResult`` values will return ``ExecuTorchResult`` outputs, even when PyTorch is installed. A call with no tensor inputs will return ``torch.Tensor`` outputs only if ``torch`` is already imported. Each call whose outputs will change warns with a ``FutureWarning``.
51+
A future release will make the outputs follow the tensor inputs of each call. A call with any ``torch.Tensor`` input will return ``torch.Tensor`` outputs. A call whose tensor inputs are all NumPy arrays, other buffers, DLPack objects, or ``ExecuTorchResult`` values will return ``ExecuTorchResult`` outputs, even when PyTorch is installed. A call with no tensor inputs keeps returning ``torch.Tensor`` outputs whenever ``torch`` can be imported. Each call whose outputs will change warns with a ``FutureWarning``.
5252

5353
To choose the output type now, set ``EXECUTORCH_PYBINDINGS_TENSOR_OUTPUT`` before importing ExecuTorch. With ``executorch``, every call returns ``ExecuTorchResult`` outputs and ExecuTorch never imports PyTorch. With ``torch``, every call returns ``torch.Tensor`` outputs, and importing ExecuTorch imports PyTorch.
5454

‎extension/pybindings/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ call, including inputs kept from an earlier call. A call with any torch tensor
100100
input will return torch tensors. A call whose tensor inputs are all NumPy
101101
arrays, other buffers, DLPack objects, or `ExecuTorchResult` values will return
102102
`ExecuTorchResult` values, even when PyTorch is installed. A call with no tensor
103-
inputs will return torch tensors only if `torch` is already imported. Each call
103+
inputs keeps returning torch tensors whenever `torch` can be imported. Each call
104104
whose outputs will change warns with a `FutureWarning`. To choose the output
105105
type now, set `EXECUTORCH_PYBINDINGS_TENSOR_OUTPUT` to `executorch` or `torch`
106106
before importing the bindings. The setting is fixed when the extension loads,

‎extension/pybindings/pybindings.cpp‎

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -737,12 +737,11 @@ constexpr char kTensorOutputEnvironmentVariable[] =
737737
"EXECUTORCH_PYBINDINGS_TENSOR_OUTPUT";
738738

739739
constexpr char kTensorOutputWarning[] =
740-
"ExecuTorch returns torch.Tensor outputs for a call without torch.Tensor "
741-
"inputs. A future release will return ExecuTorchResult outputs for such a "
742-
"call, unless it has no tensor inputs and torch is already imported. To "
743-
"keep torch.Tensor outputs, pass torch.Tensor inputs, or import torch "
744-
"before a call without tensor inputs. To choose the type for every call, "
745-
"set EXECUTORCH_PYBINDINGS_TENSOR_OUTPUT to torch or executorch before "
740+
"ExecuTorch returns torch.Tensor outputs for a call whose tensor inputs "
741+
"are not torch.Tensor. A future release will return ExecuTorchResult "
742+
"outputs for such a call. To keep torch.Tensor outputs, pass torch.Tensor "
743+
"inputs. To choose the type for every call, set "
744+
"EXECUTORCH_PYBINDINGS_TENSOR_OUTPUT to torch or executorch before "
746745
"importing ExecuTorch. With executorch, PyTorch is not loaded.";
747746

748747
// Set from EXECUTORCH_PYBINDINGS_TENSOR_OUTPUT when the extension loads.
@@ -785,10 +784,8 @@ int caller_stacklevel() {
785784
return level;
786785
}
787786

788-
// Until a later release makes outputs follow inputs, a call that would then
789-
// return ExecuTorchResult outputs keeps returning torch.Tensor outputs
790-
// whenever torch can be imported, as before, and warns.
791-
bool keep_torch_outputs() {
787+
// Whether torch is imported, importing it if it can be.
788+
bool torch_available() {
792789
// Read and written with the GIL held. An import inside a function-local
793790
// static's initializer could deadlock, since the import lets other threads
794791
// run and one of them could then wait for that static while holding the GIL.
@@ -820,6 +817,16 @@ bool keep_torch_outputs() {
820817
return false;
821818
}
822819
}
820+
return true;
821+
}
822+
823+
// Until a later release makes outputs follow inputs, a call that would then
824+
// return ExecuTorchResult outputs keeps returning torch.Tensor outputs
825+
// whenever torch can be imported, as before, and warns.
826+
bool keep_torch_outputs() {
827+
if (!torch_available()) {
828+
return false;
829+
}
823830
// Every such call warns, so Python's warning filters decide what shows. A
824831
// warning hidden by a filter at one call still shows at a later one.
825832
if (PyErr_WarnEx(
@@ -834,10 +841,11 @@ bool keep_torch_outputs() {
834841

835842
// Picks the type of a call's tensor outputs. A later release will follow the
836843
// call's tensor inputs, including inputs kept from earlier calls: torch.Tensor
837-
// if any of them is one, ExecuTorchResult if all are other tensors, and with
838-
// no tensor inputs, torch.Tensor only if torch is imported. Until then,
839-
// keep_torch_outputs() keeps torch.Tensor where that rule would change it. A
840-
// call that returns no tensor never imports torch.
844+
// if any of them is one, and ExecuTorchResult if all are other tensors. Until
845+
// then, keep_torch_outputs() keeps torch.Tensor where that rule would change
846+
// it. A call with no tensor inputs returns torch.Tensor whenever torch can be
847+
// imported, now and later, so its type never depends on what imported torch
848+
// first. A call that returns no tensor never imports torch.
841849
PortableTensorOutput portable_tensor_output(
842850
bool returns_tensor,
843851
bool torch_input,
@@ -849,9 +857,13 @@ PortableTensorOutput portable_tensor_output(
849857
(!other_tensor_input && loaded_torch_module() != nullptr)) {
850858
return PortableTensorOutput::Torch;
851859
}
852-
return returns_tensor && keep_torch_outputs()
853-
? PortableTensorOutput::Torch
854-
: PortableTensorOutput::ExecuTorch;
860+
if (!returns_tensor) {
861+
return PortableTensorOutput::ExecuTorch;
862+
}
863+
const bool torch_output =
864+
other_tensor_input ? keep_torch_outputs() : torch_available();
865+
return torch_output ? PortableTensorOutput::Torch
866+
: PortableTensorOutput::ExecuTorch;
855867
}
856868

857869
// From the declared outputs, so the type is picked before the method runs.

‎extension/pybindings/test/test_pybindings.py‎

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -485,17 +485,21 @@ def run(index):
485485
""",
486486
),
487487
}
488-
# Each of these calls returns torch.Tensor outputs now and will return
489-
# ExecuTorchResult outputs later, so it warns: calls without tensor
490-
# inputs before torch is imported, and calls with tensor inputs of
491-
# other kinds after it is.
492-
for call, torch_imported in (
493-
("module.run_method('tensor_constant')", False),
494-
("[method.get_attribute('state')]", False),
495-
("bundled.verify_result_with_bundled_expected_output('forward', 0)", False),
496-
("module.forward([x, x])", True),
497-
("module.forward(capsules)", True),
498-
("method(capsules)", True),
488+
# Calls with tensor inputs of other kinds return torch.Tensor outputs
489+
# now and will return ExecuTorchResult outputs later, so they warn,
490+
# even after torch is imported. Calls without tensor inputs keep
491+
# torch.Tensor outputs, so they do not, even before it is.
492+
for call, torch_imported, warnings_expected in (
493+
("module.run_method('tensor_constant')", False, 0),
494+
("[method.get_attribute('state')]", False, 0),
495+
(
496+
"bundled.verify_result_with_bundled_expected_output('forward', 0)",
497+
False,
498+
0,
499+
),
500+
("module.forward([x, x])", True, 1),
501+
("module.forward(capsules)", True, 1),
502+
("method(capsules)", True, 1),
499503
):
500504
scenarios[f"{call}, torch imported: {torch_imported}"] = (
501505
None,
@@ -508,8 +512,8 @@ def run(index):
508512
with open(bundled_path, "rb") as source:
509513
bundled = portable_lib._load_bundled_program_from_buffer(source.read())
510514
capsules = [x.__dlpack__(), x.__dlpack__()]
511-
(warning,), line = check(lambda: {call}, ["torch"], 1), sys._getframe().f_lineno
512-
assert warning.lineno == line, (warning.lineno, line)
515+
caught, line = check(lambda: {call}, ["torch"], {warnings_expected}), sys._getframe().f_lineno
516+
assert all(warning.lineno == line for warning in caught), caught
513517
""",
514518
)
515519
with tempfile.TemporaryDirectory() as directory:

‎runtime/__init__.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,11 @@ def execute(
179179
.. warning::
180180
181181
A future release will make tensor outputs follow the tensor inputs:
182-
torch.Tensor objects if any input is one, ExecuTorchResult objects
183-
if all are other tensors, even when PyTorch is installed, and with
184-
no tensor inputs, torch.Tensor objects only if torch is already
185-
imported. Each call whose outputs will change warns with a
186-
FutureWarning. Set ``EXECUTORCH_PYBINDINGS_TENSOR_OUTPUT`` to
182+
torch.Tensor objects if any input is one, and ExecuTorchResult
183+
objects if all are other tensors, even when PyTorch is installed.
184+
With no tensor inputs, outputs stay torch.Tensor objects whenever
185+
torch can be imported. Each call whose outputs will change warns
186+
with a FutureWarning. Set ``EXECUTORCH_PYBINDINGS_TENSOR_OUTPUT`` to
187187
``torch`` or ``executorch`` before importing ExecuTorch to choose
188188
the type now.
189189
"""

0 commit comments

Comments
 (0)