Skip to content

riscv64: make RVV TRSM (_rvv_v1) kernels VLEN-agnostic#5928

Open
hmeiland wants to merge 1 commit into
OpenMathLib:developfrom
hmeiland:fix-rvv-trsm-vlen-agnostic
Open

riscv64: make RVV TRSM (_rvv_v1) kernels VLEN-agnostic#5928
hmeiland wants to merge 1 commit into
OpenMathLib:developfrom
hmeiland:fix-rvv-trsm-vlen-agnostic

Conversation

@hmeiland

Copy link
Copy Markdown
Contributor

Make the RVV _rvv_v1 TRSM kernels VLEN-agnostic

Problem

The kernel/riscv64/trsm_kernel_{LN,LT,RN,RT}_rvv_v1.c kernels tile packed-A along M by the runtime VSETVL_MAX, but the GEMM packing routine they interoperate with packs by the compile-time GEMM_UNROLL_M. When VSETVL_MAX != GEMM_UNROLL_M, the diagonal-block stride assumed by solve() no longer matches the packed layout, so the solve reads/writes the wrong blocks and the result is corrupted.

On x280 the two happen to coincide (both 16), so the currently shipping target is unaffected — but the kernels are not actually VLEN-agnostic. Enabling them on any target where VSETVL_MAX != GEMM_UNROLL_M (e.g. RISCV64_ZVL128B, which packs at unroll 8) produces wrong results. This is exactly what surfaces in #5830, which enables these kernels for ZVL128B.

Fix

Replace only the outer control flow so packed-A is tiled by GEMM_UNROLL_M (with a power-of-2 M remainder per N-block), matching the generic kernel/generic/trsm_kernel_{LN,LT,RN,RT}.c drivers. The RVV-vectorized solve() is retained unchanged. This makes the kernels correct on both unroll-8 (ZVL128B) and unroll-16 (x280) targets.

Validation (SpaceMiT X60, VLEN=256, GCC 14.3.0)

Residual test ‖op(A)·X − B‖ / ‖X·op(A) − B‖ over m,n ∈ {1,2,4,7,8,9,10,12,15,23} × every Side/Uplo/Trans/Diag.

Built as RISCV64_ZVL128B (unroll 8), TRSM wired to _rvv_v1:

Routine Cases Fails Worst residual
STRSM 1600 0 1.4e-7
DTRSM 1600 0 3.3e-16
CTRSM (incl. ConjTrans) 2400 0 3.0e-7
ZTRSM (incl. ConjTrans) 2400 0 5.5e-16

Regression contrast, same harness, same target:

Kernel DTRSM cases Fails Worst residual
Current _rvv_v1 (VSETVL_MAX tiling) 1600 408 0.89
This PR (GEMM_UNROLL_M tiling) 1600 0 3.3e-16

An unroll-16 (ZVL256B-params) build is being validated on the same hardware; I'll post the result as a follow-up. Since x280 already uses these kernels at unroll 16, the change is expected to be a no-op there (16 == 16) while hardening against future VLEN/UNROLL divergence.

Notes

Validated on SpaceMiT X60 hardware.

The trsm_kernel_{LN,LT,RN,RT}_rvv_v1.c kernels tiled packed-A along M by
the runtime VSETVL_MAX, but the GEMM packing routine they interoperate
with packs by the compile-time GEMM_UNROLL_M. When VSETVL_MAX differs
from GEMM_UNROLL_M the diagonal-block stride assumed by solve() no longer
matches the packed layout and the solve reads/writes the wrong blocks.

On x280 the two coincide (both 16) so the existing target is unaffected,
but the kernels are not actually VLEN-agnostic: enabling them on a target
where VSETVL_MAX != GEMM_UNROLL_M (e.g. RISCV64_ZVL128B, unroll 8)
corrupts the result.

Replace the outer control flow so packed-A is tiled by GEMM_UNROLL_M with
a power-of-2 M remainder per N-block, matching the generic
kernel/generic/trsm_kernel_{LN,LT,RN,RT}.c drivers. The RVV-vectorized
solve() is retained unchanged.

Validated on SpaceMiT X60 (VLEN=256): S/D/C/Z TRSM over a full
Side/Uplo/Trans/Diag grid, m,n in {1,2,4,7,8,9,10,12,15,23}.
  - ZVL128B (unroll 8): 8000/8000 cases pass; the original kernels
    fail 408/1600 DTRSM (worst residual 0.89).
