Skip to content

RISC-V: unroll the ZVL256B SGEMM kernel K-loop by two - #6054

Open
6eanut wants to merge 1 commit into
OpenMathLib:developfrom
6eanut:riscv64-sgemm-kunroll2x
Open

6eanut wants to merge 1 commit into
OpenMathLib:developfrom
6eanut:riscv64-sgemm-kunroll2x

Conversation

@6eanut

@6eanut 6eanut commented Sep 17, 2026

Copy link
Copy Markdown

Summary

Unroll the K-loop of the ZVL256B SGEMM micro-kernel by two, with the two A
vectors loaded before the FMA stream (A double-buffering).

The main pass previously advanced one k-step per iteration: eight B scalar
loads, one 16-lane A load, eight accumulating FMAs, then the B/A pointer
bumps and the loop backedge. Folding two k-steps into one iteration halves
the k counter, one of the two pointer bumps and the backedge, and issues both
A loads up front so the A-load latency is exposed earlier.

The first k-step is already peeled ahead of the loop (the vfmul calls that
initialize the accumulators), so the restructured main pass is

for (k = K; k > 2; k -= 2) { /* two k-steps */ }
for (; --k; )              { /* one k-step  */ }

which consumes exactly K steps for every K. Each accumulator still consumes
the k-steps in ascending order, so the per-column accumulation order is
unchanged and the emitted results are bit-identical to the previous kernel.

Performance

benchmark/cblas_sgemm.goto, one X100 core at 2.2 GHz, single thread,
warm-up once and the median of three runs per size:

Size Baseline Patched Change
256 22926.65 24102.74 +5.13%
512 24957.03 26344.61 +5.56%
1024 24306.48 25139.09 +3.43%

Testing

  • make tests returns 0: 125/125 utests, 1473/1473 extension tests, no new
    CBLAS failure.
  • In a default RISCV64_ZVL256B build (USE_TRMM=1) the source is compiled
    once, for SGEMM (STRMM is built from its own strmm_kernel_16x8_zvl256b.c).
    The Makefile's USE_TRMM=0 branch instead reuses this source for the four
    STRMM variants, so the patched file was compiled in those four
    -DTRMMKERNEL configurations too; all build with a warning set unchanged
    from the unpatched tree.
  • Differential test against a double-precision reference under QEMU: 360
    packed-kernel invocations plus 20400 cblas_sgemm cases, covering the M/N/K
    tail and edge paths, all four transpose combinations and several alpha/beta
    values, produce output bit-identical to the unpatched kernel.

The ZVL256B SGEMM main pass advanced one k-step per iteration: each
iteration loaded eight B scalars and one 16-lane A vector, issued eight
accumulating FMAs, then bumped the B and A pointers and took the loop
backedge. On the tall-and-narrow shapes this kernel is used on, that
per-k loop control is a measurable fraction of the iteration.

Fold two consecutive k-steps into one iteration. Both A vectors are
loaded before the FMA stream (A double-buffering), so the A-load latency
is exposed earlier, and the k counter, one of the two B/A pointer bumps
and the backedge are halved. Each accumulator still consumes the two
k-steps of a pair in ascending order, so the per-column accumulation
order is unchanged.

The peeled first k-step (the initial vfmul, ahead of the loop) is left
alone, so the restructured main pass reads

    for (k = K; k > 2; k -= 2) { ... two k-steps ... }
    for (; --k; )               { ... one k-step ...  }

which consumes exactly K steps for every K: the unrolled loop runs
floor((K-1)/2) times and the trailing loop takes the remainder. The
unrolled loop is skipped entirely when K <= 2, in which case the trailing
loop alone (or, for K = 1, the peeled step alone) finishes the pass.
Because the accumulation order itself is preserved rather than merely
re-summed, the emitted results are bit-identical to the previous kernel.

Measured on one X100 core at 2.2 GHz, cblas_sgemm, warm-up once and the
median of three runs per size: 256x256 +5.13%, 512x512 +5.56%,
1024x1024 +3.43%.

In a default RISCV64_ZVL256B build (USE_TRMM=1) this source is compiled once,
for SGEMM; STRMM is built from its own strmm_kernel_16x8_zvl256b.c. The
Makefile's USE_TRMM=0 branch instead reuses this source for the four STRMM
variants, so the patched file was also compiled in those four -DTRMMKERNEL
configurations: all build with a warning set unchanged from the unpatched
tree. Verified further by a differential run against a double-precision
reference: 360 packed-kernel invocations and 20400 cblas_sgemm cases,
covering the M/N/K tail and edge paths, all four transpose combinations and
several alpha/beta values, produce output bit-identical to the unpatched
kernel.

Co-authored-by: Yuansheng <yuansheng@isrc.iscas.ac.cn>
Co-authored-by: Ning Tian <tianning24@iscas.ac.cn>
Signed-off-by: jiakai xu <xujiakai2025@iscas.ac.cn>
@martin-frbg martin-frbg added this to the 0.3.35 milestone Sep 18, 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