Skip to content

[CUDA] MatMulBlockQuantizedFp8Weight: fold the W8A8 activation QDQ into the decode GEMV - #31481

Open
tianleiwu wants to merge 2 commits into
mainfrom
tlwu/20260802/fp8_act_qdq_fold
Open

[CUDA] MatMulBlockQuantizedFp8Weight: fold the W8A8 activation QDQ into the decode GEMV#31481
tianleiwu wants to merge 2 commits into
mainfrom
tlwu/20260802/fp8_act_qdq_fold

Conversation

@tianleiwu

Copy link
Copy Markdown
Contributor

Description

When MatMulBlockQuantizedFp8Weight is given an activation scale (the W8A8 path), it ran QuantizeDequantizeActivationFp8Kernel over the activation A into a scratch buffer, then read that buffer back in the GEMV. At decode shapes (m <= 8) A is tiny, so that round trip is almost entirely launch overhead and a pointless trip to memory.

This applies the same quantize-dequantize in registers, on the activation values the GEMV has already loaded. Fp8ActQdq16 mirrors the standalone kernel element for element, so the result is bit-identical.

Scope is limited to the m <= 8 GEMV fast path. The cuBLAS path is unchanged and still uses the standalone kernel, so the [M, K] scratch buffer is now only allocated when that path is actually taken.

Opt out with ORT_DISABLE_FUSED_FP8_ACT_QDQ=1.

Verification

Bit-identical to the previous two-step path, checked over 26112 elements across six shapes covering m in {1, 2, 4}, block_size in {16, 128, 2048}, and both the MMA and scalar GEMV kernels.

Motivation and Context

Measured on Qwen3.6-35B-A3B MTP (N=3) on H200: the standalone QDQ kernel disappears from the trace entirely, removing 40 launches/step and 54.3 us/step.

In isolation the win is largest at small N (-9.7% at N=512) and shrinks as N grows, because the fused kernel redoes the QDQ once per A-load rather than once per element. That trade is favorable exactly in the decode regime this fast path serves.

Builds on the decode GEMV added in #31155.

…to the GEMV

With an activation scale the operator ran QuantizeDequantizeActivationFp8Kernel over
A into a scratch buffer and then read that buffer back in the GEMV. At decode shapes
(m <= 8) A is tiny, so the round trip is almost entirely launch and memory-round-trip
overhead.

Apply the same quantize-dequantize in registers on the activations the GEMV has
already loaded. Fp8ActQdq16 mirrors the standalone kernel element for element, so the
result is bit-identical -- verified over 26112 elements across six shapes covering
m in {1,2,4}, block_size in {16,128,2048} and both the MMA and scalar GEMV kernels.

Only the m <= 8 GEMV path is affected; the cuBLAS path still uses the standalone
kernel, so its [M, K] scratch buffer is now only allocated for that path.

Measured on Qwen3.6-35B-A3B MTP N=3 (H200): the kernel disappears from the trace,
-40 launches/step and -54.3 us/step. In isolation the win is largest at small N
(-9.7% at N=512) and shrinks as N grows, because the fused kernel redoes the QDQ once
per A-load.

Opt out with ORT_DISABLE_FUSED_FP8_ACT_QDQ=1.
…tenv

MSVC treats C4996 ('getenv' is unsafe) as an error in the Windows GPU CI
builds, breaking the CUDA, CUDA plugin EP, TensorRT and kernel-doc jobs.
Use ORT's cross-platform env var helper, matching other CUDA kernels.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the CUDA contrib op MatMulBlockQuantizedFp8Weight decode GEMV fast path (small M) by fusing the optional W8A8 activation quantize/dequantize (QDQ) into the GEMV kernels, avoiding a separate QDQ kernel launch and an [M, K] scratch-buffer round trip.

Changes:

  • Add an optional act_scale parameter to the FP8 decode GEMV launcher and kernels, enabling in-register W8A8 activation QDQ during GEMV.
  • Gate allocation/use of the activation scratch buffer so it’s only created for the cuBLAS GEMM path (or when fused QDQ is explicitly disabled via ORT_DISABLE_FUSED_FP8_ACT_QDQ=1).
  • Add an environment-variable kill switch to disable the fused activation QDQ.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
onnxruntime/contrib_ops/cuda/math/matmul_block_scaled_fp8.h Extends GEMV launcher API with optional act_scale and documents fused activation QDQ behavior.
onnxruntime/contrib_ops/cuda/math/matmul_block_scaled_fp8.cu Implements the in-register activation QDQ helper and wires act_scale through scalar + MMA GEMV kernels.
onnxruntime/contrib_ops/cuda/math/matmul_block_scaled_fp8.cc Adds kill-switch env var and avoids allocating activation scratch when fused QDQ is enabled for decode GEMV.

Comment thread onnxruntime/contrib_ops/cuda/math/matmul_block_scaled_fp8.cc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants