Repository navigation
perf(hip): the decode softmax stats and partial sums load sixteen ahead - #1961
Open
AbrahamCete wants to merge 2 commits into
Open
AbrahamCete wants to merge 2 commits into
AbrahamCete wants to merge 2 commits into
Conversation
…le order Past the one-block walk, the planned and flat walks run tf_softmax_stats and tf_sum_partials as their own launches, and nothing pinned their bits there. The new tests check, on spans past 4096 keys: - tf_sum_partials and tf_sum_partials_act against a host sum of each lane's tiles in tile order, bit for bit, rounded to f16 and bf16 as float_to_bf16 rounds for the activation kinds; - tf_softmax_stats' max bit for bit with the max planted at the last visible key, the first, past 4096 and mid-row, and its sum within 1e-5 of float64 (the device's expf is not the host's).
Past the one-block walk, tf_softmax_stats and tf_sum_partials ran one block a head with every load waiting on the one before: a thread's keys i, i + 256, ... for the max and then the sum, a lane's tiles one after another for the partial sums. On gfx1151 at 64K keys they took 143 and 166 us a full-attention layer for moving a few MB. Each loop now issues sixteen loads together and then folds or adds them in the same order, so the bits are unchanged. tf_sum_partials_act takes the same loop. Records: tf_softmax_stats 9 -> 61/70 VGPRs, tf_sum_partials 5 -> 23, tf_sum_partials_act 6-7 -> 24, from ROCm 7.13's code objects. One block runs a CU, so occupancy does not bind.
Owner
|
Thanks @AbrahamCete. We reviewed this independently: |
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.
What this changes
Past the one-block walk (spans of more than 4096 keys), the planned and flat decode walks run
tf_softmax_statsandtf_sum_partials(attention.hip) andtf_sum_partials_act(plan.hip) as launches of their own, one block a head. Every load in them waited on the one before:tf_softmax_stats: a thread reads keys i, i + 256, … once for the max and again for the sum of exps.tf_sum_partials: a lane adds its column's tiles one after another, 512 of them at 64K.On gfx1151 at 64K keys, Qwen3.6-35B-A3B spent 145 µs and 172 µs a full-attention layer on them, for a few MB of data.
The changes, one commit each:
test(hip): pin the decode softmax stats' max and the partial sums' tile orderkernel_tests/stats.zigchecks two things.tf_sum_partialsandtf_sum_partials_act, bit for bit, against a host sum of each lane's tiles in tile order, rounded as__float2half_rnandfloat_to_bf16round for the activation kinds. It covers 1 to 1024 tiles at d = 64, 128 and 256.tf_softmax_stats' max, bit for bit. Each row's max is planted at the last visible key, the first, past 4096 or mid-row. The sum is checked within 1e-5 of float64, since the device'sexpfis not the host's.perf(hip): the decode softmax stats and partial sums load sixteen aheadtf_sum_partials_acttakes the same loop.tf_softmax_statsgoes from 9 to 61 (gfx1030) and 70 VGPRs,tf_sum_partialsfrom 5 to 23, andtf_sum_partials_actfrom 6-7 to 24, from ROCm 7.13's code objects. One block runs a CU, so occupancy does not bind.Every max, sum and exp keeps its order, so the bits are the same. I also checked the softmax sum's bits against the previous kernel directly. With a debug print of the stats' digest, the old and new kernels wrote the same bytes on all 10 long-span cases of the test, and the digests differ from case to case.
Kernel time a full-attention layer (rocprofv3,
tf-qwen35-test laneswith drafts off, the launches at the context's span):tf_softmax_statstf_sum_partialsIn a separate sweep on the 35B at 64K, eight loads ahead left 76 µs for the two kernels together, and sixteen left 54.
Overlap with open pull requests:
plan.hipand adds score records tohip_records.zig. This PR changestf_sum_partials_actinplan.hipand three other records. The two merge cleanly in either order.hip_records.zig. hip flashnext: sparse attention with a lane a key over several heads, the scalar kernel's bits #1877 also adds tokernel_tests.zig, where this PR adds one import line.Receipt
Environment: Ryzen AI Max+ 395 (gfx1151), Ubuntu 26.04, Zig 0.17.0, hipcc with HIP 7.1.52801 and Ubuntu clang 21.1.8 (ROCm 7.13 from TheRock for the contract limits and rocprofv3). Checkpoints: Qwen3.6-35B-A3B-MLX-4bit-MTP and Qwen3.8-27B-MLX-4bit.
Base: the branch is two commits on
main45fcab6. Both sides were built fromgit archivetrees.Decode speed after a long prompt, served:
tools/bench_cache.py --require-cold --tokens 128on a 32K and a 64K prompt. One fresh server a run (--context 69632 --parallel 1 --prompt-cache-gib 0), alternated B1 C1 B2 C2, with B =mainand C = this PR. The 35B serves with its MTP drafts.maintools/bench_openai.py --tokens 256 --reps 3 --temperatures 0. 35B: code 112.2 / 112.9 against 112.6 / 112.5, chat 82.4 / 81.0 against 83.4 / 83.7. 27B: code 13.3 and chat 13.6 on both.tf-qwen35-test check --only speed, twice each on the 35B:Exactness:
61da022cat 32K andaaa410bcat 64K; 27B28bb6ebband63bf1db1.lanesreplies are byte-identical tomainat 2K, 32K and 64K on both checkpoints.tf-qwen35-test check --only invariants: 35B 46/0/1 and 27B 42/0/5, the same asmain.Prompt speed:
ttft_sof the same cold prompts is unchanged.Kernel contract:
hip-kernel-contracton gfx1151:SUMMARY gfx1151 52 kernels: 52 pass gates 1-4, 0 fail, 2 known findings; 5 controls: 5 red as planted, 0 missed; 131 limit problems.mainhas 130.tf_softmax_stats' live usage under this machine's hipcc. It differs from the ROCm 7.13 record outside its VGPRs and LDS, which match (70 and 1024). That is the same toolchain gap as the rest ofmain's 130.hip-kernel-contract-limitswith ROCm 7.13: 220 problems on both, with the three changed records current.Tests run:
mainand on this PR. They fail when the partial sums add their tiles in reverse, where Nemotron's activation-type walk test fails too. They also fail when the max skips the last visible key.hip-kernel-test: 111 passed, 1 skipped, 0 failed.mainhas 109/1/0.hip-host-test,test-qwen35-hip-hostandcheck-native-hip-hostexit 0.zig build teston agit archiveof each side: 470/470 steps, 6,207 of 6,252 passed and 45 skipped, the same asmain.tools/zig/lean_check.py: the same count asmain.zig fmt --checkis clean.Not run, and why:
attention.hipandplan.hipbuild for all three, and their records come from ROCm 7.13's code objects.Checklist
"draft": false, a resumed prompt equals a fresh one, and concurrent equals solo.SUMMARYline is in the receipt.