[OpenVINO] Enabling OpenVINO backend in test harness#17730
[OpenVINO] Enabling OpenVINO backend in test harness#17730suryasidd wants to merge 18 commits intopytorch:mainfrom
Conversation
* Fixed skip patterns logic
Fix openvino int8 tests
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/17730
Note: Links to docs will display an error until the docs builds have been completed. ❌ 17 New Failures, 6 Cancelled Jobs, 1 PendingAs of commit 8d722b4 with merge base 331ac4a ( NEW FAILURES - The following jobs have failed:
CANCELLED JOBS - The following jobs were cancelled. Please retry:
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
This PR needs a
|
There was a problem hiding this comment.
Pull request overview
This PR integrates the OpenVINO backend into the ExecuTorch backend test harness so operator/model suites can be exercised through new OpenVINO test flows and a dedicated CI workflow, while also updating the CI OpenVINO package version and adding an OpenVINO-specific preprocessing correctness pass.
Changes:
- Add OpenVINO test flows (including an INT8/PTQ flow) and register them in the test suite flow registry.
- Add OpenVINO tester plumbing (custom stages) and enable OpenVINO setup/build in CI test scripts + a scheduled workflow.
- Add an OpenVINO preprocess pass to decompose
div(rounding_mode="floor")intodiv + floorto match PyTorch semantics; extend quantize stage to plumbfold_quantize.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| backends/test/suite/flows/openvino.py | Defines OpenVINO and OpenVINO INT8 test flows (with skip patterns). |
| backends/test/suite/flow.py | Registers OpenVINO flows in all_flows() (but currently disables QNN registration). |
| backends/test/suite/conftest.py | Skips tests early when a flow’s skip_patterns match the test name. |
| backends/test/harness/stages/quantize.py | Adds fold_quantize option and forwards it into convert_pt2e. |
| backends/openvino/test/tester/tester.py | Introduces OpenVINOTester and OpenVINO-specific stage implementations. |
| backends/openvino/test/tester/init.py | Exports new OpenVINO tester symbols. |
| backends/openvino/scripts/openvino_build.sh | Changes install invocation used by OpenVINO build script. |
| backends/openvino/runtime/OpenvinoBackend.cpp | Adds scalar type mapping for Double → f64. |
| backends/openvino/preprocess.py | Runs the new floor-divide decomposition pass during preprocess. |
| backends/openvino/partitioner.py | Minor formatting change. |
| backends/openvino/_passes/decompose_floor_divide_pass.py | Adds DecomposeFloorDividePass implementation. |
| backends/openvino/_passes/init.py | Exposes the new pass from the package. |
| .github/workflows/test-backend-openvino.yml | Adds a scheduled/on-demand OpenVINO backend CI workflow. |
| .github/workflows/_test_backend.yml | Updates backend description to include OpenVINO. |
| .ci/scripts/test_backend.sh | Adds OpenVINO environment setup + build flag for OpenVINO flows. |
| .ci/scripts/setup-openvino.sh | Updates OpenVINO version/build used in CI setup. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # try: | ||
| # from executorch.backends.test.suite.flows.qualcomm import ( | ||
| # QNN_16A16W_TEST_FLOW, | ||
| # QNN_16A4W_BLOCK_TEST_FLOW, | ||
| # QNN_16A4W_TEST_FLOW, | ||
| # QNN_16A8W_TEST_FLOW, | ||
| # QNN_8A8W_TEST_FLOW, | ||
| # QNN_TEST_FLOW, | ||
| # ) | ||
|
|
||
| # flows += [ | ||
| # QNN_TEST_FLOW, | ||
| # QNN_16A16W_TEST_FLOW, | ||
| # QNN_16A8W_TEST_FLOW, | ||
| # QNN_16A4W_TEST_FLOW, | ||
| # QNN_16A4W_BLOCK_TEST_FLOW, | ||
| # QNN_8A8W_TEST_FLOW, | ||
| # ] | ||
| # except Exception as e: | ||
| # logger.info(f"Skipping QNN flow registration: {e}") |
There was a problem hiding this comment.
The QNN flow registration has been commented out here, which removes all Qualcomm flows from all_flows() and will stop the test suite from generating flow_qnn* parameters/markers. If the intent is to enable OpenVINO in addition to existing backends, please restore the QNN try: block and add OpenVINO as a separate block (or keep both), rather than leaving QNN disabled in commented code.
| # try: | |
| # from executorch.backends.test.suite.flows.qualcomm import ( | |
| # QNN_16A16W_TEST_FLOW, | |
| # QNN_16A4W_BLOCK_TEST_FLOW, | |
| # QNN_16A4W_TEST_FLOW, | |
| # QNN_16A8W_TEST_FLOW, | |
| # QNN_8A8W_TEST_FLOW, | |
| # QNN_TEST_FLOW, | |
| # ) | |
| # flows += [ | |
| # QNN_TEST_FLOW, | |
| # QNN_16A16W_TEST_FLOW, | |
| # QNN_16A8W_TEST_FLOW, | |
| # QNN_16A4W_TEST_FLOW, | |
| # QNN_16A4W_BLOCK_TEST_FLOW, | |
| # QNN_8A8W_TEST_FLOW, | |
| # ] | |
| # except Exception as e: | |
| # logger.info(f"Skipping QNN flow registration: {e}") | |
| try: | |
| from executorch.backends.test.suite.flows.qualcomm import ( | |
| QNN_16A16W_TEST_FLOW, | |
| QNN_16A4W_BLOCK_TEST_FLOW, | |
| QNN_16A4W_TEST_FLOW, | |
| QNN_16A8W_TEST_FLOW, | |
| QNN_8A8W_TEST_FLOW, | |
| QNN_TEST_FLOW, | |
| ) | |
| flows += [ | |
| QNN_TEST_FLOW, | |
| QNN_16A16W_TEST_FLOW, | |
| QNN_16A8W_TEST_FLOW, | |
| QNN_16A4W_TEST_FLOW, | |
| QNN_16A4W_BLOCK_TEST_FLOW, | |
| QNN_8A8W_TEST_FLOW, | |
| ] | |
| except Exception as e: | |
| logger.info(f"Skipping QNN flow registration: {e}") |
|
|
||
| # Build the package | ||
| ./install_executorch.sh --use-pt-pinned-commit | ||
| ./install_executorch.sh --minimal |
There was a problem hiding this comment.
Switching from --use-pt-pinned-commit to --minimal changes the PyTorch selection behavior (defaulting back to nightly) and can make OpenVINO builds/tests less reproducible. If the goal is to reduce dependencies, consider keeping the pinned PyTorch option while also using minimal installs (e.g., pass both flags or make it configurable).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| opset = _get_opset(node.target) | ||
| a, b = node.args[0], node.args[1] | ||
|
|
||
| a_dtype = _node_dtype(a) | ||
| is_integer = a_dtype is not None and not a_dtype.is_floating_point | ||
|
|
||
| with graph.inserting_before(node): | ||
| if is_integer: | ||
| a_f = graph.call_function( | ||
| opset["to_copy"], (a,), {"dtype": torch.float32} | ||
| ) | ||
| b_f = graph.call_function( | ||
| opset["to_copy"], (b,), {"dtype": torch.float32} | ||
| ) | ||
| div_node = graph.call_function(opset["div"], (a_f, b_f)) | ||
| floored = graph.call_function(opset["floor"], (div_node,)) | ||
| result = graph.call_function( | ||
| opset["to_copy"], (floored,), {"dtype": a_dtype} | ||
| ) |
There was a problem hiding this comment.
DecomposeFloorDividePass decides the integer-vs-float rewrite based only on a's dtype and then casts the result back to a_dtype. This is incorrect for mixed-dtype cases where div would promote to a floating output (e.g. int / float) and also risks wrong semantics when dtype metadata is missing. Consider using the node's output dtype (node.meta['val'].dtype) or torch.result_type(a_val, b_val) to choose the rewrite, and only cast back when the output dtype is integral.
| graph.eliminate_dead_code() | ||
| graph_module.recompile() | ||
| graph_module = super().call(graph_module).graph_module | ||
| return PassResult(graph_module, True) |
There was a problem hiding this comment.
This pass always returns PassResult(..., True) even if it didn't rewrite any nodes. That can cause unnecessary downstream work and makes pass pipelines harder to reason about. Track whether any div.Tensor_mode(..., rounding_mode='floor') nodes were replaced and return changed=False when none were found.
| for pass_cls in [DimOrderOpsRevertPass, DecomposeFloorDividePass]: | ||
| result = pass_cls()(edge_program.graph_module) | ||
| if result and result.graph_module: | ||
| edge_program._graph_module = result.graph_module |
There was a problem hiding this comment.
DecomposeFloorDividePass is now part of the OpenVINO preprocess pipeline, but there are no OpenVINO tests asserting correct floor-division semantics (especially for negative values where trunc vs floor differs). Please add a targeted unit/op test under backends/openvino/tests/ops that exercises torch.div(..., rounding_mode='floor') / torch.floor_divide on negative integer inputs and verifies OpenVINO output matches PyTorch.
| edge_program._graph_module = result.graph_module | |
| edge_program.graph_module = result.graph_module |
| try: | ||
| from executorch.backends.test.suite.flows.qualcomm import ( | ||
| QNN_16A16W_TEST_FLOW, | ||
| QNN_16A4W_BLOCK_TEST_FLOW, | ||
| QNN_16A4W_TEST_FLOW, | ||
| QNN_16A8W_TEST_FLOW, | ||
| QNN_8A8W_TEST_FLOW, | ||
| QNN_TEST_FLOW, | ||
| from executorch.backends.test.suite.flows.openvino import ( | ||
| OPENVINO_INT8_TEST_FLOW, | ||
| OPENVINO_TEST_FLOW, | ||
| ) | ||
|
|
||
| flows += [ | ||
| QNN_TEST_FLOW, | ||
| QNN_16A16W_TEST_FLOW, | ||
| QNN_16A8W_TEST_FLOW, | ||
| QNN_16A4W_TEST_FLOW, | ||
| QNN_16A4W_BLOCK_TEST_FLOW, | ||
| QNN_8A8W_TEST_FLOW, | ||
| OPENVINO_TEST_FLOW, | ||
| OPENVINO_INT8_TEST_FLOW, | ||
| ] | ||
| except Exception as e: | ||
| logger.info(f"Skipping QNN flow registration: {e}") | ||
| logger.info(f"Skipping OpenVINO flow registration: {e}") | ||
|
|
||
| # try: | ||
| # from executorch.backends.test.suite.flows.qualcomm import ( | ||
| # QNN_16A16W_TEST_FLOW, | ||
| # QNN_16A4W_BLOCK_TEST_FLOW, | ||
| # QNN_16A4W_TEST_FLOW, | ||
| # QNN_16A8W_TEST_FLOW, | ||
| # QNN_8A8W_TEST_FLOW, | ||
| # QNN_TEST_FLOW, | ||
| # ) | ||
|
|
||
| # flows += [ | ||
| # QNN_TEST_FLOW, | ||
| # QNN_16A16W_TEST_FLOW, | ||
| # QNN_16A8W_TEST_FLOW, | ||
| # QNN_16A4W_TEST_FLOW, | ||
| # QNN_16A4W_BLOCK_TEST_FLOW, | ||
| # QNN_8A8W_TEST_FLOW, | ||
| # ] | ||
| # except Exception as e: | ||
| # logger.info(f"Skipping QNN flow registration: {e}") |
There was a problem hiding this comment.
all_flows() no longer registers the Qualcomm/QNN flows (the entire QNN block is commented out), which will break QNN backend CI (e.g. test-backend-qnn.yml runs -m flow_qnn...) and removes QNN from the harness. Please restore the QNN flow registration and add the OpenVINO flows as an additional try-block rather than replacing QNN.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pull_request: | ||
| paths: | ||
| - .github/workflows/test-backend-openvino.yml | ||
| - .github/workflows/_test_backend.yml |
There was a problem hiding this comment.
pull_request.paths only watches workflow files, so changes to the OpenVINO backend/test harness (e.g. backends/openvino/**, .ci/scripts/**, backends/test/**) won’t trigger this workflow on PRs. This makes it easy for OpenVINO regressions to merge without any PR signal. Consider expanding paths similarly to other backend workflows (e.g. xnnpack) to include the relevant backend + harness directories.
| - .github/workflows/_test_backend.yml | |
| - .github/workflows/_test_backend.yml | |
| - backends/openvino/** | |
| - .ci/scripts/** | |
| - backends/test/** |
| uses: ./.github/workflows/_test_backend.yml | ||
| with: | ||
| backend: openvino | ||
| flows: '["openvino"]' |
There was a problem hiding this comment.
This workflow only runs the openvino flow, but the PR also registers an openvino_int8 flow in the test harness. If INT8 is intended to be supported/kept green, consider adding it to the flows list here so it gets CI coverage (or document why it’s intentionally excluded).
| flows: '["openvino"]' | |
| flows: '["openvino", "openvino_int8"]' |
| # Run the main function with parsed arguments | ||
| # Disable nncf patching as export of the patched model is not supported. | ||
| with nncf.torch.disable_patching(): | ||
| main( | ||
| args.suite, | ||
| args.model, | ||
| args.input_shape, | ||
| args.export, | ||
| args.model_file_name, | ||
| args.quantize, | ||
| args.validate, | ||
| args.dataset, | ||
| args.device, | ||
| args.batch_size, | ||
| args.infer, | ||
| args.num_iter, | ||
| args.warmup_iter, | ||
| args.input_tensor_path, | ||
| args.output_tensor_path, | ||
| ) | ||
| main( | ||
| args.suite, | ||
| args.model, | ||
| args.input_shape, | ||
| args.export, |
There was a problem hiding this comment.
The removal of nncf.torch.disable_patching() means this script now runs export/lowering without explicitly disabling NNCF’s Torch patching. Other OpenVINO-related examples in this repo still guard export with disable_patching() due to known side effects. If that concern still applies, consider reintroducing the context manager (or lazily importing nncf/quantize_model only when --quantize is used) to avoid export failures when NNCF is present.
Summary