Skip to content

rust bindings#29

Merged
cjpais merged 36 commits into
mainfrom
rust-bindings
Jun 15, 2026
Merged

rust bindings#29
cjpais merged 36 commits into
mainfrom
rust-bindings

Conversation

@cjpais

@cjpais cjpais commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

No description provided.

cjpais and others added 30 commits June 13, 2026 14:15
…ples

Finish the per-PR gates for the Rust binding and add the two postures and
the canonical example set, all on the shared CI rails.

M4 (conformance CI + Windows):
- rust-ci.yml wires fetch-canary + `cargo test -p transcribe-cpp`, so the
  model-gated tests actually run (two tiers per requirements §4: no-model
  always, incl. forks; model tier gated on HF_TOKEN/canary, skips cleanly).
- Add the Windows/MSVC matrix leg — first cargo-driven MSVC build of this
  tree (msvc-dev-cmd + Ninja + vcpkg zlib:x64-windows-static-md +
  CMAKE_PREFIX_PATH; dynamic CRT, matching rustc's /MD). ccache launcher
  made non-Windows-only.
- cmake/transcribe-install.cmake: complete the provisional Windows static
  zlib translation — stage the resolved zlib.lib into the lib dir and link
  it by name (vcpkg spells it `zlib`, not `z`); MSVC auto-links the CRT.

M5 (dylib posture — never exercised before; M1–M4 are all static):
- sys/build.rs: guard the shared rpath to non-Windows; new
  transcribe-cpp/build.rs re-emits it for the safe crate's own tests/examples
  (cargo:rustc-link-arg does not propagate from a dependency's build script).
  On Windows (no rpath) the -sys build.rs stages transcribe.dll + the ggml
  DLLs next to each cargo artifact (deps/, examples/, profile root).
- Posture matrix {static, dylib} × {linux, macos, windows}; new
  tests/degradation.rs asserts the CPU floor / clean unavailable-backend
  probe / init_backends in both postures (requirements §5 request path).

M6 (examples): the five canonical CI-executed examples — transcribe-file,
streaming, batch, backend-select, error-handling — plus examples/common,
run on every matrix leg under the same canary skip rules as the model tests.

The Windows legs prove on first push (no MSVC available locally); every
other leg + the full gate suite is green locally on macOS/Metal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lease)

Wire the crates.io distribution path, mirroring the Python publish flow.

- publish.yml gains two jobs:
  - rust-rehearsal (workflow_dispatch): `cargo publish --dry-run` the sys
    crate (full verify build) + run the packed-crate smoke. crates.io has no
    TestPyPI analog, so this is the rehearsal — dry-run + shipped-artifact
    test, no upload.
  - rust-release (tag v*): publish transcribe-cpp-sys first, then
    transcribe-cpp, from CI via the `crates-io` environment (approval gate),
    never a laptop (requirements §5).
- scripts/ci/rust_packed_smoke.py — the "test the shipped artifact" gate
  (requirements §4): packs the sys crate (which carries the whole C++ tree),
  and builds libtranscribe from NOTHING but the packed tarball's vendored
  sources, then transcribes the canary through the safe API. The safe crate
  is staged + redirected via [patch.crates-io] because it can't be packaged
  pre-publish (its sys dep `^0.0.1` isn't satisfied by the 0.0.0 placeholder;
  sys-first ordering is inherent).
- READMEs: install one-liner, build prerequisites (CMake + zlib; vcpkg on
  Windows), and the quickstart/examples pointer.

Verified locally on macOS: dry-run (sys, full verify) and the packed smoke
both green — built from the packed tarball and transcribed the canary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The first Windows CI run got the MSVC native build green on the first try,
but windows-static failed linking the test exe with
`LNK1181: cannot open input file 'zlib.lib'`.

Root cause: the install manifest's `_transcribe_zlib_archive` helper looked
up `ZLIB::ZLIB` / `ZLIB_LIBRARY` at the top-level scope where
cmake/transcribe-install.cmake runs, but find_package(ZLIB) runs in
src/CMakeLists.txt — and its imported target is subdirectory-local while
ZLIB_LIBRARY is a normal (scope-local) var. Neither is visible at top level,
so staging silently no-op'd and the manifest fell through to a bare `zlib`
system-lib that the linker can't find.

Fix: src/CMakeLists.txt captures the resolved archive (ZLIB_LIBRARY_RELEASE,
falling back to ZLIB_LIBRARY) into a TRANSCRIBE_ZLIB_ARCHIVE cache var while
still in find_package's scope; install.cmake prefers that. WIN32-guarded, so
linux/macos manifests are unchanged (verified: macOS build green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tic CI

The first Windows CI runs got the MSVC build green and the zlib fix landed;
two more link-level issues then surfaced across postures:

1. OpenMP (linux-static, and a stray /fopenmp on Windows): TRANSCRIBE_USE_OPENMP
   defaults ON and auto-detects, so CI builds enabled it — but its `-fopenmp`
   appears only as a manifest link_flag, i.e. a `cargo:rustc-link-arg`, which
   does NOT propagate to the safe crate's test/example binaries. Static links
   then failed with undefined GOMP_*/omp_* symbols (dylib hid it: OpenMP links
   into the shared lib). Fix: build.rs forces TRANSCRIBE_USE_OPENMP=OFF unless
   `--features openmp` opts in — a self-contained static default, deterministic
   across runners.

2. advapi32 (windows-static): ggml-cpu reads the registry for CPU feature
   detection (RegOpenKeyEx/RegCloseKey) but ggml's CMake never links advapi32
   (it rides MSVC default-lib auto-linking, which the manifest reconstruction
   loses). Add advapi32 to the Windows static system_libs.

macOS unaffected (it wasn't using OpenMP; advapi32 is WIN32-only) — local
build + safe-crate tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cancel test crashes on Windows x86 with STATUS_INTEGER_DIVIDE_BY_ZERO in
whisper's long-form abort path — a native integer ÷0 that faults on x86 but is
masked on arm64 (returns 0) and only triggers when the abort lands in a narrow
compute window (so a fast machine + UBSan never reproduce it). To get the exact
file:line:

- tests/whisper_e2e_smoke.cpp: opt-in TRANSCRIBE_DIAG_CANCEL harness — a
  threaded wall-clock cancel of a long-form run, swept over delays and repeated.
  No-op unless the env var is set (normal/native-ci runs are unaffected).
- .github/workflows/diag-cancel.yml: workflow_dispatch-only job that builds the
  e2e test with RelWithDebInfo and runs the harness under gdb (Linux) / cdb
  (Windows) to print the faulting backtrace with source lines. The debugger
  slowdown also widens the timing window, making the fault reliable.

Both are diagnostic scaffolding — remove once the bug is fixed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fault branch)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
TRANSCRIBE_WHISPER_THOLD_DISABLED / _LOGPROB_DISABLED defined ±INF as
((float)(1.0/0.0)) / ((float)(-1.0/0.0)). clang and gcc fold that to IEEE
infinity, but MSVC hard-errors on a constant divide-by-zero (C2124), so the
public header failed to compile in any MSVC C++ translation unit that touched
the sentinels (surfaced building the C++ whisper test on Windows for the first
time). Use INFINITY from <math.h> instead — same value, no divide.

FFI-neutral: the value is unchanged (+/-inf), so the bindgen drift gate and the
abihash are both untouched (verified: `cargo xtask bindgen --check` and
`generate.py --check` stay green; these float macros aren't in the hash body).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 1600-run sweep timed out the linux diag leg (each run recomputes the full
mel of 132s audio). Drop to 6x jfk (~66s, still long-form) and a small delay
sweep around the ~40ms danger point; the debugger quits on the first crash.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + dllimport (F)

The cancel conformance test tiled jfk 12x to be mid-flight, which pushed it into
whisper's long-form path — and cancelling a long-form run hits a native integer
divide-by-zero on x86 (masked on arm64, timing-gated so only the slow Windows CI
runner faults reliably). That is a pre-existing whisper-compute bug, not a
binding bug: the Rust layer maps the abort correctly.

Decouple the binding from it: tile only 2x (~22s, still short-form / one whisper
window) so the test exercises the cancellation contract (CancelToken ->
Error::Aborted + partial) without entering the long-form abort path. Same
contract, no native ÷0.

File both bugs found during the Windows bringup in docs/known-issues.md with
repro detail:
  - B: the long-form-cancel native ÷0 (localized to whisper's abort path; exact
    line needs an x86 debugger backtrace — the env-gated TRANSCRIBE_DIAG_CANCEL
    harness in tests/whisper_e2e_smoke.cpp reproduces it).
  - F: transcribe.h forces __declspec(dllimport) on Windows, breaking static C++
    consumers (LNK2019 __imp_*); the Rust/Python bindings are unaffected.

Also drops the temporary diag-cancel.yml workflow (its job is done; the harness
+ docs/known-issues.md are the durable repro).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…; correct B filing

The short-form (22s) cancel still crashed on Windows with
STATUS_INTEGER_DIVIDE_BY_ZERO, so B is NOT long-form-specific — it is reached by
any in-flight whisper cancel, and it is Windows/MSVC-specific: the same abort
passes on linux x86 (rust-build linux-dylib) and is masked on arm64. So it is an
MSVC-codegen issue (most likely an uninitialized divisor the abort path skips
initializing), not a generic-x86 or long-form bug.

The previous short-form decouple was therefore wrong. Skip
cross_thread_cancel_of_in_flight_run on Windows instead (cfg!(target_os =
"windows")); the cancellation contract stays covered on linux/macos, and the
uncancelled-run test runs everywhere (normal whisper transcription works on
Windows). docs/known-issues.md "B" updated with the corrected scope + repro.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-installed ÷0)

With cross_thread skipped, the cancel binary still crashed — and the log shows
`cross_thread ... ok` then the process dies, so the crasher is
uncancelled_run_is_not_aborted: a NORMAL run with a cancel token merely
installed (never fired). So B's trigger is the abort callback being present, not
the abort firing — Windows/MSVC only (it passes on linux x86, masked on arm64).

Skip uncancelled on Windows too, and guard the error-handling example's
cancellation demo on Windows (it installs a token). This also lets the rest of
the Windows conformance (transcribe/streaming) and the examples run for the
first time — normal token-less transcription is expected to work (the Python
wheels transcribe on Windows). docs/known-issues.md "B" updated to the
token-installed scope.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…consumers (F)

A C++ TU that includes transcribe.h and links the static transcribe.lib on
MSVC failed to link (LNK2019 __imp_transcribe_*): the header forced
__declspec(dllimport) on every Windows consumer, with no way to say "I'm
linking the static archive." Add a TRANSCRIBE_STATIC branch (empty decoration)
and have the static `transcribe` CMake target propagate it PUBLIC, so in-tree
C++ tests/examples linking the archive resolve the plain symbols. The
bindgen/Python FFIs carry no __declspec and are unaffected; ABI/bindgen-neutral
(the generator parses with _WIN32 undefined). Unblocks the C++ whisper e2e test
on MSVC, which the "B" int-divide diagnostic needs. Closes known-issues "F".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Windows/MSVC integer divide-by-zero (0xC0000094) faults ANY whisper run,
not just a cancelled one (the alphabetically-first cancel test merely masked
that it crashes before any run completes). None of our source integer divisions
can be zero (all config-derived or guarded), so the ÷0 is in ggml, reached by
our graph on MSVC Release codegen — needs a faulting backtrace to localize.

- whisper_e2e_smoke.cpp: TRANSCRIBE_DIAG_FAULT_TRACE installs a vectored
  exception handler that symbolizes the faulting IP + full stack via DbgHelp.
  Self-contained: needs no cdb on the runner, only the Release+/Z7 PDB.
- diag-b.yml: push-triggered, path-scoped. Builds the e2e test in the SAME
  codegen as the failing cargo build (CMAKE_BUILD_TYPE=Release, matching the
  -sys build.rs cfg.profile("Release")) plus /Z7 + linker /DEBUG for symbols,
  then runs a PLAIN transcription with the tracer (and under cdb if present).

Diagnostic scaffolding — remove (with the dbghelp link) once "B" is fixed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ths)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… ÷0)

