Skip to content

Commit d17e899

Browse files
authored
faiss-cpu: add build-faiss-cpu.yml for riscv64 wheels (#530)
* faiss-cpu: add build-faiss-cpu.yml for riscv64 wheels faiss v1.15.0 already carries riscv64 RVV kernels and builds its wheels with plain cibuildwheel driven entirely from pyproject.toml, so the workflow is upstream's build-pip.yml narrowed to one riscv64 job. Two patches are needed on top: - the RVV sources are compiled with -march=rv64gcv_zvfhmin, which binutils 2.41 in manylinux_2_39_riscv64 (Rocky Linux 10) rejects outright; none of them uses float16 vectors, so probe the flag and fall back to rv64gcv; - SIMDConfig::detect() offers SIMDLevel::RISCV_RVV unconditionally while only the RVV kernels are built with -march=rv64gcv, so the wheel would SIGILL on the rv64gc baseline the platform tag promises; read AT_HWCAP instead, as the aarch64 SVE path already does. pyproject.toml sets wheel.py-api = "cp310" on Linux, so one abi3 wheel covers every non-free-threaded CPython and upstream ships no free-threaded wheel at all. It is built on cp312 rather than upstream's cp310 because the registry's newest riscv64 numpy/scipy stop at 2.2.2 and 1.15.2 for cp310 while PyPI offers 2.2.6 and 1.15.3, which pip would prefer and then build from sdist. Signed-off-by: Ludovic Henry <git@ludovic.dev> * faiss-cpu: publish the image's gcc sources alongside the wheels auditwheel vendors libgomp and libgfortran out of the manylinux image into faiss_cpu.libs/, so their sources have to be published with the wheel. Signed-off-by: Ludovic Henry <git@ludovic.dev> * faiss-cpu: keep the RVV kernels out of LTO FAISS_USE_LTO is ON for Linux and only the RVV sources carry -march=rv64gcv, so lto1 regenerates them at link time from the link command line, which has no vector extension, and libfaiss.so fails with "target specific builtin not available". Compile those seven files with -fno-lto. Folded into the existing CMake patch rather than added as a third one: git apply resolves a glob of patches against the pre-patch tree, so a patch editing a line an earlier patch added does not apply. Signed-off-by: Ludovic Henry <git@ludovic.dev> * faiss-cpu: probe RVV with riscv_hwprobe, fall back to AT_HWCAP riscv_hwprobe(RISCV_HWPROBE_KEY_IMA_EXT_0) is the authoritative source for the V extension. glibc only declares __riscv_hwprobe() from 2.40 and the manylinux image is 2.39, so the syscall is issued directly; __NR_riscv_hwprobe is in the image's kernel headers (258). Kernels before 6.4 do not implement it and fail with ENOSYS, which falls back to the AT_HWCAP bit as before. Compile-tested in quay.io/pypa/manylinux_2_39_riscv64 with -Wall -Wextra. --------- Signed-off-by: Ludovic Henry <git@ludovic.dev>
1 parent 3ac870d commit d17e899

3 files changed

Lines changed: 312 additions & 0 deletions

File tree

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# SPDX-FileCopyrightText: 2026 The RISE Project
2+
# SPDX-License-Identifier: MIT
3+
---
4+
# This workflow is based on: https://github.com/facebookresearch/faiss/blob/v1.15.0/.github/workflows/build-pip.yml
5+
name: Build faiss-cpu wheels (riscv64)
6+
7+
on:
8+
workflow_dispatch:
9+
inputs:
10+
version:
11+
description: 'faiss-cpu version to build (git tag without the leading v, e.g. 1.15.0)'
12+
required: true
13+
default: '1.15.0'
14+
pull_request:
15+
paths:
16+
- '.github/workflows/build-faiss-cpu.yml'
17+
- 'patches/faiss-cpu/**'
18+
19+
concurrency:
20+
group: ${{ github.workflow }}-${{ inputs.version || '1.15.0' }}-${{ github.head_ref || github.run_id }}
21+
cancel-in-progress: true
22+
23+
permissions:
24+
contents: read # to fetch code (actions/checkout)
25+
26+
env:
27+
# `inputs.version` is empty on pull_request events; default to 1.15.0 there.
28+
FAISS_CPU_VERSION: ${{ inputs.version || '1.15.0' }}
29+
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64
30+
31+
jobs:
32+
# pyproject.toml sets wheel.py-api = "cp310" on Linux, so a single abi3 wheel
33+
# covers every non-free-threaded CPython; upstream skips free-threaded builds
34+
# entirely (`skip = "... cp3*t-*"`). Built on cp312 rather than upstream's
35+
# cp310 because our registry's newest riscv64 numpy/scipy are cp311+ only.
36+
build_wheels:
37+
name: Build faiss-cpu ${{ inputs.version || '1.15.0' }} cp310-abi3-manylinux_riscv64
38+
runs-on: ubuntu-24.04-riscv
39+
timeout-minutes: 600
40+
41+
steps:
42+
- name: Checkout faiss v${{ env.FAISS_CPU_VERSION }}
43+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
44+
with:
45+
repository: facebookresearch/faiss
46+
ref: v${{ env.FAISS_CPU_VERSION }}
47+
persist-credentials: false
48+
49+
- name: Checkout python-wheels
50+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
51+
with:
52+
path: python-wheels
53+
persist-credentials: false
54+
55+
- name: Patch faiss source
56+
run: git apply python-wheels/patches/faiss-cpu/${{ env.FAISS_CPU_VERSION }}/*.patch
57+
58+
- name: Build wheels
59+
uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0
60+
with:
61+
output-dir: wheelhouse/
62+
only: cp312-manylinux_riscv64
63+
env:
64+
CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }}
65+
# numpy (build and test), scipy and swig have no riscv64 wheels on PyPI.
66+
CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/
67+
68+
- name: Check the wheel is a compiled abi3 wheel
69+
run: |
70+
python3 - wheelhouse/*.whl <<'EOF'
71+
import sys, zipfile
72+
for whl in sys.argv[1:]:
73+
assert "-cp310-abi3-" in whl, whl
74+
names = zipfile.ZipFile(whl).namelist()
75+
for want in ("faiss/libfaiss.so", "faiss/_swigfaiss.abi3.so"):
76+
assert want in names, (whl, want)
77+
print(whl, "ok")
78+
EOF
79+
80+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
81+
with:
82+
name: faiss-cpu-${{ env.FAISS_CPU_VERSION }}-cp310-abi3-manylinux_riscv64
83+
path: wheelhouse/*.whl
84+
if-no-files-found: error
85+
86+
gpl_sources:
87+
name: Collect GPL sources
88+
runs-on: ubuntu-24.04-riscv
89+
90+
steps:
91+
- name: Checkout python-wheels
92+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
93+
with:
94+
persist-credentials: false
95+
96+
# faiss links OpenMP and OpenBLAS, so auditwheel vendors the image's
97+
# libgomp and libgfortran into faiss_cpu.libs/.
98+
- uses: ./actions/collect-gpl-sources
99+
with:
100+
image: ${{ env.MANYLINUX_RISCV64_IMAGE }}
101+
packages: gcc
102+
output: gpl-sources.tar
103+
104+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
105+
with:
106+
name: faiss-cpu-${{ env.FAISS_CPU_VERSION }}-gpl-sources
107+
path: gpl-sources.tar
108+
if-no-files-found: error
109+
110+
publish:
111+
name: Publish faiss-cpu ${{ inputs.version || '1.15.0' }} to GitLab
112+
needs: [build_wheels, gpl_sources]
113+
runs-on: ubuntu-latest
114+
permissions:
115+
contents: write
116+
pull-requests: write
117+
118+
steps:
119+
- name: Publish wheels and open docs PR
120+
uses: riseproject-dev/python-wheels/actions/publish-wheels@main
121+
with:
122+
artifact-pattern: faiss-cpu-${{ env.FAISS_CPU_VERSION }}-*-manylinux_riscv64
123+
gitlab-username: ${{ vars.GITLAB_DEPLOY_USER }}
124+
gitlab-token: ${{ secrets.GITLAB_DEPLOY_TOKEN }}
125+
gitlab-project-id: ${{ vars.GITLAB_PROJECT_ID }}
126+
gh-token: ${{ secrets.GITHUB_TOKEN }}
127+
gpl-sources-artifact: faiss-cpu-${{ env.FAISS_CPU_VERSION }}-gpl-sources
128+
gpl-sources-release-tag: faiss-cpu-v${{ env.FAISS_CPU_VERSION }}
129+
gpl-sources-description: gcc
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
From 5d56ba18506d4e1ed92330fcd368902c67bbb884 Mon Sep 17 00:00:00 2001
2+
From: Ludovic Henry <git@ludovic.dev>
3+
Date: Thu, 27 Aug 2026 07:16:00 +0200
4+
Subject: [PATCH 1/2] build: make the RVV kernels build with a distro toolchain
5+
6+
Upstream-Status: To upstream [not yet submitted; needs a maintainer decision on the intended riscv64 toolchain baseline, so it is not a drive-by PR]
7+
8+
Two things stop the FAISS_SIMD_RVV_SRC files from producing a shared
9+
library with GCC 14.3.1 / binutils 2.41, the toolchain in the riscv64
10+
manylinux image (manylinux_2_39_riscv64, Rocky Linux 10).
11+
12+
They are compiled with -march=rv64gcv_zvfhmin. zvfhmin only reached
13+
binutils in 2.42, and an older assembler rejects the whole -march string
14+
rather than just that extension, so every RVV translation unit fails with
15+
"unknown prefixed ISA extension `zvfhmin'". None of the seven files uses
16+
float16 vectors, so zvfhmin buys nothing today and all seven compile
17+
unchanged with plain rv64gcv. Probe for the flag and fall back.
18+
19+
FAISS_USE_LTO is also ON for Linux, and with -flto those objects hold
20+
GIMPLE rather than code, so lto1 regenerates them at link time from the
21+
*link* command line, which carries no vector extension:
22+
23+
lto1: fatal error: target specific builtin not available
24+
lto-wrapper: fatal error: /usr/bin/g++ returned 1 exit status
25+
26+
GCC has no per-function target attribute for RISC-V vector, so the flags
27+
cannot be carried into the LTO recompilation. Compile just those seven
28+
files with -fno-lto; every other translation unit still takes part.
29+
30+
Signed-off-by: Ludovic Henry <git@ludovic.dev>
31+
---
32+
faiss/CMakeLists.txt | 21 +++++++++++++++++++--
33+
1 file changed, 19 insertions(+), 2 deletions(-)
34+
35+
diff --git a/faiss/CMakeLists.txt b/faiss/CMakeLists.txt
36+
index 1a4a30a..6deb907 100644
37+
--- a/faiss/CMakeLists.txt
38+
+++ b/faiss/CMakeLists.txt
39+
@@ -87,6 +87,23 @@ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "(riscv64|riscv)")
40+
else()
41+
set(FAISS_SIMD_SRC "")
42+
endif()
43+
+
44+
+# zvfhmin is only recognised by binutils >= 2.42. Older assemblers reject the
45+
+# whole -march string, and no RVV source here uses float16 vectors, so probe
46+
+# for it and fall back to plain rv64gcv.
47+
+# The RVV kernels must be excluded from LTO: lto1 recompiles them from GIMPLE
48+
+# using the link command's target options, which do not include the vector
49+
+# extension, and fails with "target specific builtin not available".
50+
+set(FAISS_RVV_ARCH_FLAGS "-march=rv64gcv_zvfhmin;-mabi=lp64d;-fno-lto")
51+
+if(CMAKE_SYSTEM_PROCESSOR MATCHES "(riscv64|riscv)")
52+
+ include(CheckCXXSourceCompiles)
53+
+ set(CMAKE_REQUIRED_FLAGS "-march=rv64gcv_zvfhmin -mabi=lp64d")
54+
+ check_cxx_source_compiles("int main() { return 0; }" FAISS_HAVE_RVV_ZVFHMIN)
55+
+ unset(CMAKE_REQUIRED_FLAGS)
56+
+ if(NOT FAISS_HAVE_RVV_ZVFHMIN)
57+
+ set(FAISS_RVV_ARCH_FLAGS "-march=rv64gcv;-mabi=lp64d;-fno-lto")
58+
+ endif()
59+
+endif()
60+
# =============================================================================
61+
62+
set(FAISS_SRC
63+
@@ -595,7 +612,7 @@ if(FAISS_OPT_LEVEL STREQUAL "dd")
64+
if(FAISS_SIMD_RVV_SRC)
65+
set_source_files_properties(${FAISS_SIMD_RVV_SRC}
66+
TARGET_DIRECTORY faiss
67+
- PROPERTIES COMPILE_OPTIONS "-march=rv64gcv_zvfhmin;-mabi=lp64d"
68+
+ PROPERTIES COMPILE_OPTIONS "${FAISS_RVV_ARCH_FLAGS}"
69+
)
70+
endif()
71+
endif()
72+
@@ -624,7 +641,7 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "(riscv64|riscv)" AND NOT FAISS_OPT_LEVEL STRE
73+
if(NOT WIN32 AND FAISS_SIMD_RVV_SRC)
74+
set_source_files_properties(${FAISS_SIMD_RVV_SRC}
75+
TARGET_DIRECTORY faiss
76+
- PROPERTIES COMPILE_OPTIONS "-march=rv64gcv_zvfhmin;-mabi=lp64d"
77+
+ PROPERTIES COMPILE_OPTIONS "${FAISS_RVV_ARCH_FLAGS}"
78+
)
79+
endif()
80+
endif()
81+
--
82+
2.50.1 (Apple Git-155)
83+
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
From 2ae2e10260375f9aec6d6d546b60f7f9e621b86e Mon Sep 17 00:00:00 2001
2+
From: Ludovic Henry <git@ludovic.dev>
3+
Date: Thu, 27 Aug 2026 06:31:39 +0200
4+
Subject: [PATCH 2/2] Detect RVV at runtime instead of assuming it on riscv64
5+
6+
Upstream-Status: To upstream [not yet submitted; needs a maintainer decision on the intended riscv64 baseline, so it is not a drive-by PR]
7+
8+
Only the files in FAISS_SIMD_RVV_SRC are compiled with -march=rv64gcv; the
9+
rest of the library targets the toolchain default, which for a distro
10+
compiler is the rv64gc baseline. SIMDConfig::detect() nevertheless sets
11+
SIMDLevel::RISCV_RVV unconditionally, so a build that runs on a CPU
12+
without the V extension dispatches into vector kernels and dies with
13+
SIGILL. Most riscv64 hardware in circulation (JH7110, HiFive P550) has no
14+
vector unit, and a manylinux_riscv64 wheel is expected to run on the
15+
rv64gc baseline.
16+
17+
Probe with riscv_hwprobe(RISCV_HWPROBE_KEY_IMA_EXT_0) and offer RISCV_RVV
18+
only when RISCV_HWPROBE_IMA_V is set. glibc exposes __riscv_hwprobe() only
19+
from 2.40 and the manylinux image is 2.39, so the syscall is issued directly.
20+
Kernels before 6.4 do not implement it and fail with ENOSYS; those fall back
21+
to the AT_HWCAP bit, mirroring the existing has_sve() aarch64 path. dispatch_with_fallback() already falls
22+
back from RISCV_RVV to SIMDLevel::NONE, so the scalar kernels take over on
23+
hardware that lacks it.
24+
25+
Signed-off-by: Ludovic Henry <git@ludovic.dev>
26+
---
27+
faiss/utils/simd_levels.cpp | 28 +++++++++++++++++++++++++---
28+
1 file changed, 25 insertions(+), 3 deletions(-)
29+
---
30+
31+
diff --git a/faiss/utils/simd_levels.cpp b/faiss/utils/simd_levels.cpp
32+
index e17796b..dd6a996 100644
33+
--- a/faiss/utils/simd_levels.cpp
34+
+++ b/faiss/utils/simd_levels.cpp
35+
@@ -57,6 +57,45 @@
36+
}
37+
#endif
38+
39+
+// RISC-V vector runtime detection
40+
+#if defined(__riscv) && defined(__linux__)
41+
+
42+
+#include <sys/auxv.h>
43+
+#include <sys/syscall.h>
44+
+#include <unistd.h>
45+
+#include <cstdint>
46+
+
47+
+#ifndef COMPAT_HWCAP_ISA_V
48+
+#define COMPAT_HWCAP_ISA_V (1 << ('V' - 'A'))
49+
+#endif
50+
+
51+
+// glibc only declares __riscv_hwprobe() from 2.40 and manylinux_2_39 is older,
52+
+// so issue the syscall directly. Kernels before 6.4 do not implement it and
53+
+// fail with ENOSYS; AT_HWCAP is the fallback for those.
54+
+#ifndef __NR_riscv_hwprobe
55+
+#define __NR_riscv_hwprobe 258
56+
+#endif
57+
+#define RISCV_HWPROBE_KEY_IMA_EXT_0 4
58+
+#define RISCV_HWPROBE_IMA_V (1ULL << 2)
59+
+
60+
+static bool has_rvv() {
61+
+ struct {
62+
+ int64_t key;
63+
+ uint64_t value;
64+
+ } pair = {RISCV_HWPROBE_KEY_IMA_EXT_0, 0};
65+
+
66+
+ if (syscall(__NR_riscv_hwprobe, &pair, 1UL, 0UL, nullptr, 0U) == 0) {
67+
+ return (pair.value & RISCV_HWPROBE_IMA_V) != 0;
68+
+ }
69+
+ return (getauxval(AT_HWCAP) & COMPAT_HWCAP_ISA_V) != 0;
70+
+}
71+
+
72+
+#else
73+
+[[maybe_unused]] static bool has_rvv() {
74+
+ return false;
75+
+}
76+
+#endif
77+
+
78+
// Detect x86 microarchitecture flags used for kernel routing. Uses raw
79+
// cpuid so it is safe to run on any CPU regardless of compiled SIMD level.
80+
#if defined(__x86_64__)
81+
@@ -244,9 +283,13 @@ SIMDLevel SIMDConfig::auto_detect_simd_level() {
82+
#endif
83+
84+
#if defined(__riscv) && defined(COMPILE_SIMD_RISCV_RVV)
85+
- // RVV is always available on RISC-V builds compiled with rv64gcv.
86+
- supported_simd_levels |= (1 << static_cast<int>(SIMDLevel::RISCV_RVV));
87+
- detected_level = SIMDLevel::RISCV_RVV;
88+
+ // Only the RVV kernels are compiled with -march=rv64gcv; the rest of the
89+
+ // library targets the rv64gc baseline, so they must not be dispatched to
90+
+ // on a CPU without the V extension.
91+
+ if (has_rvv()) {
92+
+ supported_simd_levels |= (1 << static_cast<int>(SIMDLevel::RISCV_RVV));
93+
+ detected_level = SIMDLevel::RISCV_RVV;
94+
+ }
95+
#endif
96+
97+
return detected_level;
98+
--
99+
2.50.1 (Apple Git-155)
100+

0 commit comments

Comments
 (0)