Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
a6cdfe5
Ship a slim ExecuTorch TensorRT delegate through shared builds
shoumikhin Sep 10, 2026
868d450
Verify the TensorRT delegate owns its ExecuTorch registration
shoumikhin Sep 11, 2026
974a4fb
Make companion CMake discovery repeatable
shoumikhin Sep 11, 2026
59153be
Use RUNPATH for installed companion consumers
shoumikhin Sep 11, 2026
815f063
Bound companion CMake version ranges to one major
shoumikhin Sep 11, 2026
a5d12cd
Link CUDA in the mixed ExecuTorch consumer examples
shoumikhin Sep 11, 2026
3397330
Prevent the reference output from overwriting the exported program
shoumikhin Sep 11, 2026
7070cfd
Keep the device input check active under optimized Python
shoumikhin Sep 11, 2026
907acbb
Preserve released ExecuTorch loading during migration
shoumikhin Sep 11, 2026
bed4f1d
Honor false-valued companion build flags
shoumikhin Sep 11, 2026
6e03c16
Accept on for the delegate registration opt-out
shoumikhin Sep 11, 2026
c9006bf
Preserve generated files in editable companion installs
shoumikhin Sep 11, 2026
984382c
Explain why runtime tests load checkout code
shoumikhin Sep 11, 2026
4eb4942
Install TensorRT metadata before the shared companion build
shoumikhin Sep 11, 2026
538cd1f
Exercise device-resident workflow commands at execution boundaries
shoumikhin Sep 11, 2026
8120775
Isolate loose ExecuTorch requirement rejection
shoumikhin Sep 11, 2026
aa41fb9
Exercise installed companion checks with valid wheel archives
shoumikhin Sep 11, 2026
2ff4c29
Validate companion artifacts against the supported platform policies
shoumikhin Sep 11, 2026
85fe5c5
Document companion rebuilds and editable installations
shoumikhin Sep 11, 2026
1b5b90a
Clarify installed companion CMake support in tests
shoumikhin Sep 11, 2026
cc8f4e6
Require wheel tagging support in shared companion builds
shoumikhin Sep 11, 2026
d7e3b62
Mark focused ExecuTorch runtime tests as unit tests
shoumikhin Sep 11, 2026
eb928ed
Fix the documented ExecuTorch C++ runtime dependencies
shoumikhin Sep 12, 2026
cba93ac
Install wheel tooling in ExecuTorch test jobs
pytorchbot Sep 12, 2026
9d6343c
Read the delegate CUDA rows from the matrix filter
pytorchbot Sep 12, 2026
b0f409e
Keep the published companion's entry points working
pytorchbot Sep 14, 2026
49e800d
Carry the license header onto the files this change adds
pytorchbot Sep 16, 2026
562a5da
Build the delegate to the standard ExecuTorch asks for
pytorchbot Sep 16, 2026
bf9031a
Tag the Arm delegate wheel for the platform it actually needs
pytorchbot Sep 16, 2026
d5de76e
Keep the loader the published main wheel imports by name
pytorchbot Sep 16, 2026
4704b62
Stop the shared install from pulling the companion in
pytorchbot Sep 16, 2026
8b4c3e7
Harden the pin update workflow's credentials and inputs
pytorchbot Sep 16, 2026
5ed47c2
Verify each pin coordinate against the sites that carry it
pytorchbot Sep 16, 2026
3036851
Check every undefined C++ symbol, not only two of its types
pytorchbot Sep 16, 2026
358804f
Make the ExecuTorch pin actually pin the build it names
pytorchbot Sep 16, 2026
82bbdda
Let the example boundary tests fail
pytorchbot Sep 16, 2026
522c05e
Fix the shipped CMake package for C++ consumers
pytorchbot Sep 16, 2026
e044ed2
Fail with guidance where a plain import used to raise a traceback
pytorchbot Sep 16, 2026
f4bc691
Replace advice a caller cannot follow
pytorchbot Sep 16, 2026
8364598
Exercise the kept entry points instead of grepping for them
pytorchbot Sep 16, 2026
6106b85
Check the kept entry points forward by reading the parsed function
pytorchbot Sep 16, 2026
df62800
Convert every build failure to the class setuptools does not swallow
pytorchbot Sep 16, 2026
a4152ca
Finish the pin and platform tag changes at every site that names them
pytorchbot Sep 16, 2026
53126d5
Give the Arm wheel one platform floor everywhere it is named
pytorchbot Sep 17, 2026
831d35c
Keep the CUDA check first, and let the wheel checker's own test see t…
pytorchbot Sep 17, 2026
a9c665e
Select the wheel to install by what it is not
pytorchbot Sep 17, 2026
b272c30
Ship the forwarder, and make it return what callers already use
pytorchbot Sep 17, 2026
f0da903
Run the ExecuTorch checks on pull requests, and fix two stale expecta…
pytorchbot Sep 17, 2026
ce20016
Identify the CMake target, honour the opt-out, and keep accurate erro…
pytorchbot Sep 17, 2026
47e3451
Close the remaining gaps the review measured
pytorchbot Sep 17, 2026
2aa94fe
Close two guards that let the wrong thing through
pytorchbot Sep 17, 2026
fb97111
Make the comments describe what the code does
pytorchbot Sep 17, 2026
b64baa5
Check the payload's architecture, and stop overstating the run path s…
pytorchbot Sep 17, 2026
6fb9933
Resolve the delegate's ExecuTorch from the CUDA channel only
pytorchbot Sep 17, 2026
a4ab4b6
Fetch the delegate's ExecuTorch from the CUDA channel without restric…
pytorchbot Sep 17, 2026
2ffc991
Name the guard that refuses a processor-only ExecuTorch
pytorchbot Sep 17, 2026
1833c5e
Stop the shipped CMake package raising the consumer's floor
pytorchbot Sep 17, 2026
52955db
Give the shipped example binaries a run path that works in the wheel
pytorchbot Sep 17, 2026
ec0878f
Stop the example runner ignoring arguments it does not recognise
pytorchbot Sep 17, 2026
2be35d2
Ask the device whether it can read host memory, not whether it is int…
pytorchbot Sep 17, 2026
aeec7c6
Format the two C++ files this change touches
pytorchbot Sep 17, 2026
c639029
Say which output failed, and stop calling a missing buffer an invalid…
pytorchbot Sep 17, 2026
f6e8e6a
Refuse a target device the delegate cannot run on
pytorchbot Sep 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions .github/scripts/check-executorch-runtime-wheel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause

"""Validate the repaired companion wheel before it enters the shared artifact."""

import argparse
import ast
import importlib.metadata
import re
import sys
from email.parser import BytesParser
from pathlib import Path

import yaml
from packaging.requirements import Requirement
from packaging.utils import canonicalize_name, parse_wheel_filename
from packaging.version import Version
from wheel.wheelfile import WheelFile


def reject(message):
sys.exit(f"FATAL: {message}")


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("wheel", type=Path)
parser.add_argument("--architecture", choices=("x86_64", "aarch64"), required=True)
args = parser.parse_args()
root = Path(__file__).resolve().parents[2]
source = root / "py/torch-tensorrt-executorch-runtime/setup.py"
(library,) = [
ast.literal_eval(node.value)
for node in ast.parse(source.read_text()).body
if isinstance(node, ast.Assign)
and any(
getattr(target, "id", None) == "DELEGATE_LIBRARY" for target in node.targets
)
]
with WheelFile(args.wheel) as archive:
names = archive.namelist()
objects = sorted(name for name in names if re.search(r"\.so(\.\d+)*$", name))
expected = f"torch_tensorrt_executorch_runtime/lib/{library}"
if objects != [expected]:
reject(f"expected {expected} and no other shared libraries, got {objects}")
else:
# The platform tag is a claim about the payload, and until now nothing read the payload
# to check it, so a wheel tagged for one architecture could carry a library built for
# the other and pass. The ELF header names the machine in two bytes at offset 18.
header = archive.read(expected)[:20]
if header[:4] != b"\x7fELF":
reject(f"{expected} is not an ELF object")
machine = int.from_bytes(header[18:20], "little")
wanted = {"x86_64": 0x3E, "aarch64": 0xB7}[args.architecture]
if machine != wanted:
names_by_machine = {0x3E: "x86_64", 0xB7: "aarch64"}
reject(
f"{expected} is built for "
f"{names_by_machine.get(machine, hex(machine))}, but this wheel is tagged for "
f"{args.architecture}"
)
for filename in (
"torchtrt_executorch-config.cmake",
"torchtrt_executorch-config-version.cmake",
):
if (
f"torch_tensorrt_executorch_runtime/lib/cmake/torchtrt_executorch/{filename}"
not in names
):
reject(f"the wheel ships no CMake package: {filename} is missing")
forbidden = [
name
for name in names
if any(
part in name
for part in (
"_portable_lib",
"libexecutorch.so",
"libextension_cuda",
"libaoti_cuda_shims",
)
)
]
if forbidden:
reject(f"the wheel ships ExecuTorch runtime components: {forbidden}")

name, version, _, tags = parse_wheel_filename(args.wheel.name)
if name != "torch-tensorrt-executorch-runtime":
reject(f"unexpected distribution: {name}")
# The floors differ by architecture. The Arm build container carries no devtoolset, so the
# C++ runtime symbols the delegate references are not absorbed statically the way they are
# on x86, and the wheel genuinely needs the newer baseline.
floor = {"x86_64": "2_28", "aarch64": "2_35"}[args.architecture]
expected_tag = f"py3-none-manylinux_{floor}_{args.architecture}"
if {str(tag) for tag in tags} != {expected_tag}:
reject(f"expected repaired tag {expected_tag}, got {tags}")
wheel_metadata = BytesParser().parsebytes(
archive.read(f"{archive.dist_info_path}/WHEEL")
)
if wheel_metadata.get("Root-Is-Purelib") != "false":
reject("wheel declares itself pure python")
if set(wheel_metadata.get_all("Tag", [])) != {expected_tag}:
reject("WHEEL tags disagree with the filename")
metadata = BytesParser().parsebytes(
archive.read(f"{archive.dist_info_path}/METADATA")
)
if (
canonicalize_name(metadata["Name"]) != name
or Version(metadata["Version"]) != version
):
reject("METADATA name/version disagree with the filename")
requirements = [
Requirement(value) for value in metadata.get_all("Requires-Dist", [])
]
pinned = yaml.safe_load((root / "dev_dep_versions.yml").read_text())[
"__executorch_version__"
]
for distribution in (
"executorch",
"torch-tensorrt",
"torch",
"tensorrt-cu13",
"nvidia-cuda-runtime",
):
if distribution == "executorch":
# The delegate links one specific ExecuTorch build, so its requirement carries the
# label naming that build. Without it the requirement is satisfied by a
# processor-only build, or another CUDA build of the same date. Compare against the
# installed wheel, whose label is the one the delegate actually linked, and check
# the public part still matches the repository pin.
installed = Version(importlib.metadata.version(distribution))
if installed.public != pinned:
reject(
f"the repository pins executorch=={pinned}, but this wheel was built "
f"against {installed}, whose version differs from that pin"
)
expected_version = str(installed)
else:
expected_version = Version(
importlib.metadata.version(distribution)
).public
matched = [
r for r in requirements if canonicalize_name(r.name) == distribution
]
if (
len(matched) != 1
or str(matched[0].specifier) != f"=={expected_version}"
or matched[0].marker
or matched[0].extras
or matched[0].url
):
reason = (
"the repository pins"
if distribution == "executorch"
else "the build used"
)
reject(
f"{reason} {distribution}=={expected_version}, but the wheel requires {matched}"
)
# ExecuTorch is the exception, and deliberately so: the delegate links one specific build,
# and without the label naming it the requirement is satisfied by a processor-only build or
# another CUDA build of the same date. It resolves from the CUDA channel this wheel already
# requires. Every other requirement stays label-free, so it resolves anywhere.
labelled = [
requirement
for requirement in requirements
if "+" in str(requirement.specifier)
and canonicalize_name(requirement.name) != "executorch"
]
if labelled:
reject(
f"a requirement other than executorch carries a local label: {labelled}"
)
# Reading every member verifies its RECORD hash, including the delegate payload.
for filename in names:
if not filename.endswith("/"):
archive.read(filename)
print(
f"Validated {args.wheel.name}: one delegate, matching dependencies and {expected_tag}"
)