Root cause (caught by the diag-b backtrace, not the prior cancellation theory):
the fault is ggml_time_us+0x24 at ggml/src/ggml.c:532, in whisper_load's
`t_load_start = ggml_time_us()` during MODEL LOAD — not the run/abort path. On
MSVC ggml_time_us divides QueryPerformanceCounter by a global frequency that
ggml_time_init() fills in; ggml_init() calls it, but every family times its own
load before any ggml context exists, so the divisor is still 0 ->
STATUS_INTEGER_DIVIDE_BY_ZERO (0xC0000094). POSIX ggml_time_* read clock_gettime
directly (no divisor) so it is silently fine there, and arm64 masks integer ÷0
as 0 — exactly the observed platform signature. So EVERY whisper run faulted at
load on Windows; the cancel test only looked guilty for being alphabetically
first.

Fix: call ggml_time_init() exactly once (std::call_once) at the first public
entry point — transcribe_model_load_file and transcribe_init_backends — as
ggml.h instructs ("call this once at the beginning of the program"). timer_freq
is process-global, so one init covers all later mel/encode/decode timing.

diag-b: add src/transcribe.cpp to the path filter so this re-runs the e2e test
with the fix (validation before the diagnostic is removed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ggml_time_init fix removed the load-time ÷0 (the diag run shows no fault),
but Windows transcription then hangs — rust-ci's cargo-test step ran 40+ min on
a 39s (cached) build, and the C++ e2e likewise. Linux runs the same threading
(whisper never sets n_threads -> ggml default 4 on 2 vcpus) without hanging, so
this is a Windows-specific deadlock, not oversubscription. cdb is absent on the
runner, so add a self-contained watchdog (TRANSCRIBE_DIAG_WATCHDOG_SEC=N): after
N s it suspends + StackWalks every thread and symbolizes via DbgHelp, so the
stuck stack is captured in one fast (cached-build) round-trip. diag-b drops the
dead cdb step and runs a 60s watchdog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on Windows

With the load-time ÷0 fixed, Windows whisper transcription HUNG: a watchdog
thread-dump showed all CPU worker threads wedged in ggml's custom spin-barrier
(ggml_barrier, ggml-cpu.c:580) while the main thread waited inside mul_mat's
barrier — a deadlock under MSVC codegen. ggml takes that custom-barrier path
only when GGML_OPENMP is OFF; the OpenMP `#pragma omp barrier` path is the one
production builds use on Windows and it does not deadlock.

OpenMP was forced off (f35e899) because GNU/Clang `-fopenmp` is a link flag that
doesn't propagate to downstream static binaries — a Unix-only problem. Under
MSVC, OpenMP is auto-linked via the /openmp pragma baked into the ggml objects
(no -fopenmp anywhere), so force GGML_OPENMP=ON for MSVC even when our knob is
off. TRANSCRIBE_USE_OPENMP stays off, so the link manifest emits no -fopenmp and
the Parakeet host-decoder TU is unchanged; only ggml's CPU threading flips, which
is the correctness fix. vcomp140.dll ships in the VC++ runtime already required.

Validated end to end: the whisper e2e transcribes (exit 0, no hang) on the
Windows diag with TRANSCRIBE_USE_OPENMP=OFF + this override. diag-b mirrors the
cargo knobs to exercise the real fix path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Revert the Windows skips added while B was open: the cancel tests and the
error-handling example's cancellation demo run everywhere again, now that
Windows whisper load + transcription work (ggml_time_init + GGML_OPENMP-on-MSVC).
The prior "cancel token installed -> ÷0" framing was a misread — model load
itself ÷0'd before any run, and the cancel binary was just alphabetically first.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
B is root-caused and fixed (ggml_time_init at load + GGML_OPENMP=ON under MSVC),
validated green on both Windows legs. Remove the temporary diagnostics:
  - the whisper_e2e_smoke vectored-exception fault tracer + watchdog thread-dump
    + their Windows includes, and the obsolete TRANSCRIBE_DIAG_CANCEL harness
  - the dbghelp link those needed
  - .github/workflows/diag-b.yml
  - docs/known-issues.md — both tracked bugs (B int-÷0, F dllimport) are fixed;
    the resolutions live in the fix commits. De-reference it in transcribe.cpp.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The MSVC barrier-deadlock workaround (GGML_OPENMP=ON) was being forced for every
MSVC consumer, which would have silently flipped the Python Windows wheels to
OpenMP — reversing their deliberate no-OpenMP posture (numpy/torch runtime
coexistence; python-wheels.yml). Scope it to the Rust binding instead:

- CMakeLists: only force GGML_OPENMP OFF (the no-OpenMP posture) when a consumer
  hasn't explicitly set it; honor an explicit -DGGML_OPENMP=ON.
- sys/build.rs: pass -DGGML_OPENMP=ON on Windows (the Rust binding is CPU-default
  and standalone, so it needs the working CPU threading and has no numpy/torch
  coexistence concern). vcomp auto-links via the ggml objects' /openmp pragma.

The Python Windows wheels keep GGML_OPENMP unset -> still OpenMP-free. Their
latent CPU-backend deadlock (they default to Vulkan) is a separate maintainer
call (single-threaded CPU vs. OpenMP) and intentionally left untouched here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…::Busy)

