Pin FP8 GEMV residency for grids just past two blocks per SM - #32433
Draft
Tianlei Wu (tianleiwu) wants to merge 2 commits into
Draft
Pin FP8 GEMV residency for grids just past two blocks per SM#32433Tianlei Wu (tianleiwu) wants to merge 2 commits into
Tianlei Wu (tianleiwu) wants to merge 2 commits into
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The tensor-core FP8 GEMV launches ceil(N / 16) blocks. A 16-warp block only fits
twice per SM, so an N just above 32 * sm_count spills into a second, nearly empty
wave: on H200 N = 5120 launches 1.21 waves and ncu measures 66% active cycles.
Add a second entry point over the same kernel body carrying
__launch_bounds__(32 * KSplit, 3), and route the shapes in that window to it. The
hint caps registers at 40, which makes three blocks resident and collapses those
shapes to a single wave.
Kernel time on H200 (sm_90), FP16 activations, block_size 128:
N = 5120, K = 6144 13.25 -> 11.04 us
N = 6144, K = 5120 13.18 -> 9.63 us
N = 5120, K = 17408 52.00 -> 36.51 us
On a Qwen3 27B NVFP4 + FP8 decode workload the GEMV total drops from 12150 to
11712 us per forward, and end-to-end decode throughput is 1.03-1.04x at batch 1.
The window is narrow and the hint is a pessimization outside it, so the selector
in matmul_block_scaled_fp8_tiling.h gates on all four conditions that measurement
showed matter:
* grids at or below 2 blocks per SM are already one wave (N = 1024 -> 0.96x);
* grids above 3 blocks per SM stay multi-wave either way;
* 8-warp blocks (KSplit 8, taken from N >= 8192) lose 1.05-1.08x from any
explicit __launch_bounds__, and 32-warp blocks cannot host 3 blocks per SM;
* only one row tile meets the 40-register cap. Two tiles measure 0.74x and four
0.24x, both from spills.
Also adds ORT_FP8_GEMV_KSPLIT to override the K-split heuristic for A/B sweeps,
matching the override already present in the FP4 GEMV.
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
Hardware-specific CUDA residency tuning and its unmerged stacked dependency require final human validation.
Pull request overview
Pins FP8 tensor-core GEMV residency for grids between two and three blocks per SM. No actionable issues found.
Changes:
- Adds a launch-bounded kernel entry point and dispatch predicate.
- Adds a K-split override for benchmarking.
- Adds boundary and device-dependent correctness tests.
File summaries
| File | Description |
|---|---|
matmul_block_scaled_fp8.cu |
Adds pinned-kernel dispatch and K-split override. |
matmul_block_scaled_fp8_tiling.h |
Defines tiling and residency-selection predicates. |
matmul_block_scaled_fp8_test.cc |
Tests selection boundaries and pinned FP16 execution. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 0
- Review effort level: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Tianlei Wu (tianleiwu)
marked this pull request as draft
September 4, 2026 00:38
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.
Description
Stacked on #32409. That PR's commit is the first commit here; only the second commit (
Pin FP8 GEMV residency ...) belongs to this PR. Please merge #32409 first — GitHub will then collapse this diff to the residency change alone.The tensor-core FP8 GEMV launches
ceil(N / 16)blocks. A 16-warp block only fits twice per SM, so anNjust above32 * sm_countspills into a second, nearly empty wave: on H200N = 5120launches 1.21 waves andncumeasures 66% active cycles.This adds a second
__global__entry point over the same kernel body carrying__launch_bounds__(32 * KSplit, 3), and routes the shapes in that window to it. The hint caps registers at 40, which makes three blocks resident and collapses those shapes to a single wave.cuobjdump -res-usageconfirms the new entry point atREG:40 SHARED:9216, so40 * 512 * 3 = 61440registers and27 KiBof shared memory per SM — three blocks fit.The window is narrow and the hint is a pessimization outside it, so
Fp8MmaGemvPinsResidencyinmatmul_block_scaled_fp8_tiling.hgates on all four conditions that measurement showed matter:N = 1024, 64 blocks, measured 0.96x);KSplit8, taken fromN >= 8192) lose 1.05-1.08x from any explicit__launch_bounds__— declaring it replaces nvcc's implicit bounds even when the register cap is unchanged. 32-warp blocks (theKSplit32 arm from Add FP8 GEMV KSplit32 scheduling for client SM12x GPUs #32409) cannot host 3 blocks per SM at all;M = 16(two tiles) measures 0.74x andM = 32(four tiles) 0.24x, both from spills.The plain kernel is unchanged and stays the default for everything outside the window, so no currently-selected shape changes code path.
Also adds
ORT_FP8_GEMV_KSPLITto override the K-split heuristic for A/B sweeps, matching the override already present in the FP4 GEMV.Measurements (H200, sm_90, 132 SMs, CUDA 13.0)
Kernel time, FP16 activations,
block_size128:On a Qwen3 27B NVFP4 weights + FP8 KV decode workload the FP8 GEMV total drops from 12150 to 11712 us per forward. End-to-end decode throughput (median of repeats, both arms built as real binaries):
M = 32, gated out)Acceptance rate is identical to four decimals in every cell.
Tests
onnxruntime_provider_test --gtest_filter='*MatMulBlockQuantizedFp8Weight*'— 14/14 pass.Two new tests:
GemvTensorCorePinnedResidencyBoundaries— pure predicate test at a fixedsm_countof 132, asserting the(264, 396]column-block window and theKSplit/MTilesexclusions, so the expectations do not move with the test machine.GemvTensorCorePinnedResidencyFp16— runs the hinted kernel. WhichNselects it depends on the device's SM count, so the shape is derived fromcudaGetDeviceProperties(N = 16 * (2 * sm_count + 1), plus a ragged+ 5) and cross-checked againstPickFp8MmaKSplit. Skips on devices whose window lands aboveN = 8192, where the launcher drops to 8 warps and stops hinting.Note for #32409
cuobjdump -res-usageshowsMatMulBlockScaledFp8MmaGemvKernel<32, 4, ...>atSHARED:66560(65 KiB).PickFp8MmaKSplitonly returns 32 whenm <= 8, soMTilesis always 1 there and that instantiation can never be launched — but it is still compiled and emitted. Callinglaunch_mma.template operator()<32, 1>()directly instead of going throughlaunch_for_ksplit<32>()would drop it. Not changed here since it belongs to #32409.