if __name__ == "__main__":
main()
27 changes: 23 additions & 4 deletions .github/scripts/install-torch-tensorrt.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,32 @@ fi

# Install Torch-TensorRT
if [[ ${PLATFORM} == win32 ]]; then
# Same exclusion as the Linux branch below, and for the same reason: the wheel's name varies by
# variant, so anchoring on a prefix leaves the pattern unexpanded and pip reads it literally.
wheels=""
for wheel in "${RUNNER_ARTIFACT_DIR}"/torch_tensorrt*.whl; do
case "${wheel}" in
*executorch_runtime*) continue ;;
esac
wheels="${wheels} ${wheel}"
done
# pin-check: no-nightly -- Windows installs only the main wheel, without the Linux companion.
python -m pip install ${RUNNER_ARTIFACT_DIR}/torch_tensorrt*.whl || exit 1
python -m pip install ${wheels} || exit 1
else
# The companion requires the nightly ExecuTorch channel even on test/release jobs.
# Every built wheel except the companion. Installing the companion here is what forced a
# nightly index onto release jobs; the ExecuTorch workflow installs it instead, naming the
# channel it wants. Selecting by exclusion rather than by prefix, because the main wheel's name
# varies by variant and a prefix guess leaves the glob unexpanded and pip reading it literally.
wheels=""
for wheel in /opt/torch-tensorrt-builds/torch_tensorrt*.whl; do
case "${wheel}" in
*executorch_runtime*) continue ;;
esac
wheels="${wheels} ${wheel}"
done
# Exit explicitly: the caller appends its test script and this file does not use set -e.
python -m pip install /opt/torch-tensorrt-builds/torch_tensorrt*.whl --use-deprecated=legacy-resolver \
--extra-index-url "https://download.pytorch.org/whl/nightly/${CU_VERSION}" || exit 1
# pin-check: no-nightly -- the main wheel alone, which needs no ExecuTorch channel.
python -m pip install ${wheels} --use-deprecated=legacy-resolver || exit 1
fi

echo -e "Running test script";
72 changes: 52 additions & 20 deletions .github/scripts/update_executorch_pin.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,24 @@

