Skip to content

NEON kernels for the correctly rounded float32 Exp and Log - #95

Closed
tannevaled wants to merge 2 commits into
mainfrom
perf/exp32-neon
Closed

tannevaled wants to merge 2 commits into
mainfrom
perf/exp32-neon

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

NEON kernels for the correctly rounded float32 Exp and Log on arm64, which until now ran the Go transcriptions.

exp32CRNEON ports Arm's cr_expf, an AdvSIMD routine to begin with, on 8 floats per iteration.

  • FCVTL/FCVTL2 widen. VFMLA/VFMLS do expCR's fused operations in its order.
  • The table is read through two general registers per pair, as cr_expf does, because NEON has no gather. FCVTN/FCVTN2 round once.
  • A block with a lane outside [−512, 512] or a NaN (FACGE) is handed back to exp32CR before anything is stored.

log32CRNEON is the twin of log32CRAVX2, running logCR's fast path (CORE-MATH's logf).

  • The integer part works in 32-bit lanes: m, j, e, x == 1, and the positive-normal test.
  • Per pair: tz (UXTL), e (SXTL, SCVTF), tr[j] and tl[j] through general registers, then logCR's unfused VFMUL/VFADD in its order. Both bounds are rounded once.
  • A block with a lane that is not a positive normal, or whose bounds round apart, is handed back to logCR.

Instructions Go's assembler does not name (FCVTL/FCVTN and their 2 forms, FACGE, UXTL, SXTL, SCVTF, CMGT, BIC) are emitted as WORDs. go tool objdump decodes each one as intended, with operands in the right roles.

Proof on darwin/arm64 over all 2³² inputs: TestExp32CorrectlyRounded and TestLog32CorrectlyRounded now run the NEON kernels. Both find 0 incorrectly rounded, and logCR equals the independent oracle everywhere.

The tests catch planted defects:

  • Widening the wrong half (FCVTL for FCVTL2) fails TestExp32NEONMatchesGo and the exhaustive sample (1602 wrong of 65536).
  • Dropping the positive-normal BIC fails TestLog32NEONMatchesGo (log(0) = −88.03) and the sample (33016 wrong).

Both kernels are bit-identical to the Go path on 300 000 inputs, every length 0–70 and in place, and both are fenced. Their constant blocks' layouts are tested.

⚠ Not yet measured. The arm64 hosts are loaded: this Mac is at load 78–103 and cfarm424 at 48 on 64 cores. A gated abbench (v0.35.0 vs this branch, load ≤ 8) is queued. This PR will not be merged before its numbers are in and in docs/perf.md.

Also: s390x (IBM z17) is now proven for float32 Exp and Log over all 2³² inputs (0 incorrectly rounded). That completes the set of 64-bit architectures. docs/perf.md will record it with the numbers.

🤖 Generated with Claude Code

tannevaled and others added 2 commits November 7, 2026 20:46
exp32CRNEON runs expCR (Arm's cr_expf, an AdvSIMD routine to begin with)
on eight float32 per iteration: FCVTL/FCVTL2 widen, VFMLA/VFMLS do expCR's
fused operations in its order, the table is read through two general
registers per pair, FCVTN/FCVTN2 round once. A block with a lane outside
[-512, 512] or a NaN (FACGE) is handed back to exp32CR before anything is
stored. FCVTL/FCVTN/FACGE are emitted as WORDs; go tool objdump decodes
each as intended.

TestExp32NEONMatchesGo holds it to exp32CR bit for bit (random bits, a
dense range, specials, lengths 0..70, in place); widening the wrong half
(FCVTL for FCVTL2) fails it and the exhaustive sample (1602 wrong of
65536). Fenced; the constant block's layout is tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
log32CRNEON runs logCR's fast path (CORE-MATH's logf) on eight float32
per iteration, the twin of log32CRAVX2: m, j, e, x == 1 and the
positive-normal test in 32-bit lanes; per pair, tz (UXTL), e (SXTL,
SCVTF), tr[j] and tl[j] through general registers, logCR's unfused
VFMUL/VFADD in its order, and both bounds rounded once (FCVTN/FCVTN2).
A block with a lane that is not a positive normal, or whose bounds round
apart, is handed back to logCR before anything is stored. UXTL, SXTL,
SCVTF, CMGT and BIC are WORDs; go tool objdump decodes each as intended.

Proof on darwin/arm64 over all 2^32 inputs: TestLog32CorrectlyRounded and
TestExp32CorrectlyRounded, which now run the NEON kernels, find 0
incorrectly rounded. Dropping the positive-normal BIC fails
TestLog32NEONMatchesGo (log(0) = -88.03) and the exhaustive sample (33016
wrong of 65536). Fenced; the constant block's layout is tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tannevaled

Copy link
Copy Markdown
Contributor Author

On hold, not to be merged here. ndarray's 1-D kernels are moving into go-simd/floats (go-scikit performance campaign, kernel K1), so there is one implementation. These NEON kernels go there with the AVX2 ones already on main (exp32CRAVX2, log32CRAVX2, logAVX2).

The port keeps:

  • the bit-identity oracles (expCR, logCR);
  • the 2³² exhaustive tests;
  • the objdump checks of the WORD-encoded instructions;
  • the hand-back of special lanes.

This PR stays open until the go-simd/floats PR is merged, then it will be closed with a pointer to it.

🤖 Generated with Claude Code

@tannevaled

Copy link
Copy Markdown
Contributor Author

Superseded by go-simd/floats#29 (merged 2026-11-07). These NEON kernels now live in go-simd/floats as Float32Exp and Float32Log, with the AVX2 kernels from #94 and #96. ndarray will import them from there, so there is one implementation.

🤖 Generated with Claude Code

@tannevaled tannevaled closed this Nov 7, 2026
tannevaled added a commit that referenced this pull request Nov 7, 2026
…xp, log (K1) (#97)

* kernels: import go-simd/floats for sum, elementwise, extrema, clip, exp, log (K1)

K1 of the go-scikit performance campaign (go-scikit/roadmap#27): ndarray now
imports go-simd/floats v0.9.0 and deletes its copies.

- exp, log, and the correctly rounded float32 exp/log (with the AVX2 kernels of
  #94/#96 and the NEON kernel of #95, now go-simd/floats#29): deleted here
  (Go sources, tests, generator kernels, assembly); Exp, Log, Exp32, Log32 and
  their P forms call floats.
- amd64 and arm64: the sum/sqrt/max/min/add/sub/mul/div kernels are floats';
  Clip is floats.ClampScalar. Max/Min resolve a zero result with the serial
  oracle (floats leaves the sign of zero unspecified).
- Sum is floats.SumPairwise on every architecture: NumPy's order, bit-identical
  to np.sum; serial at every size. A changed last bit is the fix; the golden
  values of TestSumMatchesNumPy are NumPy 2.5.4's.
- Not moved: the float32 elementwise/sqrt/extremum/AddBlocks8F32 kernels, the
  dot inside GEMM/Dot1D, and the ppc64le, loong64, riscv64 and s390x kernels
  (floats has none for them yet).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* kernels: test the parallel exp/log forms and Log10 (100% coverage gate)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
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