Skip to content

python bindings

python bindings #3

Workflow file for this run

name: python-bindings
# Locks in what the Python bindings already guarantee (see
# docs/python-bindings-distribution-plan.md, Milestone 1):
# - lint: generated-FFI drift gate + version-sync across the three
# sources of truth (header / pyproject / __init__).
# - cpp-tests: the full C++ white-box suite against a static build.
# - python-shared: a shared libtranscribe, the pure-C api_smoke as an
# exported-symbol canary, and the Python no-model tests
# (import, ABI layout, version gate, status/enum agreement).
#
# Real-model tests are intentionally not run here: they need multi-GB GGUFs CI
# cannot ship. They live behind env vars and skip cleanly. A tiny redistributable
# canary GGUF (plan M1) will later let one real transcription run in CI.
on:
push:
branches: [main]
paths:
- "bindings/python/**"
- "include/**"
- "src/**"
- "tests/**"
- "ggml/**"
- "CMakeLists.txt"
- ".github/workflows/python-bindings.yml"
pull_request:
paths:
- "bindings/python/**"
- "include/**"
- "src/**"
- "tests/**"
- "ggml/**"
- "CMakeLists.txt"
- ".github/workflows/python-bindings.yml"
workflow_dispatch:
# The generated FFI layer is pinned to one libclang so the drift gate's embedded
# `libclang: <version>` line is reproducible across runs.
env:
LIBCLANG_PIN: "libclang==18.1.1"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Install clang (libclang resource-dir headers)
run: sudo apt-get update && sudo apt-get install -y clang
- name: Generated-FFI drift gate
run: |
uv run --no-project --with "$LIBCLANG_PIN" \
bindings/python/_generate/generate.py --check
- name: Version sync (header / pyproject / __init__)
run: uv run --no-project bindings/python/_generate/check_version_sync.py
cpp-tests:
name: cpp-tests (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-14]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Install uv (fixtures are generated via uv)
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Install build deps (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y cmake ninja-build zlib1g-dev
- name: Install build deps (macOS)
if: runner.os == 'macOS'
run: brew install ninja
- name: Configure (static white-box build)
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
- name: Build
run: cmake --build build -j
- name: Test (full white-box suite)
run: ctest --test-dir build --output-on-failure
cpp-tests-sanitized:
# ASan+UBSan over the white-box suite. This is the lane that certifies
# the C lifetime/ABI contracts the FFI bindings rely on — including the
# params copy-out regression (stream_dispatch_unit), which reproduces a
# ctypes-shaped caller freeing its params right after stream_begin.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv (fixtures are generated via uv)
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Install build deps
run: sudo apt-get update && sudo apt-get install -y cmake ninja-build zlib1g-dev
- name: Configure (static, sanitized)
run: |
cmake -B build-asan -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DTRANSCRIBE_SANITIZE=ON
- name: Build
run: cmake --build build-asan -j
- name: Test (white-box suite under ASan+UBSan)
run: ctest --test-dir build-asan --output-on-failure
provider-dl-vulkan:
# Builds the default Linux provider shape — CPU + Vulkan as dynamic
# backend modules (GGML_BACKEND_DL) — assembles it into a flat
# wheel-like directory with $ORIGIN rpaths, deletes the build tree so
# nothing can resolve outside the directory, and proves the Vulkan
# degradation contract on the real runner:
# 1. loader REMOVED (no libvulkan) -> import + CPU devices work,
# vulkan answers unavailable; the module-load failure is quiet.
# 2. mesa lavapipe installed -> the SAME artifacts discover a
# software Vulkan device.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Install build deps (Vulkan SDK pieces + patchelf)
run: |
sudo apt-get update
sudo apt-get install -y cmake ninja-build zlib1g-dev \
libvulkan-dev glslc patchelf
- name: Configure (DL provider, CPU + Vulkan modules, wheel posture)
run: |
cmake -B build-dl -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DTRANSCRIBE_BUILD_SHARED=ON \
-DTRANSCRIBE_GGML_BACKEND_DL=ON \
-DTRANSCRIBE_VULKAN=ON \
-DTRANSCRIBE_USE_OPENMP=OFF \
-DTRANSCRIBE_USE_SYSTEM_BLAS=OFF
- name: Build
run: cmake --build build-dl -j
- name: Assemble flat provider directory ($ORIGIN rpaths, build tree deleted)
run: |
mkdir -p provider
cp -L build-dl/src/libtranscribe.so provider/
cp -L build-dl/ggml/src/libggml.so.* build-dl/ggml/src/libggml-base.so.* provider/ 2>/dev/null || \
cp -L build-dl/ggml/src/libggml.so build-dl/ggml/src/libggml-base.so provider/
cp build-dl/bin/libggml-*.so provider/
for f in provider/*.so*; do patchelf --set-rpath '$ORIGIN' "$f"; done
ls -la provider/
rm -rf build-dl # isolation: nothing may resolve outside provider/
- name: "Tier 1: no Vulkan loader — CPU works, vulkan cleanly unavailable"
run: |
sudo apt-get remove -y libvulkan-dev libvulkan1 || \
sudo rm -f /usr/lib/x86_64-linux-gnu/libvulkan.so.1*
export TRANSCRIBE_LIBRARY="$PWD/provider/libtranscribe.so"
cat > /tmp/check_no_vulkan.py <<'EOF'
import transcribe_cpp as t
devs = t.backends()
print("devices (no loader):", [(d.name, d.kind) for d in devs])
assert any(d.kind == "cpu" for d in devs), devs
assert t.backend_available("cpu")
assert not t.backend_available("vulkan"), devs
print("ok: import + CPU fine without a Vulkan loader; vulkan answers False")
EOF
uv run --project bindings/python python /tmp/check_no_vulkan.py
- name: "Tier 2: lavapipe installed — same artifacts discover Vulkan"
run: |
sudo apt-get install -y libvulkan1 mesa-vulkan-drivers
export TRANSCRIBE_LIBRARY="$PWD/provider/libtranscribe.so"
cat > /tmp/check_vulkan.py <<'EOF'
import transcribe_cpp as t
devs = t.backends()
print("devices (lavapipe):", [(d.name, d.kind) for d in devs])
assert t.backend_available("vulkan"), devs
assert any(d.kind == "vulkan" for d in devs), devs
print("ok: the same provider directory discovers a Vulkan device")
EOF
uv run --project bindings/python python /tmp/check_vulkan.py
python-shared:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Install build deps
run: sudo apt-get update && sudo apt-get install -y cmake ninja-build zlib1g-dev
# Shared build in the official-wheel posture (no OpenMP / system BLAS), so
# the canary exercises the same configuration the provider wheels ship.
- name: Configure (shared, wheel posture)
run: |
cmake -B build-shared -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DTRANSCRIBE_BUILD_SHARED=ON \
-DTRANSCRIBE_USE_OPENMP=OFF \
-DTRANSCRIBE_USE_SYSTEM_BLAS=OFF
- name: Build
run: cmake --build build-shared -j
- name: api_smoke (exported-symbol canary) + extension umbrella
run: ctest --test-dir build-shared --output-on-failure
- name: Python no-model tests
run: |
# libtranscribe.so is dlopen'd by ctypes; help it resolve its sibling
# ggml shared libs in the build tree.
export LD_LIBRARY_PATH="$(find "$PWD/build-shared" -name '*.so' \
-printf '%h\n' | sort -u | tr '\n' ':')$LD_LIBRARY_PATH"
# The loader auto-discovers build-shared/src/libtranscribe.so from the
# repo root; model tests skip (no GGUF in CI).
uv run --project bindings/python --extra test \
pytest bindings/python/tests -q -rs