Skip to content

perf: float32 pairwise sum blocks on RVV (riscv64) and LSX (loong64) - #55

Merged
tannevaled merged 3 commits into
mainfrom
float32-blocks-rvv-lasx
Oct 18, 2026
Merged

tannevaled merged 3 commits into
mainfrom
float32-blocks-rvv-lasx

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

AddBlocks8F32, the inner block of NumPy's pairwise sum, gains two kernels. Each runs behind the feature bit its float64 kernels already use (useRVV, useLASX), and the scalar loop runs otherwise. Both are spelled by Go's own assembler.

  • riscv64: VSETIVLI $8, E32, M2 gives vl = 8 on every VLEN ≥ 128, the V extension's minimum. The eight accumulators sit in the group V2–V3, with one VFADDVV per block.
  • loong64: the eight accumulators sit in two 128-bit LSX registers, with one VADDF (vfadd.s) per register per block.

There is one IEEE float32 addition per lane per block, in order, so float32 sums are unchanged bit for bit.

Run on the real hardware:

  • The kernel tests (bit-identity, refusals, PROT_NONE fence) and the float32 sum tests pass on cfarm95 (SpacemiT X60, RVV, VLEN 256) and cfarm401 (Loongson 3C5000L).
  • A sabotaged kernel fails the bit-identity test on both. That proves the kernel path, not the fallback, is the one taken there.
  • All seven targets build.

Measured with old and new binaries alternating 6 times, one pinned thread, benchstat p = 0.002:

float32 Sum, 2¹⁴ / 2²⁰ change controls
X60 (RVV) −30% / −41% float64, int32 ~
3C5000L (LSX) −24% / −24% float64, int32 ~

A first loong64 version lost, and was dropped. One 256-bit LASX register for all eight accumulators came out 26–47% slower than the scalar loop on the 3C5000L, with the host 0% busy. Every block's addition waited on the previous one: a single dependency chain, where the scalar loop has eight. Two LSX registers give two chains, as SSE2 does on amd64, and that version wins.

Left: ppc64le and s390x. Go's assembler lacks xvaddsp and vfasb; go-asmgen/asmgen#27 is the start.

🤖 Generated with Claude Code

tannevaled and others added 3 commits October 18, 2026 16:04
AddBlocks8F32 gains two kernels, each behind the feature bit the float64
kernels already use (useRVV, useLASX), the scalar loop otherwise:
- riscv64: VSETIVLI $8, E32, M2 gives vl = 8 on every VLEN >= 128; the
  eight accumulators in the group V2-V3, VFADDVV per block.
- loong64: the eight accumulators fill one 256-bit register, XVADDF
  (xvfadd.s) per block.
One IEEE float32 addition per lane per block, in order: float32 sums are
unchanged bit for bit. Both are spelled by Go's own assembler.

Run on real hardware: the kernel tests (bit-identity, refusals, fence)
and the float32 sum tests pass on cfarm95 (SpacemiT X60, RVV, VLEN 256)
and cfarm401 (Loongson, LASX); a sabotaged kernel fails the bit-identity
test on both, so the kernel path is the one taken there. All seven
targets build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ne LASX

The 256-bit version kept the eight accumulators in one register, a single
dependency chain, and was 26-47% slower than the scalar loop on a
Loongson 3C5000L. Two 128-bit registers, two chains: -24% at 2^14 and
2^20 there, bit-identity test passing on the hardware.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… 3C5000L

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit 365f1d1 into main Oct 18, 2026
12 checks passed
@tannevaled
tannevaled deleted the float32-blocks-rvv-lasx branch October 18, 2026 14:24
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