Skip to content

Build and ship the OpenVINO backend in the Windows wheels - #23997

Merged
shoumikhin merged 1 commit into
pytorch:mainfrom
shoumikhin:openvino-windows
Oct 20, 2026
Merged

shoumikhin merged 1 commit into
pytorch:mainfrom
shoumikhin:openvino-windows

Conversation

@shoumikhin

@shoumikhin shoumikhin commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

OpenVINO is Intel's runtime for fast inference on Intel CPUs, GPUs and NPUs. The ExecuTorch OpenVINO delegate ships only in the Linux wheels today. Intel publishes OpenVINO for Windows x64, but Windows users must build ExecuTorch from source to use the delegate.

What this change does

The Windows wheels, CPU and CUDA, now build and ship the delegate, like the Linux wheels. pip install "executorch[openvino]" installs the runtime on Windows x64 too. On Python 3.15, and on Linux aarch64 with Python 3.14, the extra installs nothing, because the OpenVINO versions it allows have no build there. Before, it failed there on Linux.

The delegate loads the OpenVINO C library at run time, so the build needs no OpenVINO SDK. On Windows it reads OPENVINO_LIB_PATH with GetEnvironmentVariableW and loads with LoadLibraryExW, using wide paths. A bare name is searched with LOAD_LIBRARY_SEARCH_DEFAULT_DIRS, which leaves out the current folder and PATH, so an openvino_c.dll planted in the current folder is not loaded. A path is made absolute and also searches its own folder, so openvino.dll and tbb12.dll load from beside it. At unload, the delegate keeps the runtime loaded instead of calling FreeLibrary inside DllMain.

The Windows smoke tests check that the delegate registers, then install the openvino extra and nncf. In a new process, they check that the runtime loads, the model is really delegated, and its output matches eager. From a folder holding a copy of the runtime, they check that its full path loads and its bare name does not. On Python 3.15 they only check that the extra installs without a runtime. The model check loads the program from memory.

The docs and the PyPI description list Windows, note the Python limits, and explain the Windows search rules.

Testing

  • Under Wine, with a MinGW build of the loader and the OpenVINO 2025.4.1 Windows DLLs: full, relative and non-ASCII paths load, as does a value set with SetEnvironmentVariableW after startup. The bare name check passes, and fails if the copy cannot load or the loader searches the current folder.
  • On Python 3.15, the extra installs without a runtime, and the check fails if the version bound is removed.
  • On Linux, the new OpenVINO check passes with OpenVINO 2025.4.1 and nncf 3.1.0.
  • Ran the repository's linters.
  • On native Windows, the CPU and CUDA wheel jobs (Python 3.11) ran the new checks and passed.

Copilot AI balanced review requested due to automatic review settings October 19, 2026 17:30
@pytorch-bot

pytorch-bot Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

🔗 Helpful Links

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

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

❌ You can merge normally! (5 Unrelated Failures), 1 Unclassified Failure

As of commit f23eaf3 with merge base e0692c7 (image):

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

BROKEN TRUNK - The following jobs failed but were present on the merge base:

👉 Rebase onto the `viable/strict` branch to avoid these failures

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 Oct 19, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin shoumikhin added the release notes: build Changes related to build, including dependency upgrades, build flags, optimizations, etc. label Oct 19, 2026
@Gasoonjia

Copy link
Copy Markdown
Contributor

@claude review this PR plz

@claude

claude Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Claude finished @Gasoonjia's task in 3m 54s —— View job


This PR builds and ships the OpenVINO delegate in the Windows wheel, and I found nothing blocking. The wheel build, the packaging, the pybind linking and the symbol check all look correctly wired for Windows. Three non-blocking points: the model-execution check never actually runs in CI, the Windows loader uses ANSI-only APIs, and its error messages show only a number.

Detailed review

PR Review: #23997

Summary

This PR adds a Windows branch to the OpenVINO delegate's runtime loader and turns the delegate on in the Windows wheel. The changes cover the build flags, the wheel packaging, the [openvino] extra and the smoke tests. I traced the whole Windows path by reading the code and found nothing that would leave the delegate out of the wheel or unregistered:

  • setup.py passes -DEXECUTORCH_BUILD_OPENVINO=ON, which overrides the default set in the Linux-only branch of pybind.cmake.
  • Building the backend and linking it into the pybindings has no platform gate.
  • On Windows, executorch_target_shipped_runtime_path turns on WINDOWS_EXPORT_ALL_SYMBOLS and adds the /INCLUDE anchor.
  • The DLL entry in setup.py already globbed *executorch_backend_openvino.dll.
  • NOMINMAX is set for the whole tree, so including <windows.h> first is safe.

The remaining findings are minor.

Testing

  • The new execution check never runs in Windows CI (.ci/scripts/wheel/test_windows.py:96-97). test_a_model_runs_through_the_openvino_delegate (test_base.py:67-76) returns SKIP when importing OpenvinoPartitioner fails, because it needs openvino and nncf. Nothing in build-wheels-windows.yml, pre_build_script.sh or post_build_script.sh installs them. Linux has the same gap, so this matches Linux.
    • In practice CI checks only that the DLL ships, has one owner and registers. The PR description says the job "runs a model through it" and "runs the smoke test end to end", which suggests more. The comment at test_windows.py:96 also suggests more.
    • Either install openvino and nncf in the Windows wheel test step, or reword the description and that comment to say the run check skips in CI.

Code Quality

  • ANSI-only path handling on Windows (backends/openvino/runtime/OpenvinoBackend.cpp:39, :103). portable_lib.py sets OPENVINO_LIB_PATH to the full site-packages\openvino\libs\openvino_c.dll path, but std::getenv and LoadLibraryExA read it through the ANSI code page.
    • If the user's profile or venv path contains a character outside that code page (a non-ASCII user name is common), the narrow environment copy can't represent the path and loading fails. The delegate then quietly reports itself unavailable.
    • _wgetenv plus LoadLibraryExW avoids this.
    • Low-to-medium confidence: I'm reasoning from how the UCRT keeps narrow and wide copies of the environment, not from a repro. Fix this →
  • Bare Windows error codes (OpenvinoBackend.cpp:42-44). last_error() gives only "error 126". The CUDA backend already formats Windows errors with FormatMessageA in format_win_error (backends/cuda/runtime/platform/platform.cpp:28-50).
    • The two causes users will most often hit are 126 (a dependency such as TBB is missing) and 193 (wrong architecture), and both are much easier to diagnose when shown as text.
    • Consider formatting the message the same way, or moving that helper somewhere both backends can use it.
  • LOAD_WITH_ALTERED_SEARCH_PATH with a relative OPENVINO_LIB_PATH (OpenvinoBackend.cpp:39). Microsoft documents this flag's behavior as undefined when the name is a relative path (for example .\libs\openvino_c.dll). Auto-discovery always sets an absolute path, so only a hand-set relative value is affected.
    • A cheap guard: make the path absolute (GetFullPathNameW) before loading. Alternatively, say in the README that the value must be absolute.

Recommendation

Approve

None of these block the PR. The test gap is the most worthwhile fix, because without it CI cannot catch a Windows build where the delegate registers but fails at inference. If you leave the test as is, update the PR description so it doesn't claim end-to-end execution on Windows.

Copilot AI balanced review requested due to automatic review settings October 19, 2026 18:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI balanced review requested due to automatic review settings October 20, 2026 06:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin shoumikhin changed the title Build and ship the OpenVINO backend in the Windows wheel Build and ship the OpenVINO backend in the Windows wheels Oct 20, 2026
The OpenVINO delegate lets ExecuTorch run models on Intel CPUs, GPUs and
NPUs. Until now the prebuilt delegate shipped only in the Linux wheels. The
Windows wheels left it out, even though Intel publishes an OpenVINO runtime
for Windows x86-64, so Windows users had to build ExecuTorch from source.

This change builds the delegate into the Windows wheels, CPU and CUDA, the
same way the Linux wheels carry it. setup.py enables EXECUTORCH_BUILD_OPENVINO
on Windows and packages the delegate's import library (.lib) beside the other
Windows DLLs. The build file passes /EHsc and /GR when CMake reports an MSVC
style compiler, which includes the clang-cl the wheels use, and -frtti and
-fexceptions otherwise.

The delegate loads the OpenVINO C library at run time, so the build needs no
OpenVINO SDK. That loader used dlopen, with libopenvino_c.so as the default
name. It is now split by platform. On Windows it reads OPENVINO_LIB_PATH with
GetEnvironmentVariableW and loads with LoadLibraryExW, so a path outside the
active code page still works, and it reports load errors as the system's
message text. A bare name such as openvino_c.dll is searched with
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS, which covers the program's own folder,
System32 and folders added with AddDllDirectory, but not the current folder
or PATH. So an openvino_c.dll planted in the current folder is not loaded. A
path is made absolute and also searches its own folder, so the runtime's
dependencies, openvino.dll and tbb12.dll, load from beside it. The Python
package already finds the pip installed runtime and passes its full path; it
now looks for openvino_c.dll on Windows. The delegate is a global, so on
Windows its destructor keeps the runtime loaded rather than calling
FreeLibrary inside DllMain. On Linux the load error now also names the path
it tried.

pip install "executorch[openvino]" now installs the runtime on Windows x86-64
too. The OpenVINO versions the extra allows publish no build for Python 3.15,
or for Linux aarch64 on Python 3.14, so there the extra now installs nothing.
Before, the command failed on those Linux setups. The docs say so.

The Windows wheel smoke tests, CPU and CUDA, check that the delegate
registers. They then install the openvino extra and the quantizer the
partitioner needs. The extra is pinned to the installed version, so pip
cannot swap the wheel under test. In a new process, they require that the
delegate loads the runtime and that a model it runs matches eager. From a folder holding a copy
of the runtime, they also check that the delegate loads the copy by its full
path but not by its bare name. On Python 3.15 they only check that the extra
installs without a runtime. The shared model check now asserts that the
program is really delegated, and loads it from memory, because Windows does
not delete a file the loaded program keeps open. The shared library check
requires the delegate on Linux and Windows wheels, and finds it by its
is_available member rather than its class name, because the reader of
Windows exports matches decorated names, which spell a member and never a
class alone. The docs, the README and the PyPI description list Windows. The
backend README explains where Windows looks for the runtime's dependencies,
and its Linux OPENVINO_LIB_PATH example now finds the versioned library the
pip package ships.

Test plan:

- Built the loader for Windows with a MinGW cross compiler and ran it under
  Wine with the OpenVINO 2025.4.1 Windows DLLs. Full, forward slash, relative
  and non-ASCII paths load the runtime and find the CPU device, and so does a
  path set with SetEnvironmentVariableW after startup, or changed while it is
  read. A bare name does not load a copy planted in the current folder.
  Errors read as text with their code. Unloading the delegate's DLL leaves
  the runtime loaded.
- Ran the bare name check against that build under Wine. It passes with the
  real DLLs. It fails when the copied runtime cannot load, and when the
  loader searches the current folder.
- On Python 3.15 with pip, the extra installs without a runtime, and the
  check fails if the python_version bound is removed, also when an older
  release is available to pip. A resolver run shows the extra resolves on
  Linux aarch64 with Python 3.14 only with the new bound.
- On Linux, built the delegate against the wheel's C++ SDK and ran the
  Windows OpenVINO check with OpenVINO 2025.4.1 and nncf 3.1.0. The runtime
  loads and the model matches eager exactly, and the check fails when the
  runtime cannot load.
- Ran the repository's linters (black, usort, flake8, clang-format and
  others) on the changed files.
- On native Windows, the CPU and CUDA wheel jobs (Python 3.11) run the new
  checks. The Python 3.15 branch first runs in the nightly build.
Copilot AI balanced review requested due to automatic review settings October 20, 2026 09:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin
shoumikhin merged commit a39d597 into pytorch:main Oct 20, 2026
415 of 421 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. release notes: build Changes related to build, including dependency upgrades, build flags, optimizations, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants