Skip to content

Ship the OpenVINO delegate as its own library - #21770

Open
shoumikhin wants to merge 28 commits into
gh/shoumikhin/96/headfrom
gh/shoumikhin/105/head
Open

Ship the OpenVINO delegate as its own library#21770
shoumikhin wants to merge 28 commits into
gh/shoumikhin/96/headfrom
gh/shoumikhin/105/head

Conversation

@shoumikhin

@shoumikhin shoumikhin commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

The OpenVINO delegate was compiled into the Python extension, so only Python could
use it. A C++ application had no way to link it from the wheel, which is the same gap
the other delegates had before they were split out.

It now builds as its own library and ships beside the runtime, so an application can
ask for it:

find_package(executorch REQUIRED COMPONENTS backend_openvino)
target_link_libraries(my_app PRIVATE executorch::backend_openvino)

Only the adapter ships here. The OpenVINO runtime itself is loaded at run time and is
not part of the wheel, so the component's documentation says to install it separately,
and the wheel does not grow meaningfully: the adapter is a few kilobytes.

The library resolves the runtime from the shared runtime library rather than from the
static core, so the delegate registers into the one registry the process has instead of
a second private one.

Test Plan: built a wheel with OpenVINO enabled on Linux x86_64 and inspected it.
The library ships at executorch/lib/libexecutorch_backend_openvino.so. Its delegate
symbols moved out of the Python extension, 13 in the library and 0 in the extension,
so the process has one copy and one registration. Its only ExecuTorch dependency is
the shared runtime library, and it records no link dependency on OpenVINO, confirming
the runtime is still loaded at run time. Added an ownership row to the shared library
test so a future change that puts the delegate back into the extension fails there.

@pytorch-bot

pytorch-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/21770

Note: Links to docs will display an error until the docs builds have been completed.

❌ 30 New Failures, 2 Unrelated Failures, 30 Unclassified Failures

As of commit 1ccd490 with merge base ed65b12 (image):

NEW FAILURES - The following jobs have failed:

UNCLASSIFIED FAILURES - DrCI could not classify the following jobs because the workflow did not run on the merge base. The failures may be pre-existing on trunk or introduced by this PR:

FLAKY - The following jobs failed but were likely due to flakiness present on trunk:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 12, 2026
@shoumikhin
shoumikhin force-pushed the gh/shoumikhin/96/head branch from 9428da3 to 2cdf978 Compare August 12, 2026 16:19
@shoumikhin
shoumikhin force-pushed the gh/shoumikhin/105/head branch from 00563b2 to d3de6d5 Compare August 12, 2026 16:19
## The problem

The wheel can carry the CUDA delegate, but no published wheel contains one: there is no CUDA
row in any workflow, so a GPU user has to build from source.

## The change

Add the workflows that build and publish CUDA wheels for Linux x86_64 and aarch64, and a smoke test
that checks each wheel from the artifact itself. The build machines for these rows have no GPU, so
the smoke test does not execute a model; it verifies the CUDA libraries are present, that the
declared runtime matches the wheel's CUDA version, that nothing resolves through the build
machine's toolkit, and that the shipped device code covers every GPU architecture the row claims.

```
executorch-1.5.0-cp312-cp312-manylinux_2_28_x86_64.whl    +cu130
```

A release publishes CUDA 12.6, 13.0 and 13.2, for Python 3.10 through 3.13. A pull request builds a
single row instead of all twelve, because a full matrix costs hours for little extra signal.

Which GPU architectures each row compiles for is chosen per row rather than detected on the builder.
Detecting it would produce a wheel carrying device code for whatever machine happened to build it,
which installs fine and then fails at the first GPU call.

The aarch64 CUDA 12.6 row also compiles for compute capability 8.7, which is an embedded module.
Every other row lists only the architectures the published PyTorch build for that train covers, and
by that rule 8.7 would be left out, because the generic aarch64 build of this train carries 8.0 and
9.0 only. It is included because this is the only row whose CUDA major version matches what that
module's software release ships, and because this wheel declares no PyTorch dependency: a user there
supplies the build that carries their architecture. Leaving 8.7 out does not protect them from a bad
pairing, it only removes the device code they need. Without it, a model reaching one of the shipped
optional operators, quantized matrix multiply, sort or random number generation, fails at the first
launch on that device.

Two guards keep a release honest:

- if the shared matrix generator stops offering a combination this policy advertises, the step fails
  instead of quietly publishing fewer wheels. A missing job is otherwise a green check for a wheel
  that was never built.
