Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 2 additions & 9 deletions .ci/scripts/setup-openvino.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,9 @@ set -ex
# shellcheck source=/dev/null
source "$(dirname "${BASH_SOURCE[0]}")/utils.sh"

# Download and install OpenVINO from release packages
OPENVINO_VERSION="2025.3"
OPENVINO_BUILD="2025.3.0.19807.44526285f24"
OPENVINO_URL="https://storage.openvinotoolkit.org/repositories/openvino/packages/${OPENVINO_VERSION}/linux/openvino_toolkit_ubuntu22_${OPENVINO_BUILD}_x86_64.tgz"
source "$(dirname "${BASH_SOURCE[0]}")/../../backends/openvino/scripts/install_openvino.sh"
install_openvino

curl -Lo /tmp/openvino_toolkit.tgz --retry 3 --fail ${OPENVINO_URL}
tar -xzf /tmp/openvino_toolkit.tgz
mv openvino_toolkit_ubuntu22_${OPENVINO_BUILD}_x86_64 openvino

source openvino/setupvars.sh
cd backends/openvino
pip install -r requirements.txt
cd scripts
Expand Down
149 changes: 149 additions & 0 deletions .ci/scripts/test_wheel_package_openvino.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/bin/bash
# === OpenVINO Wheel Build & Test Script ===
#
# Builds the ExecuTorch wheel with OpenVINO support, installs it into both
# a conda env and a Python venv, and runs smoke tests verifying that the
# OpenVINO backend is registered and can export a simple model.


set -e
set -x
exec > >(tee -i openvino_wheel_build.log) 2>&1

REPO_ROOT=$(pwd)
PYTHON_VERSION=${1:-3.11}

# ----------------------------
# Dynamically create test script
# ----------------------------
cat > "/tmp/script_openvino_wheel_test.py" << 'EOF'
import torch
from torch.export import export
from executorch.backends.openvino.partitioner import OpenvinoPartitioner
from executorch.exir import to_edge_transform_and_lower
from executorch.exir.backend.backend_details import CompileSpec
from executorch.exir.backend.utils import format_delegated_graph
from executorch.exir.capture._config import ExecutorchBackendConfig
from executorch.extension.export_util.utils import save_pte_program


class LinearModule(torch.nn.Module):
def __init__(self):
super().__init__()
self.linear = torch.nn.Linear(3, 3)

def forward(self, x):
return self.linear(x)

def get_example_inputs(self):
return (torch.randn(3, 3),)


model = LinearModule().eval()
example_inputs = model.get_example_inputs()

exported = export(model, example_inputs, strict=True)

compile_spec = [CompileSpec("device", b"CPU")]
edge = to_edge_transform_and_lower(
exported,
partitioner=[OpenvinoPartitioner(compile_spec)],
)

# Verify OpenVINO delegation occurred
output_graph = format_delegated_graph(edge.exported_program().graph_module)
assert "OpenvinoBackend" in output_graph, \
"Expected OpenVINO delegation but no delegate call found in graph"
print("OpenVINO delegation successful")

executorch_program = edge.to_executorch(
config=ExecutorchBackendConfig(extract_delegate_segments=False)
)
save_pte_program(executorch_program, "linear_openvino", "")
print("linear_openvino.pte created successfully")
EOF

source .ci/scripts/utils.sh
# ----------------------------
# Install OpenVINO and source its setupvars.sh
# ----------------------------
echo "=== Installing OpenVINO ==="
source "${REPO_ROOT}/backends/openvino/scripts/install_openvino.sh"
install_openvino
echo "OpenVINO_DIR=${OpenVINO_DIR}"

# ----------------------------
# Build the wheel
# ----------------------------
echo "=== Building Wheel Package ==="
install_executorch

python setup.py bdist_wheel

