The Orange Pi RV2 is built on the SpaceMiT K1 SoC: eight SpacemiT X60 cores at ~1.6 GHz (rv64gcv, RVV 1.0, VLEN=256), integrated IMG BXE-2-32 GPU (OpenCL 3.0 / Vulkan 1.3), 8 GB RAM.

Video walkthrough
- BLIS vs OpenBLAS on RISC-V: 1.29× DGEMM — Then 0.5× HPL — vector vs vector; square win, panel loss
- GCC 15.2 on RISC-V: Teaching mtune=spacemit-x60 (−8.7% Canaries, +3.8% DGEMM) — one flag; canaries to DGEMM/HPL dilution
- When RVV FFT Wins 1.6× — and Gives 0% in Quantum ESPRESSO — planner trap; micro MEASURE ≠ QE
ESTIMATE - 10× ONNX on RISC-V: One Missing Attribute Unlocks X60 IME —
accuracy_level=4→ CompInt8 /smt.vmadot; 9.1× / 10.3× - 3.31× GROMACS on RISC-V: Hand-Written RVV Force Backend — Force owns ~90% of runtime;
impl_riscv_rvv/→ 3.31× whole-app - NaN Linpack on RISC-V: Fixing OpenBLAS gemv_n on Orange Pi RV2 (EESSI) — stock RVV HPL FAILED with residual
nan
Compute paths on this board
The K1 SoC exposes all four paths we benchmark: scalar baseline, RVV OpenBLAS, IME int4 matrix ops (cluster 0), and an integrated GPU.
IME (Integer Matrix Extension)
Besides RVV, SpaceMiT’s IME is a dedicated int8 matrix unit via the custom instruction smt.vmadot. One vmadot fuses a 4×4 int32 tile update from two 4×8 int8 operand tiles; this is the hardware behind the part’s quoted AI TOPS rating.
On the K1 the IME sits in cluster 0 only (cores 0–3), with those four cores sharing a 512 KB L2 — cluster 1 (cores 4–7) has no IME. Pin IME workloads to a cluster-0 core (taskset -c 0).
Toolchain support (xsmtvdot)
On X60 the usable IME subset is the vendor extension xsmtvdot / XSMTVDot (SpacemiT Vector Dot Product v1.0) — mnemonics like smt.vmadot. There are no C intrinsics and no auto-vectorization; every real user emits the instruction via inline assembly.
| Toolchain | First version with xsmtvdot |
What it gives you |
|---|---|---|
| LLVM / Clang | ≥ 22 (PR #151706) | Assembler recognizes smt.vmadot with -march=…_xsmtvdot |
| GCC | ≥ 16 (spacemit-x60 core includes …_xsmtvdot) |
-march / -mcpu=spacemit-x60 know the extension |
| Binutils (gas) | ≥ 2.46 | Named smt.* mnemonics in the assembler |
EESSI foss-2025b on this board is still GCC 14.3 + binutils 2.44 — neither understands smt.vmadot. We get IME working anyway with two patterns (both in benchmarks/ime):
- Raw encodings (no special assembler) —
ime-benchemitssmt.vmadotas a.insn/ raw word (0xe200302b|…with register fields filled in). Stock binutils 2.42 on the RV2 assembles it; any RVV-capableasis enough. - Named mnemonics + patched binutils — llama.cpp’s SpaceMiT backend uses inline asm. Upstream ggml still wrote bare
vmadot; our patch (llama.cpp-x60-ime-upstream-binutils.patch) renames those tosmt.vmadot, injects.option arch,+xsmtvdot, and gates IME2 sources (X60 is IME1-only). We deploy a standalonebinutils-2.46.1-xsmtvdotand point GCC at only thatasvia-B…/(symlink in a private dir) so linking still uses the toolchainld— see the EasyBuild recipe comments in the same directory. Without that assembler, the CMakeFindSMTIMEprobe fails and the IME backend never builds.
Same hardware and toolchain story apply on the Banana Pi F3 (identical K1 / X60 silicon).
Microbenchmarks in opensolvers/benchmarks/ime (ime-bench): pure s8s8s32 GEMM, bit-exact vs a scalar reference, timed against a plain RVV int8 baseline on this board (core 0, 1.6 GHz):
| M×N×K | RVV int8 | IME (smt.vmadot) |
IME / RVV |
|---|---|---|---|
| 512×512×512 | 5.2 GOP/s | 39 GOP/s | 7.5× |
| 768×768×512 | ~5.2 GOP/s | 42 GOP/s (peak) | 8.1× |
| 1024×1024×512 | 5.2 GOP/s | 32 GOP/s | 6.2× |
Peak ~42 GOP/s single-core — vs ~5 GOP/s for a straightforward RVV int8 path. End-to-end ORT decode (Qwen / SmolLM2 / TinyLlama int4+int8) through ONNX Runtime and isolated MLAS kernel rates use the same IME hardware; see also papers/x60-ime-block-scale-optimization in the benchmarks repo.
End-to-end llama.cpp: 10/10 Q4_0 models (0.5B–7.6B) validated — IME wins prefill ≥1.1B (up to ~2.5×), RVV wins token-gen. Q8_0 hybrid restores decode (6.68 vs 0.83 tg32 @ t4) at ~2× weight RAM. Staging fork: opensolvers/llama.cpp branch x60-ime-rvv.
IME1 scale-build prefill optimization (llama.cpp)
llama.cpp’s block-scaled Q4_0 kernel (gemm_kernel_i8i4) pays a per-block FP scale tax (~31–37% vs raw s8s8s32). Patch llama-ime1-scalebuild-opt.patch rebuilds As×Bs scales with vfmul.vv (LOAD_SCALE_4x16_FP16_OPT) instead of the masked vfmul.vf chain.
Isolated interleaved A/B on this board (30 rounds, cluster-0 pinned, performance governor, pp512 = 512³) — benchmarks/ime:
| stock | scaleopt | |
|---|---|---|
| median GOP/s | 22.50 | 23.47 |
| range | 22.4–22.7 | 23.2–23.6 |
+4.3% (+0.97 GOP/s); scaleopt faster in 30/30 rounds; zero overlap; paired t = 50.3. Bit-exact (sum/sumsq/max identical across three shapes).
The kernel win is real; end-to-end llama-bench pp512 on this multi-tenant part is not resolvable above ±15–20% noise (see the paper linked above). Decode (tg) uses the untouched M1/GEMV path.
GPU (PowerVR BXE-2-32) — compute closed
The on-die Imagination PowerVR BXE-2-32 (BVNC 36.29.52.182) is compute-capable on paper — OpenCL 3.0 + Vulkan 1.3, and /sys/kernel/debug/pvr/status shows a live CDM (Compute Data Master). Firmware (rgx.fw.36.29.52.182) loads. Source: benchmarks/gpu.
Vendor userspace is a dead end. SpaceMiT’s shipped DDK (libVK_IMG, libPVROCL, libGLESv2_PVR_MESA) embeds only "B-Series BXM-4-64" — none of the blobs know BXE. Measured on this board:
| API | Result |
|---|---|
OpenCL (libPVROCL) |
Gates on BVNC → refuses 36.29.52.182 |
Vulkan (libVK_IMG) |
vkCreateInstance → VK_ERROR_INCOMPATIBLE_DRIVER (−9) after probing /dev/dri/renderD128 |
| GLES 3.1 compute | Same DDK / same BXM allowlist |
No rebuild path: the CL/VK/GLES runtime and USC shader compiler are proprietary; every public img-gpu-powervr package is the same 24.2@6603887 BXM blob. Graphics rendering works; GPGPU on our SKU does not.
Open stack (deferred). The only self-buildable route is Mesa pvr + mainline drm/imagination — a different kernel uAPI and firmware format (powervr/rogue_36.29.52.182_v1.fw exists on freedesktop). That needs a rebuilt K1 kernel (backport onto 6.6.63-ky; no vendor/Armbian/OpenWrt tree ships the open driver). Explicitly not switching this board’s kernel for now. Even on full success the GPU ceiling is ~20 GFLOPS FP32 — roughly 50–100× below 8× X60 RVV / IME for our int8 LLM GEMM.
Bottom line: GPU compute via the vendor stack is closed. Useful accelerators on this SoC remain RVV and IME (above). Same silicon on the Banana Pi F3.
HPL via EESSI
See also the HPL app overview.
Unlike the scalar VisionFive 2, the X60 already dispatches OpenBLAS’s upstream RVV RISCV64_ZVL256B kernels from the stock EESSI stack — but OpenBLAS 0.3.30 has a bug in gemv_n that zeroes an uninitialized vector register, so stock EESSI HPL fails with residual nan (it can still report a plausible ~8.5 GFLOP/s; only the residual check reveals the answer is wrong).
End-to-end on real Orange Pi RV2 hardware using EESSI 2025.06-001 on dev.eessi.io/riscv. Peak run: N=20000, NB=256, 2×4 grid (8 MPI ranks).
| Before | After | |
|---|---|---|
| HPL (8 cores, N=20000, 2×4) | ~8.5 GFLOP/s, FAILED (nan) |
10.53 GFLOP/s, PASSED |
| Residual (N=8000, 1×8) | nan |
4.04e-03 |
With the fixed backend, scalar-vs-RVV A/B (benchmarks/hpl): 6.41 → 11.55 GFLOP/s (N=8000, 1×8) and 7.38 → 13.41 GFLOP/s (N=28672, 1×8).
Before — stock EESSI OpenBLAS 0.3.30 (RVV gemv_n bug). After — fixed OpenBLAS built with TARGET=RISCV64_ZVL256B and a backported gemv_n patch (easyconfigs#26444), swapped in via FlexiBLAS — no HPL rebuild.
The fix backports the upstream gemv_n correction from OpenBLAS ≥ 0.3.31 (OpenBLAS#5408). OpenBLAS 0.3.34 fixes this natively — verified end-to-end on RV2 (BLAS: dgemv NaN 0, SYRK/CTRSM PASS, DGEMM 15.54 GFLOP/s @ t8; HPL: 11.04 / 10.97 GFLOP/s PASSED on HPL.dat / HPL-sweep). A future EESSI bump to ≥ 0.3.34 should make the manual patch unnecessary.
Reproducing the fixed run
- Set up CVMFS + EESSI on
riscv64(EESSI_VERSION_OVERRIDE=2025.06-001). - Baseline:
module load HPL/2.3-foss-2025b→ stock HPL fails residual check (nan). - Build fixed OpenBLAS:
eb --from-pr 26444 --robot(viaEESSI-extenduser install,EASYBUILD_OPTARCH='-march=rv64imafdcv_zvl256b'). - Register the new backend with FlexiBLAS and re-run the same
xhpl.
Full walkthrough: EESSI blog — Chasing a NaN: correct RVV HPL on a RISC-V SpaceMiT X60.
BLIS vs OpenBLAS
See BLIS — FLAME BLIS rv64iv with hand-written RVV assembly vs patched OpenBLAS on the same bench_dgemm.c. Single-thread DGEMM at N=4096: 2.95 vs 2.28 GFLOP/s (1.29× BLIS); 8-thread OpenBLAS still leads at large N. TRSM: 2400 cases, 0 fails on BLIS.
End-to-end HPL on BLIS: all PASSED, but 0.35–0.53× OpenBLAS-RVV (best full-memory 5.87 GFLOP/s at N=25600, 2×4) — the DGEMM win does not carry to Linpack.
FFTW RVV
See FFTW — r5v wins 1.06–1.60× in tests/bench, but ~0% end-to-end in Quantum ESPRESSO (FFTW_ESTIMATE).
GROMACS
See GROMACS — two axes on this board:
| Axis | Result |
|---|---|
FFT (libfftw3f swap) |
1.23× on isolated PME 3D-FFT; ~0% whole-app (Force = 90%) |
Force (rvv-backend/, SIMD: RISCV_RVV) |
4.38× on Force, 3.31× whole-app (0.380 → 1.256 ns/day) |
LAMMPS
See LAMMPS — RVV-Kokkos whole-app MD (five upstream bench/ workloads, 32000 atoms / 100 steps):
| Regime | Best back-end | Peak vs serial |
|---|---|---|
Compute-bound pair (lj, eam) |
Kokkos/OpenMP (8 threads) | 6.20× / 7.21× |
Comm / bonded-heavy (chain, chute, rhodo) |
MPI (8 ranks) | 5.67× / 4.90× / 5.94× |
Speedups are parallel scaling on one RVV-vectorized binary (not RVV-vs-scalar). Pair force ≈ 84% of lj wall. Hand RVV Pair: eam/rvv 1.27× vs stock; LJ in-app ~1.02× — see Kokkos.
Kokkos
See Kokkos — portable OpenMP/Serial for LAMMPS; no RVV SIMD backend in 4.6.2; hand RVV LJ microbench ~1.64×, EAM plugin 1.27×.
GCC mtune
See GCC — EasyBuild-facing SpacemiT X60 patches for stock 14.3.0 and 15.2.0; A/B vs -mtune=generic-ooo:
| Probe | 14.3 | 15.2 |
|---|---|---|
Canaries fma_chain / div_mix |
−5.0% / −6.7% | −8.7% / −7.7% ns/call |
| OpenBLAS DGEMM N=512–2048 | −3…−7% | +2.2–3.8% GF/s |
| HPL N=3000 | +6.8% | +0.8% (both PASSED) |
Local proof only — not an EESSI PR yet.
OpenFOAM
See OpenFOAM — motorBike simpleFoam (4 ranks): GCC auto-vec ~0%; hand RVV Amul ~50% slower (whole solve ~3–4% worse); GS RVV mild regression. Sparse gather on short rows.
waLBerla
See waLBerla — contiguous auto-vec campaign:
| Probe | Result |
|---|---|
BasicLBM ISA-tag (rv64gc → rv64gcv) |
~1–4% |
| HeatEquation Jacobi np1 | 1.64× |
UniformGrid --not-fused collide |
1.54× (WALL 1.30×) |
SoA auto-vec vs hand simd::double4_t |
~9× favour auto-vec |
PETSc
See PETSc — FlexiBLAS A/B + hand RVV SpMV on overlay PETSc/3.24.0-foss-2025b:
| Probe | Result |
|---|---|
| Jacobi-CG AIJ (n=400) | patched ~1.06× vs scalar |
| Dense MatMult n=2048 | patched ~1.70×; stock RVV NaN |
| SuperLU_DIST / UMFPACK | stock RVV NaN; patched finite |
| MUMPS 2D/3D (small) | finite on stock; little patched speedup |
| MUMPS 3D n=80 (512k) | RVV ~1.5× vs scalar |
| Hand RVV CSR SpMV | ≈ no win vs scalar CSR / trails MatMult |
| Structured 5-pt stencil RVV | ~3.6× vs PETSc MatMult |
FlexiBLAS language / ML stack (2026-08-22)
| Probe | Result |
|---|---|
| NumPy DGEMM / EIGH | patched 3.13× / 1.61×; stock eig fails |
| Armadillo | DGEMM 1.82×, EIG 1.63× |
| R | GEMM 1.80×, EIGEN 1.45× |
| scikit-learn | PCA 1.22×, Ridge 1.90× |
| MODFLOW ex-gwf-lgrv-lgr | FlexiBLAS ~1.00× (flat) |
Soft-matter / Coulomb / MPI (2026-08-21)
| Probe | Result |
|---|---|
| ESPResSo P3M FFT | r5v ~1.12× |
| ScaFaCoS P3M FFT | r5v ~0.99× |
| MetalWalls tip4p | FFT/BLAS ~1.00–1.02× |
| PLUMED SPRINT | patched RVV 1.25× |
| Voro++ auto-vec | ~0.99× (negative) |
| OSU on-node MPI | 1.12 μs @ 1 B; ~2 GB/s uni BW |
ScaLAPACK
See ScaLAPACK — PDSYEV on 2×4 grid: stock RVV hangs; patched RVV 107.23 s vs scalar 116.87 s (1.09×).