perf(dsv4): compute the hyper-connection pre-norm in one kernel - #101
Open
gdevenyi wants to merge 1 commit into
Open
perf(dsv4): compute the hyper-connection pre-norm in one kernel#101gdevenyi wants to merge 1 commit into
gdevenyi wants to merge 1 commit into
Conversation
``hc_pre`` and ``hc_head`` compute the inverse RMS as
xf = x.flatten(2).float()
rsqrt = torch.rsqrt(xf.square().mean(-1, keepdim=True) + eps)
``square()`` materialises a second full fp32 copy of the hidden state purely to
reduce it away. At DSV4-Flash shapes (hc_dim 16384, T=8192) that is a 537 MB
write plus a 537 MB read on top of the 537 MB read -- to produce [T, 1].
``inv_rms`` does it in one pass over the bf16 source (the fp32 upcast is exact,
so the per-element squares are unchanged, and reading the source halves the
bytes again). RTX 6000 Ada, hc_dim 16384:
T=8192 1.944 ms -> 0.304 ms (6.4x, ~880 GB/s of a 960 GB/s peak)
T=4096 0.972 ms -> 0.153 ms (6.4x)
T= 64 0.020 ms -> 0.013 ms (1.5x -- decode benefits too)
Measured on a running server: two nsys captures of main, one without this commit
and one with it, serving DSV4-Flash under --moe-backend offload at TP=1 with 16
concurrent ~1700-token prompts. Both runs completed 33 requests in the 45-second
window, so the work is comparable.
kernel baseline with inv_rms
vectorized_elementwise (the square) 633, 181.8 ms absent
reduce_kernel<512,1> (the mean) 633, 91.9 ms absent
_inv_rms_kernel absent 629, 48.9 ms
total GPU kernel time 5.830 s 5.587 s
273.7 ms of kernel time becomes 48.9 ms; total GPU kernel time falls 4.2%. That is
small in wall-clock terms on this machine, where offload puts the routed experts on
the CPU and leaves the GPU 13% busy. A GPU-resident deployment spends a larger
share of its time here.
Not bit-identical, and it cannot be. ATen's reduction order for
``mean(x**2, -1)`` is chosen per shape: a tree/2 fold reproduces it exactly at
M=4 and nothing reproduces it at M=64, because the block/grid split changes with
M. There is no fixed order to target, so any fused form changes the last ULP --
and on a deterministic server that changes greedy continuations. Two baseline
runs produced byte-identical output, so this was verified, not assumed.
What the fused form must not do is get *worse*, and this one does not. Against
an fp64 reference the mean relative error is 3.65e-08 vs ATen's 3.60e-08 at
M=8192, and 3.60e-08 vs 3.63e-08 at M=4096 -- ahead at some shapes, behind at
others, always within 8% of ATen's own distance from the truth. A test pins
that. Two faster-looking alternatives were rejected on this basis:
``linalg.vector_norm`` is the same speed but consistently ~23% worse (4.47e-08)
because sqrt-then-square rounds twice, and ``einsum`` over bf16 accumulates in
bf16 (1.9e-03). Bit-identical alternatives exist but none is faster:
``vecdot(xf, xf)`` lowers to the same path at 1.944 ms, and vecdot with fp32
accumulate over bf16 is 4.557 ms.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GpGe2fQ5pDShGrnSuksfun
gdevenyi
force-pushed
the
perf/hc-prenorm-fused
branch
from
August 23, 2026 16:18
6504ada to
84c5b82
Compare
calvarado2004
added a commit
to calvarado2004/FreeToken
that referenced
this pull request
Aug 23, 2026
Extend Gabriel Devenyi's fused inv_rms path to the DSpark drafter, keep HC combines on the bf16 source, and honor the configured prefill chunk limit so one long OpenWebUI history cannot materialize an unbounded activation. Retain opt-in one-shot verification profiling for deployment cost measurements; it remains disabled during normal serving. Based-on: FlashML-org#101
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 problem
Block.hc_preandTransformer.hc_headcompute the inverse RMS of the hidden state:square()writes a second full fp32 copy of the hidden state.mean()then reads that copy back and reduces it to one value per row. At DeepSeek-V4-Flash shapes (hc_dim16384, T=8192) the pair moves 1.6 GB to produce a[T, 1]result.The change
This PR adds
inv_rmstokernel/triton/dsv4/norm.py, beside the existingrms_norm. It reduces each row in one pass and returns the scalar.rms_normcannot serve here. It applies the scale toxand writes a full-size output, but the hyper-connection pre-norm multiplies the scalar into a[.., mix_hc]tensor instead.The kernel reads the bf16 source rather than the fp32 upcast. The upcast is exact, so the squares do not change, and this halves the bytes again.
Measurements
One RTX 6000 Ada,
hc_dim16384, measured onmain.The whole pre-norm block, which also includes the upcast and the narrow fp32 linear that follows:
At 8192 rows the kernel moves 268 MB in 0.304 ms. That is 880 GB/s, against a 960 GB/s peak on this card.
Effect on a running server
Two nsys captures of
main, one without the change and one with it. DeepSeek-V4-Flash,--moe-backend offload, TP=1, 16 concurrent requests, prompts of about 1700 tokens. Both runs completed 33 requests in the 45-second window, so the work is comparable.inv_rmsvectorized_elementwise_kernel(thesquare)reduce_kernel<512,1,ReduceOp<float>>(themean)_inv_rms_kernel273.7 ms of kernel time becomes 48.9 ms. Total GPU kernel time falls 4.2%.
That figure is small in wall-clock terms on the machine it was measured on, because
--moe-backend offloadputs the routed experts on the CPU and leaves the GPU 13% busy. A GPU-resident deployment spends a larger share of its time here.Accuracy
The result is not bit-identical to
square().mean(), and it cannot be. ATen chooses its reduction order per shape. A tree-of-two fold reproduces it exactly at M=4, and no candidate order reproduces it at M=64, because the block and grid split changes with M. Any fused form therefore moves the last bit.What matters is that accuracy does not get worse. Measured against an fp64 reference:
inv_rmsinv_rmsis ahead at 4096 rows and behind at the other three, always within 8% of ATen's own distance from the truth. A test pins this.Two faster-looking forms were rejected on that test.
linalg.vector_normmatches the speed but is consistently about 23% worse, at 4.47e-08, because taking a square root and then squaring it rounds twice.einsumover bf16 accumulates in bf16 and reaches 1.9e-03.Bit-identical forms exist, but none is faster.
vecdot(xf, xf)lowers to the same path and takes 1.944 ms.vecdotwith fp32 accumulation over bf16 takes 4.557 ms.Effect on generated text
The server is deterministic. Two baseline runs produced byte-identical greedy output. Moving the last bit therefore changes greedy continuations. Reviewers who depend on exact-output tests should treat this as a deliberate trade, not an invisible one.
Testing
tests/dsv4/test_hc_inv_rms.pycovers shape, dtype, leading dimensions, magnitude range, and the accuracy floor.On
mainwith this change:tests/dsv4 tests/models tests/kernelsgives 265 passed, 1 skipped, 1 failed. The failure istest_e4m3_compat.py::test_forced_emu_matches_native, which also fails onmainwithout this change. #85 fixes it.🤖 Generated with Claude Code
https://claude.ai/code/session_01GpGe2fQ5pDShGrnSuksfun