python bindings #12
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: python-wheels | |
| # Builds, repairs, and proves the distributable Python artifacts (plan M5): | |
| # - wheel-linux: manylinux_2_28 x86_64, CPU + Vulkan backend modules | |
| # (Vulkan toolchain built from pinned Khronos tags inside | |
| # the container; cached across runs) | |
| # - wheel-macos: macosx arm64, Metal with embedded shaders — built AND | |
| # Metal-compute-tested on a bare-metal self-hosted M4 mini | |
| # (VM runners' paravirtual GPUs can't do Metal compute) | |
| # - wheel-windows: win_amd64, CPU + Vulkan backend modules (LunarG SDK) | |
| # - sdist: the from-source fallback, proven by actually compiling | |
| # and transcribing from the tarball | |
| # - api-wheel: the pure-Python transcribe-cpp package | |
| # - co-import: numpy/torch coexistence with a real transcription, both | |
| # import orders, on all three platforms | |
| # | |
| # Every native wheel is tested AFTER repair (auditwheel/delocate/delvewheel) | |
| # in a fresh venv — cibuildwheel's test phase guarantees the tested artifact | |
| # is the one that ships. Real-model tests need the private canary GGUFs and | |
| # run when the HF_TOKEN secret exists; forks degrade to no-model tests. | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - "bindings/**" | |
| - "include/**" | |
| - "src/**" | |
| - "ggml/**" | |
| - "CMakeLists.txt" | |
| - "cmake/**" | |
| - "pyproject.toml" | |
| - "scripts/ci/**" | |
| - ".github/workflows/python-wheels.yml" | |
| pull_request: | |
| paths: | |
| - "bindings/**" | |
| - "include/**" | |
| - "src/**" | |
| - "ggml/**" | |
| - "CMakeLists.txt" | |
| - "cmake/**" | |
| - "pyproject.toml" | |
| - "scripts/ci/**" | |
| - ".github/workflows/python-wheels.yml" | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CIBW_PIN: "cibuildwheel>=4,<5" | |
| jobs: | |
| wheel-linux: | |
| runs-on: ubuntu-latest | |
| env: | |
| HF_TOKEN: ${{ secrets.HF_TOKEN }} | |
| # Container path of the toolchain cache volume (see CIBW_CONTAINER_ENGINE). | |
| VK_TOOLCHAIN_CACHE: /vk-toolchain-cache | |
| 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: Cache the source-built Vulkan toolchain | |
| uses: actions/cache@v4 | |
| with: | |
| path: vk-toolchain-cache | |
| key: vk-toolchain-${{ hashFiles('scripts/ci/manylinux-vulkan-toolchain.sh') }} | |
| - name: Canary model cache (avoid HF rate limits — 429s were real) | |
| if: env.HF_TOKEN != '' | |
| uses: actions/cache@v4 | |
| with: | |
| path: canary | |
| key: canary-models-v1 | |
| - name: Fetch canary models (cache miss only; container sees /project) | |
| if: env.HF_TOKEN != '' | |
| run: | | |
| [ -f canary/whisper-tiny-Q5_K_M.gguf ] || \ | |
| uvx --from huggingface_hub hf download handy-computer/whisper-tiny-gguf \ | |
| whisper-tiny-Q5_K_M.gguf --local-dir canary | |
| [ -f canary/moonshine-streaming-tiny-Q8_0.gguf ] || \ | |
| uvx --from huggingface_hub hf download handy-computer/moonshine-streaming-tiny-gguf \ | |
| moonshine-streaming-tiny-Q8_0.gguf --local-dir canary | |
| # cibuildwheel bind-mounts the project at /project inside the | |
| # manylinux container; these paths resolve there. | |
| echo "TRANSCRIBE_SMOKE_MODEL=/project/canary/whisper-tiny-Q5_K_M.gguf" >> "$GITHUB_ENV" | |
| echo "TRANSCRIBE_SMOKE_STREAMING_MODEL=/project/canary/moonshine-streaming-tiny-Q8_0.gguf" >> "$GITHUB_ENV" | |
| - name: Build, repair, and test the wheel (cibuildwheel) | |
| env: | |
| CIBW_CONTAINER_ENGINE: "docker; create_args: --volume ${{ github.workspace }}/vk-toolchain-cache:/vk-toolchain-cache" | |
| run: | | |
| mkdir -p vk-toolchain-cache | |
| uvx --from "$CIBW_PIN" cibuildwheel --output-dir wheelhouse | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist-native-linux-x86_64 | |
| path: wheelhouse/*.whl | |
| wheel-macos: | |
| # Bare-metal M4 Mac mini (self-hosted, runner "m4-mini"): builds the | |
| # SHIPPING macOS wheel and proves Metal COMPUTE on it in one lane — the | |
| # artifact that ships is the artifact tested on a real GPU. VM-based | |
| # macOS CI cannot do this: the probe proved (2026-06-11) that Apple's | |
| # Virtualization.framework paravirtual GPU decodes garbage on both | |
| # GitHub-hosted and Blacksmith M4 VMs (the former Blacksmith lane was | |
| # retired 2026-06-11; flip runs-on back to a hosted label if the mini's | |
| # availability ever becomes a problem). Notes: cibuildwheel needs a | |
| # python.org CPython framework on the runner (installed once, manually — | |
| # the runner service cannot sudo); the Apple-clang CC/CXX pin in | |
| # pyproject [tool.cibuildwheel.macos] is harmless here. | |
| runs-on: [self-hosted, macOS, ARM64] | |
| timeout-minutes: 45 # a sleeping/offline mini must not hang the run | |
| env: | |
| HF_TOKEN: ${{ secrets.HF_TOKEN }} | |
| 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: Canary model cache (avoid HF rate limits) | |
| if: env.HF_TOKEN != '' | |
| uses: actions/cache@v4 | |
| with: | |
| path: canary | |
| key: canary-models-v1 | |
| - name: Fetch canary models (cache miss only) | |
| if: env.HF_TOKEN != '' | |
| run: | | |
| [ -f canary/whisper-tiny-Q5_K_M.gguf ] || \ | |
| uvx --from huggingface_hub hf download handy-computer/whisper-tiny-gguf \ | |
| whisper-tiny-Q5_K_M.gguf --local-dir canary | |
| [ -f canary/moonshine-streaming-tiny-Q8_0.gguf ] || \ | |
| uvx --from huggingface_hub hf download handy-computer/moonshine-streaming-tiny-gguf \ | |
| moonshine-streaming-tiny-Q8_0.gguf --local-dir canary | |
| echo "TRANSCRIBE_SMOKE_MODEL=$PWD/canary/whisper-tiny-Q5_K_M.gguf" >> "$GITHUB_ENV" | |
| echo "TRANSCRIBE_SMOKE_STREAMING_MODEL=$PWD/canary/moonshine-streaming-tiny-Q8_0.gguf" >> "$GITHUB_ENV" | |
| - name: Build, repair, and test the wheel on real Metal (cibuildwheel) | |
| run: uvx --from "$CIBW_PIN" cibuildwheel --output-dir wheelhouse | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist-native-macos-arm64 | |
| path: wheelhouse/*.whl | |
| wheel-macos-x86: | |
| # Intel macOS (GitHub macos-13 = x86_64): the CPU-ONLY x86 wheel. No Metal | |
| # — Intel-Mac GPUs are out of scope (no fleet hardware to validate Metal | |
| # compute, and the hosted runner is the same paravirtual-GPU VM whose Metal | |
| # compute is broken); Metal / tuned CPU on Intel is served by the sdist. | |
| # The smoke is CPU-steered (TRANSCRIBE_SMOKE_BACKEND=cpu): wheel_smoke.py | |
| # asserts the artifact exposes only CPU (no metal/vulkan/cuda) and runs the | |
| # full suite on CPU. The arm64/Metal lane is `wheel-macos`. cibuildwheel | |
| # provides its own CPython here, so unlike the self-hosted mini there is no | |
| # manual runtime setup. Overrides the pyproject macos defaults (arm64/metal) | |
| # via CIBW_ARCHS_MACOS / CIBW_ENVIRONMENT_MACOS. | |
| runs-on: macos-13 | |
| timeout-minutes: 45 | |
| env: | |
| HF_TOKEN: ${{ secrets.HF_TOKEN }} | |
| CIBW_ARCHS_MACOS: x86_64 | |
| CIBW_ENVIRONMENT_MACOS: >- | |
| TRANSCRIBE_WHEEL_LANE=cpu | |
| TRANSCRIBE_SMOKE_BACKEND=cpu | |
| MACOSX_DEPLOYMENT_TARGET=11.0 | |
| CC=/usr/bin/clang | |
| CXX=/usr/bin/clang++ | |
| 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: Canary model cache (avoid HF rate limits) | |
| if: env.HF_TOKEN != '' | |
| uses: actions/cache@v4 | |
| with: | |
| path: canary | |
| key: canary-models-v1 | |
| - name: Fetch canary models (cache miss only) | |
| if: env.HF_TOKEN != '' | |
| run: | | |
| [ -f canary/whisper-tiny-Q5_K_M.gguf ] || \ | |
| uvx --from huggingface_hub hf download handy-computer/whisper-tiny-gguf \ | |
| whisper-tiny-Q5_K_M.gguf --local-dir canary | |
| [ -f canary/moonshine-streaming-tiny-Q8_0.gguf ] || \ | |
| uvx --from huggingface_hub hf download handy-computer/moonshine-streaming-tiny-gguf \ | |
| moonshine-streaming-tiny-Q8_0.gguf --local-dir canary | |
| echo "TRANSCRIBE_SMOKE_MODEL=$PWD/canary/whisper-tiny-Q5_K_M.gguf" >> "$GITHUB_ENV" | |
| echo "TRANSCRIBE_SMOKE_STREAMING_MODEL=$PWD/canary/moonshine-streaming-tiny-Q8_0.gguf" >> "$GITHUB_ENV" | |
| - name: Build, repair, and test the CPU-only wheel (cibuildwheel) | |
| run: uvx --from "$CIBW_PIN" cibuildwheel --output-dir wheelhouse | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist-native-macos-x86_64 | |
| path: wheelhouse/*.whl | |
| wheel-windows: | |
| runs-on: windows-latest | |
| env: | |
| HF_TOKEN: ${{ secrets.HF_TOKEN }} | |
| # LunarG prunes old SDK downloads — when bumping, verify the URL exists. | |
| VULKAN_VERSION: "1.4.350.0" | |
| # The hf CLI prints ✓ marks; Windows' default cp1252 console codec | |
| # chokes on them (charmap codec error). Force UTF-8 for all Python. | |
| PYTHONUTF8: "1" | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # vcvars for the whole job: the Ninja generator (CMAKE_GENERATOR in | |
| # pyproject [tool.cibuildwheel.windows]) needs cl.exe on PATH — without | |
| # it CMake silently picks the runner image's MinGW gcc (observed: | |
| # gcc-flavored M_PI errors). The VS generator didn't need this but is | |
| # ~3x slower on this lane. | |
| - uses: ilammy/msvc-dev-cmd@v1 | |
| with: | |
| arch: x64 | |
| - name: Install uv | |
| run: | | |
| powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" | |
| Add-Content $env:GITHUB_PATH "$env:USERPROFILE\.local\bin" | |
| - name: Install Vulkan SDK ${{ env.VULKAN_VERSION }} | |
| run: | | |
| curl.exe -o "$env:RUNNER_TEMP\vulkan_sdk.exe" -L "https://sdk.lunarg.com/sdk/download/${env:VULKAN_VERSION}/windows/vulkan_sdk.exe" | |
| & "$env:RUNNER_TEMP\vulkan_sdk.exe" --accept-licenses --default-answer --confirm-command install | |
| Add-Content $env:GITHUB_ENV "VULKAN_SDK=C:\VulkanSDK\${env:VULKAN_VERSION}" | |
| Add-Content $env:GITHUB_PATH "C:\VulkanSDK\${env:VULKAN_VERSION}\bin" | |
| - name: Canary model cache (avoid HF rate limits) | |
| if: env.HF_TOKEN != '' | |
| uses: actions/cache@v4 | |
| with: | |
| path: canary | |
| key: canary-models-v1 | |
| - name: Fetch canary models (cache miss only) | |
| if: env.HF_TOKEN != '' | |
| shell: bash | |
| run: | | |
| [ -f canary/whisper-tiny-Q5_K_M.gguf ] || \ | |
| uvx --from huggingface_hub hf download handy-computer/whisper-tiny-gguf \ | |
| whisper-tiny-Q5_K_M.gguf --local-dir canary | |
| [ -f canary/moonshine-streaming-tiny-Q8_0.gguf ] || \ | |
| uvx --from huggingface_hub hf download handy-computer/moonshine-streaming-tiny-gguf \ | |
| moonshine-streaming-tiny-Q8_0.gguf --local-dir canary | |
| # GITHUB_WORKSPACE keeps the Windows-style path Python can open | |
| # (bash's $PWD here would be an MSYS path). | |
| echo "TRANSCRIBE_SMOKE_MODEL=$GITHUB_WORKSPACE/canary/whisper-tiny-Q5_K_M.gguf" >> "$GITHUB_ENV" | |
| echo "TRANSCRIBE_SMOKE_STREAMING_MODEL=$GITHUB_WORKSPACE/canary/moonshine-streaming-tiny-Q8_0.gguf" >> "$GITHUB_ENV" | |
| - name: Install zlib (vcpkg, static — libtranscribe requires it) | |
| # Static (-static-md: static lib, dynamic CRT) so no zlib1.dll exists | |
| # at runtime and delvewheel has nothing to vendor for it. The | |
| # toolchain file reaches scikit-build-core via CMAKE_ARGS. | |
| run: | | |
| vcpkg install zlib:x64-windows-static-md | |
| # Forward slashes: CMAKE_ARGS values pass through scikit-build-core's | |
| # CMakeInit.txt cache file, where backslashes are eaten as escapes. | |
| $tc = "$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -replace '\\','/' | |
| Add-Content $env:GITHUB_ENV "CMAKE_ARGS=-DCMAKE_TOOLCHAIN_FILE=$tc -DVCPKG_TARGET_TRIPLET=x64-windows-static-md" | |
| - name: Build, repair, and test the wheel (cibuildwheel) | |
| run: uvx --from "$env:CIBW_PIN" cibuildwheel --output-dir wheelhouse | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist-native-windows-amd64 | |
| path: wheelhouse/*.whl | |
| sdist: | |
| # The universal fallback must actually work: build the sdist, audit what | |
| # went into it, then compile-and-transcribe from the tarball alone. | |
| runs-on: ubuntu-latest | |
| env: | |
| HF_TOKEN: ${{ secrets.HF_TOKEN }} | |
| 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: Canary model cache (avoid HF rate limits) | |
| if: env.HF_TOKEN != '' | |
| uses: actions/cache@v4 | |
| with: | |
| path: canary | |
| key: canary-models-v1 | |
| - name: Fetch canary models (cache miss only) | |
| if: env.HF_TOKEN != '' | |
| run: | | |
| [ -f canary/whisper-tiny-Q5_K_M.gguf ] || \ | |
| uvx --from huggingface_hub hf download handy-computer/whisper-tiny-gguf \ | |
| whisper-tiny-Q5_K_M.gguf --local-dir canary | |
| [ -f canary/moonshine-streaming-tiny-Q8_0.gguf ] || \ | |
| uvx --from huggingface_hub hf download handy-computer/moonshine-streaming-tiny-gguf \ | |
| moonshine-streaming-tiny-Q8_0.gguf --local-dir canary | |
| echo "TRANSCRIBE_SMOKE_MODEL=$PWD/canary/whisper-tiny-Q5_K_M.gguf" >> "$GITHUB_ENV" | |
| echo "TRANSCRIBE_SMOKE_STREAMING_MODEL=$PWD/canary/moonshine-streaming-tiny-Q8_0.gguf" >> "$GITHUB_ENV" | |
| - name: Build sdist | |
| run: uv build --sdist | |
| - name: Audit sdist contents | |
| run: | | |
| python3 - <<'EOF' | |
| import glob, tarfile | |
| [path] = glob.glob("dist/*.tar.gz") | |
| with tarfile.open(path) as tf: | |
| names = tf.getnames() | |
| total = sum(m.size for m in tf.getmembers()) | |
| print(f"{path}: {len(names)} files, {total/1e6:.1f} MB uncompressed") | |
| # Weights by extension anywhere; heavy repo dirs at the ROOT only | |
| # (docs/models/*.md is documentation and belongs in the sdist). | |
| ROOT_BANNED = {"models", "dumps", "reports", "canary", "wheelhouse"} | |
| banned = [n for n in names if n.endswith((".gguf", ".safetensors", ".bin")) | |
| or (len(n.split("/")) > 1 and n.split("/")[1] in ROOT_BANNED)] | |
| assert not banned, f"sdist contains artifacts that must never ship: {banned[:10]}" | |
| assert any(n.endswith("src/arch/canary/model.cpp") for n in names), \ | |
| "sdist excludes over-matched (root-anchoring regression)" | |
| assert total < 40e6, f"sdist unexpectedly large: {total/1e6:.1f} MB (28 MB expected)" | |
| for required in ("CMakeLists.txt", "include/transcribe.h", | |
| "ggml/CMakeLists.txt", "cmake/python-wheel-install.cmake", | |
| "bindings/python-native/_contract.py.in"): | |
| assert any(n.endswith(required) for n in names), f"missing {required}" | |
| print("sdist audit ok") | |
| EOF | |
| - name: Compile + transcribe from the sdist (no repo sources) | |
| run: | | |
| sudo apt-get update && sudo apt-get install -y zlib1g-dev | |
| uv venv --seed sdist-venv --python 3.12 | |
| # The tarball is the only native source here: pip drives the full | |
| # scikit-build-core compile (cmake/ninja arrive as build deps). | |
| ./sdist-venv/bin/pip install dist/*.tar.gz | |
| ./sdist-venv/bin/pip install pytest numpy huggingface_hub | |
| ./sdist-venv/bin/python scripts/ci/wheel_smoke.py "$PWD" | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist-sdist | |
| path: dist/*.tar.gz | |
| api-wheel: | |
| 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: Build transcribe-cpp (pure wheel + sdist) | |
| run: | | |
| cd bindings/python | |
| rm -rf dist && uv build | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist-api | |
| path: bindings/python/dist/* | |
| # Test audio for the checkout-free clean-install job. | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: smoke-assets | |
| path: samples/jfk.wav | |
| cuda-wheel-modal: | |
| # The cu12 provider is built and smoked on Modal, not on CI runners: the | |
| # manylinux_2_28 + CUDA-toolkit environment is a cached Modal image | |
| # layer, the 16-core build bills by the second, and the flow ends with a | |
| # REAL T4 runtime smoke (scripts/ci/modal_cuda_build.py). This job is | |
| # just the 2-vCPU driver. Gated on the Modal token secrets — absent on | |
| # forks, steps no-op cleanly. | |
| runs-on: ubuntu-latest | |
| env: | |
| MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }} | |
| MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }} | |
| HF_TOKEN: ${{ secrets.HF_TOKEN }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Build, repair, packaging-smoke, and T4 runtime smoke (Modal) | |
| if: env.MODAL_TOKEN_ID != '' | |
| run: | | |
| pip install -q modal | |
| modal run scripts/ci/modal_cuda_build.py | |
| - name: Fetch the repaired wheel from the Modal volume | |
| if: env.MODAL_TOKEN_ID != '' | |
| run: | | |
| mkdir -p wheelhouse-cu12 | |
| # Root-path download is recursive (glob patterns are deprecated in | |
| # the modal CLI); assets/ comes along but the artifact glob below | |
| # only picks the wheels. | |
| modal volume get transcribe-cu12-wheelhouse / wheelhouse-cu12/ --force | |
| ls -la wheelhouse-cu12/ | |
| # Named OUTSIDE the dist-* pattern on purpose: co-import merges dist-* | |
| # and must not pick up the cu12 provider (it would outrank the default | |
| # provider and change what that job tests). | |
| - uses: actions/upload-artifact@v4 | |
| if: env.MODAL_TOKEN_ID != '' | |
| with: | |
| name: cuda-dist-linux-x86_64 | |
| path: wheelhouse-cu12/*.whl | |
| clean-install: | |
| # Punch-list "clean machine": a bare python:3.12-slim container — no | |
| # compilers, no repo checkout, no env vars. Installs ONLY the built | |
| # wheels (resolver pulls the native provider through the hard pin) and | |
| # transcribes. This is also the shipped-artifact proof of the | |
| # Vulkan-by-default degradation contract: | |
| # tier A (bare container, no Vulkan loader): the bundled module stays | |
| # quietly unloaded — CPU transcribes, vulkan answers unavailable, | |
| # backend="vulkan" raises a clean error. | |
| # tier B (apt install loader + lavapipe): the SAME installed wheel | |
| # discovers a Vulkan device and transcribes on it. | |
| needs: [wheel-linux, api-wheel] | |
| runs-on: ubuntu-latest | |
| container: python:3.12-slim | |
| env: | |
| HF_TOKEN: ${{ secrets.HF_TOKEN }} | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: dist-native-linux-x86_64 | |
| path: wheelhouse | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: dist-api | |
| path: wheelhouse | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: smoke-assets | |
| path: assets | |
| - name: Canary model cache (avoid HF rate limits) | |
| if: env.HF_TOKEN != '' | |
| uses: actions/cache@v4 | |
| with: | |
| path: canary | |
| key: canary-models-v1 | |
| - name: Fetch canary model (cache miss only) | |
| if: env.HF_TOKEN != '' | |
| run: | | |
| pip install -q huggingface_hub | |
| [ -f canary/whisper-tiny-Q5_K_M.gguf ] || \ | |
| hf download handy-computer/whisper-tiny-gguf \ | |
| whisper-tiny-Q5_K_M.gguf --local-dir canary | |
| - name: Install from the built wheels only (no index) | |
| run: pip install --no-index --find-links wheelhouse transcribe-cpp | |
| - name: "Tier A: bare machine — CPU transcribes, Vulkan quietly absent" | |
| run: | | |
| python - <<'EOF' | |
| import array, os, wave | |
| import transcribe_cpp as t | |
| from transcribe_cpp import errors | |
| devs = [(d.name, d.kind) for d in t.backends()] | |
| print("devices (bare):", devs) | |
| assert t.native_provider() == "transcribe-cpp-native" | |
| assert t.backend_available("cpu") | |
| assert not t.backend_available("vulkan"), devs | |
| if not os.path.exists("canary/whisper-tiny-Q5_K_M.gguf"): | |
| print("!! no canary (fork without HF_TOKEN) — install-only check") | |
| raise SystemExit(0) | |
| # An explicit vulkan request on a machine without a loader must be | |
| # a clean Python exception, not a crash. | |
| try: | |
| t.Model("canary/whisper-tiny-Q5_K_M.gguf", backend="vulkan") | |
| except errors.TranscribeError as e: | |
| print("vulkan request correctly rejected:", e) | |
| else: | |
| raise AssertionError("backend='vulkan' must fail without a loader") | |
| with wave.open("assets/jfk.wav", "rb") as w: | |
| pcm16 = array.array("h"); pcm16.frombytes(w.readframes(w.getnframes())) | |
| pcm = array.array("f", (s / 32768.0 for s in pcm16)) | |
| with t.Model("canary/whisper-tiny-Q5_K_M.gguf") as m, m.session() as s: | |
| text = s.run(pcm).text | |
| print("text (cpu):", text.strip()) | |
| assert "country" in text.lower(), text | |
| print("ok: clean machine, one pip install, transcribed on CPU") | |
| EOF | |
| - name: "Tier B: install a Vulkan loader + lavapipe — same wheel uses it" | |
| if: env.HF_TOKEN != '' | |
| run: | | |
| apt-get update -q && apt-get install -y -q libvulkan1 mesa-vulkan-drivers | |
| # lavapipe is a CPU-type Vulkan device; ggml's default filter only | |
| # admits real GPUs (CI-only override, real GPUs need none). | |
| export GGML_VK_VISIBLE_DEVICES=0 | |
| python - <<'EOF' | |
| import array, wave | |
| import transcribe_cpp as t | |
| devs = [(d.name, d.kind) for d in t.backends()] | |
| print("devices (lavapipe):", devs) | |
| assert t.backend_available("vulkan"), devs | |
| with wave.open("assets/jfk.wav", "rb") as w: | |
| pcm16 = array.array("h"); pcm16.frombytes(w.readframes(w.getnframes())) | |
| pcm = array.array("f", (s / 32768.0 for s in pcm16)) | |
| with t.Model("canary/whisper-tiny-Q5_K_M.gguf", backend="vulkan") as m: | |
| print("model backend:", m.backend) | |
| with m.session() as s: | |
| text = s.run(pcm).text | |
| print("text (vulkan):", text.strip()) | |
| assert "country" in text.lower(), text | |
| print("ok: the same installed wheel transcribed on the Vulkan device") | |
| EOF | |
| co-import: | |
| # numpy/torch coexistence (the reason the wheels vendor no OpenMP/BLAS), | |
| # proven by real transcriptions in both import orders. Also a de-facto | |
| # clean-install test: pip resolves transcribe-cpp + its native pin purely | |
| # from the built artifacts. | |
| name: co-import (${{ matrix.label }}) | |
| needs: [wheel-linux, wheel-macos, wheel-windows, api-wheel] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - label: ubuntu-latest | |
| runner: ubuntu-latest | |
| torch-args: "--index-url https://download.pytorch.org/whl/cpu" | |
| # macOS arm64 runs on the bare-metal M4 mini, not a GitHub VM: the | |
| # transcription here exercises the REAL shipping Metal path alongside | |
| # numpy/torch, and avoids the broken "Apple Paravirtual device" Metal | |
| # that GitHub-hosted macos runners expose (it decodes garbage — the | |
| # exact reason wheel-macos is on the mini too). | |
| - label: macos-arm64 | |
| runner: [self-hosted, macOS, ARM64] | |
| torch-args: "" | |
| - label: windows-latest | |
| runner: windows-latest | |
| torch-args: "" | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 45 # bound the run if the self-hosted mini is offline | |
| env: | |
| HF_TOKEN: ${{ secrets.HF_TOKEN }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| pattern: dist-* | |
| merge-multiple: true | |
| path: wheelhouse | |
| - name: Install the built artifacts (resolver picks the platform wheel) | |
| run: | | |
| pip install --no-index --find-links wheelhouse transcribe-cpp | |
| pip install numpy huggingface_hub | |
| - name: Canary model cache (avoid HF rate limits) | |
| if: env.HF_TOKEN != '' | |
| uses: actions/cache@v4 | |
| with: | |
| path: canary | |
| key: canary-models-v1 | |
| - name: Fetch canary model (cache miss only) | |
| if: env.HF_TOKEN != '' | |
| shell: bash | |
| run: | | |
| [ -f canary/whisper-tiny-Q5_K_M.gguf ] || \ | |
| hf download handy-computer/whisper-tiny-gguf \ | |
| whisper-tiny-Q5_K_M.gguf --local-dir canary | |
| echo "TRANSCRIBE_SMOKE_MODEL=$GITHUB_WORKSPACE/canary/whisper-tiny-Q5_K_M.gguf" >> "$GITHUB_ENV" | |
| - name: numpy co-import smoke | |
| run: python scripts/ci/co_import_smoke.py numpy . | |
| - name: torch co-import smoke | |
| run: | | |
| pip install torch ${{ matrix.torch-args }} | |
| python scripts/ci/co_import_smoke.py torch . |