pywhispercpp is a Python binding for whisper.cpp (the ggml ASR engine, same
family as llama.cpp). It is an alternative transcription backend to
faster-whisper/CTranslate2, which on this machine cannot use the GPU (see
"Background" below).
pywhispercpp 1.5.1 bundles libwhisper.so.1.8.5 +
ggml 0.13.1 with the CPU backend only (libggml-cpu; no
libggml-vulkan, no libggml-cuda). The model-load log prints
whisper_backend_init_gpu: no GPU found.ggml-small,
at RTF ≈ 0.18 (11 s of audio in ≈ 2.0 s).GGML_VULKAN=1 or
GGML_CUDA=1. The PyPI wheel cannot be reconfigured.faster-whisper/CTranslate2 is hard-locked to CUDA 12 + cuDNN 9, and the CT2
PyPI wheels lack sm_100/sm_120 (Blackwell) kernels. The CUDA 13 toolkit and the
Vulkan stack are already installed, so a whisper.cpp build is the practical GPU
path.
CUDA 13 build, cloned to ~/Repositories/pywhispercpp, built into a vendored
wheel and installed into the project .venv. Runtime log:
whisper_init_with_params_no_state: use gpu = 1
ggml_cuda_init: found 1 CUDA devices (Total VRAM: 15888 MiB):
Device 0: NVIDIA RTX PRO 2000 Blackwell, compute capability 12.0
whisper_model_load: CUDA0 total size = 487.01 MB
whisper_backend_init_gpu: using CUDA0 backend
JFK sample with ggml-small:
| backend | transcribe (11 s audio) | RTF |
|---|---|---|
| CPU wheel | 2.00 s | 0.18 |
| CUDA 13 build | 0.15 s | 0.01 |
≈13× faster, transcript correct. sm_120 (Blackwell) kernels are present and run.
The GPU build is kept as a local wheel so uv sync restores it instead of
pruning it.
vendor/pywhispercpp-1.5.1-cp313-cp313-linux_x86_64.whl (~136 MB).
vendor/.gitignore contains *, so it is not committed — it is a local
artifact. If it is deleted, uv sync fails; rebuild it (step 2).pyproject.toml: the dependency is gated to the wheel's ABI, and the
source is the local file:
dependencies = [
# ...
"pywhispercpp>=1.5.1 ; python_full_version == '3.13.*'",
]
[tool.uv.sources]
pywhispercpp = { path = "vendor/pywhispercpp-1.5.1-cp313-cp313-linux_x86_64.whl" }
The marker is required: the project supports Python >=3.11, but the wheel is
cp313-only, so an unconditional dependency fails universal resolution. On
Python 3.11/3.12 the package is simply not installed.
uv.lock records pywhispercpp with source = { path = "vendor/..." } and
its hash, so uv sync reinstalls the CUDA build. uv sync --dry-run reports
"Would make no changes".git, gcc/g++ 14.2libvulkan-dev, glslc, glslang-tools, vulkan-tools/usr/local/cuda-13.3 (nvcc 13.3.73, cuBLAS 13.6)The clone already exists at ~/Repositories/pywhispercpp (recursive clone;
pybind11 and whisper.cpp are submodules). If re-cloning, use
git clone --recursive with full history (the build uses setuptools-scm,
which needs git tags).
Run from the clone, and build with the project venv's Python (the wheel must
be cp313, matching the venv):
cd ~/Repositories/pywhispercpp
CUDA_HOME=/usr/local/cuda-13.3 \
CUDACXX=/usr/local/cuda-13.3/bin/nvcc \
PATH="/usr/local/cuda-13.3/bin:$PATH" \
PYWHISPERCPP_VERSION=1.5.1 \
SETUPTOOLS_SCM_PRETEND_VERSION=1.5.1 \
GGML_CUDA=1 \
CMAKE_INSTALL_RPATH='$ORIGIN' \
CMAKE_BUILD_WITH_INSTALL_RPATH=ON \
uv build --wheel \
--python /home/efren/Repositories/STT-TTS/.venv/bin/python \
--out-dir /home/efren/Repositories/STT-TTS/vendor
GGML_CUDA=1 selects the CUDA backend; setup.py forwards every env var
to CMake as -DKEY=VALUE, so CMAKE_INSTALL_RPATH /
CMAKE_BUILD_WITH_INSTALL_RPATH can be set as plain env vars (no CMAKE_ARGS
needed).CMAKE_INSTALL_RPATH='$ORIGIN' makes the wheel self-contained: the ggml libs
resolve from the package directory, not the clone's build/ tree.--python <venv>/bin/python is essential; without it the build uses whatever
Python it finds and produces the wrong ABI tag (e.g. cp312).Then install it through uv (already done; re-run only if the wheel changed):
cd /home/efren/Repositories/STT-TTS
uv lock && uv sync
# Self-contained: RUNPATH should be $ORIGIN, libs under site-packages
readelf -d .venv/lib/python3.13/site-packages/_pywhispercpp*.so | grep -i runpath
ldd .venv/lib/python3.13/site-packages/_pywhispercpp*.so | grep -iE 'ggml|cublas'
# Runtime init log (whisper.cpp logs to stderr by default)
uv run python -c "from pywhispercpp.model import Model; Model('small')"
Success: ggml_cuda_init: found 1 CUDA devices / using CUDA0 backend, and not
the no GPU found line. Note redirect_whispercpp_logs_to accepts None, a
filepath string, or a file-like object — not True.
uv remove pywhispercpp # drops the dependency + source entry, then re-locks
uv sync
cp313-only. Any interpreter switch (or a requires-python
change) breaks resolution; the dependency marker and the wheel tag must stay in
sync.vendor/.gitignore = *). A fresh clone has no
wheel and uv sync fails until step 2 is re-run. The clone itself is not
needed at runtime once the wheel is built (RUNPATH is $ORIGIN).LD_LIBRARY_PATH needed. The CUDA libs (libcudart.so.13,
libcublas.so.13, libcublasLt.so.13) resolve via libggml-cuda's own
link-path rpath, so a plain import works.GGML_VULKAN_DEVICE=<index> and confirm in the init log.Set [whisper] backend = "pywhispercpp" in config.toml to use it. The default is
faster-whisper, so nothing changes unless you opt in.
The backend lives behind build_transcriber(), which returns either the
faster-whisper or the pywhispercpp implementation from the same config.
device and compute_type are ignored: the GPU support comes from the built
wheel and quantization is a property of the ggml model file, not a config key.
vad_filter is also ignored, with a warning (we already run webrtcvad upstream).
Everything else maps to the whisper.cpp equivalent: language auto-detect and
sticky pinning, no_speech_prob_max → no_speech_thold, avg_logprob_min →
segment probability gate, beam_size → beam search, and cpu_threads.
The GPU build is verified ≈13× faster than the CPU wheel for the same small
model (RTF 0.01 vs 0.18). The remaining question is whether whisper.cpp's
transcript quality and latency beat the current faster-whisper CPU backend on
real utterances — compare the two on the same clip before wiring anything in.