fix(void): report pywhispercpp 1.5.0 and load CPU variants at runtime
CI / security (pull_request) Successful in 1m11s
CI / lint (pull_request) Successful in 25s
CI / ai-review (pull_request) Successful in 8s

python3-pywhispercpp was installed as 0.0.0. setup.py passes no version to
setup() and takes it from setuptools-scm, which the Void build did not
install; the template's PYWHISPERCPP_VERSION export is never read. The app
read 0.0.0 as an old binding and skipped GPU device selection. Install
setuptools-scm, pin the version with SETUPTOOLS_SCM_PRETEND_VERSION, and
make the app treat a 0.0.0 distribution as unknown so the runtime feature
probe decides.

The CPU backend was built with every SIMD option off, so the real-time
factor on an AVX2 host was about 1.7. Build with GGML_BACKEND_DL and
GGML_CPU_ALL_VARIANTS instead: one CPU library per instruction-set level,
with the x86-64 baseline always shipped. The binding loads the variants
from its own directory (patches/), and pre_install places them beside the
extension. The app's library preloader skips those variants: they export
identical symbols, so preloading them RTLD_GLOBAL would let one variant
serve every other. The install smoke asserts that the installed package
reports 1.5.0, that the baseline variant holds no AVX code, that the AVX2
variant does, and that the CPU and Vulkan backends load from the package.

Fixes #43
Fixes #44
This commit is contained in:
2026-11-07 00:03:22 +05:30
parent a4a831c3c6
commit 97f93e9492
11 changed files with 376 additions and 66 deletions
@@ -0,0 +1,39 @@
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -16,6 +16,10 @@
#include <pybind11/numpy.h>
#include "whisper.h"
+#include "ggml-backend.h"
+
+#include <dlfcn.h>
+#include <string>
#define STRINGIFY(x) #x
@@ -850,7 +854,25 @@
////////////
+// With GGML_BACKEND_DL the CPU and GPU backends are libraries next to this
+// extension, and whisper.cpp cannot create a context until a CPU device is
+// registered. ggml searches the executable's directory (the interpreter's, for
+// Python) and the working directory, so load them from this module's directory.
+static void load_ggml_backends_beside_module() {
+ Dl_info info;
+ if (dladdr(reinterpret_cast<void *>(&load_ggml_backends_beside_module), &info) == 0 ||
+ info.dli_fname == nullptr) {
+ ggml_backend_load_all();
+ return;
+ }
+ const std::string path(info.dli_fname);
+ const std::size_t slash = path.find_last_of('/');
+ const std::string dir = slash == std::string::npos ? std::string(".") : path.substr(0, slash);
+ ggml_backend_load_all_from_path(dir.c_str());
+}
+
PYBIND11_MODULE(_pywhispercpp, m) {
+ load_ggml_backends_beside_module();
m.doc() = R"pbdoc(
Pywhispercpp: Python binding to whisper.cpp
-----------------------
@@ -3,7 +3,7 @@ pkgname=python3-pywhispercpp
version=1.5.0
revision=1
build_style=python3-module
hostmakedepends="cmake pkg-config python3-setuptools python3-wheel $(vopt_if vulkan shaderc)"
hostmakedepends="cmake pkg-config python3-setuptools python3-setuptools_scm python3-wheel $(vopt_if vulkan shaderc)"
makedepends="python3-devel $(vopt_if vulkan vulkan-loader-devel)"
depends="python3-numpy python3-requests python3-tqdm python3-platformdirs $(vopt_if vulkan vulkan-loader)"
short_desc="Python bindings for whisper.cpp speech recognition"
@@ -20,21 +20,23 @@ build_options="vulkan"
build_options_default="vulkan"
pre_build() {
# The sdist derives its version from git metadata, which is absent in
# the xbps build root, so inject the pinned version explicitly. This is
# the same version install.sh (PYWHISPERCPP_VERSION) and uv.lock pin.
export PYWHISPERCPP_VERSION=${version}
# Portable x86-64 baseline: GGML_NATIVE is ggml's default and compiles
# libggml-cpu with -march=native, so the package would inherit the
# builder's instruction set and SIGILL on older CPUs (#938).
# GGML_NATIVE=OFF alone is not enough: while the native default is on,
# ggml turns every GGML_* SIMD option on and still compiles -mavx2
# -mfma (#957). Each option is pinned off, mirroring the Flatpak build.
# Shared libraries with an $ORIGIN rpath let the extension resolve the
# whisper.cpp libraries installed beside it (as install.sh does), with
# no LD_LIBRARY_PATH at runtime. (setup.py install never builds a
# wheel, so NO_REPAIR is meaningless here and deliberately unset.)
export CMAKE_ARGS='-DCMAKE_INSTALL_RPATH=$ORIGIN -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DBUILD_SHARED_LIBS=ON -DGGML_NATIVE=OFF -DGGML_SSE42=OFF -DGGML_AVX=OFF -DGGML_AVX2=OFF -DGGML_BMI2=OFF -DGGML_FMA=OFF -DGGML_F16C=OFF'
# Version (#43). setup.py passes no version to setup(); the version comes
# from setuptools-scm, which the sdist declares as a build requirement and
# which hostmakedepends provides. The xbps build root has no git metadata,
# so pin the version through setuptools-scm. Without it setuptools falls
# back to 0.0.0. The sdist's PYWHISPERCPP_VERSION hook is never called.
export SETUPTOOLS_SCM_PRETEND_VERSION=${version}
# CPU (#44). GGML_BACKEND_DL builds each CPU variant (x64 baseline,
# sandybridge, haswell, icelake, zen4, ...) as its own library. The
# extension, through patches/, loads the variants that sit beside it, and
# ggml picks the best one the host CPU supports. The x64 baseline always
# ships, so the package still runs on any x86-64 CPU, and hosts with AVX2
# use it. GGML_NATIVE must stay OFF: ggml refuses it with GGML_BACKEND_DL,
# and it keeps -march=native out of every variant (#938).
# Shared libraries with an $ORIGIN rpath let each variant resolve
# libggml-base beside it, with no LD_LIBRARY_PATH at runtime. (setup.py
# install never builds a wheel, so NO_REPAIR is meaningless here.)
export CMAKE_ARGS='-DCMAKE_INSTALL_RPATH=$ORIGIN -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DBUILD_SHARED_LIBS=ON -DGGML_NATIVE=OFF -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON'
if [ -n "$build_option_vulkan" ]; then
# Vulkan acceleration for every GPU vendor. There is no CUDA
# build: Void ships no CUDA toolkit package.
@@ -42,6 +44,20 @@ pre_build() {
fi
}
pre_install() {
# The backend modules (libggml-cpu-*.so, libggml-vulkan.so) are written to
# build/bin, a CMake directory whose whisper.cpp setup fixes, and setup.py
# installs only the build/lib tree that holds the extension. Copy them
# beside the extension, where patches/ loads them from.
local backend found=""
for backend in build/temp.*/_pywhispercpp/bin/libggml-*.so; do
[ -f "$backend" ] || continue
cp "$backend" build/lib.*/
found=1
done
[ -n "$found" ] || msg_error "no ggml backend modules were built; the CPU variants are missing\n"
}
post_install() {
vlicense LICENSE
}