Repository navigation
Conversation
…ribing Accelerate brings its own thread pool to every cblas call. Where geist has already spread a matmul across its OpenMP threads -- the quantized prefill kernels dequantize a tile per thread and call sgemm on it -- each of those calls asks for a pool of its own, and the machine waits on itself. On a 27B PQ2_0 prefill, sample(1) counted ~35k samples parked in worker threads and ~11.6k in OpenMP waits against ~30k actually inside BLAS. BLASSetThreading fixes that, but its setting is thread-local, so it has to be set on the calling thread and only there: a lone caller -- the small F32 projections that run outside any parallel region -- should keep Accelerate's own threading rather than be pinned to one core. So the threading model is matched to the caller, and only rewritten when it changes. The OpenBLAS branch has had the equivalent pin since it was written; this gives the Apple branch the same treatment instead of leaving it a stub. macOS 15+; resolved through dlsym so older systems keep the default, and GEIST_BLAS_THREAD_MATCH=0 turns it off for A/B. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Campaign finished — the numbers do not support this change25 paired runs, 26 discarded for contention. pp256 + 32 decode steps,
In five of six models the scatter inside a single arm is larger than the The two 27B models, which are the reason the change exists, disagree in So the oversubscription the Two corrections to this PR's description
RecommendationDo not merge on these numbers. The code is correct and does what it says — I would rather close this and keep the negative result findable than leave a The CPU prefill gap to the PrismML fork (13.6 vs 17.35 t/s at pp256, #429) 🤖 Generated with Claude Code |
|
Closing: the campaign above refutes the premise. No measurable throughput benefit at pp256, and in the one model tight enough to resolve half a percent the lever is slightly slower. The mechanism and the negative result stay findable here; thread oversubscription is crossed off the list of candidates for the CPU prefill gap in #429. The branch |
Summary
Accelerate brings its own thread pool to every
cblascall. Where geist hasalready spread a matmul across its OpenMP threads — the quantized prefill
kernels dequantize a tile per thread and call
sgemmon it — each of thosecalls asks for a pool of its own, and the machine waits on itself. On a 27B
PQ2_0 prefill,
sample(1)counted ~35 k samples parked in worker threads and~11.6 k in OpenMP waits against ~30 k actually inside BLAS. That was the
remaining candidate for the CPU prefill gap to the PrismML fork measured in
#429 (13.6 vs 17.35 t/s at pp256).
The OpenBLAS branch of
geist_blas_pin_single_threadhas had the equivalentpin since it was written; the Apple branch was an empty stub. This fills it in.
Changes
src/backends/common/geist_gemm.c: an__APPLE__branch ofgeist_blas_pin_single_threadthat sets Accelerate's threading to match thecaller — single-threaded inside an OpenMP region, Accelerate's own threading
outside one.
BLASSetThreading's setting is thread-local (vecLibthread_api.h), soit is set on the calling thread and only there. A lone caller — the small F32
projections that run outside any parallel region — keeps Accelerate's
threading rather than being pinned to one core. The value is cached per thread
and rewritten only when
omp_in_parallel()changes, so the steady state costsone comparison.
dlsym, so systems without it keep today's behaviour;GEIST_BLAS_THREAD_MATCH=0disables it for A/B.Testing
make testpasses — unit suite 36 passed / 21 skipped / 0 failed(
BACKENDS="cpu_neon cpu_scalar metal"). Skips are the usualfixture- and hardware-gated ones.
make MODE=asan test— same, 0 failed.make format-check— fails onsrc/archs/transformer/arch_ops.c,which this PR does not touch. The
clang-formaton PATH here is23.1.0 and CI uses llvm@22; under llvm@22 both
arch_ops.cand thisPR's only changed file are clean. Pre-existing version skew, not this
change.
For perf/kernel changes, before/after numbers and host:
=0)=1)Host: M1 Max, macOS 27.0,
TARGET=mac-omp, Accelerate + libomp,BACKENDS="cpu_neon cpu_scalar metal".BLASSetThreadingresolves on thissystem (checked with a standalone
dlsymprobe), so the lever does engage —without that the A/B would measure nothing.
API impact
include/geist.h🤖 Generated with Claude Code