Skip to content

Switch QBLAS dependency to 1.5.0 (new C ABI, runtime CPU dispatch)#8

Closed
SwayamInSync wants to merge 6 commits into
mainfrom
qblas-rewrite-integration
Closed

Switch QBLAS dependency to 1.5.0 (new C ABI, runtime CPU dispatch)#8
SwayamInSync wants to merge 6 commits into
mainfrom
qblas-rewrite-integration

Conversation

@SwayamInSync

@SwayamInSync SwayamInSync commented May 14, 2026

Copy link
Copy Markdown
Owner

Summary

Bumps the QBLAS dependency to QBLAS 1.5.0

For users of numpy_quaddtype, nothing visible in the Python API changes. A @ B and np.matmul(A, B) continue to work exactly as before. The C-level shim's old QuadBLAS:: C++ namespace is replaced by free C functions (cblas_qdot, cblas_qgemv, cblas_qgemm, ...) inside src/csrc/quadblas_interface.cpp.

Performance impact

Measured on AMD EPYC 7V13 (Zen 3, AVX2 tier), same numpy float64 / scipy-openblas baseline (Haswell tier), same harness on both QBLAS versions. Harness: bench/bench_quad_vs_numpy.py in the QBLAS repo.

op size threads f64 ref (OpenBLAS) QBLAS 1.0 QBLAS 1.5 new / old
gemm 128 x 128 16 73.4 GFLOPS 0.034 GFMA/s (2167x) 0.81 GFMA/s (84x) 24x
gemm 256 x 256 16 239 GFLOPS 0.034 GFMA/s (7072x) 0.83 GFMA/s (287x) 24x
gemm 512 x 512 16 356 GFLOPS 0.243 GFMA/s (1465x) 0.83 GFMA/s (449x) 3.4x
gemm 1024 x 1024 16 462 GFLOPS 0.487 GFMA/s (933x) 0.84 GFMA/s (552x) 1.7x
gemm 256 x 256 1 47.1 GFLOPS 0.034 GFMA/s (1392x) 0.058 GFMA/s (809x) 1.7x
gemv 2048 16 168 GFLOPS 0.506 GFMA/s (332x) 0.83 GFMA/s (204x) 1.6x
dot 1 M 16 43.9 GFLOPS 0.252 GFMA/s (174x) 0.41 GFMA/s (103x) 1.6x

Two gains stack:

  • Uniform ~1.6 to 1.7x single-core kernel speedup from the per-ISA OBJECT-library design, replacing the old SSE2-locked header-only template path. Runtime CPUID dispatch picks AVX2 on this hardware automatically.
  • ~24x speedup at small-N multi-threaded gemm specifically. Old QBLAS shipped a fixed nc = NC_DEFAULT = 512, so a 128x128 gemm at 16 threads ran on a single thread (the others spun up, hit the barrier, and slept). New QBLAS auto-scales nc so each thread gets at least two blocks.
  • This PR also enables the fast kernel path for supporting F-contiguous/non-row-major inputs ([BUG] matmul produces incorrect results for F-contiguous / non-row-major inputs numpy/numpy-quaddtype#89)

Caveat: Single-thread quad-precision gemm is still ~800x slower than f64 OpenBLAS. That gap is intrinsic to SLEEF's triple-double FMA implementation. The roadmap to bring it down to ~50 to 100x lives in docs/performance_bottlenecks.md, and none of it is in 1.5.0. The value-add of 1.5 is correctness and dispatch hygiene (proper threading, runtime SIMD selection, transpose flags), not lifting the FMA ceiling.

The new compiled QBLAS uses GCC-only flags (-march, -mavx2), POSIX-only
APIs (clock_gettime, sysconf, _SC_LEVEL2_CACHE_SIZE), and GCC built-ins
for CPUID detection - none of which build under MSVC. The legacy
header-only QBLAS happened to compile on Windows because the wrap was
five lines and produced no objects.

Add a disable_quadblas meson option (default false; auto-enabled on
Windows) that:

  - skips dependency('qblas', fallback: ...) entirely
  - declares an empty qblas_dep so the rest of the build proceeds
  - adds -DDISABLE_QUADBLAS to project args so quadblas_interface.cpp
    and umath/matmul.cpp take the naive-kernel branches that already
    exist behind that guard

This removes the CFLAGS=/DDISABLE_QUADBLAS hack from the Windows CI
since meson now sets the preprocessor flag itself (and translates -D
to /D for MSVC). Verified on Linux: -Ddisable_quadblas=true builds
without pulling the QBLAS subproject; default still builds it.
Meson caches the first subproject() call by name. When the QBLAS
dependency was resolved first, qblas's internal subproject('sleef')
ran with no options, configuring SLEEF with FMA enabled. Our later
subproject('sleef', default_options: ['disable_fma=true']) was then
silently a no-op - meson returned the already-configured instance.

Net effect on the old-CPU CI: the SLEEF in the wheel still had the
PURECFMA scalar code path enabled, and Intel SDE trapped on the first
vfnmadd132sd from Sleef_log10q1_u10purecfma when emulating Sandy
Bridge.

Move the whole SLEEF resolution block above the qblas dependency line.
SLEEF gets configured with the right options on its first (and only)
load; QBLAS's later subproject('sleef') call returns the same
instance, so there's now exactly one SLEEF in the build with the
options we wanted.
quaddtype's own sources don't use OpenMP (no #pragma omp or omp_*
calls). Both quaddtype's meson.build and qblas's meson.build called
dependency('openmp'), and qblas_dep also propagated the OpenMP dep
through its 'dependencies:' list. The same OpenMP instance therefore
appeared twice in quaddtype's compile-args closure.

On Apple's clang++ that double inclusion left an orphan -Xpreprocessor
in the args stream. Meson's ninja rule appends dependency-generation
flags ('-MD -MQ -MF ...') after $ARGS, so the orphan -Xpreprocessor
paired with -MD on the next line. clang++ then passed -MD to the
preprocessor verbatim, the preprocessor rejected it as unknown, and
every C++ compile failed - sinking the macos-15 wheel build.

qblas_dep already brings OpenMP transitively when QBLAS is enabled.
When QBLAS is disabled (Windows / -Ddisable_quadblas=true) we don't
need OpenMP at all because none of quaddtype's own sources use it.
Removing the second dependency('openmp') makes the closure single-
sourced and eliminates the duplicate compile args.
@SwayamInSync

Copy link
Copy Markdown
Owner Author

Single-threaded (1 core)

op N f64 ref (GFLOPS) OLD quad GFMA/s OLD slowdown NEW quad GFMA/s NEW slowdown new/old
dot 65 536 4.69 0.018 266× 0.029 160× 1.61×
dot 262 144 5.00 0.018 285× 0.029 177× 1.61×
dot 1 048 576 4.64 0.018 264× 0.029 157× 1.61×
dot 4 194 304 2.45 0.017 140× 0.029 85× 1.71×
gemv 512 16.76 0.035 480× 0.057 287× 1.63×
gemv 1024 16.04 0.035 457× 0.058 274× 1.66×
gemv 2048 12.41 0.035 353× 0.058 208× 1.66×
gemm 128 43.11 0.034 1 270× 0.058 739× 1.71×
gemm 256 47.06 0.034 1 392× 0.058 809× 1.71×
gemm 512 47.08 0.034 1 387× 0.058 794× 1.71×

Multi-threaded (16 cores)

op N f64 ref (GFLOPS) OLD quad GFMA/s OLD slowdown NEW quad GFMA/s NEW slowdown new/old
dot 65 536 6.69 0.247 27× 0.402 18× 1.63×
dot 262 144 25.10 0.250 100× 0.411 69× 1.64×
dot 1 048 576 43.95 0.252 174× 0.414 103× 1.64×
dot 4 194 304 51.88 0.252 206× 0.415 119× 1.65×
gemv 512 13.96 0.500 28× 0.807 17× 1.61×
gemv 1024 127.58 0.505 253× 0.821 153× 1.63×
gemv 2048 167.85 0.506 332× 0.829 204× 1.64×
gemm 128 73.42 0.034 2 167× 0.809 84× 23.79×
gemm 256 238.78 0.034 7 072× 0.825 287× 24.26×
gemm 512 356.01 0.243 1 465× 0.833 449× 3.43×
gemm 1024 454.02 0.487 933× 0.837 552× 1.72×

@SwayamInSync SwayamInSync changed the title Switch to QBLAS overhaul-rewrite: new C ABI Switch QBLAS dependency to 1.5.0 (new C ABI, runtime CPU dispatch) May 14, 2026
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