WHEEL_FILE=$(ls dist/*.whl | head -n 1)
echo "Found wheel: ${WHEEL_FILE}"


# ----------------------------
# Helpers
# ----------------------------
run_core_tests() {
local PYBIN="$1"
local PIPBIN="$2"
local LABEL="$3"

echo "=== [${LABEL}] Installing wheel ==="
"${PIPBIN}" install --upgrade pip
"${PIPBIN}" install "${WHEEL_FILE}"

echo "=== [${LABEL}] Import smoke tests ==="
"${PYBIN}" -c "import executorch; print('executorch imported')"
"${PYBIN}" -c "import executorch.backends.openvino; print('executorch.backends.openvino imported')"

echo "=== [${LABEL}] Verify OpenvinoBackend is registered ==="
"${PYBIN}" - <<'PY'
from executorch.extension.pybindings.portable_lib import _get_registered_backend_names
backends = _get_registered_backend_names()
print(f"Registered backends: {backends}")
assert "OpenvinoBackend" in backends, \
f"OpenvinoBackend not found in registered backends: {backends}"
print("OpenvinoBackend is registered")
PY

echo "=== [${LABEL}] Run export script to generate linear_openvino.pte ==="
(cd "${REPO_ROOT}" && "${PYBIN}" /tmp/script_openvino_wheel_test.py)

if [[ -f "${REPO_ROOT}/linear_openvino.pte" ]]; then
echo "[${LABEL}] linear_openvino.pte created successfully"
rm -f "${REPO_ROOT}/linear_openvino.pte"
else
echo "ERROR: [${LABEL}] linear_openvino.pte was not created"
exit 1
fi
}

# ----------------------------
# Conda environment tests
# ----------------------------
echo "=== Testing in Conda env ==="
TEMP_ENV_DIR=$(mktemp -d)
echo "Using temporary directory for conda: $TEMP_ENV_DIR"
conda create -y -p "${TEMP_ENV_DIR}/env" python="${PYTHON_VERSION}"
CONDA_PY="${TEMP_ENV_DIR}/env/bin/python"
CONDA_PIP="${TEMP_ENV_DIR}/env/bin/pip"
run_core_tests "${CONDA_PY}" "${CONDA_PIP}" "conda"
conda env remove -p "${TEMP_ENV_DIR}/env" -y || true
rm -rf "${TEMP_ENV_DIR}"

# ----------------------------
# Python venv tests
# ----------------------------
echo "=== Testing in Python venv ==="
TEMP_VENV_DIR=$(mktemp -d)
python3 -m venv "${TEMP_VENV_DIR}/venv"
VENV_PY="${TEMP_VENV_DIR}/venv/bin/python"
VENV_PIP="${TEMP_VENV_DIR}/venv/bin/pip"
run_core_tests "${VENV_PY}" "${VENV_PIP}" "venv"
rm -rf "${TEMP_VENV_DIR}"

echo "=== All OpenVINO wheel tests completed! ==="
8 changes: 8 additions & 0 deletions .ci/scripts/wheel/pre_build_script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,11 @@ if [[ "$(uname -s)" == "Linux" && "$(uname -m)" == "x86_64" ]]; then
echo "QNN_SDK_ROOT=${QNN_SDK_ROOT}" >> "${GITHUB_ENV}"
echo "QNN SDK downloaded to ${QNN_SDK_ROOT}"
fi

# Install OpenVINO on Linux x86_64 and source its setupvars.sh so the wheel
# build can include the OpenVINO backend.
if [[ "$(uname -s)" == "Linux" && "$(uname -m)" == "x86_64" ]]; then
source "${GITHUB_WORKSPACE}/${REPOSITORY}/backends/openvino/scripts/install_openvino.sh"
install_openvino
echo "OpenVINO_DIR=${OpenVINO_DIR}" >> "${GITHUB_ENV}"
fi
5 changes: 5 additions & 0 deletions .ci/scripts/wheel/test_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
), f"QnnBackend not found in registered backends: {registered}"
print("✓ QnnBackend is registered")

assert (
"OpenvinoBackend" in registered
), f"OpenvinoBackend not found in registered backends: {registered}"
print("OpenvinoBackend is registered")

test_base.run_tests(
model_tests=[
test_base.ModelTest(
Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/pull.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,33 @@ jobs:
PYTHON_EXECUTABLE=python bash .ci/scripts/test_model.sh "mv3" "buck2" "mediatek"
# placeholder for mediatek to add more tests

test-openvino-wheel-packages-linux:
name: test-openvino-wheel-packages-linux
uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main
permissions:
id-token: write
contents: read
strategy:
fail-fast: false
matrix:
python-version: [ "3.10", "3.11", "3.12", "3.13" ]
with:
runner: linux.2xlarge
docker-image: ci-image:executorch-ubuntu-22.04-gcc11
submodules: 'recursive'
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
timeout: 180
script: |
# The generic Linux job chooses to use base env, not the one setup by the image
CONDA_ENV=$(conda env list --json | jq -r ".envs | .[-1]")
conda activate "${CONDA_ENV}"

# Create a clean env for each python version
conda create -y -n test_env_${{ matrix.python-version }} python=${{ matrix.python-version }}
conda activate test_env_${{ matrix.python-version }}

PYTHON_EXECUTABLE=python bash .ci/scripts/test_wheel_package_openvino.sh "${{ matrix.python-version }}"

test-openvino-linux:
name: test-openvino-linux
uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main
Expand Down
2 changes: 1 addition & 1 deletion backends/openvino/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
nncf==3.0.0
git+https://github.com/openvinotoolkit/nncf@b101e7e#egg=nncf
42 changes: 42 additions & 0 deletions backends/openvino/scripts/install_openvino.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/bin/bash
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.
#
# Downloads the OpenVINO toolkit archive and sources setupvars.sh to set
# OpenVINO_DIR and related environment variables.

set -ex

OPENVINO_VERSION="2026.0"
OPENVINO_BUILD="2026.0.0.20965.c6d6a13a886"
OPENVINO_ARCHIVE="openvino_toolkit_ubuntu22_${OPENVINO_BUILD}_x86_64"
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if this is executed on another Ubuntu version?

OPENVINO_URL="https://storage.openvinotoolkit.org/repositories/openvino/packages/${OPENVINO_VERSION}/linux/${OPENVINO_ARCHIVE}.tgz"

install_openvino() {
# Skip if OpenVINO_DIR is already set and valid
if [[ -n "${OpenVINO_DIR:-}" && -d "${OpenVINO_DIR:-}" ]]; then
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we have any version checks here? Sometimes OpenVINO_DIR can be set for a previous unsupported version

echo "OpenVINO already set to ${OpenVINO_DIR} - skipping installation"
return
fi

# Skip if already extracted
if [[ -f "openvino/setupvars.sh" ]]; then
echo "OpenVINO already extracted at $(pwd)/openvino"
source openvino/setupvars.sh
return
fi

echo "Downloading OpenVINO ${OPENVINO_VERSION}..."
curl -Lo /tmp/openvino_toolkit.tgz --retry 3 --fail "${OPENVINO_URL}"

echo "Extracting OpenVINO archive..."
tar -xzf /tmp/openvino_toolkit.tgz
mv "${OPENVINO_ARCHIVE}" openvino
rm -f /tmp/openvino_toolkit.tgz

source openvino/setupvars.sh
echo "OpenVINO_DIR=${OpenVINO_DIR}"
}
12 changes: 12 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,15 @@ def run(self): # noqa C901
f"-DQNN_SDK_ROOT={qnn_sdk_root}",
]

# Check if OpenVINO is available (via OpenVINO_DIR env var set by
# setupvars.sh), and if so, enable building the OpenVINO backend by
# default.
openvino_dir = os.environ.get("OpenVINO_DIR", "").strip()
if openvino_dir and install_utils.is_cmake_option_on(
cmake_configuration_args, "EXECUTORCH_BUILD_OPENVINO", default=True
):
cmake_configuration_args += ["-DEXECUTORCH_BUILD_OPENVINO=ON"]

with Buck2EnvironmentFixer():
# Generate the cmake cache from scratch to ensure that the cache state
# is predictable.
Expand Down Expand Up @@ -790,6 +799,9 @@ def run(self): # noqa C901
cmake_build_args += ["--target", "custom_ops_aot_lib"]
cmake_build_args += ["--target", "quantized_ops_aot_lib"]

if cmake_cache.is_enabled("EXECUTORCH_BUILD_OPENVINO"):
cmake_build_args += ["--target", "openvino_backend"]

if cmake_cache.is_enabled("EXECUTORCH_BUILD_QNN"):
cmake_build_args += ["--target", "qnn_executorch_backend"]
cmake_build_args += ["--target", "PyQnnManagerAdaptor"]
Expand Down
Loading