Skip to content

Commit ebddd49

Browse files
authored
use ggml native threadpool (#49)
1 parent 2963874 commit ebddd49

28 files changed

Lines changed: 1013 additions & 931 deletions

File tree

.github/workflows/typescript-ci.yml

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -166,16 +166,14 @@ jobs:
166166
# transcription/streaming/cancel/extension coverage. jfk.wav ships in-repo;
167167
# only the model paths need exporting (fetch-canary handles that).
168168
#
169-
# Windows is no-model-only for now: that is the koffi DLL-resolution proof
170-
# this leg was added for, and it is unaffected. The model tier is deferred
171-
# because loading a model spins up the ggml CPU threadpool, and on MSVC with
172-
# OpenMP (vcomp) the runtime crashes (0xC0000005) at process exit under
173-
# node/koffi, after the run itself succeeds. Skipping the canary makes the
174-
# model-gated tests and the (model-loading) examples self-skip cleanly. Drop
175-
# this guard to re-enable the Windows model tier once that teardown crash in
176-
# the MSVC OpenMP runtime is fixed.
169+
# Windows now runs the full model tier too. It was previously no-model-only
170+
# because loading a model spun up ggml's CPU threadpool, and on MSVC with
171+
# OpenMP (vcomp) the runtime crashed (0xC0000005) at process exit under
172+
# node/koffi. The build now defaults to ggml's native threadpool (no OpenMP
173+
# — see the OpenMP CENTRAL POLICY in CMakeLists.txt), so there is no vcomp
174+
# pool to crash at teardown, and the native barrier is MSVC-correct (the
175+
# transcribe_threadpool_oversubscription test guards it). Re-enabled here.
177176
- uses: ./.github/actions/fetch-canary
178-
if: runner.os != 'Windows'
179177
with:
180178
hf-token: ${{ secrets.HF_TOKEN }}
181179
- name: Conformance (no-model tier always; model tier when canary present)

CMakeLists.txt

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
132135
option(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)
134137
option(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)
265268
set(GGML_VULKAN ${TRANSCRIBE_VULKAN} CACHE BOOL "" FORCE)
266269
set(GGML_CUDA ${TRANSCRIBE_CUDA} CACHE BOOL "" FORCE)
267270
set(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()
284290
endif()
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.
314322
if(NOT TRANSCRIBE_USE_OPENMP AND NOT DEFINED CACHE{GGML_OPENMP})
315323
set(GGML_OPENMP OFF CACHE BOOL "" FORCE)
316324
endif()

bindings/rust/sys/build.rs

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -157,29 +157,27 @@ fn main() {
157157
if feature("CUDA") {
158158
cfg.define("TRANSCRIBE_CUDA", "ON");
159159
}
160-
// Force OpenMP OFF unless explicitly opted in. TRANSCRIBE_USE_OPENMP
161-
// defaults ON and auto-detects, but its `-fopenmp` shows up only as a
162-
// manifest link_flag → a `cargo:rustc-link-arg` that does NOT propagate to
163-
// downstream binaries, so a static consumer link fails with undefined
164-
// GOMP_*/omp_* symbols. A self-contained static build is the default;
165-
// `--features openmp` opts in (and then owns providing the OpenMP runtime).
160+
// Keep OpenMP OFF unless explicitly opted in. TRANSCRIBE_USE_OPENMP already
161+
// defaults OFF in CMake (the native ggml threadpool is the default path); we
162+
// set it explicitly here so `--features openmp` is the single switch. We keep
163+
// it OFF by default because OpenMP's `-fopenmp` shows up only as a manifest
164+
// link_flag → a `cargo:rustc-link-arg` that does NOT propagate to downstream
165+
// binaries, so a static consumer link would fail with undefined GOMP_*/omp_*
166+
// symbols. A self-contained static build is the default; `--features openmp`
167+
// opts in (and then owns providing the OpenMP runtime).
166168
cfg.define(
167169
"TRANSCRIBE_USE_OPENMP",
168170
if feature("OPENMP") { "ON" } else { "OFF" },
169171
);
170-
// ggml's non-OpenMP CPU threadpool barrier deadlocks under MSVC on Windows;
171-
// OpenMP's `#pragma omp barrier` is the only working multi-threaded CPU path
172-
// there. The Rust binding is CPU-default and standalone (no numpy/torch
173-
// OpenMP-coexistence concern), so it opts ggml into OpenMP on Windows.
174-
// GGML-internal only: TRANSCRIBE_USE_OPENMP stays off, so the link manifest
175-
// emits no (GNU-only) -fopenmp and the Parakeet host-decoder TU is unchanged;
176-
// MSVC auto-links vcomp via the ggml objects' /openmp pragma. CMakeLists
177-
// honors this explicit GGML_OPENMP and skips its force-off. See the full
178-
// per-consumer policy (incl. why the Python wheels stay OpenMP-free) in the
179-
// "OpenMP — CENTRAL POLICY" block of the root CMakeLists.txt.
180-
if target_os == "windows" {
181-
cfg.define("GGML_OPENMP", "ON");
182-
}
172+
// Windows no longer needs ggml's OpenMP. ggml's native CPU threadpool barrier
173+
// used to deadlock under MSVC (its spin `relax` compiled to a no-op, starving
174+
// an un-arrived worker), so OpenMP was the only multi-threaded CPU path there.
175+
// That barrier is fixed upstream (ggml-cpu.c: YieldProcessor relax + a
176+
// bounded-spin ggml_thread_yield fallback), so the native pool is correct on
177+
// MSVC — and avoids the vcomp pool that crashes at teardown when a host
178+
// dlopen/dlcloses this lib. We therefore leave GGML_OPENMP at its
179+
// CMakeLists-forced OFF on every platform; `--features openmp` is the only
180+
// opt-in. See the "OpenMP — CENTRAL POLICY" block in the root CMakeLists.txt.
183181

184182
// Escape hatch: forward arbitrary configure args so the curated features are
185183
// never a hard ceiling. Anything CMake accepts (-DGGML_*, a -DTRANSCRIBE_*

docs/build-windows.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,12 @@ cmake --build build --target transcribe-cli --config Release
9393
Notes:
9494
- The Visual Studio generator is **multi-config**, so pass `--config Release`
9595
at build time (omitting it yields a Debug build).
96-
- A successful configure prints `Found OpenMP`. **OpenMP matters on
97-
Windows**: ggml's non-OpenMP CPU threadpool barrier deadlocks under MSVC,
98-
so OpenMP is the working multi-threaded CPU path. MSVC auto-links it
99-
(`vcomp`) — no action needed, just don't disable it.
96+
- OpenMP is **not** required on Windows. ggml's native CPU threadpool is the
97+
default everywhere (see the OpenMP CENTRAL POLICY in the top-level
98+
`CMakeLists.txt`); the MSVC barrier bug that once forced OpenMP here is fixed,
99+
so the native pool is correct under MSVC and oversubscription. Build with the
100+
defaults; opt into OpenMP only with `-DTRANSCRIBE_USE_OPENMP=ON` if you have a
101+
specific reason.
100102
- `no BLAS found — decoder uses scalar fallback` is fine; it only affects
101103
host-side decoder speed, not correctness.
102104
- `uv not found on PATH` is harmless — it only skips an optional test.

src/CMakeLists.txt

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -124,27 +124,6 @@ add_library(transcribe
124124
third_party/miniz/miniz.c
125125
)
126126

127-
# The parakeet host decoder's matmul fallback (linear()) parallelizes its
128-
# large output projection across rows. Compile ONLY this TU with OpenMP so
129-
# the `#pragma omp parallel for` in linear() activates; libgomp is already
130-
# linked transitively via ggml. Scoped to one TU and numerically identical
131-
# (rows summed in the same order). The primary out_w path is the ggml graph
132-
# in joint_step; this row-parallelism covers the host fallback and the
133-
# remaining host-side matmuls.
134-
#
135-
# Gated on TRANSCRIBE_USE_OPENMP so official provider wheels (which pass it OFF,
136-
# alongside GGML_OPENMP=OFF) do not link an OpenMP runtime into this TU. When
137-
# off, the `#pragma omp` is an ignored no-op and linear() runs serially — the
138-
# same path taken when OpenMP simply is not found.
139-
if(TRANSCRIBE_USE_OPENMP)
140-
find_package(OpenMP QUIET)
141-
if(OpenMP_CXX_FOUND)
142-
set_source_files_properties(arch/parakeet/decoder.cpp PROPERTIES
143-
COMPILE_OPTIONS "${OpenMP_CXX_FLAGS}")
144-
target_link_libraries(transcribe PRIVATE OpenMP::OpenMP_CXX)
145-
endif()
146-
endif()
147-
148127
target_include_directories(transcribe
149128
PUBLIC
150129
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>

src/arch/canary/model.cpp

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -902,22 +902,7 @@ transcribe_status run(
902902
}
903903

904904
// Thread count.
905-
{
906-
int n_threads = cc->n_threads;
907-
if (n_threads <= 0) {
908-
n_threads = std::min(8, std::max(1, static_cast<int>(
909-
std::thread::hardware_concurrency())));
910-
}
911-
for (int i = 0; i < ggml_backend_sched_get_n_backends(cc->sched); ++i) {
912-
ggml_backend_t be = ggml_backend_sched_get_backend(cc->sched, i);
913-
ggml_backend_dev_t dev = ggml_backend_get_device(be);
914-
ggml_backend_reg_t reg = dev ? ggml_backend_dev_backend_reg(dev) : nullptr;
915-
if (reg == nullptr) continue;
916-
auto * fn = reinterpret_cast<ggml_backend_set_n_threads_t>(
917-
ggml_backend_reg_get_proc_address(reg, "ggml_backend_set_n_threads"));
918-
if (fn != nullptr) fn(be, n_threads);
919-
}
920-
}
905+
transcribe::configure_sched_n_threads(cc->sched, cc->n_threads);
921906

922907
const int64_t t_enc_start = ggml_time_us();
923908
if (const ggml_status gs = ggml_backend_sched_graph_compute(cc->sched, eb.graph);
@@ -1570,20 +1555,7 @@ transcribe_status encode_one_to_host(
15701555
pos_buf.size() * sizeof(float));
15711556
}
15721557

1573-
{
1574-
int n_threads = cc->n_threads;
1575-
if (n_threads <= 0) n_threads = std::min(8, std::max(1,
1576-
static_cast<int>(std::thread::hardware_concurrency())));
1577-
for (int i = 0; i < ggml_backend_sched_get_n_backends(cc->sched); ++i) {
1578-
ggml_backend_t be = ggml_backend_sched_get_backend(cc->sched, i);
1579-
ggml_backend_dev_t dev = ggml_backend_get_device(be);
1580-
ggml_backend_reg_t reg = dev ? ggml_backend_dev_backend_reg(dev) : nullptr;
1581-
if (reg == nullptr) continue;
1582-
auto * fn = reinterpret_cast<ggml_backend_set_n_threads_t>(
1583-
ggml_backend_reg_get_proc_address(reg, "ggml_backend_set_n_threads"));
1584-
if (fn != nullptr) fn(be, n_threads);
1585-
}
1586-
}
1558+
transcribe::configure_sched_n_threads(cc->sched, cc->n_threads);
15871559

15881560
const int64_t t0 = ggml_time_us();
15891561
if (ggml_backend_sched_graph_compute(cc->sched, eb.graph) != GGML_STATUS_SUCCESS)
@@ -1672,8 +1644,7 @@ transcribe_status run_batch(
16721644
std::vector<std::vector<float>> mel_bufs(n);
16731645
std::vector<int> mel_nf(n, 0);
16741646
int n_threads = cc->n_threads;
1675-
if (n_threads <= 0) n_threads = std::min(8, std::max(1,
1676-
static_cast<int>(std::thread::hardware_concurrency())));
1647+
if (n_threads <= 0) n_threads = transcribe::default_n_threads();
16771648
int64_t mel_us = 0, enc_us = 0;
16781649
const int64_t t_mel0 = ggml_time_us();
16791650
transcribe::parallel_for_all(n, n_threads, [&](int b) {

src/arch/canary_qwen/model.cpp

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -830,20 +830,7 @@ transcribe_status init_context(
830830
// ---------------------------------------------------------------------------
831831

832832
void apply_thread_policy(CanaryQwenSession * cc) {
833-
int n_threads = cc->n_threads;
834-
if (n_threads <= 0) {
835-
n_threads = std::min(8, std::max(1, static_cast<int>(
836-
std::thread::hardware_concurrency())));
837-
}
838-
for (int i = 0; i < ggml_backend_sched_get_n_backends(cc->sched); ++i) {
839-
ggml_backend_t be = ggml_backend_sched_get_backend(cc->sched, i);
840-
ggml_backend_dev_t dev = ggml_backend_get_device(be);
841-
ggml_backend_reg_t reg = dev ? ggml_backend_dev_backend_reg(dev) : nullptr;
842-
if (reg == nullptr) continue;
843-
auto * fn = reinterpret_cast<ggml_backend_set_n_threads_t>(
844-
ggml_backend_reg_get_proc_address(reg, "ggml_backend_set_n_threads"));
845-
if (fn != nullptr) fn(be, n_threads);
846-
}
833+
transcribe::configure_sched_n_threads(cc->sched, cc->n_threads);
847834
}
848835

849836
void build_relpos_emb_host(std::vector<float> & pos_buf,
@@ -1559,8 +1546,7 @@ transcribe_status run_batch(
15591546
std::vector<int> mel_nf(n, 0);
15601547
int n_mel_threads = cc->n_threads;
15611548
if (n_mel_threads <= 0)
1562-
n_mel_threads = std::min(8, std::max(1, static_cast<int>(
1563-
std::thread::hardware_concurrency())));
1549+
n_mel_threads = transcribe::default_n_threads();
15641550
const int64_t t_mel0 = ggml_time_us();
15651551
transcribe::parallel_for_all(n, n_mel_threads, [&](int b) {
15661552
if (pcm[b] == nullptr || n_samples[b] <= 0) return true;

src/arch/cohere/model.cpp

Lines changed: 3 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -941,24 +941,7 @@ transcribe_status run(
941941
}
942942

943943
// Set thread count.
944-
{
945-
int n_threads = cc->n_threads;
946-
if (n_threads <= 0) {
947-
n_threads = std::min(8, std::max(1, static_cast<int>(
948-
std::thread::hardware_concurrency())));
949-
}
950-
for (int i = 0; i < ggml_backend_sched_get_n_backends(cc->sched); ++i) {
951-
ggml_backend_t be = ggml_backend_sched_get_backend(cc->sched, i);
952-
ggml_backend_dev_t dev = ggml_backend_get_device(be);
953-
ggml_backend_reg_t reg = dev ? ggml_backend_dev_backend_reg(dev) : nullptr;
954-
if (reg == nullptr) continue;
955-
auto * fn = reinterpret_cast<ggml_backend_set_n_threads_t>(
956-
ggml_backend_reg_get_proc_address(reg, "ggml_backend_set_n_threads"));
957-
if (fn != nullptr) {
958-
fn(be, n_threads);
959-
}
960-
}
961-
}
944+
transcribe::configure_sched_n_threads(cc->sched, cc->n_threads);
962945

963946
// Compute encoder graph.
964947
const int64_t t_enc_start = ggml_time_us();
@@ -1686,20 +1669,7 @@ transcribe_status encode_one_to_host(
16861669
pos_buf.size() * sizeof(float));
16871670
}
16881671

1689-
{
1690-
int n_threads = cc->n_threads;
1691-
if (n_threads <= 0) n_threads = std::min(8, std::max(1,
1692-
static_cast<int>(std::thread::hardware_concurrency())));
1693-
for (int i = 0; i < ggml_backend_sched_get_n_backends(cc->sched); ++i) {
1694-
ggml_backend_t be = ggml_backend_sched_get_backend(cc->sched, i);
1695-
ggml_backend_dev_t dev = ggml_backend_get_device(be);
1696-
ggml_backend_reg_t reg = dev ? ggml_backend_dev_backend_reg(dev) : nullptr;
1697-
if (reg == nullptr) continue;
1698-
auto * fn = reinterpret_cast<ggml_backend_set_n_threads_t>(
1699-
ggml_backend_reg_get_proc_address(reg, "ggml_backend_set_n_threads"));
1700-
if (fn != nullptr) fn(be, n_threads);
1701-
}
1702-
}
1672+
transcribe::configure_sched_n_threads(cc->sched, cc->n_threads);
17031673

17041674
const int64_t t0 = ggml_time_us();
17051675
if (ggml_backend_sched_graph_compute(cc->sched, eb.graph) != GGML_STATUS_SUCCESS)
@@ -1786,8 +1756,7 @@ transcribe_status run_batch(
17861756
std::vector<std::vector<float>> mel_bufs(n);
17871757
std::vector<int> mel_nf(n, 0);
17881758
int n_threads = cc->n_threads;
1789-
if (n_threads <= 0) n_threads = std::min(8, std::max(1,
1790-
static_cast<int>(std::thread::hardware_concurrency())));
1759+
if (n_threads <= 0) n_threads = transcribe::default_n_threads();
17911760
int64_t mel_us = 0, enc_us = 0;
17921761
const int64_t t_mel0 = ggml_time_us();
17931762
transcribe::parallel_for_all(n, n_threads, [&](int b) {

0 commit comments

Comments
 (0)