Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2211542
wipe legacy QuadBLAS for full rewrite
SwayamInSync May 13, 2026
989337f
scaffold: new CMake build, runtime dispatch, kernel template, lvl1/2/…
SwayamInSync May 13, 2026
4ccc16f
all correctness tests passing (lvl1/2/3 gemm syrk trmm trsm)
SwayamInSync May 13, 2026
c989139
add Google Benchmark perf suite
SwayamInSync May 13, 2026
d2b5de5
GEMM 50× faster via fine-grained jc parallelism + dynamic scheduling
SwayamInSync May 13, 2026
0a3c9c3
gitignore Testing/ tree
SwayamInSync May 13, 2026
b85cada
thread qnrm2 / qasum (qnrm2 ≥40× faster on large vectors)
SwayamInSync May 13, 2026
4b7b5f1
rewrite CLAUDE.md for new architecture; document perf baseline
SwayamInSync May 13, 2026
9a05c34
perf: honest before/after comparison; document 11× single-thread + 22…
SwayamInSync May 13, 2026
7699f59
dynamic thresholds: detect L1/L2/L3, time OMP fork, derive mc/kc/nc/L…
SwayamInSync May 13, 2026
51b3305
lower L1 thread threshold floor: 8× gain at n=4000 axpy/nrm2
SwayamInSync May 13, 2026
6ee30bf
Goto-blocked qtrsm and qtrmm using cblas_qgemm for bulk update
SwayamInSync May 13, 2026
a1ed773
parallelize diag block of trsm/trmm: 5-15× speedup at all sizes
SwayamInSync May 13, 2026
fcc6a6d
expand perf comparison: full table with asum/scal/syrk/trmm/trsm
SwayamInSync May 13, 2026
00e94b2
fix qblas_resolve_threads to use measured fork cost; CI rewrite; READ…
SwayamInSync May 13, 2026
3e29e84
PERF_COMPARISON: add 96-thread results with pinned OMP; note small-pr…
SwayamInSync May 13, 2026
374ff18
qblas_resolve_threads: scale t by measured overhead, not hardcoded 8K
SwayamInSync May 13, 2026
de60724
guard QBLAS_DISPATCH override against unsupported tiers; correct NUMA…
SwayamInSync May 14, 2026
7e5165e
add numpy-vs-qblas correctness test (rounded-to-double comparison, 85…
SwayamInSync May 14, 2026
f4b01cf
README: document numpy comparison test
SwayamInSync May 14, 2026
f1ef0c4
qblas_set_num_threads no longer mutates OMP global; only caps qblas i…
SwayamInSync May 14, 2026
222cc2d
add meson.build for use as subproject in numpy-quaddtype
SwayamInSync May 14, 2026
ee907c7
meson.build: resolve sleef via fallback so it works when used as quad…
SwayamInSync May 14, 2026
d009edf
meson: force subproject SLEEF when invoked as subproject (avoid syste…
SwayamInSync May 14, 2026
be7c7f0
trim narrative comments throughout the codebase
SwayamInSync May 14, 2026
16c32c9
fix macOS CI: silence non-OpenMP warnings and pick venv numpy
SwayamInSync May 14, 2026
0e379c1
test_against_numpy: load .dylib on macOS; add macos-15 to CI
SwayamInSync May 14, 2026
b41da45
test_against_numpy: discover lib filenames from the directory
SwayamInSync May 14, 2026
aa0dc9f
test_against_numpy: put SLEEF on loader path, drop manual lookup
SwayamInSync May 14, 2026
212abf5
qblas_cpu: define _DARWIN_C_SOURCE so macOS exposes _SC_NPROCESSORS_ONLN
SwayamInSync May 14, 2026
e260297
meson uses subproject sleef
SwayamInSync May 14, 2026
e799661
fix macos15 ci
SwayamInSync May 14, 2026
b133efe
fix typos in perf-doc filenames (comparson->comparison, performace->p…
SwayamInSync May 14, 2026
320c2dd
fix README links to renamed perf docs
SwayamInSync May 14, 2026
989731b
stop tracking .pyc files; add __pycache__/ to gitignore
SwayamInSync May 14, 2026
fd09728
drop orphaned OpenBLAS submodule (never referenced in build)
SwayamInSync May 14, 2026
ddc5aaf
release: bump version to 1.5.0
SwayamInSync May 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 97 additions & 76 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,115 +1,136 @@
name: QuadBLAS Testing
name: QBLAS CI

on:
push:
branches: [main]
branches: [main, overhaul-rewrite]
pull_request:
workflow_dispatch:

jobs:
test-ubuntu:
name: Test Ubuntu
runs-on: ubuntu-latest
test:
name: ${{ matrix.os }} / ${{ matrix.compiler }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
compiler: gcc
cc: gcc
cxx: g++
- os: ubuntu-latest
compiler: clang
cc: clang
cxx: clang++
- os: macos-14
compiler: clang
cc: clang
cxx: clang++
- os: macos-15
compiler: clang
cc: clang
cxx: clang++

steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install dependencies
- name: Install dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y cmake build-essential libomp-dev
sudo apt-get install -y cmake ninja-build build-essential libomp-dev python3 python3-pip python3-venv
if [ "${{ matrix.compiler }}" = "clang" ]; then
sudo apt-get install -y clang libomp-dev
fi

- name: Build SLEEF
- name: Install dependencies (macOS)
if: runner.os == 'macOS'
run: |
git clone --branch 3.8 --depth 1 https://github.com/shibatch/sleef.git
cd sleef
cmake -S . -B build -DSLEEF_BUILD_QUAD=ON -DSLEEF_BUILD_SHARED_LIBS=ON
cmake --build build --parallel $(nproc)
sudo cmake --install build --prefix /usr/local
brew install cmake ninja libomp python@3.12

- name: Build QuadBLAS
- name: Install numpy >= 2.0
run: |
python3 -m venv .venv
.venv/bin/pip install --upgrade pip
.venv/bin/pip install 'numpy>=2.0'
echo "$(pwd)/.venv/bin" >> $GITHUB_PATH
# Make cmake's find_package(Python3) prefer the venv on macOS,
# where it otherwise picks the framework python without numpy.
echo "VIRTUAL_ENV=$(pwd)/.venv" >> $GITHUB_ENV

- name: Build SLEEF with full SIMD coverage
env:
SLEEF_ROOT: /usr/local
CC: ${{ matrix.cc }}
CXX: ${{ matrix.cxx }}
run: |
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make -j$(nproc)
bash scripts/bootstrap_sleef.sh

- name: Run tests
- name: Configure qblas
env:
CC: ${{ matrix.cc }}
CXX: ${{ matrix.cxx }}
run: |
cd build
./quadblas_test
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release

- name: Run benchmarks
- name: Build qblas + tests + bench
run: cmake --build build --parallel

- name: Run correctness tests (C + Python-vs-numpy)
run: ctest --test-dir build --output-on-failure

- name: Smoke-run the benchmark (small problem sizes)
run: |
cd build
if [ -f "./quadblas_benchmark" ]; then
echo "Running Google Benchmark tests..."
./quadblas_benchmark --benchmark_min_time=0.1s --benchmark_filter="BM_DotProduct|BM_AXPY|BM_VectorNorm"
else
echo "Google Benchmark not available, running legacy benchmark..."
./quadblas_benchmark_legacy
fi
./build/bench/qblas_bench --benchmark_min_time=0.05s \
--benchmark_filter='BM_qdot/1024|BM_qaxpy/1024|BM_qgemm/64'

test-macos:
name: Test macOS ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-14, macos-15]
- name: Force-test each dispatch tier (Linux only)
if: runner.os == 'Linux'
run: |
for tier in generic sse2 avx2; do
echo "==== Testing tier: $tier ===="
QBLAS_DISPATCH=$tier ctest --test-dir build --output-on-failure
done

bench-baseline:
name: benchmark / ubuntu-gcc
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install dependencies
run: |
brew install cmake libomp
sudo apt-get update
sudo apt-get install -y cmake ninja-build build-essential libomp-dev

- name: Install SLEEF
env:
MACOSX_DEPLOYMENT_TARGET: "11.0"
run: |
git clone --branch 3.8 --depth 1 https://github.com/shibatch/sleef.git
cd sleef
cmake -S . -B build \
-DSLEEF_BUILD_QUAD=ON \
-DSLEEF_BUILD_SHARED_LIBS=ON \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
-DCMAKE_INSTALL_RPATH="@loader_path/../lib" \
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON
cmake --build build --clean-first -j
sudo cmake --install build --prefix /usr/local

- name: Build QuadBLAS
env:
SLEEF_ROOT: /usr/local
DYLD_LIBRARY_PATH: "/usr/local/lib:$DYLD_LIBRARY_PATH"
run: |
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make -j$(sysctl -n hw.ncpu)
- name: Build SLEEF
run: bash scripts/bootstrap_sleef.sh

- name: Run tests
env:
DYLD_LIBRARY_PATH: "/usr/local/lib:$DYLD_LIBRARY_PATH"
- name: Build qblas
run: |
cd build
./quadblas_test
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel

- name: Run benchmarks
env:
DYLD_LIBRARY_PATH: "/usr/local/lib:$DYLD_LIBRARY_PATH"
run: |
cd build
if [ -f "./quadblas_benchmark" ]; then
echo "Running Google Benchmark tests..."
./quadblas_benchmark --benchmark_min_time=0.1s --benchmark_filter="BM_DotProduct|BM_AXPY|BM_VectorNorm"
else
echo "Google Benchmark not available, running legacy benchmark..."
./quadblas_benchmark_legacy
fi
mkdir -p bench-results
OMP_NUM_THREADS=$(nproc) OMP_PLACES=cores OMP_PROC_BIND=close \
./build/bench/qblas_bench_compare \
--benchmark_min_time=0.2s \
--benchmark_repetitions=3 \
--benchmark_report_aggregates_only=true \
--benchmark_format=json > bench-results/qblas_bench.json
./build/bench/qblas_bench_compare \
--benchmark_min_time=0.2s \
--benchmark_repetitions=3 \
--benchmark_report_aggregates_only=true | tee bench-results/qblas_bench.txt

- uses: actions/upload-artifact@v4
with:
name: qblas-bench-results
path: bench-results/
retention-days: 30
17 changes: 16 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,16 @@
build/
build/
build-*/
.sleef-prefix/
.cache/
compile_commands.json
*.o
*.a
*.so
*.so.*
*.dylib
*.pyc
__pycache__/
.DS_Store
.vscode/
Testing/
upstream-project/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "third_party/benchmark"]
path = third_party/benchmark
url = https://github.com/google/benchmark.git
[submodule "third_party/sleef"]
path = third_party/sleef
url = https://github.com/shibatch/sleef.git
108 changes: 108 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

QBLAS is a BLAS Level 1/2/3 library for IEEE-754 binary128 (quad-precision, `Sleef_quad`) built on top of SLEEF. It ships a compiled shared library `libqblas.so` with a CBLAS-style C API (`cblas_qdot`, `cblas_qgemm`, …) and runtime CPU dispatch (scalar / SSE2 / AVX2 / AVX-512 / NEON).

This repo was rewritten from scratch on the `overhaul-rewrite` branch; the older header-only template implementation on `main` is obsolete.

## Build

```bash
scripts/bootstrap_sleef.sh # builds SLEEF 3.8 with all SIMD paths
# and installs into ./.sleef-prefix/
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . --parallel
ctest # ~5 s, 5 test executables
./bench/qblas_bench --benchmark_min_time=0.1s
```

`scripts/bootstrap_sleef.sh` forces `SLEEF_ENABLE_{SSE2,SSE4,AVX,AVX2,AVX512F}=ON` so every dispatch tier the binary contains has its underlying SLEEF symbols available; SLEEF's default config only builds for the host CPU and leaves unresolved references for other tiers in `libsleefquad.so`.

The top-level CMake auto-discovers SLEEF via `SLEEF_ROOT` (env var or cmake var), then `./.sleef-prefix/`, then pkg-config. The binary RPATH is set with `--disable-new-dtags` so `libtlfloat.so.1` (an indirect dep of `libsleefquad.so`) resolves; without that, GLIBC's DT_RUNPATH semantics drop the path for transitive deps.

## Architecture

The library is a single shared object with multiple internal "kernel tiers", each compiled as an OBJECT library with its own `-march` flags. Runtime dispatch picks the best available tier at library init time.

```
include/qblas/ Public C API. Each cblas_q* declaration ties to a
Sleef_quad function (no template metaprogramming).
src/common/ Internal helpers (dispatch table, threading,
aligned alloc, branch hints).
src/cpu/ CPUID-based feature detection + dispatch init.
Reads QBLAS_DISPATCH=avx2|sse2|generic to override.
src/kernels/ One TU per tier (generic/sse2/avx2/avx512/neon).
Each defines QV_WIDTH + QV_ISA_SUFFIX and includes
kernels_template.h, which contains the actual SIMD
kernels parametrised by width and ISA suffix.
src/level1/ Front-end for vec ops (delegates to dispatch + threads).
src/level2/ Front-end for gemv/ger/symv/trsv.
src/level3/ qgemm.c (Goto-style blocking) + qblas_level3_extra.c
(syrk/trmm/trsm; the last two are reference, not yet
blocked).
tests/ Smoke + per-level correctness suites. Each test compares
against a naive scalar reference across positive and
negative strides, row/col-major layouts, all transpose
combos, and edge tile sizes (m=1, k=1, prime, multi-block).
bench/ Google Benchmark suite (BM_qdot, BM_qgemm, …).
items/sec ≈ quad FMA throughput.
cmake/QBLASArch.cmake Per-tier compile flags. Edit here to add new ISAs.
```

### Kernel template ([src/kernels/kernels_template.h](src/kernels/kernels_template.h))

A single header parametrised over `QV_WIDTH` (1, 2, 4, 8) and `QV_ISA_SUFFIX` (`sse2`, `advsimd`, …). For widths 1/4/8 there is only one SLEEF symbol set so the suffix is implicit; for width 2 we have to pick between `sse2` (x86) and `advsimd` (ARM) at the call site, which is why `QV_ISA_SUFFIX` is its own macro. Each ISA TU defines those macros and `#include`s the template — no source duplication.

The template implements: `qdot`, `qaxpy`, `qscal`, `qasum`, `qiamax`, `qgemv_n`, `qgemv_t`, and the packed `qgemm_kernel`. The corresponding `qblas_register_<tier>` function publishes its function pointers into the global dispatch table.

### GEMM ([src/level3/qblas_gemm.c](src/level3/qblas_gemm.c))

Goto / van de Geijn 5-loop blocking around a register tile published by the dispatch tier (MR×NR = 4×4 currently). Key correctness trick: **column-major C is computed as row-major C^T by swapping M↔N and A↔B with original (not flipped) transpose flags** — the buffer addresses come out identical because additions commute. This eliminated a separate col-major code path and a class of off-by-something bugs.

Parallelism is over the outermost `jc` loop. `nc` is auto-scaled down so each thread gets ≥2 blocks (`target_blocks = nthreads * 2`); without this, large square GEMMs ran single-threaded because `nc=QBLAS_NC_DEFAULT (512)` swallowed the whole problem.

### Threading

OpenMP. Each Level 1 entry point checks `qblas_resolve_threads()` (a work-vs-overhead heuristic) and only forks above `QBLAS_PARALLEL_THRESHOLD_L1 = 8192`. Reduction-style ops (`qdot`, `qasum`, `qnrm2`) sum per-thread partials into a stack array (max 256 threads); `qnrm2` is implemented as `sqrt(qdot(x,x))` so it inherits the threading for free.

### Dispatch override

Set `QBLAS_DISPATCH=generic|sse2|avx2|avx512|neon` to force a tier — useful for comparing per-tier throughput in the bench and for diagnosing tier-specific bugs. The auto-selected tier is printed by `qblas_get_dispatch_tier()`.

## Common gotchas

- **Negative strides**: BLAS lets `incx < 0` walk a vector in reverse. The convention here: the caller (in `cblas_q*` front-ends) shifts `x` to its logical first element via `neg_offset(incx, n)`. The kernel iterates `ix = 0; ix += incx` from there — **never re-applies** the offset. There's a class of bugs where the offset gets applied twice (caller + kernel), so always cross-check the kernel's strided branch.

- **SLEEF naming**: ULP-bounded ops (add, fma, mul, sqrt) use `Sleef_<op>q<W>_u05<isa>` (no underscore before isa) — e.g. `Sleef_fmaq4_u05avx2`. Non-ULP ops (splat, load, store, get, fabs, neg) use `Sleef_<op>q<W>_<isa>` — e.g. `Sleef_storeq2_sse2`. The template's `QV_NAME` macro encodes this.

- **`Sleef_storeq2` / `Sleef_loadq2` (no ISA suffix) are not exported** even though declared. Always use explicit-ISA suffixes for load/store.

- **`tlfloat.so` is not installed by SLEEF's `cmake --install`** even when it builds. The bootstrap script copies it manually into `.sleef-prefix/lib/`.

## Performance baseline (AMD EPYC 7V13, 96 cores, AVX2 tier)

| Routine | Size | Throughput (items/s, multi-thread) |
| ------- | ---- | --------- |
| qdot | 1 M | 836 M |
| qaxpy | 4 M | 2.15 G |
| qscal | 1 M | 1.90 G |
| qnrm2 | 4 M | 1.29 G |
| qasum | 1 M | 1.85 G |
| qgemv | 4096² | 935 M |
| qgemv_t | 4096² | 822 M |
| qgemm | 1024³ | **1.61 G FMA** |
| qsyrk | 1024² | 1.60 G FMA |

Single-thread GEMM peak is ~30 MFMA/s; the parallel speedup is ~55× across 96 cores (≈57 % efficiency). The remaining gap is mostly per-quad-FMA latency inside SLEEF's `Sleef_fmaq4_u05avx2` (software DD over AVX2 doubles), not our blocking — further gains would need a hand-written micro-kernel that interleaves the DD doubles across multiple FMAs.

## Repo layout reference

- [include/qblas/qblas.h](include/qblas/qblas.h) — umbrella public header (pulls in level1/2/3)
- [src/CMakeLists.txt](src/CMakeLists.txt) — `qblas_add_tier()` macro for per-ISA OBJECT libs
- [src/cpu/qblas_cpu.c](src/cpu/qblas_cpu.c) — CPUID detection + `__attribute__((constructor))` library init
- [src/kernels/kernels_template.h](src/kernels/kernels_template.h) — the generic SIMD kernel template
- [scripts/bootstrap_sleef.sh](scripts/bootstrap_sleef.sh) — required first-time setup
Loading
Loading