Review (High): the safe wrapper only locked individual native calls, so an
active stream — which spans begin..feed*..finalize — could overlap a second
stream or a run on ANOTHER session of the same model, reaching the documented
per-model UB (corrupted decodes / command-buffer failures, include/transcribe.h)
from safe code.

Make the per-model lock carry a "stream active" flag (Mutex<()> -> Mutex<bool>):
one-shot run/run_batch still serialize on it, stream begin claims the lease for
the whole Stream lifetime (released in Drop), and any run/batch/stream that would
overlap an active stream is refused with the new Error::Busy instead of racing.
Erroring (not blocking) avoids deadlocking a single thread that holds a Stream
and then runs on another session. Adds a regression test: two sessions on one
model, second stream and a mid-stream run both return Busy, and the lease frees
on drop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rol)

Review (High): `transcribe-cpp --no-default-features` still pulled
transcribe-cpp-sys's `default = ["metal"]`, so Metal (and TRANSCRIBE_METAL=ON)
stayed active — the safe crate's feature flags were not the real control surface.
Set default-features=false on the dependency; the backend features forward
explicitly, so the default still enables Metal via `metal =
["transcribe-cpp-sys/metal"]` while `--no-default-features` is now a true CPU
build. Confirmed with `cargo tree -e features`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review fixes:
- WhisperRunOptions.initial_prompt with an interior NUL was silently dropped
  (CString::new(..).ok()); now propagates Error::Nul like language /
  target_language. materialize() returns Result and the caller `?`-forwards it.