@hmeiland

Copy link
Copy Markdown
Contributor Author

✅ Unroll-16 (ZVL256B) confirmation — VLEN-agnostic proven

Follow-up to the note in the PR description. I built the same fix as RISCV64_ZVL256B (GEMM_UNROLL_M = 16, -march=rv64imafdcv_zvl256b) on the SpaceMiT X60 and re-ran the full residual harness. Same test matrix as before (m,n ∈ {1,2,4,7,8,9,10,12,15,23} × every Side/Uplo/Trans/Diag).

Routine Cases Fails Worst residual
STRSM 1600 0 1.8e-7
DTRSM 1600 0 3.3e-16
CTRSM (incl. ConjTrans) 2400 0 3.6e-7
ZTRSM (incl. ConjTrans) 2400 0 6.7e-16

Combined with the ZVL128B (unroll 8) results in the description, this confirms the kernels are correct at both GEMM_UNROLL_M = 8 and GEMM_UNROLL_M = 16 — i.e. genuinely VLEN/UNROLL-agnostic rather than relying on the VSETVL_MAX == GEMM_UNROLL_M coincidence. Since x280 already uses these kernels at unroll 16, this is the expected no-op there while removing the latent divergence hazard.

Marking ready for review.

Validated on SpaceMiT X60 hardware, GCC 14.3.0.

@hmeiland
hmeiland marked this pull request as ready for review July 18, 2026 22:53
@hmeiland

Copy link
Copy Markdown
Contributor Author

Performance check — no regression (SpaceMiT X60, RVV 1.0, VLEN=256)

I benchmarked the fixed kernels against the original _rvv_v1 kernels on hardware to confirm the VLEN-agnostic rewrite does not cost throughput. Harness is benchmark/trsm.c, single-thread, L/U/N/N, OPENBLAS_LOOPS=20, best-of-3 runs on an 8-core X60 @ EESSI GCC 14.3.0, both libs built ZVL128B (GEMM_UNROLL_M=8).

Δ = fixed vs original, MFlops:

size STRSM DTRSM CTRSM ZTRSM
8 +4.3% +4.3% −3.0% +0.3%
16 +32.6% +0.2% +31.9% +2.6%
32 +40.9% +0.4% +29.2% +1.6%
64 +35.6% +1.4% +21.4% +1.5%
128 +26.0% ≈0% +8.6% −2.6%
256 +17.3% −3.9% +6.6% −4.2%
400 +11.5% −6.9% −0.8% −1.5%
  • STRSM / CTRSM: consistent speedups (+10–40%), largest at small/mid sizes — GEMM_UNROLL_M tiling reuses the packed diagonal block better than the per-call VSETVL_MAX retiling.
  • DTRSM / ZTRSM: within measurement noise (±~4%) on this loaded shared board. The scattered small negatives at large sizes sit inside the observed run-to-run variance — a 6-rep re-measure at DTRSM m=128 gave overlapping distributions (orig 1273–1412, fixed 1257–1371; equal medians), so the earlier single-run negatives there were jitter, not a systematic slowdown.

Important caveat: on ZVL128B the original _rvv_v1 kernels are numerically broken (this is the bug the PR fixes — e.g. 408/1600 DTRSM cases fail, worst residual ~0.89), so their timings are not a legitimate baseline. The fix makes ZVL128B correct for the first time while being at least as fast, and usually faster, than the broken code it replaces. On ZVL256B / x280 (VSETVL_MAX == GEMM_UNROLL_M == 16) the tile width is unchanged, so the rewrite is a structural no-op for throughput there.

Correctness: 8000/0 pass on both ZVL128B (unroll 8) and ZVL256B (unroll 16) across S/D/C/Z.

hmeiland added a commit to hmeiland/benchmarks that referenced this pull request Jul 19, 2026
…EN-fix sweep + perf

Rename dgemm/ -> OpenBLAS/ (it verifies the whole OpenBLAS RISC-V backend, not
just DGEMM). Add verify_ctrsm.c, the full-parameter TRSM correctness sweep used
to validate the VLEN-agnostic _rvv_v1 fix (OpenMathLib/OpenBLAS#5928):
408/1600 -> 8000/0 on ZVL128B, also 8000/0 on ZVL256B. README gains a TRSM
section (root cause, correctness table, fix summary, no-regression perf table
with broken-baseline caveat). Fix ../dgemm links in root/numpy/hpl READMEs.
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