# This allowlist prevents a release version from rewriting unrelated dependencies
# or content-addressed wheel URLs. The repository guard inventories sites separately.
_PIN_SITES = (
".github/workflows/build_linux.yml",
".github/workflows/executorch-test-linux.yml",
"MODULE.bazel",
"docker/MODULE.bazel.docker",
"docker/MODULE.bazel.ngc",
"justfile",
"pyproject.toml",
"py/torch-tensorrt-executorch-runtime/README.md",
"py/torch-tensorrt-executorch-runtime/pyproject.toml",
"toolchains/ci_workspaces/MODULE.bazel.tmpl",
"examples/executorch_reference_runner/README.md",
)
# Which coordinate each site carries. Checking "a version or a commit was found" lets a file that
# carries both satisfy the check on the commit alone, so a requirement the pattern stops matching
# would leave the version stale while the commit moves. That split is the whole thing these two pins
# exist to prevent, so each site declares what it must contain and each is verified on its own.
_SITE_COORDINATES: dict[str, frozenset[str]] = {
".github/workflows/build_linux.yml": frozenset({"version"}),
".github/workflows/executorch-test-linux.yml": frozenset({"version"}),
"MODULE.bazel": frozenset({"version", "commit"}),
"docker/MODULE.bazel.docker": frozenset({"version", "commit"}),
"docker/MODULE.bazel.ngc": frozenset({"version", "commit"}),
"justfile": frozenset({"version"}),
"pyproject.toml": frozenset({"version"}),
"py/torch-tensorrt-executorch-runtime/README.md": frozenset({"version"}),
"py/torch-tensorrt-executorch-runtime/pyproject.toml": frozenset({"version"}),
"toolchains/ci_workspaces/MODULE.bazel.tmpl": frozenset({"version", "commit"}),
"examples/executorch_reference_runner/README.md": frozenset({"commit"}),
}
_PIN_SITES = tuple(_SITE_COORDINATES)
_CLAUSE = r"(?:===|==|>=|<=|~=|!=|<|>)\s*[^\s\"'`,;()]+"
_MARKER_VALUE = r"""(?:[a-z_]+|"[^"\n]*"|'[^'\n]*')"""
_MARKER_ATOM = rf"(?:\([ \t]*)*{_MARKER_VALUE}[ \t]*(?:===|==|>=|<=|~=|!=|<|>|not[ \t]+in|in)[ \t]*{_MARKER_VALUE}(?:[ \t]*\))*"
Expand Down Expand Up @@ -81,7 +86,10 @@ def available_versions(index_args: list[str]) -> list[str]:
)
match = re.search(r"^\s*Available versions:\s*(.+)$", out, re.MULTILINE)
if match is None:
raise SystemExit("pip index versions printed no Available versions line")
# A channel with no ExecuTorch release yet prints no such line. That is the expected state
# between adding a CUDA minor's rows and the first ExecuTorch build for it, so report it as
# an empty list and let the caller say which channel is not ready.
return []
return [v.strip() for v in match.group(1).split(",") if v.strip()]


Expand Down Expand Up @@ -255,12 +263,36 @@ def rewrite_requirement(match: re.Match[str]) -> str:
else:
updated, count = _REQUIREMENT.subn(rewrite_requirement, text)
updated = updated.replace(old_commit, new_commit)
# Either coordinate may legitimately be the only one a site carries, and a
# site already at the target is satisfied rather than broken, which is what
# lets a later run finish an interrupted one.
if not count and old_commit not in text:
if new_version not in text and new_commit not in text:
raise ValueError("no current version or source pin found")
try:
name = str(path.relative_to(_REPO_ROOT)).replace("\\", "/")
except ValueError:
name = str(path)
# A site outside the declaration, which only a caller substituting its own list
# produces. It is required to carry whichever single coordinate it appears to hold,
# rather than either of the two, since there is nothing declaring what it should.
expected = _SITE_COORDINATES.get(
name,
(
frozenset({"version"})
if _REQUIREMENT.search(text)
else frozenset({"commit"})
),
)
# Each declared coordinate on its own, and by the same pattern that does the
# rewriting. Accepting a bare occurrence of the target version anywhere in the file
# let an unrelated package at that version stand in for the requirement, and it was
# never needed for convergence: a site a previous run already moved still matches
# the pattern, so it still counts.
if "version" in expected and not count:
raise ValueError(
"carries no ExecuTorch version requirement to move"
)
if (
"commit" in expected
and old_commit not in text
and new_commit not in text
):
raise ValueError("carries no ExecuTorch source commit to move")
pending.append((path, text, updated))
except (
OSError,
Expand Down
Loading
Loading