Repository navigation
fix(qmoe): choose the decode GEMV tiling before computing, not on real calls - #1328
Open
masteryi-0018 wants to merge 4 commits into
Open
masteryi-0018 wants to merge 4 commits into
masteryi-0018 wants to merge 4 commits into
Conversation
|
Thanks for opening a PR! This project follows LLVM's incremental-development and AI-tool-use Before requesting review, please check that:
Reviewers are assigned through |
L2 Accuracy Results (EP vs CPU)
Threshold: 0.01 | Run: 7968 - Commit: |
Binary sizesBaseline: run 40457802257 -
MorphiZen EP Performance Results
EPContext Export Performance
EPContext Import Performance
OGA Benchmark Results
OGA Wheel Smoke (Python benchmark_e2e.py)
VLM Benchmark Results
TTFT / TPS vs mainBaseline: run 40457802257 -
Run: 7968 - Commit: |
Kernel Unit Test Results
Leaves: 17 (0 failed) | Cases: 321 (0 failed) Mode: lookup | Coverage: 1 | Run: 7968 - Commit: |
masteryi-0018
marked this pull request as ready for review
October 27, 2026 01:45
masteryi-0018
requested review from
CaymanYang,
amd-bqiao,
amd-mingw,
asparkhi,
edelaye,
fhanuman,
qianglin-amd,
sambanerxilinxamd and
wcy123
as code owners
October 27, 2026 01:45
…l calls The fused QMoE decode path picked its fc1/fc2 (BLOCK_SIZE, TILE_N) by sampling every candidate on real decode calls, round-robin, for nine rounds per shape. The block size sets the reduction order, so for the first ~100 calls of a process the same inputs gave slightly different outputs depending on which candidate a call happened to sample (Gemma 4 26B: decode logits off by up to 0.07 from one sequence to the next, 4.4 with dp4a's int8 activations magnifying it). The first call of a shape now times every candidate up front, each launch on a different set of k experts so the weights are read cold as in steady state, writes only the call's scratch, and keeps the fastest; that call and every later one compute with it. While a stream is captured nothing is timed and the default is used. hip_qmoe_decode_fused(_dp4a) take num_experts for the timing sets. Co-Authored-By: Claude <noreply@anthropic.com>
Every launch of hip_qmoe_decode_fused(_dp4a) returns hipGetLastError(), but neither entry point cleared it first, so an error left by an earlier, unrelated call was reported as a QMoE decode failure. With the tiling now timed on the first call, the same stale error would also abandon the timing and fall back to the default tiling. Clear it once on entry, as the other custom kernels do. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Made-with: Claude Code
Run the single-token fused decode 129 times in one session with the same input and require every output to be bit-identical to the first: more calls than a tiling sampled on real calls would take (12 candidates x 9 rounds). It takes the dp4a decode, the one the EP runs with 32-aligned blocks and sizes. The tiling choice is cached per shape for the process, so it uses a shape no other QMoE test uses, and one where the reduction order shows in the fp16 output. Without the tiling fix, it fails on gfx1151 from the third call on. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Made-with: Claude Code
Add the qmoe_decode/Xfp16_Wu4_Yfp16 leaf for hip_qmoe_decode_fused and hip_qmoe_decode_fused_dp4a, and register it with the package build, the suite controller and the local Makefile. For fp and dp4a, block size 32, 64 and 128, with and without zero points and bias, it checks the first call against a CPU reference that rounds and quantizes where the kernels do, then 128 more calls on the same inputs that must each be bit-identical to the first, on shapes COVERAGE thins (up to GPT-OSS-20B and a 128-expert, k = 8 layer). Every tier also runs the first call of an untimed shape over a pending HIP error and the arguments the launchers must refuse. On gfx1151 it passes at coverage 1 (32 cases) and 3 (80). Linked against main's kernel, 25 cases on the two larger shapes differ from their first call by the second, third or eighth, and both pending-error cases fail; against the kernel without the error-state fix, the dp4a pending-error case fails. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Made-with: Claude Code
masteryi-0018
force-pushed
the
fix/qmoe-decode-tiling-up-front
branch
from
October 27, 2026 09:03
af119d6 to
e48ff3c
Compare
This branch has not been deployed
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.
Summary
The fused QMoE decode (
hip_qmoe_decode_fusedand its dp4a variant) now picks its fc1/fc2 GEMV tiling before the first call of a shape computes anything. It no longer samples candidates on real decode calls, so the same input gives the same output on every call. Before, the first ~100 decode calls of a process could differ in the low bits.Related issue or design
burst_keyper burst size to the same tuner keys and launch calls. Whichever lands second has to be rebased:Why
(BLOCK_SIZE, TILE_N)for fc1/fc2 by sampling every candidate on real decode calls, round-robin: 12 candidates × 9 rounds per shape.What
fix(qmoe): Choose the decode GEMV tiling before computing, not on real calls(original patch):hip_qmoe_decode_fused(_dp4a)takenum_expertsfor the timing sets.fix(qmoe): clear the HIP error state before the fused decode launches:hipGetLastError(), but neither entry point cleared it first.test(qmoe): check the fused decode gives the same output on every call:test(qmoe): add a kernel unit test for the fused decode:unit_test/qmoe_decode/Xfp16_Wu4_Yfp16leaf callinghip_qmoe_decode_fusedandhip_qmoe_decode_fused_dp4adirectly, registered with the package build, the suite controller and the local Makefile.COVERAGEthins (up to GPT-OSS-20B and a 128-expert, k = 8 layer).Test plan
Linux, gfx1151 (Radeon 8060S),
./docker/run.sh build, this branch on main 84c0443. Numeric tests run withHIPDNN_EP_STRICT=1; the framework disables CPU fallback.test_qmoe.pytest_qmoe_decode_repeatable(dp4a)New kernel unit test
qmoe_decode/Xfp16_Wu4_Yfp16, coverage 3 (80 cases), linked against three kernels:num_expertsargument; it was built with main's header and linked by name, which the C calling convention allows (the extra trailing argument is ignored).test_qmoe_gpt_oss_shape[1]: the fused decode exceedsatol=0.1, with cosine 0.99996. Its diff is identical on both branches.test_qmoe_gpt_oss_shape_mix_weights[1]is already xfailed for the same reason.HIPDNN_EP_DEBUG=1, the numeric test logs oneautotuneline for fc1 and one for fc2, and all 129 calls take the dp4a decode.check-hip-mlir-lit): 663 passed, 9 unsupported.pre-commiton the changed files passes.Performance
gfx1151 (Radeon 8060S), Linux.
Steady-state call time from the kernel unit test's shapes, after the 129 repeat calls (main has finished sampling by then). Each of 200 timed calls uses a different set of k experts, so the weights are read cold as in a real decode step; mean of the 200, block size 32 / 64 / 128:
With the same experts on every call (hot weights, the regime main's tuner samples in), main's picks can look faster instead: on the 128-expert layer, dp4a at block size 32 took 42.1 µs on main against 53.8 µs here. A real decode step reads each layer's weights cold.
Through the EP, GPT-OSS-20B decode with dp4a (block size 32), 300 calls in one session; GPU times from
rocprofv3 --kernel-trace, wall times host-sidesession.run:session.runmedian, last 100 callsNotes for reviewers
hip_qmoe_decode_fusedaccepts any even block size andwrap_qmoeroutes single-token SwiGLU QMoE to it whenever dp4a does not apply. At block size 16 every output is wrong (relL2 0.2-1.1 against the reference, on main's kernel and on this branch's alike). With dp4a on (the default), 32-aligned blocks take dp4a, so the fp decode is reached by default exactly for those block sizes. The new kernel unit test therefore runs only 32, 64 and 128.unit_test/CMakeLists.txtskips the package build off Windows, and the leaf's Makefile driveshipcc.exe. On Linux the leaf was built with TheRock'shipccusing the same compile and link commands; the package registration is left to the Windows kernel-ut CI job.act_out/slot_buf), which the real launches overwrite.AI assistance
Co-Authored-Bytrailers.Checklist
🤖 Generated with Claude Code