- init_backends() used to_string_lossy(), which mangles a non-UTF-8 path with
  U+FFFD instead of rejecting/passing it. Reuse model.rs's path_bytes (now
  pub(crate)): Unix paths pass through as bytes, non-UTF-8 is rejected on
  Windows — matching model loading.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review (Medium): the MSVC OpenMP workaround (Rust sets GGML_OPENMP=ON, wheels
stay OpenMP-free) read as an inconsistent boundary smell. Document it as the
deliberate per-consumer policy it is, centrally in the root CMakeLists "OpenMP —
CENTRAL POLICY" block: the Rust binding needs OpenMP (CPU-default, standalone),
the Python wheels deliberately stay OpenMP-free (numpy/torch coexistence; default
to Vulkan) at the cost of a documented limitation — multi-threaded CPU compute on
Windows is unsupported for the wheels. build.rs points at that block.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review (High): the safe crate disabled transcribe-cpp-sys/default, but
sys/build.rs only ever set TRANSCRIBE_METAL=ON (never OFF), and CMake defaults
TRANSCRIBE_METAL ON on Apple Silicon — so a `--no-default-features` build still
enabled Metal, contradicting Cargo.toml's "pure CPU build on macOS is
default-features = false". Set TRANSCRIBE_METAL explicitly on Apple to track the
`metal` feature (ON+embed when present, OFF when absent). Verified: the
--no-default-features build cache now has TRANSCRIBE_METAL=OFF / GGML_METAL=OFF.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… drop

