Commit ce65e3f
committed
Drop the ExecuTorch runtime from the Torch-TensorRT runtime wheel
The torch-tensorrt-executorch-runtime wheel shipped a full ExecuTorch Python
runtime alongside the TensorRT delegate. This ships only the delegate: a single
shared library that registers TensorRTBackend with the ExecuTorch runtime that
the executorch distribution already provides, rather than bundling a second copy
of that runtime. Shipping a second copy is also what made the old wheel prone to
a libstdc++ clash, because two C++ runtimes could end up in one process.
The native build produces just the delegate library, its RUNPATH points at the
executorch package the delegate links against, and setup.py packages the one
shared object. The runtime dependency stays commented out in the top-level
setup.py because the delegate wheel is not published to any index yet, so the
docs and the load-time and save-time errors direct users to build it from
py/torch-tensorrt-executorch-runtime/README.md.
The delegate links the C++ runtime dynamically, the way every other shared
object in the process already does. The build toolchain is newer than the
libstdc++ on a user's machine, so an optimized build emits out-of-line calls
into the newer runtime, for example std::string::_M_replace_cold. Naming stdc++
as a link library puts the reference after the objects, where the toolchain's
own libstdc++.so linker script resolves it: the old, stable symbols bind
dynamically to the system libstdc++.so.6 and only the newer helpers are pulled
statically from the toolchain's companion archive. The delegate ends up needing
no C++ runtime version above what the ExecuTorch it loads beside already needs.
A static C++ runtime is deliberately avoided: this library is loaded next to
libtorch and ExecuTorch, and a private libstdc++ would give it its own exception
type_info and locale state, which breaks exceptions and dynamic_cast across the
boundary. The build guard checks the shape: the delegate keeps a dynamic
libstdc++ dependency, has no unversioned C++ runtime symbol left undefined, and
requires no symbol version above the paired runtime.
The wheel is tagged py3-none rather than per-interpreter, because the delegate
is a plain shared object with no Python ABI and one build serves every CPython.
test_api.py checks the shipped layout: the delegate resolves through the loader
in the layout that ships, the wheel's RUNPATH is compared whole against the one
the build asks for, the symbol versions and the C++ runtime dependency are
compared against the runtime the delegate links, and the wheel's own metadata is
checked. The reachability scans that assert the import and static-C++ checks run
in CI parse each language's grammar rather than matching text, and none of them
execute the workflow they inspect.
The wheel exposes no runtime API at all. Loading and running a program belongs to
ExecuTorch, which already ships Runtime, Program and Method, so the Python wrapper
this wheel used to carry is gone along with the load(format="executorch") entry
point that reached it. That wrapper duplicated ExecuTorch's own classes down to the
line that keeps the file buffer alive, and its CPU copy of top-level inputs quietly
defeated programs exported for device-resident inputs. A consumer now imports this
package and uses executorch.runtime directly.
Registration happens on import, so there is nothing to call. ExecuTorch's own
delegates register because they are linked into its pybindings extension, and
loading that extension pulls them in; a delegate in a separate wheel cannot join
that link and ExecuTorch has no discovery hook for out-of-tree backends, so this
package performs the equivalent step itself. A load it cannot complete raises from
the import rather than being swallowed, because the diagnosis here names the real
cause, a CPU-only ExecuTorch wheel or an ABI mismatch, which a later "backend not
available" cannot. TORCH_TENSORRT_SKIP_DELEGATE_REGISTRATION=1 imports the module
without the side effect, for tooling that wants the metadata only.
The wheel now follows the layout ExecuTorch uses for its own backends, so the
TensorRT delegate is an out-of-tree sibling of them rather than a Python-only
artifact. The shared library moves to lib/, next to where executorch keeps
libexecutorch_backend_cuda.so and friends, and the wheel ships a CMake package
under share/cmake so a C++ app can link it:
find_package(executorch REQUIRED COMPONENTS backend_cuda)
find_package(torchtrt_executorch REQUIRED)
target_link_libraries(app PRIVATE executorch::runtime torchtrt::executorch_backend)
Before this the shared library was reachable only from Python, even though it is
a drop-in sibling of ExecuTorch's backends: same naming, same soname convention,
register_backend imported rather than defined. What was missing was the discovery
layer, so the only way for C++ to get the delegate was add_subdirectory against a
source checkout of this repository.
The imported target links with --no-as-needed, bracketed by push-state and
pop-state. Nothing in a consumer references a symbol the delegate defines, so the
default would drop the dependency and the backend would never register: the app
would build, load the program, and fail with an unregistered backend. That is the
shared-library counterpart of the --whole-archive the in-repo source build needs
for the same reason. No headers ship, because a consumer calls no Torch-TensorRT
code; registration happens in the library's static initializer and the rest is
ExecuTorch's runtime API.
Moving the library under lib/ also moves what $ORIGIN means, so the delegate's
own RUNPATH gains a level: $ORIGIN/../../executorch/lib rather than
$ORIGIN/../executorch/lib, and likewise for tensorrt_libs and nvidia/cu13/lib.
Without that the entries resolve inside the package directory instead of
site-packages, the delegate cannot find libexecutorch.so, libcudart or libnvinfer,
and a C++ consumer fails to link it with undefined references to cudaMemcpyAsync
and friends. The depth and the install location are one decision, so the test that
reads the declaration now rejects the single-level form it used to require.
The CMake package installs to lib/cmake/torchtrt_executorch, which is where ExecuTorch
puts its own: find_package resolves executorch from
site-packages/executorch/lib/cmake/executorch, so following that layout rather than
share/ means a consumer points CMAKE_PREFIX_PATH at the two package roots and both
resolve the same way. The walk that locates the package root now looks for the delegate
itself instead of for a directory named lib, because the config now lives inside lib/ and
stopping at the first lib/ it meets would set IMPORTED_LOCATION to that directory.
The CMake package test now configures the package with real CMake and asks for the imported
target back, because a string search over the config cannot tell a working package from a
broken one: inserting return() after cmake_minimum_required makes the config define nothing
and every string assertion still passes. The README command locates ExecuTorch through its
distribution metadata, since it is a namespace package whose __file__ is None, so the
documented one-liner raised TypeError before CMake ran.
The delegate builds for CUDA 12 as well as CUDA 13, because torch-tensorrt publishes both
channels. Three places assumed one major. The version check now accepts either, since a minor
bump inside a major does not change the ABI the delegate links. The RUNPATH carries both
layout directories, because the two majors package their runtime differently: the CUDA 13
wheels install nvidia/cu13/lib while the CUDA 12 wheels install nvidia/cuda_runtime/lib. The
artifact check maps the CUDA runtime the delegate asks for to the directory that carries it and
fails when the RUNPATH has no matching entry, which is the case that would link cleanly and then
find nothing at load time.
The symbol version ceiling is compared against the manylinux platform the wheel ships under
rather than against the ExecuTorch distribution beside it, and that platform is passed in per
architecture because the two rows use different builder images. A symbol version requirement is a
floor on the host, not a ceiling a library imposes on its neighbours: two libraries in one
process may need different versions, and the loader only needs the host to satisfy the highest.
Comparing against the sibling rejected the delegate wherever TensorRT itself was built with a
newer toolchain than ExecuTorch, which is the case on aarch64 today, and by that rule the check
would reject TensorRT too.
The delegate is built for aarch64 as well as x86_64, matching the architectures the
torch-tensorrt wheel it pairs with already ships. The native build already selected the right
TensorRT per architecture; what was missing is that the only caller generated an x86_64 matrix,
and the architecture input defaults to x86_64, so nothing ever asked for the other rows. The
aarch64 workflow now calls the same build against its own matrix, ordered after the job that
uploads the wheel it downloads, and deliberately outside that workflow's gate so a delegate
failure cannot block pull requests that have nothing to do with the delegate.
The check that the downloaded wheel carries the C++ runtime looks for the library instead of
importing the compiler package. That import reaches torch.cuda.get_device_capability() while
deciding whether it is running on Tegra, so it needs a GPU, and the aarch64 builder has none.
The symbol version cases in the guard's own test pass the manylinux tag, without which the
ceiling is skipped and every one of them passes for the wrong reason. Three of them asserted the
old rule, that a version above the ExecuTorch distribution's own is a rejection, and now expect
the artifact to be accepted: all three sit below what the platform guarantees, and the host
provides the C++ runtime rather than the sibling wheel.
The export and the reference runner run only where a GPU is present. Both compile and execute a
TensorRT engine, and the aarch64 builders are CPU-only instances, which is why the wheel's own
aarch64 lanes build without running their tests. The delegate is still built and checked on
aarch64; its runtime behaviour stays covered by the x86_64 rows, which have a GPU. Keyed on
whether the device is usable rather than on the architecture, so a GPU runner never skips it.
The delegate follows the main wheel's CUDA versions, CUDA 12.6 included. Both read the
same matrix filter, so the rows agree by construction: 25 rows, with cu126 on x86_64
only and the Arm rows on CUDA 13, which is what the main wheel publishes. The TensorRT
distribution is resolved from the CUDA the build actually uses rather than hardcoded, so
a cu126 row declares tensorrt-cu12 and a cu13 row declares tensorrt-cu13. The RUNPATH
and the ELF guard already carried both CUDA layouts, nvidia/cuda_runtime/lib for 12 and
nvidia/cu13/lib for 13, so no packaging change was needed for the new rows.
Test plan:
Ran the real filter from this branch against a full three-CUDA, five-Python,
two-architecture input and compared it against main's filter on the same input: both
return the same 25 rows. Added a test that asserts cu126 is present on x86_64, absent on
aarch64, and that both CUDA 13 rows survive on each architecture. It fails when cu126 is
removed from the x86 list. Replaced the test that asserted a hardcoded tensorrt-cu13,
which would have kept passing while a cu126 row declared the wrong dependency; the
replacement fails when the resolution is hardcoded again.
The release lane installs ExecuTorch too, so it is a pin site and now names the pinned
nightly from the nightly channel. It arrived with the CUDA 12.6 rows naming a stale
release off the default index, which resolves no ExecuTorch at all, and the pin checks
caught it.
A URL is no longer mistaken for a comment. The trailing-comment strip cut at the first
"//", so any line carrying an index URL was truncated before its requirement and the
site was reported as missing rather than as wrong. It now skips a "//" that follows a
colon, which is a scheme rather than a comment.
The channel variable that install reaches through is asserted to stay in scope. The URL
is built from CU_VERSION, which the reusable build workflow exports from the matrix row;
if that export is renamed the URL collapses to a channel that does not exist, pip falls
back to the default index, and the install resolves the wrong ExecuTorch without failing.1 parent 4d4033c commit ce65e3f
25 files changed
Lines changed: 4324 additions & 863 deletions
File tree
- .github/workflows
- cpp/src/torch_tensorrt/executorch
- examples
- executorch_reference_runner
- torchtrt_executorch_example
- py
- torch-tensorrt-executorch-runtime
- cmake
- native
- torch_tensorrt_executorch_runtime
- torch_tensorrt
- executorch
- tests/py/dynamo/executorch
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
114 | 114 | | |
115 | 115 | | |
116 | 116 | | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
117 | 137 | | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
118 | 141 | | |
119 | 142 | | |
120 | 143 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
26 | 30 | | |
27 | 31 | | |
28 | 32 | | |
| |||
49 | 53 | | |
50 | 54 | | |
51 | 55 | | |
| 56 | + | |
52 | 57 | | |
53 | 58 | | |
54 | 59 | | |
| |||
85 | 90 | | |
86 | 91 | | |
87 | 92 | | |
88 | | - | |
| 93 | + | |
89 | 94 | | |
90 | 95 | | |
91 | 96 | | |
92 | 97 | | |
93 | 98 | | |
94 | 99 | | |
95 | 100 | | |
96 | | - | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
97 | 124 | | |
98 | 125 | | |
99 | 126 | | |
| |||
105 | 132 | | |
106 | 133 | | |
107 | 134 | | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
108 | 288 | | |
109 | 289 | | |
110 | 290 | | |
| |||
142 | 322 | | |
143 | 323 | | |
144 | 324 | | |
145 | | - | |
146 | | - | |
147 | | - | |
148 | | - | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
0 commit comments