Repository navigation
perf(backend-cpu): let C2 intrinsify the elementwise Vector API paths - #1360
Merged
Merged
Conversation
JvmVectorKernels.binaryFloat/unaryFloat and DefaultCpuOpsJvm.vectorFloatBinary/Unary took the FloatVector op as a Function1/2. Shared by every elementwise op the call is megamorphic, the vectors escape into the generic invoke, and the Vector API runs its allocating Java fallback (fromArray0Template: 61 % of decode allocation in a BitNet 2B4T JVM profile). They are now inline, so each op gets a monomorphic loop. DefaultCpuOpsJvm.floatSpecies was an instance field; C2 only constant-folds static finals and the Vector API needs a constant species, so it now reads the object's (static) species. The scalar- and bias-broadcast loops go through the same inline helpers (binaryFloat with outOffset, binaryFloatScalarLeft/Right). Refs #1357 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 17, 2026
michalharakal
added a commit
that referenced
this pull request
Oct 21, 2026
CHANGELOG [0.62.0] - 2026-10-21 with the parity headline over #1363, #1364, #1368, #1369, #1426, #1427, #1428 and #1430, plus entries for the operator coverage matrix (#1370), the BitNet decode allocation and threading fixes (#1359, #1360, #1361, #1362), the ClassificationSupport extraction (#1429) and the toolchain bumps (Kotlin 2.4.21, kotest 6.2.6, Gradle 9.8.1). VERSION_NAME, CITATION, Antora version, README bom coordinate, What's New and contributors, Android tutorial coordinates; regenerated operator and kernel-support matrices; SKEEP-007 rollout step 3 marked shipped.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
In a JVM decode profile of BitNet b1.58 2B4T (#1357), 61 % of all allocation came from
FloatVector.fromArray0Template←JvmVectorKernels.binaryFloat←DefaultCpuOpsJvm.vectorFloatBinary: 9.6 GB in a 45 s window. That's the Vector API's Java fallback, not intrinsified code. There were two causes:binaryFloat/unaryFloatandvectorFloatBinary/vectorFloatUnarytook theFloatVectorop asFunction2/Function1. One shared helper serves add/sub/mul/div/relu/…, so the call is megamorphic. The vectors escape into the genericinvoke, and C2 can't box-eliminate them.DefaultCpuOpsJvm.floatSpecieswas an instanceval. C2 constant-foldsstatic finalfields only, and the Vector API intrinsics need a constant species.relualone allocated 3× its output because of this.Change
binaryFloat,unaryFloat,vectorFloatBinaryandvectorFloatUnaryare nowinline, so each op gets its own monomorphic loop with the lambda body in place.DefaultCpuOpsJvm.floatSpeciesreadsJvmVectorKernels.floatSpecies(anobjectproperty, so a static final field) through an inline getter.binaryFloatgainsoutOffset, plus newbinaryFloatScalarLeft/Right. That removes about 60 lines of duplicated loop code.All of it is internal API;
apiCheckpasses.Evidence
:skainet-backends:skainet-backend-cpu:jvmTest: 490/490 pass.relu: 31 016 → 10 536 B/op, 6.9 → 1.5 µs/op.-XX:-TieredCompilationscalar-mul is output-only. I didn't chase that further, because what matters is the real decode below.settings=profile, 45 s decode window):FloatVector.fromArray0Templatedisappears from the allocation-by-site list (it was 61 %). Greedy 128-token output is byte-identical (md552f57d1d…).Not in this PR: the next allocation sites the profile now surfaces
With the fallback gone, decode allocation is dominated by generic element-wise copies that spread
get(*indices), which copies anIntArrayand boxes a Float per element:RMSNormalization.eagerFusedRmsNorm→MmapFloatTensorData.copyToFloatArray()(TensorData default, per forward, on mmap'd norm gains): ~10 GB / 45 s. A bulkFloatBufferoverride removes it; I verified that locally.DefaultCpuOpsBase.split(used byRoPE.applyRoPESplitHalfFull): ~7.8 GB / 45 s.I'm happy to send those as follow-ups.
Refs #1357
🤖 Generated with Claude Code