Review (Medium): the lease was held from stream begin until Stream::drop, so a
finalized or reset stream still blocked every other session on the model until
the handle dropped — over-conservative, since the C contract is "one ACTIVE
stream" and finalize/reset end the active stream. Track lease ownership on the
Stream (holds_lease) and release it the moment the stream stops being active
(finalize/reset), keeping drop as the fallback; the per-stream flag means drop
never releases a lease another session has since acquired. Sound because the C
side state-guards feed/finalize (INVALID_ARG on a non-active session, before any
compute), so a stray call after release cannot race. Adds a test asserting a
second session may stream after the first finalizes/resets without dropping it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e-cpp)

Review (Low): the comment said bindings/rust/safe/; the safe wrapper lives at
bindings/rust/transcribe-cpp/ (as the manifest list right below it shows).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…locks on MSVC

Build the SHARED libtranscribe both ways and run a CPU whisper transcription
through the ctypes Python binding under a faulthandler watchdog:
  A) GGML_OPENMP=OFF (the official Windows wheel posture) -> expect HANG
  B) GGML_OPENMP=ON  (the Rust-side fix)                  -> expect PASS
This tests the exact native code the wheels ship, at the Python layer, to
confirm whether the wheels are exposed to the MSVC ggml-cpu barrier deadlock.
Validated locally on macOS (clang barrier works -> PASS, exercises the script).

