silu: serve f16 from a table instead of widening every chunk to f32 - #2583
Open
czoli1976 wants to merge 1 commit into
Open
silu: serve f16 from a table instead of widening every chunk to f32#2583czoli1976 wants to merge 1 commit into
czoli1976 wants to merge 1 commit into
Conversation
The aarch64 f16 SiLU converted each chunk into an f32 scratch, ran the NEON f32 kernel over it and converted back, on every call. Map the 65536 f16 bit patterns through that same kernel once and keep the result, so the activation is a single load per element. The table is filled by calling the f32 kernel over an aligned whole number of nr-blocks, which is what the element-wise frame hands it directly, so the two agree bit for bit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
The aarch64 f16 SiLU (
arm64simd_silu_f16_4n) converts each chunk into an f32 scratch, runs the NEON f32 kernel over it and converts back, on every call — it carried a// TODO: Change this SiLU kernel once we have a native-FP16 one. Since SiLU is a unary f16 -> f16 map, the whole thing collapses into a 2^16-entry table: one load per element.Why it is safe
The table is filled by calling the same f32 kernel the roundtrip path runs, over an aligned whole number of
nr-blocks — which is exactly the slice the element-wise frame hands that kernel directly. f16 -> f32 is exact and the store rounds to nearest-even, so the table reproduces the roundtrip bit for bit rather than re-deriving SiLU from a formula.lut_matches_the_f32_roundtrip_on_every_f16checks that over the full domain.Calling the kernel raw rather than through the frame is also load-bearing, not a shortcut:
map_slice_with_alignmentholds aborrow_muton the frame's thread-local scratch for the duration of a kernel call, so building the table through the boxedElementWisepanics withRefCell already borrowedthe first time a model hits the op. Worth knowing before anyone adds a second table this way.It is built lazily behind a
OnceLock, so a model with no f16 SiLU never allocates the 128 KiB.Numbers
Kernel, all three variants in one process so there is no cross-run drift:
2.0-2.35x over the path it replaces. End to end, f16 through a real plan:
down(silu(gate(x)) * up(x))matmul -> silu -> matmulThe SwiGLU shape is the one that matters, since that is what Llama/Qwen/Mistral actually run — SiLU is the activation inside the gate, not the block itself, so the extra projection dilutes the kernel win. Arms were interleaved within one session; on this box a criterion baseline compared across processes drifts far enough to invert the sign of a result this size.
x86 is untouched — it has its own
x86_64_avx512_silu_f16_16n.Tests
tract-linalg 4415, tract-core 270, test-f16 2378, test-unit-core 816 — green.
cargo fmt --allclean;cargo clippyclean (the one warning inbenches/silu.rsis pre-existing, in the scalar reference helper).🍍