Install CLBlast on GitHub-hosted runners and prove a GEMM on it produces correct numbers before the job continues.
Installing a BLAS library is one package-manager line on Linux. Getting a correct one is harder, and getting it wrong is quiet in a way a linker cannot catch. On macOS, Homebrew's clblast bottle links Apple's OpenCL.framework and segfaults when loaded alongside the Khronos ICD loader. On any platform, CLBlastSgemm can return success and leave the output buffer holding garbage when the device's runtime kernel compilation fails. This action builds against the loader you actually installed, then runs a 64x64 SGEMM and compares it element-by-element against a reference computed on the host — checking the numbers, not a status code, is the only way to catch that failure mode.
name: test
on: [push, pull_request]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-24.04, macos-latest, windows-2025]
steps:
- uses: actions/checkout@v7
- uses: coatless-actions/setup-opencl@v1
- uses: coatless-actions/setup-clblast@v1
- run: |
# Windows runners ship mingw `gcc` rather than `cc`.
compiler=cc
command -v cc >/dev/null 2>&1 || compiler=gcc
"$compiler" -o demo demo.c $CLBLAST_CPPFLAGS $OPENCL_CPPFLAGS $CLBLAST_LIBS $OPENCL_LIBS
shell: bash
setup-clblast composes with coatless-actions/setup-opencl and must run after it. It fails closed — with an ::error:: naming the missing variable and telling you to add the setup-opencl step — when OpenCL_ROOT (or OpenCL_LIBRARY) is not already set in the job environment, rather than silently building against whatever loader it happens to find.
| Input | Description | Default |
|---|---|---|
version |
CLBlast version (git tag) to install. Empty means the tested default: the distribution package on Linux, 1.7.0 on macOS and Windows. Honored for a source build on any OS and for the Windows release asset; ignored, with a warning, for a Linux package install. CLBlast tags carry no v prefix — use 1.7.0, not v1.7.0. A non-default value requires a paired commit whenever this action builds from source. |
'' |
commit |
Commit SHA the version tag must resolve to, for a source build. Required alongside a non-default version whenever this action builds from source (always on macOS; on Linux only when source resolves to build); ignored otherwise. Empty means the commit pinned for the default version. |
'' |
source |
Where CLBlast comes from: auto, package, or build. auto resolves to package on Linux and Windows and build on macOS. package is rejected on macOS (Homebrew's bottle links Apple's framework and segfaults alongside the Khronos loader) and build is rejected on Windows (upstream already publishes a binary of the newest release). |
auto |
verify |
Compile and run a 64x64 SGEMM against the installed library and fail the job when the result is numerically wrong ('true' or 'false'). Never fatal when no OpenCL device is present. |
true |
cache |
Cache the macOS source-build tree and the Windows release archive ('true' or 'false'). No-op on Linux: the apt package install has nothing to cache, and a Linux source build recompiles every run regardless of this input. |
true |
source, verify, and cache are validated by hand, since the runner enforces neither input types nor allowed values. An empty string (for example verify: '', which is what an absent key in a build matrix expands to) is rejected rather than falling back to the default — the action cannot distinguish "omitted" from "explicitly empty". Supplying version without commit, or commit without version, is an error whenever the resolved source is build; a package install only warns and ignores both.
| Output | Description |
|---|---|
clblast-root |
Root directory of the installed CLBlast. |
clblast-include-dir |
Directory containing clblast_c.h. |
clblast-library |
Full path to the CLBlast library. |
clblast-version |
CLBlast version actually installed. |
source-used |
package or build. |
verify-status |
ok, wrong-result, no-platform, no-device, context-failed, queue-failed, buffer-failed, a gemm-status-N code, or skipped when verify is false. |
max-abs-error |
Largest absolute difference between the device GEMM and a host reference. Zero is the correct value, not a missing one — do not test it for truthiness. nan whenever verify-status is anything other than ok or wrong-result (no comparable result was produced); empty when verify is false and the check was skipped. |
platform-name |
CL_PLATFORM_NAME of the platform the GEMM ran on. Empty when no platform was found, and when verify is false. |
device-name |
CL_DEVICE_NAME of the device the GEMM ran on. Empty when no device was found, and when verify is false. |
clblast-module |
Fully resolved path of the CLBlast shared library the verifier was actually bound to — the .so/.dylib/.dll the loader mapped. On Linux and macOS that is the versioned file behind the symlink clblast-library names; on Windows it is the DLL rather than the import library. unknown when it could not be determined; empty when verify is false. |
opencl-module |
Fully resolved path of the OpenCL ICD loader the verifier was actually bound to. Compare against setup-opencl's OpenCL_LIBRARY. unknown when it could not be determined; empty when verify is false. |
clblast-module and opencl-module answer a question none of the other outputs do: where the verified GEMM ran. A correct GEMM is not by itself evidence that it used the library this action installed. On Linux the apt package's link flags are a bare -lclblast with no -L, so any libclblast on the default search path satisfies the link; on Windows the PE search order reaches the application directory and System32 before it reaches PATH, and Windows ships its own OpenCL.dll in System32. Either substitution produces perfectly correct numbers. If your job depends on the library it asked for, compare these two paths in resolved form (realpath, or cygpath -m -a folded to lower case on Windows) against clblast-library and OpenCL_LIBRARY — symlink chains, macOS /private prefixes and drive-letter case make a raw string comparison useless. tests/assert-module-identity.sh in this repository does exactly that and is usable as a reference.
The action also exports CLBLAST_CPPFLAGS, CLBLAST_LIBS, and CLBlast_ROOT to the job environment, plus CLBlast_DIR when a usable CLBlastConfig.cmake is available, and PKG_CONFIG_PATH on Linux and macOS. On Windows, CLBlast's bin directory (containing clblast.dll) is prepended to PATH. A caller's own explicit setting of CLBLAST_CPPFLAGS, CLBLAST_LIBS, CLBlast_ROOT, or CLBlast_DIR always wins over the action's; PKG_CONFIG_PATH is prepended to rather than replaced, because it is a search path a caller legitimately accumulates entries in.
Ubuntu's apt package lags upstream (see below). To get a specific fixed version, pair version with the exact commit that tag resolves to:
- uses: coatless-actions/setup-clblast@v1
with:
source: build
version: '1.6.3'
commit: '2a081972b20911ddf76a6b40df717c7d0c181268'
Find a tag's commit with git ls-remote --tags https://github.com/CNugteren/CLBlast <version>.
- uses: coatless-actions/setup-clblast@v1
with:
source: build
This is not cached — see cache in Inputs above — so it recompiles on every run.
- uses: coatless-actions/setup-clblast@v1
with:
verify: 'false'
- uses: coatless-actions/setup-clblast@v1
id: clblast
- if: steps.clblast.outputs.verify-status == 'ok'
run: ./run-blas-tests
| Runner | Source | Version | Support |
|---|---|---|---|
ubuntu-24.04, ubuntu-24.04-arm |
apt libclblast-dev |
1.6.2 | Tested on every push and pull request |
macos-latest |
source build | 1.7.0 | Tested on every push and pull request |
windows-2025, windows-2022 |
upstream release archive | 1.7.0 | Tested on every push and pull request |
ubuntu-26.04, ubuntu-26.04-arm |
apt libclblast-dev |
1.6.3 | Tested nightly, best effort |
ubuntu-22.04 |
apt libclblast-dev |
1.5.2 | Tested nightly — currently failing |
macos-15-intel |
source build | 1.7.0 | Tested nightly, best effort |
ubuntu-latest, macos-latest, windows-latest (floating labels) |
whatever auto resolves to on that image |
varies | Tested nightly, best effort. These labels move without notice |
windows-11-arm |
— | — | Not supported. Upstream publishes no Windows ARM64 build, and setup-opencl already rejects the platform |
ubuntu-22.04's CLBlast 1.5.2 is not verified working: the nightly job currently fails there because that image's PoCL runtime cannot build the GEMM kernel (clBuildProgram reports unknown target CPU 'generic', and the check reports verify-status=gemm-status--11). Do not treat that row as passing.
source: package is rejected on macOS and source: build is rejected on Windows. Both are deliberate: Homebrew's macOS bottle is unusable with the Khronos loader, and upstream already ships a Windows binary of the newest release.
Upstream fixed a GEMM correctness bug in 1.6.3: "Fixed a bug in the GEMMK=1 kernel (with 2D register tiling) when MWG!=NWG." The action installs 1.6.2 on ubuntu-24.04 anyway and emits a ::warning:: naming the fix.
That is a considered decision, not an oversight. The affected code path is only reachable on tuning-database entries with GEMMK=1 and MWG != NWG. Every CPU device a GitHub-hosted runner offers falls back to entries with GEMMK=0, which is the unaffected kernel — so the bug cannot execute there, and the action's own GEMM check would not detect it either way. Use source: build with a paired version/commit (see Examples above) to get 1.6.3 or newer if your workload needs the fix, such as on a self-hosted GPU runner.
Homebrew's clblast bottle links /System/Library/Frameworks/OpenCL.framework. Loading that bottle alongside the Khronos opencl-icd-loader that setup-opencl installs produces a SIGSEGV. source: package on macOS is therefore an error rather than a fallback.
If a Homebrew clblast keg is present on the runner for unrelated reasons, this action emits a ::warning::: build systems that probe Homebrew prefixes directly may find it instead of the library this action installed.
The build takes roughly 30 to 60 seconds cold on a hosted runner, and around 10 seconds when the tree is restored from cache — which it is by default.
Every Windows archive is checked against a pinned SHA-256 digest before extraction. CLBlast 1.7.0's digest comes from the GitHub Releases API's own digest field; older versions this action supports (currently 1.6.3) are verified against digests computed out of band and hardcoded in scripts/install-windows.ps1. A version with neither is refused rather than installed unverified — there is no fallback that skips the check.
Windows on ARM64 has no CLBlast archive upstream and is rejected outright with an actionable ::error::.
CLBlast ships no CLBlastConfigVersion.cmake in any channel, so find_package(CLBlast 1.7.0 REQUIRED) fails everywhere. Use the version-less form, and note that the imported target is lowercase clblast, not CLBlast::CLBlast:
find_package(CLBlast REQUIRED)
target_link_libraries(consumer PRIVATE clblast)
On Windows, upstream's shipped CLBlastConfig.cmake bakes the build machine's vcpkg tree into its interface properties (C:/vcpkg/packages/opencl_x64-windows/...), paths that do not exist on a runner. This action rewrites those two lines to point at the loader setup-opencl installed. If the rewrite cannot be verified safe, CLBlast_DIR is not exported and the CMake config itself is replaced with a stub that fails loudly at configure time, rather than leaving a broken import target silently reachable through CLBlast_ROOT. A ::warning:: names the version; use CLBLAST_CPPFLAGS and CLBLAST_LIBS instead in that case.
- Linux needs
apt-getand a C compiler (cc).source: buildadditionally needscmakeandgit, which the action installs on that branch. - macOS needs a C compiler (the Xcode Command Line Tools),
cmake, andgit— all preinstalled on everymacos-*image. - Windows needs
gccand 7-Zip, both preinstalled onwindows-2022andwindows-2025.
A C compiler and apt-get are preinstalled on every GitHub-hosted ubuntu-* image, but neither is guaranteed inside a container: job. Missing either fails the job with an ::error:: and remediation, not a bare command not found.
Pin the action by major version (@v1) for painless upgrades, by full tag (@v1.0.0) for strict reproducibility, or by SHA for the strictest pin.
AGPL-3.0. See LICENSE.