-
Notifications
You must be signed in to change notification settings - Fork 28
Description
The upstream OpenVINO C API has once again broken its C ABI, limiting which versions of OpenVINO will work with these Rust bindings. This issue serves as an explainer for users who may run into errors.
Problem
The upstream OpenVINO project releases a C API; this is what the openvino-sys
crate interfaces with for using OpenVINO in Rust. In #143, we noticed that an insertion to the middle of ov_element_type_e
changed the enum values for items after the insertion. This meant a tensor of one type was actually interpreted as a tensor of another type, potentially an unsafe operation. Fortunately, tests in this repository caught the issue.
This happened again upstream in #28766 (specifically d9c2aee): ov_element_type_e::UNDEFINED
and ov_element_type_e::DYNAMIC
where merged into a single value. This innocent-seeming refactoring changes all the enum values, ensuring that any users with code compiled against the previous header (C, Rust, etc.) could have incorrect results and possibly buffer security problems.
What does this mean?
The breaking change to ov_element_type
appears first in v2025.1.0:
- users of this crate that want to use OpenVINO v2025.0.0 and prior should use this crate at version 0.8.0
- users that want to use v2025.1.0 and above must upgrade to the latest version of the crate.
Internally, this means this crate must find some way to protect itself against unwitting ABI changes. It is unfortunate that these breakages occur across minor versions (e.g., 2025.0 to 2025.1), so it is impossible to support a version subset.
Current Solution
The current solution is for the latest version of the crate to dynamically check the OpenVINO library version and fail if it is prior to some version. This is not ideal (we would like to support as many versions as possible) but is safe.
Internally, we do something like:
openvino-rs/crates/openvino-sys/src/lib.rs
Lines 89 to 95 in a13bd73
/// Parse the version string and return true if it is older than 2024.2. | |
fn is_pre_2024_2_version(version: &str) -> bool { | |
let mut parts = version.split(['.', '-']); | |
let year: usize = parts.next().unwrap().parse().unwrap(); | |
let minor: usize = parts.next().unwrap().parse().unwrap(); | |
year < 2024 || (year == 2024 && minor < 2) | |
} |
Future Solution
In the future, we could explore other options:
- interface with the C++ API directly: using
cxx
, e.g., one could imagine being immune to more API breakage by relying directly on the C++ API. It is unclear if this is any more stable than - integrate more tightly with upstream OpenVINO: unfortunately, upstream developers may not be aware that their changes have downstream effects; moving the Rust bindings into the upstream OpenVINO repository would help them see test failures immediately. This has been discussed in [Question] Moving to OpenVINO Contrib #70 and requires more work to happen.
- release new Rust bindings for each new upstream release: this crate would begin to release versions such as v2025.2.0, v2025.3.0, etc., to align with the upstream version numbers; there would be no guarantee of compatibility between crate versions, much like upstream today. The unfortunate side effect of this would be more frequent releases of this crate (i.e., more chores!).
I'm interested in other ideas to protect these bindings from this kind of breakage in the future. This problem does come with the territory, though: this crate cannot be stable if upstream is unstable. And I sympathize with upstream maintainers who may want to change APIs when they want, regardless of semantic versioning. For the time being, we'll continue to try to maintain compatibility for as many versions as we can!