Repository navigation
perf(hip): narrowest stream columns at RDNA3.5's one- and two-row launches - #1855
Conversation
25920f3 to
dd2947c
Compare
|
Thanks @AbrahamCete. We read this through, and it looks right. We'd like to land it right after #1830, which it should follow. We don't have an RDNA3.5 machine, though, so could you post these from your gfx1151 on this branch rebased onto #1830?
The rule also covers gfx1150 to 1153, but only gfx1151 has been measured. That's fine to say in the body. The overlaps with #1694, #1658 and #1612/1614/1622 are one-line textual ones, so whichever lands second rebases. |
dd2947c to
92b35dd
Compare
|
Thanks @ashhart. The branch is rebased onto #1830 (493704a) with no conflicts, and it has a third commit, 92b35dd From gfx1151, #1830 at 493704a against this branch on top of it at 92b35dd, both built from
The body now says that gfx1150, 1152 and 1153 take the same rule and only gfx1151 has been measured. |
…nches The stream tile gives a lane the widest column count that still reaches 3000 waves. On gfx1151 that leaves one-row launches short of loads in flight: Qwen3.6-35B-A3B's GDN in_proj takes 4 columns a lane (772 blocks) and the 248320-column head 8. With 2 columns a lane, the in_proj drops from 98 to 74 us and the head from 1558 to 1212 us. Two-row launches (MTP verify and drafts) gain the same way. Four rows keep the wave target, which measured slower narrow. A column's sum order does not depend on the column count, so outputs are byte-identical. The rule moves to launches/stream_pick.zig with unit tests; the fused relu^2 pair launch and its contract case call it instead of keeping their own copies. Other generations keep the current choice.
…_pick The callers of stream_pick derived the row tile again and recomputed the block width from the returned column index. rowTile is now the one row mapping, pick returns the block width with the index, and its pair flag names what the old divisor meant: a lane's columns are the gate and up halves of one output. When no width fits a lane's registers, pick falls back to the narrowest tile and its block width, as before. Launch geometry is unchanged.
A column's start round is (col0 >> 3) % rounds, the same for each width only while the width divides 8, which keeps stream_pick's choice of width out of the bits. A width that does not divide 8 now fails the build.
92b35dd to
6e3619e
Compare
|
Thank you @AbrahamCete. This is merged into main with your commits under your own name, so you're listed as a TensorFold contributor. It ships in the next release, credited to you in the changelog and the release notes. |
What this changes
The stream tile gives a lane the widest column count (CB 8, 4 or 2) that still reaches
streams_wanted= 3000 waves. On gfx1151 that leaves one-row decode launches short of loads in flight. Qwen3.6-35B-A3B's GDN in_proj (12,352 columns) takes 4 columns a lane, 772 blocks, and the 248,320-column head takes 8. With 2 columns a lane, on rocprofv3 kernel traces of 64 decode steps:Two-row launches (MTP verify and drafts) gain the same way. Four-row launches measured slower narrow: 35B decode at four streams dropped 5-6%. So they keep the wave target.
The changes, one commit each:
perf(hip): narrowest stream columns at RDNA3.5's one- and two-row launchesstreamWithintolaunches/stream_pick.zig, aspick..rdna3_5, one- and two-row launches take the narrowest columns their registers allow.affine_pair.zig) and its contract case had their own copies of the rule. Both now callpick.refactor(hip): the stream tile's row tile and block width from stream_pickrowTile(m)is the one row mapping.pickreturns the block width with the column index, so callers no longer recompute it.pairflag replaces the old divisor.pickfalls back to the narrowest tile and its block width, as the inline rule did.refactor(hip): assert at comptime that every stream tile width divides 8(from review)comptimeblock instream_pick.zigasserts that each width inaffine.stream_cbsdivides 8, so a width that would change a column's start round fails the build.A column's sum order does not depend on the column count. The start-round rotation uses
col0 >> 3, and every column count divides 8 (tiles/stream.hpp, asserted instream_pick.zig). The lanes a column (fixed by K), the chunk order and the shuffle tree are also unchanged, so the outputs are byte-identical.Overlap with open pull requests:
streamWith, next to this PR'stable[b][ri][tile.at]line. Whichever lands second rebases; the two changes are independent.stream_pickimport inaffine.zig.launches.zig, where this PR adds one export line.Receipt
mainba7b2c4). The served A/B below ran onmain5718638 with the first two commits; none of the 34 commits from there to 9e8c368 toucheszig/src/hip. On fix(qwen35/hip): time shared rounds at the served streams and their row buckets #1830, against fix(qwen35/hip): time shared rounds at the served streams and their row buckets #1830:lanesreplies with drafts on, are byte-identical to fix(qwen35/hip): time shared rounds at the served streams and their row buckets #1830's on both checkpoints.check --only invariantsmatches as well.--parallel 2,tools/bench_concurrent.py --levels 1,2 --tokens 256 --reps 3 --temperatures 0 --alone --serial, two servers a side. 35B chat ×1: 78.2 and 81.6 on fix(qwen35/hip): time shared rounds at the served streams and their row buckets #1830, 92.2 and 92.7 here. Code ×1: 113.3 and 113.1, then 112.2 and 112.3. ×2 is within 2%. 27B: +5% at ×1 and +4% at ×2 on both prompts. Every server has the same token hashes, with 0 unequal and 0 failed.SUMMARYas fix(qwen35/hip): time shared rounds at the served streams and their row buckets #1830.hip-host-test,test-qwen35-hip-hostandcheck-native-hip-hostexit 0.--context 16384 --prompt-cache-gib 0), alternated B1 C1 B2 C2, with B =mainand C = this PR.Decode speed,
tools/bench_openai.py --tokens 256 --reps 3 --temperatures 0, median tok/s:Concurrency,
tools/bench_concurrent.py --tokens 256 --reps 3 --temperatures 0 --alone --serial:--parallel 4, levels 1 and 4): code ×1 112.9/112.7 vs 113.9/112.5 and chat ×1 77.7/82.4 vs 92.2/92.6. At ×4, code is 226.4/227.6 vs 228.5/211.3 and chat 191.7/191.4 vs 190.8/193.4. The C2 code ×4 cell is the per-process slow mode of lanes: on HIP, Qwen3.6-35B-A3B MTP at four streams falls into a slower, deeper-drafting mode that holds per server process #1695, fixed by fix(qwen35/hip): time shared rounds at the served streams and their row buckets #1830, which also hitsmain.--parallel 16, levels 1, 4, 8, 16): ×1 code 13.3 vs 14.0 and chat 13.6 vs 14.3. ×4, ×8 and ×16 are within 0.2 tok/s on both prompts.tf-qwen35-test check --only speed, 35B one stream with drafts off: 71.2 and 70.4 onmain, 76.3 and 76.3 with this PR.9f2923ac14d0, chata8c28b8297b4, cold 8,192-token prompt393096f974da. 27B: code2b28aa2108de, chata06fab348fbe, cold prompt1ebfd15d62c7.--aloneand--serial: 0 unequal and 0 failed, out of 136 checks on 35B and 728 on 27B.tf-qwen35-test logits --decodeover 64 steps is byte-identical tomainon both checkpoints.tf-qwen35-test check --only invariants: 27B 42/0/5 and 35B 46/0/1, the same asmain.ttft_sfromtools/bench_cache.py --require-cold:main, 5.351 and 5.449 s with this PR.main, 22.197 and 22.458 s with this PR.SUMMARYonmainand on this PR.gfx1151 52 kernels: 52 pass gates 1-4, 0 fail, 2 known findings; 5 controls: 5 red as planted, 0 missed; 130 limit problems.hip-kernel-contract-limitswith ROCm 7.13: 220 problems on both.stream_pick.zig, through the publicpickandrowTile:stream_cbsset to 2, 4 and 16, the build fails at thecomptimeassert.hip-kernel-test: 114 passed, 1 skipped, 0 failed;mainhas 109/1/0.hip-host-test,test-qwen35-hip-hostandcheck-native-hip-hostexit 0.zig build test: 465/465 steps, with 6,168 of 6,213 tests passed and 45 skipped, the same as fix(qwen35/hip): time shared rounds at the served streams and their row buckets #1830. Both ran on trees exported withgit archive.tools/zig/lean_check.py: 424 problems, the same asmain.zig fmt --checkis clean.Checklist
"draft": false, a resumed prompt equals a fresh one, and concurrent equals solo.SUMMARYline is in the receipt.