Skip to content
Closed
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
3 changes: 3 additions & 0 deletions backends/openvino/runtime/OpenvinoApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ using ov_core_free_fn = void (*)(ov_core_t*);
using ov_core_get_available_devices_fn =
ov_status_e (*)(const ov_core_t*, ov_available_devices_t*);
using ov_available_devices_free_fn = void (*)(ov_available_devices_t*);
using ov_core_set_property_fn =
ov_status_e (*)(const ov_core_t*, const char*, ...);
using ov_core_import_model_fn = ov_status_e (*)(
const ov_core_t*,
const char*,
Expand Down Expand Up @@ -115,6 +117,7 @@ struct OpenvinoFunctions {
ov_core_free_fn core_free = nullptr;
ov_core_get_available_devices_fn core_get_available_devices = nullptr;
ov_available_devices_free_fn available_devices_free = nullptr;
ov_core_set_property_fn core_set_property = nullptr;
ov_core_import_model_fn core_import_model = nullptr;
ov_compiled_model_create_infer_request_fn
compiled_model_create_infer_request = nullptr;
Expand Down
60 changes: 48 additions & 12 deletions backends/openvino/runtime/OpenvinoBackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ bool OpenvinoBackend::ensure_loaded() const {
LOAD_SYM(core_free, ov_core_free)
LOAD_SYM(core_get_available_devices, ov_core_get_available_devices)
LOAD_SYM(available_devices_free, ov_available_devices_free)
LOAD_SYM(core_set_property, ov_core_set_property)
LOAD_SYM(core_import_model, ov_core_import_model)
LOAD_SYM(
compiled_model_create_infer_request,
Expand Down Expand Up @@ -321,17 +322,44 @@ exr::Result<exr::DelegateHandle*> OpenvinoBackend::init(
size_t data_size = processed->size();

std::string device = "CPU";
std::string inference_precision;
for (auto& compile_spec : compile_specs) {
std::string* target = nullptr;
if (std::strcmp(compile_spec.key, "device") == 0) {
const char* buf = static_cast<const char*>(compile_spec.value.buffer);
size_t len = compile_spec.value.nbytes;
// Strip trailing null bytes that may be included in nbytes.
while (len > 0 && buf[len - 1] == '\0') {
--len;
}
if (len > 0) {
device.assign(buf, len);
}
target = &device;
} else if (std::strcmp(compile_spec.key, "inference_precision") == 0) {

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.

The title and description read like a test-only fix, but this also changes the runtime that ships in the wheels. There is a new inference_precision compile spec that users can pass. And 0-d (scalar) tensors now work as delegate inputs and outputs, where before they failed. The test runner change matters too. Before, the OpenVINO job stayed green even when tests failed, so the failures were hidden. Now the job blocks pull requests on the op and model tests. Could you list these in the description and add the "release notes: openvino" label?

target = &inference_precision;
} else {
continue;
}
const char* buf = static_cast<const char*>(compile_spec.value.buffer);
size_t len = compile_spec.value.nbytes;
// Strip trailing null bytes that may be included in nbytes.
while (len > 0 && buf[len - 1] == '\0') {
--len;
}
if (len > 0) {
target->assign(buf, len);
}
}

// The device picks its default precision when the model is imported, e.g.

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.

The precision hint is only applied when the model is loaded. That works on x86, where the CPU plugin picks the precision again at import. It does not work on ARM. There, compile_model already converts the weights to f16 at export, and the exported blob keeps them. preprocess.py drops inference_precision, so the export always uses the default. We tried a 512x512 f32 matmul exported on an ARM CPU. With inference_precision set to f32, the compiled model reports f32, but the error is the same as plain f16: about 0.36, where real f32 gives about 4e-5. A blob exported on x86 does not load on ARM, so ARM users always hit this, and the new test line does not fix the tolerance failures on an ARM machine. Could preprocess.py also pass the value to compile_model as INFERENCE_PRECISION_HINT? On ARM that brought the error down to 4e-5. Please keep the load time call too, because on x86 a hint given only at export is lost at import. The new comment about picking the precision at import would then need to say it is about x86. GPU and NPU blobs are probably fixed at export as well.

// bf16 for f32 models on CPUs with native bf16 support.
if (!inference_precision.empty()) {
status = ov_.core_set_property(
core,
device.c_str(),
"INFERENCE_PRECISION_HINT",
inference_precision.c_str());
if (status != OV_STATUS_OK) {

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.

OpenVINO does not check the value in ov_core_set_property. On a new core it only stores it, and checks it later, when the import loads the device plugin. So a typo such as fp32 never reaches this branch. With OpenVINO 2026.4.1 the import fails, init returns Internal, and OpenVINO's own message, which lists the supported values, is not logged. With 2025.1 the import succeeds and the value is quietly read as dynamic. The branch does fire for a device name OpenVINO rejects, but then the log blames the precision. Could we check the value up front (f32, f16, bf16) and return InvalidArgument, the way execute checks its arguments? Logging OpenVINO's error text when the import fails would also help.

ET_LOG(
Error,
"OpenVINO: failed to set inference precision '%s' for device '%s' (status=%d)",
inference_precision.c_str(),
device.c_str(),
status);
ov_.core_free(core);
return exr::Error::InvalidArgument;
}
}

Expand All @@ -343,8 +371,10 @@ exr::Result<exr::DelegateHandle*> OpenvinoBackend::init(
if (status != OV_STATUS_OK || !compiled_model) {
ET_LOG(
Error,
"OpenVINO: failed to import model for device '%s' (status=%d)",
"OpenVINO: failed to import model for device '%s'%s%s (status=%d)",
device.c_str(),
inference_precision.empty() ? "" : " with inference_precision ",
inference_precision.c_str(),
status);
return exr::Error::Internal;
}
Expand Down Expand Up @@ -541,8 +571,12 @@ exr::Result<ov_tensor_t*> OpenvinoBackend::create_ov_tensor(
}
// shape is zero-initialized; shape_free is only needed after a successful
// shape_create (the zero state is safe to skip).
// ov_shape_create rejects rank 0, so 0-d tensors use the zero shape as-is.
ov_shape_t shape = {};
ov_status_e status = ov_.shape_create(rank, dims, &shape);
ov_status_e status = OV_STATUS_OK;
if (rank > 0) {
status = ov_.shape_create(rank, dims, &shape);
}
dims_heap.reset(); // Release heap dims (no-op if stack was used).
if (status != OV_STATUS_OK) {
return exr::Error::Internal;
Expand All @@ -551,7 +585,9 @@ exr::Result<ov_tensor_t*> OpenvinoBackend::create_ov_tensor(
ov_tensor_t* ov_tensor = nullptr;
status = ov_.tensor_create_from_host_ptr(
ov_type, shape, tensor.mutable_data_ptr(), &ov_tensor);
ov_.shape_free(&shape);
if (rank > 0) {
ov_.shape_free(&shape);
}
if (status != OV_STATUS_OK || !ov_tensor) {
return exr::Error::Internal;
}
Expand Down
3 changes: 3 additions & 0 deletions backends/openvino/tests/ops/base_openvino_op_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ def execute_layer_test(

# Convert to edge dialect and lower the module to the backend with a custom partitioner
compile_spec = [CompileSpec("device", self.device.encode())]
# CPUs with native bf16 otherwise run f32 models in bf16, which misses atol.
if self.device == "CPU":
compile_spec.append(CompileSpec("inference_precision", b"f32"))
lowered_module: EdgeProgramManager = to_edge_transform_and_lower(
aten_dialect,
partitioner=[
Expand Down
2 changes: 2 additions & 0 deletions backends/openvino/tests/test_runner.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import argparse
import sys
import unittest


Expand Down Expand Up @@ -71,3 +72,4 @@ def parse_arguments():
print("OpenVINO backend tests completed successfully")
else:
print("OpenVINO backend tests completed with failures")
sys.exit(1)
Loading