Repository navigation
[CUDA] Bound the FP8 weight dequant scratch by tiling over N - #32129
Merged
Tianlei Wu (tianleiwu) merged 3 commits intoAug 18, 2026
Merged
Tianlei Wu (tianleiwu) merged 3 commits into
Tianlei Wu (tianleiwu) merged 3 commits into
Conversation
MatMulBlockQuantizedFp8Weight's fallback path dequantized the entire [N, K] weight into a scratch buffer before calling cuBLAS. That buffer is 2.37 GiB for a 248320 x 5120 LM head, and it is live for the whole GEMM even though the GEMM reads it once. Cap the scratch and run the dequantize + GEMM pair over N tiles that fit the cap. The row-major [M, N] output is column-major [N, M] to cuBLAS, so an N tile is a plain row offset into Y and no extra copy or reduction is needed. ORT_FP8_DEQUANT_SCRATCH_MIB sets the cap in MiB (default 256), chosen by sweeping it on Qwen3.8-27B at 8K: peak memory 32609 -> 28503 MiB for +1.1% TTFT, where 128 MiB costs 13.9% TTFT and 1 GiB gives up a quarter of the saving. Shapes that fit the cap take a single tile and are bitwise unchanged.
Contributor
There was a problem hiding this comment.
Pull request overview
Caps FP8 weight-dequantization scratch memory by tiling GEMM work over the N dimension.
Changes:
- Adds configurable N-dimension tiling with a 256 MiB default cap.
- Adds FP16 tiling coverage.
- Documents behavior and performance results.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
onnxruntime/contrib_ops/cuda/math/matmul_block_scaled_fp8.cc |
Implements tiled dequantization and GEMM. |
onnxruntime/test/contrib_ops/matmul_block_scaled_fp8_test.cc |
Adds tiling correctness coverage. |
docs/contrib_ops/cuda/matmul_block_scaled_fp8.md |
Documents configuration and benchmarks. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Contributor
Author
|
/azp run Linux Android Emulator QNN CI Pipeline |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Baiju Meswani (baijumeswani)
approved these changes
Aug 18, 2026
Baiju Meswani (baijumeswani)
left a comment
Contributor
There was a problem hiding this comment.
PR description still says N=384, three tiles, and 10 tests. The current test uses N=769, seven tiles, and brings the suite to 11 tests.
Tianlei Wu (tianleiwu)
merged commit Aug 18, 2026
8b9258f
into
microsoft:main
92 of 93 checks passed
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
The
MatMulBlockQuantizedFp8Weightfallback path (M > 8, i.e. every prefill matmul) expands the packed weight into an[N, K]scratch buffer of the activation type and then calls cuBLAS. That scratch is 2.37 GiB for a 248320 x 5120 LM head, and it stays live for the duration of the GEMM even though the GEMM reads it exactly once.This caps the scratch and runs the dequantize + GEMM pair over N tiles that fit inside the cap:
The row-major
[M, N]output is column-major[N, M]to cuBLAS, so an N tile is a plain row offset intoY— there is no extra copy, no split-K reduction, and no change to the arithmetic performed per output element.ORT_FP8_DEQUANT_SCRATCH_MIBsets the cap in MiB (default 256). Shapes small enough to fit the cap take a single tile and are completely unaffected.Verification
Memory and speed on Qwen3.8-27B at an 8K prompt (H200). The cap was chosen by sweeping it:
Numerics. Per-element arithmetic is unchanged, but tiling changes the N extent handed to cuBLAS, so the library is free to select a different kernel for a tile than for the whole matrix. Measured tiled vs untiled, FP16,
K = 5120,block_size = 128:0.125is exactly one FP16 ULP at the magnitude of these outputs (values around 200, where FP16 spacing is2^-3). So where the split changes cuBLAS's kernel choice the result moves by at most one last-bit rounding step — the same order of change as cuBLAS picking a different tactic for any other reason. Single-tile shapes are bitwise identical.Tests.
WeightDequantScratchTilingFp16setsORT_FP8_DEQUANT_SCRATCH_MIB=1so a small shape (N = 769,K = 4096) still splits into six tiles, with a distinct scale on every N row so that a tile reading the wrong weight/scale offset or writing the wrong output column changesY. Confirmed under nsys that the test issues 3 dequant launches while a single-tile control issues 1. All 11MatMulBlockQuantizedFp8WeightOpTestcases pass.Motivation and Context
On Qwen3.8-27B the untiled scratch was the single largest transient allocation in the model and pushed peak memory ~4.1 GB above what the weights and KV cache actually need. Recovering it makes room for longer contexts or a larger batch at the same memory budget, and costs ~1% of TTFT.
This is independent of #32128 (which speeds up the NVFP4 dequantization kernel); the two touch different operators and can land in either order.