Note: whisper does not apply session n_threads to its ggml graph (uses the ggml
default of 4), so n_threads=1 would not avoid the hang for whisper — the
decisive axis tested here is OpenMP off/on. Diagnostic scaffolding; remove once
the wheel CPU-on-Windows posture is decided.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cjpais and others added 6 commits June 14, 2026 16:19
First diag-py-cpu run was surprising — the SHARED (wheel-posture) build with
GGML_OPENMP=OFF transcribed CPU whisper fine, while the STATIC Rust build
reliably deadlocked. Re-run to settle whether shared is reliably safe or an
intermittent race: loop the shared CPU run x12 (count hangs) and add a
same-runner STATIC control (C++ e2e under a 90s timeout) to confirm static
still hangs. If shared is N/N clean and static hangs, the deadlock is
static-specific and the Python wheels are not exposed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…adlock (W1)

The diag-py-cpu run confirmed the wheel posture IS exposed: shared lib with
GGML_OPENMP=OFF hangs ~12/12 on CPU whisper (the first run's single pass was a
fluke race-win), and the static control hangs too — so the non-OpenMP ggml-cpu
barrier deadlock under MSVC is near-deterministic on both static and shared, not
static-specific. Remove the throwaway CI scaffolding now that the question is
answered, and capture the finding + a Windows-machine debugging plan in
docs/known-issues.md (W1) so the deferred root-cause work is ready to resume.

The Rust Windows fix (GGML_OPENMP=ON) and the documented Python wheel limitation
both stand confirmed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cjpais cjpais merged commit da3eaca into main Jun 15, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant