Repository navigation
perf(hip): the planned decode score takes four keys a warp on long spans, two on short ones - #1932
Open
AbrahamCete wants to merge 2 commits into
Open
AbrahamCete wants to merge 2 commits into
AbrahamCete wants to merge 2 commits into
Conversation
The planned walk test compared the walk's output with the flat walk and float64, so nothing pinned the score kernel's own bits. It now checks every score, fp32 and activation-type queries, against a host emulation of the kernel's order: lane l sums q * k over l, l + 32, ... with fp32 multiplies and adds, the wave folds its lanes by xor from 16 down to 1, and the sum is scaled; positions past the visible ones are -inf.
…ans, two on short ones The score kernel took one key a warp: eight two-byte loads a lane, the heads' queries read again from LDS for every key, and a wave sum through ds_bpermute for every head. On long contexts it read the cache at about 72 GB/s and was most of a full-attention layer's decode time. A warp now scores four contiguous keys from 16384 keys on and two below (tf_plan_score2_*), where four would leave the card short of warps. Their loads go out together, the page table and descriptor are read once, each head's query slice is read from LDS once for the warp's keys, and the wave sum folds by DPP row_xmask and permlanex16 in wave_sum's order, xor 16 down to 1. Every score keeps its lane mapping, its in-lane order and its folds, so the bits are the same; the planned walk test pins both kernels, with a 16384-key span for the four-key one. The two- key kernels' records live in hip_records_score.zig.
Owner
|
Thanks @AbrahamCete. We gave this a full independent review. The four-key and two-key kernels keep the lane mapping, in-lane order and the xor-16-to-1 folds, the host-emulated test pins every score for both query types including 16,384-key tails ending mid-warp, and your served receipt has identical tokens and logits. The four extra contract limit problems are the same one-VGPR live-against-recorded gap, so they don't block. It lands now: it's gated and queued, and we've written the changelog line. |
9 tasks done
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
tf_plan_score_*(zig/kernels/hip/decode/plan.hip) is the planned decode walk's Q·K: one score per key for each query head of a KV group. It took one key a warp:ds_bpermutewave sum for every head.On long contexts it read the cache at about 72 GB/s and was most of a full-attention layer's decode time. On gfx1151 at 64K tokens, Qwen3.6-35B-A3B spent 921 µs a layer on it (2 KV heads × 33.5 MB of keys), against 388 µs for the values' pass over the same bytes.
The changes, one commit each:
test(hip): pin the planned decode score's bits to its lane orderperf(hip): the planned decode score takes four keys a warp on long spans, two on short onestf_plan_score2_*), where four would leave the card short of warps.row_xmaskandpermlanex16inwave_sum's order, xor 16 down to 1. After the last fold every lane holds the sum, so the broadcast is gone.tf_plan_score_*move from 17 to 48 VGPRs. The fourtf_plan_score2_*records (31 VGPRs) live inhip_records_score.zig, chained fromhip_records.zigas the prompt tile's are, sincehip_records.zigwould pass 600 lines.Every score keeps its lane mapping, its in-lane order and its folds, so the bits are the same.
Score kernel time by keys a warp (Qwen3.6-35B-A3B, rocprofv3, the launches at the context's span):
main)The 2- and 4-key columns are from the one-kernel build with both bodies; the shipped four-key kernel alone runs 330 µs at 32K and 630 µs at 64K.
Overlap with open pull requests: none touch
plan.hiporlaunches/plan.zig.hip_records.zig. hip: a 64-row routed prompt tile for many-expert plans on gfx1151 #1658 chains its own records file fromhip_records_pair.zig, not from the line this PR changes.launches.zig.hip_records_score.zigchain, and adds two lines tolaunches.zig.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
main21c8a0f. The receipts below ran on 9e8c368; none of the 105 commits between them touches these files. On 21c8a0f:main's.hip-kernel-testpasses 109 with 1 skipped.hip-host-test,test-qwen35-hip-hostandcheck-native-hip-hostexit 0.lean_checkand the ROCm 7.13 contract limits give the same counts asmain(424 and 220).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.6 / 113.3 vs 113.0 / 113.0, chat 79.6 / 79.5 vs 81.6 / 81.4; 27B code 13.4 / 13.3 vs 13.3 / 13.3, chat 13.6 / 13.7 vs 13.7 / 13.6.tf-qwen35-test check --only speed, twice each on 35B: one stream drafts off 70.4 / 70.4 vs 70.3 / 70.2, drafts on 95.8 / 95.6 vs 95.8 / 95.9; four streams drafts off 138.9 / 139.1 vs 139.1 / 138.8, drafts on 127.3 / 126.7 vs 126.1 / 127.4. 27B the same within 0.1.tf-qwen35-test laneswith drafts off: 35B 42.0 → 39.8 µs at 2K, 467.6 → 330.1 at 32K, 921.3 → 630.1 at 64K; 27B 64.2 → 58.2, 822.4 → 562.9, 1634.5 → 1099.9.Exactness:
61da022cat 32K,aaa410bcat 64K; 27B28bb6ebband63bf1db1.lanesreplies are equal 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: 35B 27.5 / 27.4 vs 27.4 / 27.4 s at 32K and 71.2 / 71.2 vs 71.1 / 71.3 s at 64K; 27B 103.5 / 103.2 vs 104.2 / 103.7 s and 246.1 / 245.4 vs 247.1 / 245.6 s. The prompt path doesn't use these kernels.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; 134 limit problems.mainhas 130.main's 130.planCausalandplanCausalAscases pass every gate.hip-kernel-contract-limitswith ROCm 7.13: 220 problems on both, with every planned-walk score record current.Tests run:
hip-kernel-test: 109 passed, 1 skipped, 0 failed.hip-host-test,test-qwen35-hip-hostandcheck-native-hip-hostexit 0.zig build teston agit archiveof each side: 465/465 steps, 6,185 of 6,230 passed and 45 skipped, the same asmain.tools/zig/lean_check.py: the same count asmain.zig fmt --checkis clean.Not run, and why:
plan.hipbuilds for all three with the build's flags; the DPProw_xmaskandpermlanex16folds exist on gfx10 and later. The contract records for them 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.