Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 26 additions & 11 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ optional Intel MKL-style API that drops into MKL's compact ecosystem.

## Build and test

The default configure builds the portable library and its BLAS-free tests and
needs nothing but CMake and a C++17 compiler:
The default configure builds the portable library and its tests. The library
needs nothing but CMake and a C++17 compiler; the tests validate against a
real LAPACKE + CBLAS (`cmake/FindLAPACKE.cmake`: OpenBLAS or Netlib without
MKL, `libopenblas-dev liblapacke-dev` / `liblapacke-dev liblapack-dev` on
Debian, MKL's own with the extension; `-DBLA_VENDOR=OpenBLAS|Generic` picks
one through CMake's FindLAPACK -- do that, since Debian's alternatives
symlinks let either stand in for the other):

```sh
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
Expand All @@ -18,8 +23,8 @@ ctest --test-dir build --output-on-failure
```

`-DCBK_WITH_MKL=ON` adds the MKL-style API, the MKL-backed suites (validated
against MKL's compact kernels and LAPACK/LAPACKE), the example and the
benchmarks; it needs Intel MKL (`sudo apt-get install libmkl-dev` on
against MKL's compact kernels; every suite's LAPACKE is then MKL's), the
example and the benchmarks; it needs Intel MKL (`sudo apt-get install libmkl-dev` on
Debian/Ubuntu). Run that build too before pushing changes to the kernels or
the wrappers: it is the one that cross-checks against MKL.

Expand Down Expand Up @@ -61,9 +66,12 @@ src/ the templated kernels (cbk_*_compact.hpp, one per routine, on the
and the owning MatrixBatch the tests, benchmarks and examples share
(internal: src/ is on their include path, but the public API stays
include/)
tests/ portable (no BLAS) and MKL-backed suites, templated on the scalar
type; test_compact_util.hpp / test_mkl_util.hpp hold the helpers and
the compact<T> / compat<T> / mkl<T> / lapack<T> dispatch structs
tests/ the portable C API's suites (vs LAPACKE/CBLAS on any stack) and
the MKL-backed ones, templated on the scalar type;
test_compact_util.hpp (compact<T>, the generators, pack/unpack),
test_lapack_util.hpp (lapack<T> and the ref_* procedures over
views, matmul, tri_apply, solve_errors) and test_mkl_util.hpp
(compat<T>, mkl<T>) hold the helpers and dispatch structs
(tests/install/: the install check's consumer project and script)
examples/ the worked solve and the benchmarks (BENCHMARKS.md), on bench_util.hpp
docs/ one design document per routine, the guides README.md indexes
Expand Down Expand Up @@ -175,10 +183,12 @@ workspace contract, or the benchmarks' threading.
- **Build and test with both gcc and clang before pushing.** CI runs both, and
the packs' alignment is exactly the kind of contract only one of them
enforces: both alignment faults so far (issue #34 and the one above) were
invisible to gcc. The default (portable) tree needs nothing but the compiler:
invisible to gcc. The default (portable) tree needs only the compiler and a
LAPACKE stack for the tests (CI runs OpenBLAS and Netlib under both
compilers, and Accelerate on macOS through `.github/workflows/macos.yml`):

```sh
CXX=clang++ cmake -S . -B build-clang && cmake --build build-clang && ctest --test-dir build-clang
CXX=clang++ cmake -S . -B build-clang -DBLA_VENDOR=OpenBLAS && cmake --build build-clang && ctest --test-dir build-clang
```

- **Threading over groups.** Every all-groups driver is a call to
Expand All @@ -201,10 +211,15 @@ workspace contract, or the benchmarks' threading.
library and its tests are one internal codebase and share these views on
purpose; what keeps the suites honest is that they compute the *answers*
independently -- scalar LAPACK references, dense LAPACK/MKL cross-checks.
The dense references are the library's: `test_lapack_util.hpp` hands a
view's `data` and leading dimension to LAPACKE/CBLAS (`is_rowmajor`,
`lapack_ld`, `lapack_layout`), reads a BLAS-3 operand stored the other way
round as its transpose, and stages the few LAPACKE operands that have no
transpose flag (`Staged`).
A routine that takes views (or batches) `assert`s what its contract
assumes: dimension compatibility across operands (`matmul`, `tri_apply`,
`solve_errors`, the scalar references), squareness where required
(`gen_spd`, `gen_tri`, `ref_potf2`, ...), and index ranges
`solve_errors`, the `ref_*` procedures), squareness where required
(`gen_spd`, `gen_tri`, `ref_potrf`, ...), and index ranges
(`MatrixView::operator()`, `MatrixBatch::operator[]`). Costs nothing in
Release, and the Debug suite run is what exercises it. `BatchView` is the
exception by construction -- it carries strides, not extents, so a kernel
Expand Down
9 changes: 9 additions & 0 deletions .claude/hooks/session-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
# libmkl-dev Intel MKL (see the Prerequisites section of README.md):
# the distro package, since Intel's own apt repo
# (apt.repos.intel.com) is blocked by the web network policy.
# libopenblas-dev, liblapacke-dev, liblapack-dev
# the two non-MKL LAPACKE stacks the test suites validate
# against (cmake/FindLAPACKE.cmake), so a session can run
# the tests with -DBLA_VENDOR=OpenBLAS and =Generic as CI
# does.
# libomp-dev clang's OpenMP runtime and its omp.h, which clang-tidy
# needs (.pre-commit-config.yaml explains).
# pre-commit the formatting and linting driver, with its hook
Expand Down Expand Up @@ -38,6 +43,9 @@ fi
# without them ever shows up.
missing=()
[ -f /usr/include/mkl/mkl_compact.h ] || missing+=(libmkl-dev)
dpkg -s libopenblas-dev >/dev/null 2>&1 || missing+=(libopenblas-dev)
dpkg -s liblapacke-dev >/dev/null 2>&1 || missing+=(liblapacke-dev)
dpkg -s liblapack-dev >/dev/null 2>&1 || missing+=(liblapack-dev)
dpkg -s libomp-dev >/dev/null 2>&1 || missing+=(libomp-dev)
command -v clang-format >/dev/null 2>&1 || missing+=(clang-format)
command -v clang-tidy >/dev/null 2>&1 || missing+=(clang-tidy)
Expand Down Expand Up @@ -68,5 +76,6 @@ else
fi

echo "session-start: build with 'cmake -S . -B build -DCBK_WITH_MKL=ON && cmake --build build && ctest --test-dir build' (MKL is off by default)"
echo "session-start: the tests validate against LAPACKE; without MKL pick the stack with -DBLA_VENDOR=OpenBLAS|Generic (default: the first found)"
echo "session-start: 'pre-commit run --all-files' checks the style; see .claude/CLAUDE.md"
echo "session-start: for clang-tidy, 'CXX=clang++ cmake -S . -B build-tidy' then 'pre-commit run --hook-stage manual clang-tidy --all-files'"
26 changes: 16 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,47 @@ concurrency:

jobs:
build-and-test:
name: ${{ matrix.cxx }} / MKL ${{ matrix.mkl }}${{ matrix.interface == 'ilp64' && ' (ilp64)' || '' }}
name: ${{ matrix.cxx }} / ${{ matrix.stack }}${{ matrix.interface == 'ilp64' && ' (ilp64)' || '' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
cc: [gcc, clang]
mkl: ["ON", "OFF"]
# The LAPACKE + CBLAS stack the tests validate against, and the
# packages that provide it: MKL's own with the MKL extension (which
# libmkl-dev also supplies the Compact API for), else OpenBLAS or the
# reference LAPACK through CMake's FindLAPACK, picked by BLA_VENDOR
# (cmake/FindLAPACKE.cmake). Debian ships OpenBLAS's LAPACKE as
# Netlib's liblapacke, hence that package in both rows.
stack: [MKL, OpenBLAS, Generic]
interface: [lp64]
include:
- { cc: gcc, cxx: g++ }
- { cc: clang, cxx: clang++ }
- { stack: MKL, packages: libmkl-dev }
- { stack: OpenBLAS, packages: libopenblas-dev liblapacke-dev }
- { stack: Generic, packages: liblapacke-dev liblapack-dev libblas-dev }
# MKL's 64-bit integer interface: MKL_INT is part of the MKL-style
# API's ABI, so the wrappers and their suites must build and pass
# against it too (libmkl-dev ships both interface layers).
- { cc: gcc, cxx: g++, mkl: "ON", interface: ilp64 }
- { cc: gcc, cxx: g++, stack: MKL, packages: libmkl-dev, interface: ilp64 }
env:
CC: ${{ matrix.cc }}
CXX: ${{ matrix.cxx }}
steps:
- uses: actions/checkout@v4

# libmkl-dev supplies the Compact extension and MKL's LAPACK/LAPACKE, so
# the MKL-backed suites validate against real MKL + LAPACK. The portable
# (MKL OFF) build needs nothing beyond the C++17 toolchain the image ships.
- name: Install Intel MKL
if: matrix.mkl == 'ON'
- name: Install ${{ matrix.stack }}
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends libmkl-dev
sudo apt-get install -y --no-install-recommends ${{ matrix.packages }}

- name: Configure
run: >
cmake -S . -B build -G Ninja
-DCMAKE_BUILD_TYPE=Release
-DCBK_WITH_MKL=${{ matrix.mkl }}
-DCBK_WITH_MKL=${{ matrix.stack == 'MKL' && 'ON' || 'OFF' }}
-DBLA_VENDOR=${{ matrix.stack }}
-DMKLCompact_INTERFACE=${{ matrix.interface }}

- name: Build
Expand Down
88 changes: 88 additions & 0 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: macOS

# The test suites on Apple silicon against Accelerate: Apple's framework
# provides LAPACK and CBLAS but no LAPACKE, so the job first builds
# accelerate-lapacke (https://github.com/lepus2589/accelerate-lapacke, Netlib's
# LAPACKE over Accelerate's LAPACK interface; its configure needs a Fortran
# compiler, which Homebrew's gcc supplies) into its own prefix, then
# configures cbk with -DBLA_VENDOR=Apple (cmake/FindLAPACKE.cmake: Accelerate
# from CMake's FindLAPACK, lapacke.h and liblapacke from that prefix,
# <vecLib/cblas.h> from the SDK). macOS runners
# cost several times a Linux minute, so this is one job, not a matrix, and
# the LAPACKE build (several minutes, most of it configuring Netlib LAPACK's
# Fortran tree) is cached: the install prefix is restored by actions/cache
# under a key naming the pinned accelerate-lapacke release and the macOS
# SDK the dylib was linked against (its Accelerate stubs must match the
# system), so a runner image upgrade or a version bump rebuilds it and
# nothing else does.
on:
push:
branches: [main]
tags: ['v*']
pull_request:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
accelerate:
name: AppleClang / Accelerate
runs-on: macos-latest
env:
ACCELERATE_LAPACKE_REF: v2.0.0
steps:
- uses: actions/checkout@v4

- name: Identify the SDK
id: sdk
run: |
sdk=$(xcrun --show-sdk-version)
echo "path=$(xcrun --show-sdk-path)" >> "$GITHUB_OUTPUT"
echo "version=$sdk" >> "$GITHUB_OUTPUT"
echo "macOS $(sw_vers -productVersion), SDK $sdk"

# The install prefix, $RUNNER_TEMP/accelerate-lapacke-install below, is
# an absolute path without `.` or `..` segments: actions/cache rejects
# those and then saves nothing. (The runner context that names it is
# only available inside steps, so it is spelled out rather than an env.)
- name: Cache accelerate-lapacke
id: lapacke-cache
uses: actions/cache@v4
with:
path: ${{ runner.temp }}/accelerate-lapacke-install
key: accelerate-lapacke-${{ env.ACCELERATE_LAPACKE_REF }}-${{ runner.os }}-${{ runner.arch }}-sdk${{ steps.sdk.outputs.version }}

# accelerate-lapacke's project enables Fortran (Netlib's LAPACK build
# system does, even though only its C LAPACKE sources are compiled and
# the result links nothing but Accelerate). Its accelerate-lapack
# dependency insists on an explicit SDK (CMAKE_OSX_SYSROOT); the
# workflow preset takes no cache variables, so configure and build are
# two calls, and the install prefix overrides the preset's ~/.local.
- name: Build accelerate-lapacke
if: steps.lapacke-cache.outputs.cache-hit != 'true'
run: |
brew install gcc
export FC=$(ls "$(brew --prefix)"/bin/gfortran* | head -n 1)
"$FC" --version | head -n 1
git clone --depth 1 --branch "$ACCELERATE_LAPACKE_REF" https://github.com/lepus2589/accelerate-lapacke.git "$RUNNER_TEMP/accelerate-lapacke"
cd "$RUNNER_TEMP/accelerate-lapacke"
cmake --preset accelerate-lapacke32 \
-DCMAKE_OSX_SYSROOT="${{ steps.sdk.outputs.path }}" \
-DCMAKE_INSTALL_PREFIX="$RUNNER_TEMP/accelerate-lapacke-install"
cmake --build --preset accelerate-lapacke32-install
ls "$RUNNER_TEMP/accelerate-lapacke-install/include/lapacke.h" "$RUNNER_TEMP/accelerate-lapacke-install/lib/"

- name: Configure
run: >
cmake -S . -B build
-DCMAKE_BUILD_TYPE=Release
-DBLA_VENDOR=Apple
-DCMAKE_PREFIX_PATH="$RUNNER_TEMP/accelerate-lapacke-install"

- name: Build
run: cmake --build build -j

- name: Test
run: ctest --test-dir build --output-on-failure
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ All notable changes to cbk are recorded here. The format follows
numbers [Semantic Versioning](https://semver.org/): until 1.0.0, a minor
release may change the API.

## [Unreleased]

### Changed

- Tests: every suite validates against a real LAPACKE + CBLAS stack
(OpenBLAS, Netlib, or MKL's own with the MKL extension;
`cmake/FindLAPACKE.cmake` over `find_package(LAPACK)` and `BLA_VENDOR`)
instead of hand-rolled
scalar references (issue #27). The dense-LAPACK cross-checks of the MKL
suites run in the portable suites, on any stack; the unpivoted LDL^T keeps
its scalar reference, itself validated against CBLAS. Building the tests
now needs such a stack; the library does not. A macOS workflow tests
against Accelerate through accelerate-lapacke.

## [0.1.0] - 2026-09-13

The first release: batched QR, Cholesky and unpivoted LDL^T factorizations,
Expand Down
31 changes: 22 additions & 9 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,16 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
endif()

# C++17, no GNU extensions (the kernels' vector_size attribute compiles under
# strict -std=c++17). Optimization flags are the caller's to choose; for a
# performance build pass them through CMAKE_CXX_FLAGS, e.g.
# strict -std=c++17), for every target here: the library's own requirement
# below is PRIVATE (its headers are C), so nothing would pass it to the
# tests, examples and benchmarks, and AppleClang's default standard is older
# than gcc's and clang's on Linux. Optimization flags are the caller's to
# choose; for a performance build pass them through CMAKE_CXX_FLAGS, e.g.
# cmake -S . -B build -DCMAKE_CXX_FLAGS="-O3 -march=native"
# so the SIMD kernels target the host's widest vectors.
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# OpenMP threads the group loop inside every routine (see the threading note
# in src/cbk_common.hpp); the benchmarks use it for their outer loops.
Expand All @@ -42,7 +48,7 @@ endif()
# ---------------------------------------------------------------------------
# The library: one target, cbk (cbk::cbk). By default the portable C API of
# include/cbk.h (no BLAS, no MKL), which is what a default configure
# builds and tests. Static by default; -DBUILD_SHARED_LIBS=ON builds it shared.
# builds and tests (the tests, not the library, need a LAPACKE stack). Static by default; -DBUILD_SHARED_LIBS=ON builds it shared.
# ---------------------------------------------------------------------------
add_library(cbk src/cbk.cpp)
target_include_directories(cbk
Expand Down Expand Up @@ -109,23 +115,30 @@ endif()
if(CBK_BUILD_TESTS)
enable_testing()

# Portable self-contained suites (no BLAS): each kernel vs a scalar reference
# of the same unblocked algorithm, plus the C API's LAPACK-style validation.
# The dense references of every suite come from a real LAPACKE + CBLAS
# (tests/test_lapack_util.hpp): MKL's own with the MKL extension, otherwise
# whatever CMake's FindLAPACK picks under BLA_VENDOR (OpenBLAS, Generic for
# Netlib, Apple) plus its LAPACKE (cmake/FindLAPACKE.cmake).
find_package(LAPACKE REQUIRED)

# The portable C API's suites: each kernel vs LAPACKE/CBLAS (its unblocked
# LAPACK counterpart elementwise where that is the same algorithm, residual
# and invariant gates elsewhere), plus the C API's LAPACK-style validation.
foreach(routine geqrf ormqr orgqr potrf sytrfnp trsm gels)
add_executable(test_${routine}_compact tests/test_${routine}_compact.cpp)
target_link_libraries(test_${routine}_compact PRIVATE cbk)
target_link_libraries(test_${routine}_compact PRIVATE cbk LAPACKE::LAPACKE)
target_include_directories(test_${routine}_compact
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
add_test(NAME portable_${routine} COMMAND test_${routine}_compact)
endforeach()

if(CBK_WITH_MKL)
# MKL + dense-LAPACK validation of each cbk_?*_compact routine (the
# design docs' section 7): invariants vs LAPACK, cross-checks vs MKL's own
# The MKL-style API's suites (the design docs' section 7): the compat
# entry points through MKL's own pack/unpack, cross-checked against MKL's
# compact kernels, and end-to-end solves.
foreach(routine geqrf ormqr orgqr potrf sytrfnp trsm gels)
add_executable(test_${routine}_mkl tests/test_${routine}_mkl.cpp)
target_link_libraries(test_${routine}_mkl PRIVATE cbk MKL::Compact)
target_link_libraries(test_${routine}_mkl PRIVATE cbk MKL::Compact LAPACKE::LAPACKE)
target_include_directories(test_${routine}_mkl
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
add_test(NAME mkl_${routine}_suites COMMAND test_${routine}_mkl)
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ groups of `V` matrices with OpenMP (`-DCBK_WITH_OPENMP=OFF` disables it).

## Getting started

Requires CMake >= 3.28 and a C++17 compiler (GCC/Clang); nothing else.
Requires CMake >= 3.28 and a C++17 compiler (GCC/Clang); the library needs
nothing else, and the tests a LAPACKE + CBLAS stack (OpenBLAS, Netlib or MKL)
to validate against.

```sh
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -march=native"
Expand Down
Loading
Loading