- if a row reaches the architecture list with no CUDA version, the build refuses rather than falling
  back to the builder's GPU. A TORCH_CUDA_ARCH_LIST that holds only named GPU families PyTorch
  accepts, such as "Hopper", now fails to configure instead of quietly leaving
  CMAKE_CUDA_ARCHITECTURES unset and taking the compiler default. The three named forms CMake
  itself understands, "native", "all" and "all-major", are rejected before this logic runs:
  torch resolves the list with its own bundled CUDA architecture module, which does not know
  those names and stops the configure. That is upstream behaviour, not something this change
  introduces or can work around, so a caller has to name architectures explicitly.

Windows CUDA is deliberately absent. The separate shared libraries this wheel exists to ship are
Linux only today, so a Windows CUDA wheel would carry a delegate a C++ application still could not
link.

## Test plan

- built the full release matrix, twelve wheels, and confirmed each one's contents match the row it
  claims: the CUDA libraries present, the CUDA runtime declared, and device code for every GPU
  architecture the row advertises.
- ran a GPU model end to end from a CI-built wheel on three NVIDIA GPUs covering three device
  architectures, with output identical to eager PyTorch on each (largest absolute difference 0),
  and inspected the wheel for a fourth device it cannot execute on.
- ran the matrix filter over generated inputs, including incomplete and malformed ones, and
  confirmed it refuses rather than publishing a partial release: a missing CUDA version, a missing
  python, or a python present on rows this policy does not build are each reported by name.
- confirmed a CPU row still produces a CPU wheel on a builder that happens to have a CUDA toolkit
  installed.
- the newest architecture also ships in its portable form, so a GPU newer than any in the row can
  still run by having the driver compile it at load time. Checked with `cuobjdump --list-ptx`, since
  `--list-elf` prints identical output whether or not the portable form is present.
- every library that carries GPU device code covers the whole row on its own.
- the declared CUDA packages are compared against the expected set in BOTH directions. A one-way
  comparison accepted a wheel that omitted required packages, and a name-suffix comparison accepted
  cross-train names because for CUDA 13 the suffix is empty.
- the python axis is an allowlist, matching the CUDA axis. Testing only the disabled list let any
  python not on it through: a 3.9 row was emitted successfully.
- `install_utils.py` is in both CUDA workflows' path filters. It owns the supported CUDA train list
  and the toolkit detection, so a change there previously ran no CUDA wheel job.
- requesting the JetPack rows fails with its own reason instead of the generic empty-matrix message,
  since both of its lists are deliberately empty and no workflow asks for them.
- torchao keeps its CUDA channel where that channel exists. Falling back to the plain nightly index
  was needed only on aarch64, where the CUDA channel publishes nothing, and doing it everywhere
  changed which torchao an x86_64 install resolves.
- the CUDA smoke test now asserts the QnnBackend and OpenvinoBackend registrations that a CPU Linux
  row asserts. The CUDA build enables OpenVINO on every Linux architecture and downloads the QNN
  SDK on x86_64, so a CUDA wheel carries both backends; a previous premise that "a CUDA row is not
  built with them" was false, and dropping the checks meant those two backends were unverified on
  every CUDA wheel.

Known gap: no automated job runs a CUDA model on real hardware before publication. Running a model
on real hardware is a separate release-time step that a person owns today, not an automated job
wired into these workflows.

ghstack-source-id: 95d67c7
ghstack-comment-id: 5220374521
Pull-Request: #21668
The OpenVINO delegate was compiled into the Python extension, so only Python could
use it. A C++ application had no way to link it from the wheel, which is the same gap
the other delegates had before they were split out.

It now builds as its own library and ships beside the runtime, so an application can
ask for it:

    find_package(executorch REQUIRED COMPONENTS backend_openvino)
    target_link_libraries(my_app PRIVATE executorch::backend_openvino)

Only the adapter ships here. The OpenVINO runtime itself is loaded at run time and is
not part of the wheel, so the component's documentation says to install it separately,
and the wheel does not grow meaningfully: the adapter is a few kilobytes.

The library resolves the runtime from the shared runtime library rather than from the
static core, so the delegate registers into the one registry the process has instead of
a second private one.

Test Plan: built a wheel with OpenVINO enabled on Linux x86_64 and inspected it.
The library ships at executorch/lib/libexecutorch_backend_openvino.so. Its delegate
symbols moved out of the Python extension, 13 in the library and 0 in the extension,
so the process has one copy and one registration. Its only ExecuTorch dependency is
the shared runtime library, and it records no link dependency on OpenVINO, confirming
the runtime is still loaded at run time. Added an ownership row to the shared library
test so a future change that puts the delegate back into the extension fails there.

ghstack-source-id: b6b210a
ghstack-comment-id: 5263017453
Pull-Request: #21770
@linux-foundation-easycla

linux-foundation-easycla Bot commented Aug 13, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: shoumikhin / name: Anthony Shoumikhin (fac460c)
  • ✅ login: shoumikhin / name: shoumikhin (7fb60b0, 9d4616b)

[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
[ghstack-poisoned]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ciflow/trunk CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. module: arm Issues related to arm backend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant