rms_norm: run f16 rows natively instead of widening the whole tensor - #2580
Open
czoli1976 wants to merge 1 commit into
Open
rms_norm: run f16 rows natively instead of widening the whole tensor#2580czoli1976 wants to merge 1 commit into
czoli1976 wants to merge 1 commit into
Conversation
The f16 fast path converted the entire tensor to f32, ran the f32 kernel, and converted back, so a memory-bandwidth-bound op moved three times its own size through memory and allocated twice. Add rms_norm_f16 to the linalg dispatch table with an aarch64 kernel that widens each 16-element group into the same registers the f32 kernel uses, leaving every arithmetic instruction unchanged, and let core normalise the f16 tensor in place. Hosts with no native f16 kernel widen a row at a time into a reused buffer and defer to the f32 kernel they already registered. 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.
RmsNorm's f16 fast path converted the whole tensor to f32, ran the f32 kernel, and converted back — three times the tensor's size through memory and two allocations, for an op that is otherwise memory-bandwidth bound. This addsrms_norm_f16to the linalg dispatch table with an aarch64 kernel, and normalises the f16 tensor in place.The kernel
rms_norm_f16_inneris the existingrms_norm_f32_innerwith the loads and stores changed and nothing else.FCVTL/FCVTL2widen each 16-element group into the samev4-v7the f32 kernel accumulates from, so the FMLA chain, the horizontal reduce, the scalarmean/eps/rsqrtand the scalar tail are untouched. On the way outFCVTN/FCVTN2round to nearest-even under the default FPCR, which is whatf16::from_f32does.Since f16 -> f32 is exact, that makes the kernel bit-identical to widening the row in the caller, which is what the path did before.
f16_matches_widening_the_row_to_f32asserts exactly that at ten lengths spanning the 16-element body and every tail shape.Not regressing hosts without an f16 kernel
Worth flagging, because the naive version of this change would quietly hurt x86: with only an aarch64 kernel registered, x86 would drop from "widen + AVX-512 f32 kernel" to a generic scalar f16 loop. So the generic
rms_norm_f16widens one row at a time into a reused thread-local buffer and calls whicheverrms_norm_f32the host registered — AVX-512 keeps its arithmetic and its speed, and still loses the whole-tensor allocation. Bit-identical there too.Numbers
f16
RmsNormon the trailing axis, through a real plan, criterion against a saved baseline on the merge-base:p = 0.00 throughout.
What I tried first
Doing this core-side — keeping the f32 kernel and converting a row at a time into a scratch — is a regression (+7.7% at 128x768, +6.5% at 2048x4096). Row-local scratch buys locality, but the conversion loops are then scalar
to_f32()per element, and that costs more than the bulkcast_toit replaces. The conversion has to happen in-register for this to pay, which is why the kernel is asm rather than core Rust.Tests
tract-linalg 4415, tract-core 270, test-f16 2378, test-unit-core 816 — green.
cargo fmt --allandcargo clippyclean.🍍