Skip to content

Commit 980c012

Browse files
[OpenVINO] Enabling OpenVINO backend in test harness (#17730)
### Summary - This PR enables OpenVINO test flows into the test harness to enable operator tests and models tests on OpenVINO backend. - Also updates OpenVINO version to 2026.0 - Added Qwen2.5 support --------- Co-authored-by: dlyakhov <daniil.lyakhov@intel.com>
1 parent 8d43d97 commit 980c012

21 files changed

Lines changed: 589 additions & 130 deletions

.ci/scripts/setup-openvino.sh

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,41 @@ set -ex
1010
# shellcheck source=/dev/null
1111
source "$(dirname "${BASH_SOURCE[0]}")/utils.sh"
1212

13+
# Parse arguments
14+
USE_NIGHTLY=false
15+
for arg in "$@"; do
16+
case $arg in
17+
--nightly) USE_NIGHTLY=true ;;
18+
esac
19+
done
20+
1321
# Download and install OpenVINO from release packages
14-
OPENVINO_VERSION="2025.3"
15-
OPENVINO_BUILD="2025.3.0.19807.44526285f24"
16-
OPENVINO_URL="https://storage.openvinotoolkit.org/repositories/openvino/packages/${OPENVINO_VERSION}/linux/openvino_toolkit_ubuntu22_${OPENVINO_BUILD}_x86_64.tgz"
22+
OPENVINO_VERSION="2026.0"
23+
OPENVINO_BUILD="2026.0.0.20965.c6d6a13a886"
24+
OPENVINO_STABLE_URL="https://storage.openvinotoolkit.org/repositories/openvino/packages/${OPENVINO_VERSION}/linux/openvino_toolkit_ubuntu22_${OPENVINO_BUILD}_x86_64.tgz"
25+
26+
OPENVINO_NIGHTLY_BUILD_ID="2026.1.0-21310-c694fbc2b6d"
27+
OPENVINO_NIGHTLY_BUILD="2026.1.0.dev20260312"
28+
OPENVINO_NIGHTLY_URL="https://storage.openvinotoolkit.org/repositories/openvino/packages/nightly/${OPENVINO_NIGHTLY_BUILD_ID}/openvino_toolkit_ubuntu22_${OPENVINO_NIGHTLY_BUILD}_x86_64.tgz"
29+
30+
if [ "${USE_NIGHTLY}" = true ]; then
31+
OPENVINO_URL="${OPENVINO_NIGHTLY_URL}"
32+
OPENVINO_EXTRACTED_DIR="openvino_toolkit_ubuntu22_${OPENVINO_NIGHTLY_BUILD}_x86_64"
33+
echo "Using OpenVINO nightly build: ${OPENVINO_NIGHTLY_BUILD_ID}"
34+
else
35+
OPENVINO_URL="${OPENVINO_STABLE_URL}"
36+
OPENVINO_EXTRACTED_DIR="openvino_toolkit_ubuntu22_${OPENVINO_BUILD}_x86_64"
37+
echo "Using OpenVINO stable release: ${OPENVINO_BUILD}"
38+
fi
1739

1840
curl -Lo /tmp/openvino_toolkit.tgz --retry 3 --fail ${OPENVINO_URL}
1941
tar -xzf /tmp/openvino_toolkit.tgz
20-
mv openvino_toolkit_ubuntu22_${OPENVINO_BUILD}_x86_64 openvino
42+
mv "${OPENVINO_EXTRACTED_DIR}" openvino
2143

44+
set +u
2245
source openvino/setupvars.sh
23-
cd backends/openvino
24-
pip install -r requirements.txt
25-
cd scripts
46+
set -u
47+
pip install -r backends/openvino/requirements.txt
48+
pushd backends/openvino/scripts
2649
./openvino_build.sh --enable_python
50+
popd

.ci/scripts/test_backend.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ if [[ "$FLOW" == *arm* ]]; then
7878
fi
7979
fi
8080

81+
if [[ "$FLOW" == *openvino* ]]; then
82+
# Setup OpenVINO environment
83+
source .ci/scripts/setup-openvino.sh --nightly
84+
EXTRA_BUILD_ARGS+=" -DEXECUTORCH_BUILD_OPENVINO=ON"
85+
fi
86+
8187
if [[ $IS_MACOS -eq 1 ]]; then
8288
SETUP_SCRIPT=.ci/scripts/setup-macos.sh
8389
else

.github/workflows/_test_backend.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
workflow_call:
55
inputs:
66
backend:
7-
description: 'Backend to test (xnnpack, coreml, vulkan, qnn)'
7+
description: 'Backend to test (xnnpack, coreml, vulkan, qnn, openvino)'
88
required: true
99
type: string
1010
flows:

.github/workflows/pull.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,7 +1034,6 @@ jobs:
10341034
test-openvino-linux:
10351035
name: test-openvino-linux
10361036
uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main
1037-
if: false # TODO: fix https://github.com/pytorch/executorch/issues/17684
10381037
permissions:
10391038
id-token: write
10401039
contents: read
@@ -1145,7 +1144,7 @@ jobs:
11451144
# The generic Linux job chooses to use base env, not the one setup by the image
11461145
CONDA_ENV=$(conda env list --json | jq -r ".envs | .[-1]")
11471146
conda activate "${CONDA_ENV}"
1148-
1147+
11491148
# Install eIQ packages
11501149
pip install -r backends/nxp/requirements-eiq.txt
11511150
@@ -1164,13 +1163,13 @@ jobs:
11641163
# Run aot examples:
11651164
PYTHON_EXECUTABLE=python bash examples/nxp/run_aot_example.sh cifar10
11661165
PYTHON_EXECUTABLE=python bash examples/nxp/run_aot_example.sh mobilenetv2
1167-
1166+
11681167
# Run e2e example with Simulator:
11691168
PYTHON_EXECUTABLE=python bash examples/nxp/run.sh cifar10
1170-
1171-
# Run lightweight model tests:
1169+
1170+
# Run lightweight model tests:
11721171
PYTHON_EXECUTABLE=python pytest -c /dev/null backends/nxp/tests_models/ \
1173-
--nxp_runner_path "./examples/nxp/executor_runner/build/nxp_executor_runner"
1172+
--nxp_runner_path "./examples/nxp/executor_runner/build/nxp_executor_runner"
11741173
11751174
test-samsung-quantmodels-linux:
11761175
name: test-samsung-quantmodels-linux
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Test OpenVINO Backend
2+
3+
on:
4+
schedule:
5+
- cron: 0 2 * * *
6+
push:
7+
branches:
8+
- release/*
9+
tags:
10+
- ciflow/nightly/*
11+
pull_request:
12+
paths:
13+
- .github/workflows/test-backend-openvino.yml
14+
- .github/workflows/_test_backend.yml
15+
workflow_dispatch:
16+
17+
concurrency:
18+
group: ${{ github.workflow }}--${{ github.event.pull_request.number || github.sha }}-${{ github.event_name == 'workflow_dispatch' }}
19+
cancel-in-progress: true
20+
21+
jobs:
22+
test-openvino:
23+
uses: ./.github/workflows/_test_backend.yml
24+
with:
25+
backend: openvino
26+
flows: '["openvino"]'
27+
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
28+
timeout: 120
29+
run-linux: true
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) Intel Corporation
2+
#
3+
# Licensed under the BSD License (the "License"); you may not use this file
4+
# except in compliance with the License. See the license file found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
from .decompose_floor_divide_pass import DecomposeFloorDividePass
8+
9+
__all__ = ["DecomposeFloorDividePass"]
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Copyright (c) Intel Corporation
2+
#
3+
# Licensed under the BSD License (the "License"); you may not use this file
4+
# except in compliance with the License. See the license file found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
import torch
8+
from executorch.exir.dialects._ops import ops as exir_ops
9+
from executorch.exir.pass_base import ExportPass, PassResult
10+
11+
# Ops to match
12+
DIV_TENSOR_MODE_OPS = {
13+
exir_ops.edge.aten.div.Tensor_mode,
14+
torch.ops.aten.div.Tensor_mode,
15+
}
16+
17+
# Replacement op sets per dialect
18+
EDGE_OPS = {
19+
"div": exir_ops.edge.aten.div.Tensor,
20+
"floor": exir_ops.edge.aten.floor.default,
21+
"to_copy": exir_ops.edge.aten._to_copy.default,
22+
}
23+
24+
ATEN_OPS = {
25+
"div": torch.ops.aten.div.Tensor,
26+
"floor": torch.ops.aten.floor.default,
27+
"to_copy": torch.ops.aten._to_copy.default,
28+
}
29+
30+
31+
def _get_opset(op):
32+
if op is exir_ops.edge.aten.div.Tensor_mode:
33+
return EDGE_OPS
34+
if op is torch.ops.aten.div.Tensor_mode:
35+
return ATEN_OPS
36+
raise RuntimeError(f"Unexpected op: {op}")
37+
38+
39+
def _node_dtype(node):
40+
"""Return the dtype of a graph node's output, or None if unknown."""
41+
if isinstance(node, torch.fx.Node):
42+
val = node.meta.get("val")
43+
if val is not None:
44+
return val.dtype
45+
return None
46+
47+
48+
class DecomposeFloorDividePass(ExportPass):
49+
"""Decompose div with rounding_mode='floor' for correct semantics.
50+
51+
ExecuTorch decomposes floor_divide into aten.div.Tensor_mode with
52+
rounding_mode='floor'. OpenVINO implements this with truncation-toward-zero
53+
semantics instead of PyTorch's floor-toward-negative-infinity.
54+
55+
For float inputs, replaces div(x, y, rounding_mode='floor') with
56+
floor(div(x, y)).
57+
58+
For integer inputs, OpenVINO's integer division truncates toward zero, so
59+
floor(int_div(x, y)) still gives truncation semantics. Instead we cast to
60+
float32, divide, floor, then cast back:
61+
_to_copy(floor(div(_to_copy(x, float32), _to_copy(y, float32))), int_dtype)
62+
"""
63+
64+
def call(self, graph_module: torch.fx.GraphModule) -> PassResult:
65+
graph = graph_module.graph
66+
67+
for node in list(graph.nodes):
68+
if node.op != "call_function":
69+
continue
70+
if node.target not in DIV_TENSOR_MODE_OPS:
71+
continue
72+
73+
rounding_mode = node.kwargs.get("rounding_mode")
74+
if rounding_mode != "floor":
75+
continue
76+
77+
opset = _get_opset(node.target)
78+
a, b = node.args[0], node.args[1]
79+
80+
a_dtype = _node_dtype(a)
81+
is_integer = a_dtype is not None and not a_dtype.is_floating_point
82+
83+
with graph.inserting_before(node):
84+
if is_integer:
85+
a_f = graph.call_function(
86+
opset["to_copy"], (a,), {"dtype": torch.float32}
87+
)
88+
b_f = graph.call_function(
89+
opset["to_copy"], (b,), {"dtype": torch.float32}
90+
)
91+
div_node = graph.call_function(opset["div"], (a_f, b_f))
92+
floored = graph.call_function(opset["floor"], (div_node,))
93+
result = graph.call_function(
94+
opset["to_copy"], (floored,), {"dtype": a_dtype}
95+
)
96+
else:
97+
div_node = graph.call_function(opset["div"], (a, b))
98+
result = graph.call_function(opset["floor"], (div_node,))
99+
100+
node.replace_all_uses_with(result)
101+
graph.erase_node(node)
102+
103+
graph.eliminate_dead_code()
104+
graph_module.recompile()
105+
graph_module = super().call(graph_module).graph_module
106+
return PassResult(graph_module, True)

backends/openvino/preprocess.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88

99
from typing import final, List
1010

11+
from executorch.backends.openvino._passes import DecomposeFloorDividePass
12+
1113
from executorch.exir.backend.backend_details import (
1214
BackendDetails,
1315
ExportedProgram,
1416
PreprocessResult,
1517
)
1618
from executorch.exir.backend.compile_spec_schema import CompileSpec
17-
1819
from executorch.exir.passes.memory_format_ops_pass import DimOrderOpsRevertPass
1920
from openvino.frontend.pytorch.torchdynamo.compile import ( # type: ignore[import-untyped]
2021
openvino_compile,
@@ -38,11 +39,10 @@ def preprocess(
3839
Returns:
3940
PreprocessResult: The result of preprocessing, including the compiled model bytes.
4041
"""
41-
transformed_ep = DimOrderOpsRevertPass()(edge_program.graph_module)
42-
43-
# Update the edge_program with the transformed graph
44-
if transformed_ep and transformed_ep.graph_module:
45-
edge_program._graph_module = transformed_ep.graph_module
42+
for pass_cls in [DimOrderOpsRevertPass, DecomposeFloorDividePass]:
43+
result = pass_cls()(edge_program.graph_module)
44+
if result and result.graph_module:
45+
edge_program._graph_module = result.graph_module
4646

4747
input_names = edge_program.graph_signature.user_inputs
4848
args = []

backends/openvino/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
nncf==3.0.0
1+
git+https://github.com/openvinotoolkit/nncf@b101e7e#egg=nncf

backends/openvino/runtime/OpenvinoBackend.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ ov::element::Type OpenvinoBackend::convert_to_openvino_type(
185185
switch (scalar_type) {
186186
case exa::ScalarType::Float:
187187
return ov::element::f32;
188+
case exa::ScalarType::Double:
189+
return ov::element::f64;
188190
case exa::ScalarType::Half:
189191
return ov::element::f16;
190192
case exa::ScalarType::Int:

0 commit comments

Comments
 (0)