Repository navigation
Return torch tensors from the Python runtime only when torch is imported - #24321
shoumikhin wants to merge 2 commits into
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24321
Note: Links to docs will display an error until the docs builds have been completed. ❌ 1 New FailureAs of commit 87e0d97 with merge base f1240a2 ( NEW FAILURE - The following job has failed:
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 20m 33s —— View job Needs Discussion: picking the output type on every call from Detailed reviewPR Review: #24321SummaryThis PR stops the portable Python bindings from importing PyTorch. Each call returns API Design
Backward Compatibility
Thread Safety
Testing
Code Quality
RecommendationNeeds Discussion This draft asks for discussion of the per-call detection and of changing the default without deprecation. That decision shapes the rest of the change. Whichever way it goes, the always-skipped aliasing test, the stale |
Importing executorch.runtime loads PyTorch whenever PyTorch is installed,
even for a caller that never touches a torch tensor. Both the Python
wrapper (portable_lib.py) and the native module import torch while they
load, only to decide that outputs should be torch.Tensor objects. A
caller that wants to stay free of torch, for example a robot control
loop that feeds NumPy arrays, still gets torch in its process unless it
sets EXECUTORCH_PYBINDINGS_TENSOR_OUTPUT before anything imports
ExecuTorch.
Now ExecuTorch never imports torch on its own. Each call that returns
outputs (Method.execute, call(), get_outputs(), get_attribute(), and the
module calls) checks whether torch is already imported. If it is, the
outputs are torch.Tensor objects as before. If not, they are
ExecuTorchResult values, which NumPy and other DLPack libraries read
directly:
from executorch.runtime import Runtime
method = Runtime.get().load_program("add.pte").load_method("forward")
out = method.execute([np.ones((2, 2), np.float32)])[0]
# ExecuTorchResult, and torch is still not loaded
import torch
out = method.execute([np.ones((2, 2), np.float32)])[0]
# torch.Tensor
A caller that passes torch tensors has imported torch, so it sees no
change. The environment variable still fixes the output type:
"executorch" keeps ExecuTorchResult outputs even after torch is
imported, and "torch" imports torch at import time and always returns
torch tensors.
"Imported" means fully imported. A torch that another thread is still
importing, a None entry in sys.modules, and a torch whose import failed
all count as not imported, so the call returns ExecuTorchResult instead
of reading a half-built module. Before, a NumPy call that ran while
another thread imported torch could raise AttributeError, and a None
entry made every call with array inputs fail.
Tested on Linux x86_64 with an NVIDIA GPU:
- The import policy test runs each case in a fresh process: torch
installed but never imported, imported first, blocked, set to None in
sys.modules, failing on import, and still being imported. It checks
the output type from a module call and from set_inputs(), execute()
and get_outputs(), that only the "torch" setting loads torch, and that
importing torch later switches the default to torch tensors but not
the "executorch" setting. It fails on the previous code.
- 10 out of 10 runs of a thread importing torch while another runs a
method with NumPy inputs finished with no errors.
- The standalone no-ATen bindings test now expects torch tensors once it
has imported torch, unless the setting is "executorch".
- The pybindings, CUDA DLPack and runtime test files pass, except two
quantized-op tests that fail the same way without this change.
Without EXECUTORCH_PYBINDINGS_TENSOR_OUTPUT, the bindings now pick the output type on each call, and _C._tensor_output is None. The policy test for hosts without torch still expected "executorch" there. It now expects None, and still checks that the outputs are ExecuTorchResult. Tested by importing the bindings with torch blocked and the setting unset: _tensor_output is None. The test itself only runs on a host without torch, which was not available here.
f914424 to
8903f4c
Compare
|
@claude review this PR |
|
Claude finished @shoumikhin's task in 5m 10s —— View job Needs Discussion: the output type still flips partway through a process depending on whether anything has imported torch yet, and a stable API's default changes with no deprecation step; the aliasing test is still always skipped and the Detailed reviewPR Review: #24321SummaryThis PR stops the portable Python bindings from importing PyTorch. Each call returns The three reviewers agreed on the main design problem: the output type depends on global import state that is read on every call. Some items from the earlier review are fixed: the policy test now checks All 8 files were reviewed. Nothing was built or run, so every finding comes from reading the code. The checkout is shallow, so the diff was taken against the PR's parent commit Thread Safety
Backward Compatibility
API Design
Testing
Code Quality
RecommendationNeeds Discussion The draft asks whether to change the default, and the reviewers agree that deciding the type on every call from |
|
@claude review this PR |
|
Claude finished @shoumikhin's task in 5m 17s —— View job Needs Discussion: the output type still changes partway through a process depending on whether anything has imported torch yet. The new LLM-runner setup (2b3c024) can be delayed indefinitely, and if it fails, its error is raised in unrelated main-thread code. Four items from earlier reviews are still open: the always-skipped aliasing test, the stale Detailed reviewPR Review: #24321SummaryThis PR stops the portable Python bindings from importing PyTorch. Each call returns Three independent reviewers covered all 8 files, and every finding below was re-checked against the code. Nothing was built or run. Since the last review, commit 2b3c024 is new, and the earlier open items are still open. Thread Safety
API Design
Testing
Code Quality
RecommendationNeeds Discussion The default still has to be decided: per-call detection from |
2b3c024 to
87e0d97
Compare
What is wrong today
Importing
executorch.runtimeloads PyTorch whenever it is installed, even for a caller that only passes NumPy arrays and never asks fortorch.Tensoroutputs. On a small board that costs memory: running a pi0.5 bundle on a Jetson Orin Nano 8 GB took about 430 MB more peak memory, plus some swap, with PyTorch loaded. Today the only way out is settingEXECUTORCH_PYBINDINGS_TENSOR_OUTPUT=executorchbefore the first import.What this change does
ExecuTorch no longer imports PyTorch on its own. A call returns
torch.Tensoroutputs only if the caller has already imported torch, andExecuTorchResultoutputs (readable by NumPy through DLPack) otherwise. The check only trusts a fully imported torch, so a thread importing torch during a call does not break it.EXECUTORCH_PYBINDINGS_TENSOR_OUTPUTstill fixes the output type either way.Behavior change: a script that never imports torch but calls torch methods on outputs now gets
ExecuTorchResultand should addimport torch. The two in-tree callers that did this are updated.This is a draft to discuss the default. Keeping today's default and making this opt-in is the alternative.
The runtime log fixes that used to be in this pull request are now in #24602.
Tests
On Linux with an A100 GPU: with torch installed but not imported, import, load and execute never load torch, and outputs are
ExecuTorchResult; afterimport torch, outputs aretorch.Tensor; a thread importing torch during a call passes 10 of 10 runs; the environment variable overrides both ways.cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani