Skip to content

openvino: Add version 2026.3.1 - #2122

Open
luhenry wants to merge 5 commits into
mainfrom
openvino
Open

luhenry wants to merge 5 commits into
mainfrom
openvino

Conversation

@luhenry

@luhenry luhenry commented Sep 20, 2026 •

Copy link
Copy Markdown
Member

Compiles the OpenVINO runtime, its IR/ONNX/TensorFlow/TF-Lite/PyTorch/Paddle frontends and the RISC-V CPU plugin - vendored oneDNN at DNNL_TARGET_ARCH=RV64 plus the RVV JIT emitters built on xbyak_riscv - then the Python API once per interpreter. Upstream publishes no riscv64 wheel.

Mirrors upstream's manylinux_2_28.yml, the recipe behind the published wheels, with the riscv64 cmake options of linux_riscv.yml. The wheel's platform tag, the RVV CPU plugin and the prebuilt riscv64 oneTBB all come from branches upstream already carries.

Differs from upstream

  • JS API off - the npm package is out of scope for a wheel
  • Distro cmake installed - vendored snappy and xbyak_riscv predate CMake 4's floor
  • Tests component off - only the wheels are needed

Testing

  • pyopenvino suite narrowed to test_runtime/test_graph/test_transformations - the rest need the template plugin that only the tests component builds

License: Wheel bundles oneTBB (Apache-2.0), fetched by upstream's own riscv64 branch.

@github-actions

github-actions Bot commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://riseproject-dev.github.io/python-wheels/pr-preview/pr-2122/

Built to branch gh-pages at 2026-09-27 10:28 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

luhenry added a commit that referenced this pull request Sep 20, 2026

luhenry commented Sep 20, 2026 •

Copy link
Copy Markdown
Member Author

First CI run: the wheels build, the wheel can't run — needs a decision before any re-run

Run 35480766550, job 105998045327, 01:12–13:36 UTC (12h24m).

The build is not the problem. Build wheels succeeded after 12h18m and produced all four wheels (cp312/cp313/cp314/cp314t, artifact uploaded, 129 MB). The job then failed 2m12s into Test wheels:

/openvino/riscv64-build-and-test.sh: line 32:    41 Illegal instruction     (core dumped) "${python_exe}" -c '
##[error]Process completed with exit code 132.

Exit 132 = 128+4 = SIGILL. None of the usual suspects apply: no Killed, out of memory, ICE or No space left anywhere; 12h24m against timeout-minutes: 1440 is not a timeout; and the run never reached pytest.

The failing point is exact — in the cp312 smoke check, print(ov.get_version()) printed 2026.3.1-1-759c5a6ab8c and print(core.available_devices) never printed. So it dies inside ov.Core() / device enumeration, i.e. when the CPU plugin is dlopened and constructed. set -e aborted the loop there, so cp313/cp314/cp314t were never tested.

Root cause: the RISC-V CPU plugin requires RVV 1.0, this fleet does not have it

Diagnosed from the uploaded artifact rather than by spending another runner slot. Of the 20 libraries in the wheel, libopenvino_riscv_cpu_plugin.so is the only one whose ELF Tag_RISCV_arch carries vector:

libopenvino_riscv_cpu_plugin.so  rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_v1p0_…_zve64d1p0_zvl128b1p0
every other library             rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0_zifencei2p0_zmmul1p0

It holds 64,554 vector (OP-V) instructions, 34,526 of them vsetvl*, in a contiguous ~10.7 MB region of its 20.6 MB .text — against exactly zero across the other 19 libraries' 52 MB, which is the control showing the scan has no false positives.

Upstream's gate is a runtime probe (cpu_isa_traits.cpp: case gv: return mayiuse(g) && cpu.hasExtension(RISCVExtension::V) && can_compile_rvv100();), and the probe is visible in the binary as the only RVV outside that region: two isolated vsetvli+vmv.v.i pairs at .text+0x10b92 (e8, mf2, ta, ma) and .text+0x10d7a (e64, m1, ta, ma) — can_compile_rvv100() / can_compile_zvfh(), which execute the instruction under a SIGILL handler. mf2 doesn't exist in RVV 0.7.1 and the vsetvli encoding differs between 0.7.1 and 1.0, so on this hardware the probe traps exactly as gotcha 272 documents (HWCAP advertises V; the first RVV-1.0 vsetvli is illegal) — and the process core-dumps instead of the probe returning false.

Static evidence can't separate "the probe's SIGILL recovery fails" from "a static initializer inside one of the RVV-compiled TUs runs at dlopen with no mayiuse in front of it". Both land on the same log line. That question is cheap to settle without rebuilding: the artifact is downloadable until 2026-12-19, so a test-only re-run against the existing wheels (LD_DEBUG=libs, or gdb -batch -ex run -ex bt) answers it in minutes.

Why the triage didn't catch this: upstream's linux_riscv.yml runs its functional tests only under qemu-riscv64 -cpu rv64,v=true,vext_spec=v1.0, and docs/dev/build_riscv64.md's hardware list mixes RVV 0.7.1 (Lichee Pi 4A) with RVV 1.0 (BPI-F3, Orange Pi RV2) boards. "Upstream has a real riscv64 CPU plugin with RVV JIT" turns out to be evidence about an emulator with V forced on, not about baseline hardware.

Second, independent blocker: the bundled oneTBB is a T-Head vendor build

Found the same way. The prebuilt oneTBB that ov_download_tbb() fetches and setup.py bundles is a Xuantie build: Tag_RISCV_arch = rv64i2p0_…_xtheadc2p0, with 906 CUSTOM-0 (opcode 0x0B) instructions in libtbb.so.12 and 892 in libtbbmalloc.so.2 — against zero in the 17 libraries we compile ourselves. The triage confirmed e_machine 243, which was true and not sufficient.

It works here only because the runner fleet is itself T-Head (which also explains gotcha 272's otherwise-odd "HWCAP says V but vsetvli SIGILLs": a T-Head C9xx core means RVV 0.7.1). The wheel would SIGILL on SiFive U74/P550, JH7110 or plain QEMU rv64gc — so even with the RVV problem solved, a green run here would not prove the wheel portable.

Why I have not re-triggered

There is no off switch: src/plugins/intel_cpu/CMakeLists.txt adds the four riscv64/ source dirs and XBYAK_RISCV_V=1 whenever RISCV64, intel_cpu/thirdparty/CMakeLists.txt sets XBYAK_RISCV_V ON and DNNL_TARGET_ARCH=RV64 unconditionally, no option() governs any of it, and -march=rv64gcv appears nowhere in the build (only in clang_tidy.cmake). Unlike zlib-ng's -DWITH_RVV=OFF or libpng's PNG_RISCV_RVV=off, this needs a real patch against a scalar-fallback path upstream's own CI never exercises without v=true.

And an RVV-off build still couldn't ship while the T-Head TBB blob is in the wheel. A shippable wheel needs both an RVV patch and a replacement for the prebuilt TBB (source-built oneTBB, or THREADING=SEQ) — two material divergences from the manylinux_2_28.yml recipe this port exists to mirror, producing a scalar-only OpenVINO with none of the vector acceleration that motivated it, at ~12.5h per validation round.

At this cost that's a call worth making deliberately rather than by pushing a speculative re-run. The options as I see them:

  1. Park as disproportionate (my recommendation) — same framing as the paddlepaddle cost discussion.
  2. Spend minutes, not hours, first: a test-only run against the existing artifact to get the exact faulting frame, then decide with that in hand.
  3. Accept an RVV-1.0-only wheel — which the manylinux_riscv64 tag forbids (gotcha 139), so this is really a non-option unless the wheel is scoped to specific hardware.

No code pushed to this branch; PR left open and draft status untouched. Findings recorded on main as gotchas 448 and 449, with the .queue.yml entry updated.

Two confirmed riscv64 blockers from CI run 35480766550 (gotchas 448/449),
targeting the generic rv64gc baseline this fleet actually runs:

- cmake/dependencies.cmake's ov_download_tbb() fetches a prebuilt oneTBB
  for riscv64 (oneapi-tbb-2022.3.0-lin-riscv-release.tgz) that turns out to
  be a T-Head Xuantie toolchain build carrying vendor CUSTOM-0 opcodes
  (xtheadc) -- it "works" only because this runner fleet also happens to be
  T-Head silicon, not because it's a portable riscv64 build. oneAPI/Intel
  does not publish a riscv64 oneTBB release themselves; this asset is
  OpenVINO's own CI team cross-compiling the same source with (evidently) a
  T-Head-flavored default -march. Build oneTBB v2022.3.0 -- the exact
  version OpenVINO pins -- from its real upstream source
  (oneapi-src/oneTBB) with the manylinux image's own generic-rv64gc
  toolchain instead, and point OpenVINO at it with -DTBB_DIR, which
  ov_find_package_tbb() already honors ahead of ever calling
  ov_download_tbb(). No source patch needed for this half.

- libopenvino_riscv_cpu_plugin.so unconditionally probes for RVV 1.0 by
  executing a v1.0 vsetivli under a SIGILL handler
  (nodes/kernels/riscv64/cpu_isa_traits.cpp); on this fleet's RVV 0.7.1
  T-Head hardware the probe doesn't recover, it SIGILLs the whole process
  during ov.Core()/device enumeration. The CPU plugin already has a
  complete non-RVV fallback for riscv64 built in and already exercised by
  this exact mayiuse(gv) gate -- scalar/generic node executors, and
  Transformations::MainSnippets() already turns Snippets/Subgraph
  tokenization off entirely when is_supported_isa() is false -- so
  hardcoding the two probes to report "unsupported" (patches/openvino/
  2026.3.1/0001-...) routes this target through that existing path
  unconditionally instead of ever attempting RVV 1.0 codegen.

Adds a python-wheels self-checkout and `git apply` step (this workflow
previously had nowhere to source patches from), following the
build-tensorstore.yml checkout-into-subdir pattern.
Run 35995279535 (with patch 0001 applied and compiled) still died with
SIGILL at exactly the same point as run 35480766550: ov.get_version()
printed, print(core.available_devices) did not (podman run -t, so stdout
is line-buffered and the missing line is real). So the RVV 1.0 probe was
not the crash.

The crash is a static initializer of libopenvino_riscv_cpu_plugin.so,
run by the dynamic loader when available_devices dlopens the CPU plugin:
.init_array entry 17 points at plugin offset 0x34926a, whose first
instruction is `vsetivli zero, 8, e8, mf2, ta, ma` (a GCC auto-vectorized
zero-fill of globals, followed by vmv.v.i/vse8.v). vsetivli and fractional
LMUL do not exist in RVV 0.7.1, and no probe or HWCAP check runs before
it. It is the only one of the plugin's 26 initializers with vector code.

Reproduced from the CI artifact under QEMU: QEMU_CPU=rv64,v=false dies
with SIGILL right after ov.Core() and before available_devices prints,
si_addr = plugin base + 0x34926a; with v=true,vext_spec=v1.0 the whole
smoke test passes (which is why upstream's QEMU-with-V CI is green).

Where the vector code comes from: oneDNN's cmake/platform.cmake, for
DNNL_TARGET_ARCH=RV64, appends -march=rv64gcv to all of oneDNN's C/C++
flags whenever the compiler can build RVV intrinsics (the CI log says
"Using RV64 march flag: -march=rv64gcv"). There is no runtime dispatch
behind it, and oneDNN is linked statically into the plugin. The plugin's
own sources and every other library in the wheel are built rv64gc; the
riscv64 JIT emitters generate vector code at run time only.

Pre-seed CAN_COMPILE_RVV_INTRINSICS=OFF so oneDNN takes its existing
non-RVV branch: -march=rv64gc, DNNL_RISCV_USE_RVV_INTRINSICS off, the
rvv_* sources excluded, and every reference to them already compiled
out by #ifdef. Checked by configuring oneDNN 2026.3.1's submodule with
it in the manylinux_riscv64 image ("Using RV64 march flag:
-march=rv64gc", 177 of 180 TUs left), and by compiling the guarded TUs
(gemm.cpp, cpu_matmul_list.cpp, cpu_pooling_list.cpp): they build, with
zero vector instructions and no V in Tag_RISCV_arch.

Patch 0002 closes the one remaining unguarded vector instruction on the
compile_model() path: mayiuse() constructs Xbyak_riscv::CPU for every
query, and its constructor runs a JIT-generated `csrr a0, vlenb` with no
SIGILL handler whenever AT_HWCAP has V. The gv case now checks the
(hardcoded) RVV 1.0 probe first and only g builds the CPU object. It is
defensive -- not yet seen crashing by itself -- and compiles with
-Wall -Wextra -Werror in the manylinux_riscv64 image.
Run 36145686942 was cancelled at the 24h job limit, but not by the
build: the portable-oneTBB/no-RVV build finished in 12h20m (14:14 to
02:34 UTC), all four wheels were built and uploaded, and the
cp312/cp313/cp314 legs each passed the smoke check and
7019 passed / 61 skipped in about three minutes. The earlier SIGILL
is gone.

The cp314t leg passed the smoke check too (Core(), ['CPU'], a relu
compiled and run on CPU), then hung in pytest right after
test_async_infer_request.py::test_start_async[True] from 02:49 until
the cancellation at 14:11, 11h22m without output. The next test,
test_start_async[False], sets a Python callback that runs on an
inference worker thread while the main thread waits. The free-threaded
build (ENABLE_GIL_PYTHON_API=OFF) is the only one that hangs there.

Keep building and smoke-testing cp314t, since upstream ships it, but
run the pytest suite only on the GIL interpreters. Also give the test
step its own 60-minute timeout. Upstream's job_python_api_tests.yml
caps its run at 30, and without a limit here a hang holds the runner
until the 24h job limit.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant