Skip to content

perf(backend-cpu): dense FP32 products through the platform BLAS, on by default on macOS - #1449

Merged
michalharakal merged 3 commits into
SKaiNET-developers:developfrom
michalharakal:feat/jvm-accelerate-sgemm
Oct 25, 2026
Merged

michalharakal merged 3 commits into
SKaiNET-developers:developfrom
michalharakal:feat/jvm-accelerate-sgemm

Conversation

@michalharakal

Copy link
Copy Markdown
Contributor

Summary

Dense FP32 products on the JVM can go to the platform BLAS, and on macOS they do by default: Accelerate's SGEMM runs on the matrix units of Apple silicon and threads itself, several times what any NEON kernel reaches per core. Everywhere else the default stays off and nothing changes.

Motivation: the x86-64 work (#1442, #1445) left the arm64 FP32 kernels where x86 was — an untiled 4-lane loop for A · B and a single latency-bound chain for x · Wᵀ. On a Mac the fastest fix is not a NEON tile but the BLAS the platform ships; the NEON tiles (for Android, iOS and Linux arm64) follow in a separate PR.

What was wrong

JvmBlas never reached a BLAS. It looked the framework up by the bare name "Accelerate", which dlopen cannot resolve (a framework lives under /System/Library/Frameworks), and passed MemorySegment.ofArray heap segments to a downcall, which JDK 21 rejects. Both failures were swallowed by runCatching, so skainet.cpu.blas.enabled=true was a silent no-op.

What changes (skainet-backend-cpu, JVM only)

  • JvmBlas rewritten: resolves cblas_sgemm from -Dskainet.cpu.blas.library / SKAINET_CPU_BLAS_LIBRARY (path or soname), then the Accelerate framework by path on macOS, then the common Linux sonames (libopenblas.so.0, libblas.so.3, libcblas.so.3); libraryDescription says which one. Two faces: sgemm(transA, transB, …) over off-heap segments with byte offsets and leading dimensions, and a heap face for Fp32MatmulKernel-shaped operands that stages its arrays (as the native heap kernel does) and writes only the m × n block.
  • Three products route to it when blasEnabled and above a small-work floor (2¹⁸ multiply-adds):
    • A · B on heap arrays, now with offsets (slab windows) — replaces the dead block that required offset 0 and 67 M multiply-adds;
    • A · B on two segment-backed operands, read in place;
    • the direct x · Wᵀ on a resident dense weight: sgemm with B transposed and ldb = k reads the [out, in] weight where it lies, the activation is staged (m × k) — taken before the schedule split, since Accelerate threads itself.
  • JvmCpuBackendConfig.blasEnabled defaults to true on macOS (os.name), false elsewhere; the property and env override either way. Documented in perf/jvm-cpu.adoc.
  • Test task forwards skainet.cpu.blas.enabled / .library to the forked JVM so a suite can be run against a BLAS.
  • CHANGELOG ### Changed.

Numerics: a BLAS orders its sums as it likes, so results move within rounding against the bundled kernels — the same category as the lane differences already accepted between x86, NEON and wasm. Within one host the choice is fixed per process.

Tests

New JvmBlasTest (runs wherever a cblas_sgemm resolves, reports itself skipped otherwise):

  • segment face, both transposes, four shapes incl. 63 × 1021 × 1023 and k = 0, against a double reference;
  • heap face with offsets and strides, padding untouched;
  • the three routed ops products with BLAS on and off agree with the reference and with each other.

Run here on Linux against the reference BLAS (libblas.so.3, the same cblas ABI Accelerate exports):

  • skainet-backend-cpu:jvmTest with -Dskainet.cpu.blas.enabled=true -Dskainet.cpu.blas.library=/lib/x86_64-linux-gnu/libblas.so.3 — 495 tests, 0 failures (the macOS proxy: every product in the suite went through BLAS);
  • skainet-backend-cpu:jvmTest with defaults — 495 tests, 0 failures; apiCheck green (no public API change).

Not verified here

No Apple hardware in this session and the macOS CI leg does not run jvmTest. The Accelerate path (framework resolution by path, and the speed) needs a run on a Mac: ./gradlew :skainet-backends:skainet-backend-cpu:jvmTest --tests '*JvmBlasTest*' should print [blas] /System/Library/Frameworks/Accelerate.framework/Accelerate, and -Dskainet.cpu.blas.enabled=false restores the bundled kernels for an A/B.

…by default on macOS

JvmBlas never reached a BLAS: it looked the Accelerate framework up by
its bare name, which dlopen cannot resolve, and passed heap arrays to a
downcall, which JDK 21 rejects, so skainet.cpu.blas.enabled was a no-op.
It now resolves cblas_sgemm from skainet.cpu.blas.library (path or
soname), the Accelerate framework by path on macOS, or the common Linux
sonames, and has an off-heap face with both transposes.

Three products use it when blasEnabled and above a small-work floor:
A·B on heap arrays (staged, offsets allowed), A·B on two segment-backed
operands (in place) and the direct x·Wᵀ on a resident dense weight
(sgemm with the weight transposed, no copy of it), ahead of the schedule
split since the BLAS threads itself. On macOS the default is now on —
Accelerate's SGEMM runs on the matrix units of Apple silicon, several
times the NEON kernel's throughput; -Dskainet.cpu.blas.enabled=false
restores the bundled kernels. Elsewhere the default stays off.

JvmBlasTest runs wherever a cblas_sgemm resolves and reports itself
skipped otherwise; the test task forwards the BLAS properties so a suite
can be run against a BLAS. Verified here against the reference BLAS on
Linux (same cblas ABI); the Accelerate path itself needs a Mac.
@michalharakal
michalharakal merged commit 59254d7 into SKaiNET-developers:develop Oct 25, 2026
20 checks passed
@michalharakal
michalharakal deleted the feat/jvm-accelerate-sgemm branch October 25, 2026 16:55
@michalharakal michalharakal mentioned this pull request Oct 25, 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.

2 participants