@@ -125,12 +125,15 @@ option(TRANSCRIBE_LTO "Enable LTO in Release" OFF)
125125# and bench workflow relies on. The Python wheel/provider build sets it ON to
126126# produce a shared libtranscribe the FFI layer can dlopen.
127127#
128- # TRANSCRIBE_USE_OPENMP / TRANSCRIBE_USE_SYSTEM_BLAS default ON to keep the
129- # developer build fast (ggml OpenMP, Accelerate/system BLAS host decoder).
130- # Official provider wheels pass them OFF so no libgomp/libiomp or OpenBLAS/MKL
131- # runtime is vendored into the wheel where it can collide with PyTorch/NumPy.
128+ # TRANSCRIBE_USE_OPENMP defaults OFF: we use ggml's native CPU threadpool
129+ # everywhere (see the OpenMP CENTRAL POLICY below — OpenMP's process-global pool
130+ # is a teardown/coexistence liability for an embeddable library, and the native
131+ # pool is now correct on MSVC and under oversubscription). Opt in with
132+ # -DTRANSCRIBE_USE_OPENMP=ON. TRANSCRIBE_USE_SYSTEM_BLAS stays ON for the
133+ # Accelerate/system-BLAS host decoder; official provider wheels pass it OFF so no
134+ # OpenBLAS/MKL runtime is vendored where it can collide with PyTorch/NumPy.
132135option (TRANSCRIBE_BUILD_SHARED "Build libtranscribe + ggml as shared libraries" OFF )
133- option (TRANSCRIBE_USE_OPENMP "Use OpenMP ( ggml + Parakeet host-decoder TU)" ON )
136+ option (TRANSCRIBE_USE_OPENMP "Use OpenMP for ggml's CPU threadpool" OFF )
134137option (TRANSCRIBE_USE_SYSTEM_BLAS "Link non-Apple system BLAS for the host decoder" ON )
135138
136139# Dynamic ggml backends: each backend (CPU, Vulkan, CUDA, ...) becomes a
@@ -265,6 +268,9 @@ set(GGML_METAL ${TRANSCRIBE_METAL} CACHE BOOL "" FORCE)
265268set (GGML_VULKAN ${TRANSCRIBE_VULKAN} CACHE BOOL "" FORCE )
266269set (GGML_CUDA ${TRANSCRIBE_CUDA} CACHE BOOL "" FORCE )
267270set (GGML_BLAS OFF CACHE BOOL "" FORCE )
271+ # tinyBLAS (Justine Tunney's llamafile_sgemm CPU kernels): ~29% faster encoder on
272+ # CPU (q8_0 GEMM), numerically WER-equivalent. On by default; CPU-backend only.
273+ set (GGML_LLAMAFILE ON CACHE BOOL "" FORCE )
268274
269275# Conservative x86 floor (see the option's comment above): one switch fans
270276# out to GGML_NATIVE plus the full x86 SIMD tier list. Placed BEFORE
@@ -283,34 +289,36 @@ if(TRANSCRIBE_X86_CONSERVATIVE)
283289 endforeach ()
284290endif ()
285291
286- # OpenMP — CENTRAL POLICY (read before changing the Windows/MSVC threading) .
292+ # OpenMP — CENTRAL POLICY.
287293#
288- # ggml defaults GGML_OPENMP ON and probes for it gracefully, so leave ggml's own
289- # default in place when we want OpenMP. Only force it OFF (matching the
290- # GGML_OPENMP=OFF posture official wheels use) when OpenMP is switched off, so
291- # one TRANSCRIBE_USE_OPENMP knob covers ggml and our host-decoder TU.
294+ # DEFAULT: OFF. We use ggml's native CPU threadpool everywhere; OpenMP is an
295+ # opt-in (-DTRANSCRIBE_USE_OPENMP=ON), not the default. The TRANSCRIBE_USE_OPENMP
296+ # knob simply drives ggml's GGML_OPENMP.
292297#
293- # The catch: ggml's NON-OpenMP CPU threadpool barrier (ggml_barrier's custom
294- # spin path) DEADLOCKS under MSVC codegen on Windows — any multi-threaded CPU
295- # run wedges its workers in the barrier spin (it works on every other compiler).
296- # OpenMP's `#pragma omp barrier` is the only working multi-threaded CPU path on
297- # MSVC. But OpenMP is a per-CONSUMER tradeoff, not a global flag, so this is not
298- # one switch — it is a deliberate, documented split:
298+ # Why native, not OpenMP — OpenMP's runtime pool is process-global and outlives
299+ # any single compute, which is a liability for an embeddable/dlopen'd library:
300+ # - Teardown crash: a binding (node/koffi, ctypes, ...) calls in on a worker
301+ # thread, libgomp/vcomp spawns its pool there, and at process teardown the
302+ # loader unmaps the runtime out from under those still-live pool threads ->
303+ # SIGSEGV / 0xC0000005. The native pool joins its threads per graph_compute,
304+ # so nothing outlives the call and there is nothing to unmap-race.
305+ # - Coexistence: a vendored libgomp/libiomp can collide with numpy/torch/MKL's
306+ # own OpenMP in one process. The native pool vendors no second runtime.
299307#
300- # - Rust binding (CPU is its default backend, standalone process): MUST have
301- # OpenMP on Windows. Its build.rs passes -DGGML_OPENMP=ON; the guard below
302- # honors that explicit value. MSVC auto-links vcomp via the /openmp pragma in
303- # the ggml objects (no -fopenmp flag, so the link manifest is untouched);
304- # vcomp140.dll ships in the VC++ runtime the binary already needs.
305- # - Python wheels (default to the Vulkan GPU backend; deliberately vendor NO
306- # OpenMP so ggml's runtime cannot collide with numpy/torch's own OpenMP/MKL
307- # in one process — see python-wheels.yml): leave GGML_OPENMP unset and get
308- # the force-off below. KNOWN LIMITATION as a consequence: multi-threaded CPU
309- # compute on Windows is unsupported for the wheels (they use Vulkan). Lifting
310- # it needs a non-OpenMP fix (e.g. single-threaded CPU on Windows, or patching
311- # ggml's barrier) rather than forcing OpenMP and the coexistence hazard back .
308+ # This used to be a per-consumer SPLIT because ggml's native barrier deadlocked
309+ # under MSVC (its spin `relax` was a no-op on MSVC, so a waiter starved an
310+ # un-arrived worker under oversubscription) — OpenMP was the only working
311+ # multi-threaded CPU path on Windows. That barrier is now fixed (ggml-cpu.c:
312+ # YieldProcessor() relax + a bounded-spin ggml_thread_yield fallback), so the
313+ # native pool is correct on MSVC and under oversubscription. The split is gone:
314+ # - Rust binding: no longer needs -DGGML_OPENMP=ON on Windows; native pool.
315+ # - Python wheels: already vendored no OpenMP; multi-threaded CPU now works on
316+ # every platform (the old "CPU-on-Windows unsupported" limitation is lifted).
317+ # - perf: native persistent/ephemeral pool is on par with OpenMP (it is
318+ # llama.cpp's default). Thread count is sized affinity-aware in
319+ # transcribe-batch-util (default_n_threads) to avoid needless oversubscription .
312320#
313- # So: honor an explicit -DGGML_OPENMP=... (the Rust opt-in); otherwise force OFF.
321+ # So: honor an explicit -DGGML_OPENMP=... (opt-in); otherwise force OFF.
314322if (NOT TRANSCRIBE_USE_OPENMP AND NOT DEFINED CACHE{GGML_OPENMP})
315323 set (GGML_OPENMP OFF CACHE BOOL "" FORCE )
316324endif ()
0 commit comments