Skip to content

arm64: NEON BFDOT sbgemm kernel for VORTEX (Apple M1-M3) - #6145

Open
0tomhoyt wants to merge 4 commits into
OpenMathLib:developfrom
0tomhoyt:sbgemm-bfdot
Open

0tomhoyt wants to merge 4 commits into
OpenMathLib:developfrom
0tomhoyt:sbgemm-bfdot

Conversation

@0tomhoyt

Copy link
Copy Markdown

Fills the last big sbgemm gap on the Apple targets: VORTEX (M1-M3) so far ran sbgemm on the generic C fallback, ~1.7 GFLOPS on an M3 Pro. The new kernel uses NEON BFDOT (FEAT_BF16), probed at run time via hw.optional.arm.FEAT_BF16 so cores without it (early M1) keep the plain-loop fallback — no regression path, and GCC builds keep the generic kernels as before.

Stacked on #6142 (it reuses and renames that PR's bfloat16 packing copies to role-based names); merge #6142 first and this applies cleanly on top.

Design

  • SBGEMM_UNROLL_M = SBGEMM_UNROLL_N = 8: with the two unrolls equal the driver routes the A side through the O copies as well (common_sb.h), so both packed panels come in the same power-of-two grouping and the kernel walks them symmetrically.
  • One BFDOT does a two-wide dot per lane, so rows l and l+1 of the [k][8] panels are zipped into the lane layout (i, k&1): the B pair of a column is duplicated across the lanes, the A pairs cover four rows each, and acc[n][h] holds four consecutive rows of C column n — one quad load/store per (column, row group). Sixteen quad accumulators, i.e. an 8×8 C block per call.
  • An odd k feeds a zero vector as the second row; blocks narrower than eight rows or columns (and the no-FEAT_BF16 fallback) run in scalar code on the same packed layout.

Measurements (M3 Pro, 11 threads, best of 5)

shape develop (generic) this PR speedup
512³ 1.6 GFLOPS 31.5 20x
1024³ 1.7 57.4 34x
2048×2048×1024 1.8 113.6 63x
64×2000×256 1.4 124.8 91x

For context, fp32 sgemm of the same build reaches 305/403 GFLOPS at the last two shapes, so bf16 lands at ~28% of fp32 — the 8×8 block and the scalar packing copies leave tuning headroom (wider blocks, vectorized transpose in the copies) that can follow once the layout is in place.

Verification

  • 39,116-case randomized cblas_sbgemm sweep (all transpose combinations, m/n/k 1–257 plus 1000–1200 sized cases, α/β variants, odd strides) against a double-precision reference: 0 failures, same as develop — and unlike arm64: SME2 bf16 sbgemm kernel for VORTEXM4 (Apple M4) #6142 this was validated with the vector path actually executing (the M3 Pro has FEAT_BF16).
  • Static TARGET=VORTEX, DYNAMIC_ARCH=1 (correctness re-run through the runtime-detected VORTEX table entry: 0 failures), and CMake all build and pass.

Add an SME2 sbgemm (bf16 x bf16 -> fp32) kernel in the style of
gemm_kernel_sme2.c, for a 512-bit streaming vector length:

- BFMOPA ZA32 with .H operands accumulates 2-wide dot products, so the
  kernel zips rows l and l+1 of the packed [k][m] and [k][n] panels
  into the lane layout 2*i + (k & 1); a missing second row of an odd k
  is loaded under a false predicate, which zero-fills. C accumulates
  in four ZA32 tiles of 16x16 fp32 via the helpers of
  sme2_gemm_tile.h (SBGEMM_UNROLL_M 64, SBGEMM_UNROLL_N 16).
- Custom bfloat16 A and B copies (sbgemm_{n,t}copy_{64,16}_sme2.c)
  emit [k][w] panels for both operand layouts, in 64-row chunks on
  the M side and power-of-two groups on the N side, like the A copies
  of the fp32 SME2 kernels (the generic copies emit column-major
  groups on the T side).
- Without SME2 at run time, or in the compiler, plain loops run on
  the same packed layout.

Also fix a crash of the existing SME2 kernels on cores without
FEAT_SME2 in s2_usable(): clang if-converts the conjunction and
speculates svcntsw() (RDSVL, undefined without FEAT_SME) above the
sysctl test. Reading the vector length through a noinline helper with
inline asm prevents that.
Only the UNROLL values are specific to the SME2 kernel (the packed
panel layout); P, Q and R stay at the shared defaults, which are
compatible with the 64-row M chunks.
The edit that added the SBGEMM P/Q/R overrides to the VORTEXM4 block
matched on the SBGEMM UNROLL undef lines, which occur in several
other cpu blocks as well, so the undefs landed in four of them and
broke their setparam compilation under DYNAMIC_ARCH (SBGEMM_DEFAULT_P
undeclared, e.g. for NEOVERSEV1 with GCC). This is what the review
comment about unrelated cpus was about; the overrides themselves were
already dropped in 0bc31e8.
sbgemm on the Apple targets so far ran the generic C fallback; with
FEAT_BF16 (present at least since M2, probed at run time so older
cores keep the fallback) the new kernel accumulates C in sixteen quad
registers of eight columns by two row groups. One BFDOT does a
two-wide dot per lane, so rows l and l+1 of the packed panels are
zipped into the lane layout (i, k & 1); the B pair of a column is
duplicated across the lanes and the A pairs cover four rows each. An
odd k is a zero vector, blocks narrower than eight run in scalar code.

With SBGEMM_UNROLL_M = SBGEMM_UNROLL_N the driver routes the A side
through the O copies as well, so both panels come in the power-of-two
grouping of OpenMathLib#6142's copies, which this renames to role-based names
(oncopy/otcopy for both sides, ncopy/tcopy kept for the M-side-only
use of the M4 SME2 target).

On an M3 Pro: 39116-case randomized sbgemm sweep against a
double-precision reference passes unchanged, and against the generic
fallback of develop the packed path is 34x at 1024^3 (57 vs 1.7
GFLOPS) and 63x at 2048x2048x1024 (114 vs 1.8 GFLOPS), about a
quarter of the fp32 NEON sgemm of the same build.

This branch has not been deployed

No deployments
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