Skip to content

Commit 8b99eec

Browse files
authored
GEMM+RoPE+MXFP8 fusion (#367)
* Add fused gemm+rope+mxfp8quant kernel. * Add documentation and general interface names * Address coderabbitai's suggestions * Additional tests for fused gemm+rope+mxfp8 * Remove NUM_HEADS constant
1 parent 1ffbf98 commit 8b99eec

8 files changed

Lines changed: 1511 additions & 0 deletions

File tree

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# GEMM + RoPE + MXFP8 Projection (SM100)
2+
3+
**This is an experimental API and subject to change.**
4+
5+
## Overview
6+
7+
**Fused projection GEMM + per-head YARN RoPE + dual-direction MXFP8 quantize**: a persistent dense GEMM on NVIDIA Blackwell GPUs (SM100+) that projects bf16 activations, applies the Megatron MLA-YARN rotary embedding to each attention head's trailing rotary features, and MXFP8 (E4M3, block=32) quantizes the result in **both** the rowwise (D-direction) and columnwise (S-direction) layouts. Implemented with CUTLASS/CUTE.
8+
9+
Emitting both scale directions makes the output directly consumable by block-scaled matmuls that need either operand orientation. For example, in DeepSeek-V3 the rowwise output feeds the forward `QK^T` and the columnwise output feeds the backward `dK = dS^T · Q` on the cuDNN `is_input_fp8` attention path.
10+
11+
- **Inputs**: bf16 activations `x`, bf16 projection weight `w`, and bf16 rotary tables `cos`/`sin`.
12+
- **Outputs**: rowwise and columnwise MXFP8 data (`out_fp8_row`, `out_fp8_col`) and their E8M0 scale factors (`out_scales_row`, `out_scales_col`).
13+
14+
The kernel is tuned for the DeepSeek-V3 Q up-projection shapes: `NUM_HEADS=128`, `HEAD_DIM=192` (`QK_NOPE=128` + `QK_ROPE=64`), MXFP8 `BLOCK=32`, tile `TILE_M=128` (one head per CTA).
15+
16+
### Shapes
17+
18+
- **Inputs**
19+
- `x`: `(tokens, Q_LORA)``tokens % TILE_M == 0`
20+
- `w`: `(Q_LORA, NUM_HEADS·HEAD_DIM)` when `w_out_in=False`, or the transformer-engine-native transposed `(NUM_HEADS·HEAD_DIM, Q_LORA)` when `w_out_in=True`
21+
- `cos`, `sin`: `(tokens, QK_ROPE)`
22+
23+
- **Outputs**
24+
- `out_fp8_row`, `out_fp8_col`: `(tokens, NUM_HEADS, HEAD_DIM)`
25+
- `out_scales_row`: `(tokens, NUM_HEADS, HEAD_DIM // BLOCK)`
26+
- `out_scales_col`: `(tokens // BLOCK, NUM_HEADS, HEAD_DIM)`
27+
28+
### Equations
29+
30+
Project and reshape per head, then apply the interleaved-in / halves-out YARN RoPE to the trailing `QK_ROPE` features of each head:
31+
32+
$$
33+
Y[t, h, :] = (x \, W)\;\text{reshaped to}\;[\text{tokens}, \text{NUM\_HEADS}, \text{HEAD\_DIM}]
34+
$$
35+
36+
$$
37+
Y_{\text{pe}} = \operatorname{RoPE}(Y[\ldots, \text{QK\_NOPE}:],\; \cos, \sin)
38+
$$
39+
40+
MXFP8 quantize with block size `BLOCK=32`, independently for each direction (E8M0 per-block scale, E4M3 data):
41+
42+
$$
43+
(\text{out\_fp8\_row}, \text{out\_scales\_row}) = \operatorname{MXFP8}_{\text{D}}(Y)\quad\text{(blocks along HEAD\_DIM)}
44+
$$
45+
46+
$$
47+
(\text{out\_fp8\_col}, \text{out\_scales\_col}) = \operatorname{MXFP8}_{\text{S}}(Y)\quad\text{(blocks along tokens)}
48+
$$
49+
50+
### Diagram
51+
52+
```text
53+
x (tokens x Q_LORA), w (Q_LORA x NUM_HEADS*HEAD_DIM)
54+
| GEMM
55+
v
56+
Y (tokens x NUM_HEADS x HEAD_DIM) ---- per-head YARN RoPE on trailing QK_ROPE
57+
|
58+
+--> MXFP8 rowwise (D) -> out_fp8_row, out_scales_row
59+
+--> MXFP8 columnwise(S)-> out_fp8_col, out_scales_col
60+
```
61+
62+
## API Usage
63+
64+
### High-level wrapper
65+
```python
66+
result = gemm_proj_rope_mxfp8_wrapper_sm100(
67+
x,
68+
w,
69+
cos,
70+
sin,
71+
w_out_in=False,
72+
stream=None,
73+
)
74+
out_fp8_row, out_scales_row, out_fp8_col, out_scales_col = result
75+
# Key access: result["out_fp8_row"], result["out_scales_col"], ...
76+
```
77+
78+
### Class API
79+
```python
80+
from cuda.bindings import driver as cuda
81+
82+
op = GemmProjRopeMxfp8Sm100(
83+
sample_x=x,
84+
sample_w=w,
85+
sample_cos=cos,
86+
sample_sin=sin,
87+
sample_out_fp8_row=out_fp8_row,
88+
sample_out_scales_row=out_scales_row,
89+
sample_out_fp8_col=out_fp8_col,
90+
sample_out_scales_col=out_scales_col,
91+
w_out_in=False,
92+
)
93+
assert op.check_support()
94+
op.compile()
95+
op.execute(x, w, cos, sin, out_fp8_row, out_scales_row, out_fp8_col, out_scales_col, current_stream=None)
96+
```
97+
98+
---
99+
100+
## Parameters
101+
102+
### Input/Output tensors
103+
- Input **x**: `x` (wrapper) or `sample_x`/`x` (class)
104+
- Shape: `(tokens, Q_LORA)`; Dtype: `bfloat16`
105+
- Input **w**: `w` (wrapper) or `sample_w`/`w` (class)
106+
- Shape: `(Q_LORA, NUM_HEADS·HEAD_DIM)` (`w_out_in=False`) or `(NUM_HEADS·HEAD_DIM, Q_LORA)` (`w_out_in=True`); Dtype: `bfloat16`
107+
- Input **cos**, **sin**: Shape `(tokens, QK_ROPE)`; Dtype: `bfloat16`
108+
- Output **out_fp8_row** / **out_fp8_col**: Shape `(tokens, NUM_HEADS, HEAD_DIM)`; Dtype: `float8_e4m3fn`
109+
- Output **out_scales_row**: Shape `(tokens, NUM_HEADS, HEAD_DIM // BLOCK)`; Dtype: `uint8` (E8M0)
110+
- Output **out_scales_col**: Shape `(tokens // BLOCK, NUM_HEADS, HEAD_DIM)`; Dtype: `uint8` (E8M0)
111+
112+
### Common parameters
113+
- `w_out_in: bool`
114+
- Whether `w` is stored `[out, in]` (`True`) or `[in, out]` (`False`). The kernel consumes both as the logical `[out, in]` B operand via the cutlass major mode (like cuBLAS `transb`); no transposed copy is materialized. Default: `False`
115+
- CUDA stream (`current_stream` in class API, `stream` in wrapper). Defaults to the current torch stream (required for CUDA-graph capture).
116+
117+
### Wrapper return values
118+
119+
Returns a `TupleDict` with keys `out_fp8_row`, `out_scales_row`, `out_fp8_col`, `out_scales_col`. Tuple unpacking order is `(out_fp8_row, out_scales_row, out_fp8_col, out_scales_col)`.
120+
121+
---
122+
123+
## Support surface and constraints
124+
125+
### Dtypes
126+
- `x`, `w`, `cos`, `sin` must be `bfloat16`.
127+
- `out_fp8_row`, `out_fp8_col` must be `float8_e4m3fn`; `out_scales_row`, `out_scales_col` must be `uint8` (E8M0).
128+
129+
### Shapes and divisibility
130+
- `tokens % TILE_M == 0` (`TILE_M = 128`); no tail handling.
131+
- The projected weight dimension must equal `NUM_HEADS·HEAD_DIM`.
132+
133+
### Environment
134+
- Requires CUDA with SM100+ compute capability.
135+
136+
---
137+
138+
## Source provenance
139+
140+
Integrated from the DeepSeek-V3 MLA fused Q up-projection kernel developed for Megatron-LM MXFP8 training (Blackwell / customte CUTLASS 4.4.1). The public entry point `run(...)` and the pure-PyTorch oracle `gemm_proj_rope_mxfp8_reference(...)` live in `python/cudnn/gemm_proj_rope_mxfp8/gemm_proj_rope_mxfp8.py`.
141+
142+
## Installation
143+
144+
Requires the optional CuTeDSL dependencies:
145+
146+
```bash
147+
pip install nvidia-cudnn-frontend[cutedsl]
148+
```
149+
150+
## Usage examples
151+
152+
For usage examples, see test cases in `test/python/fe_api/test_gemm_proj_rope_mxfp8.py`.

docs/fe-oss-apis/overview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
This folder documents the Python FE APIs implemented under `python/cudnn`. For details on currently implemented operations, see:
66
- [GEMM + Amax](gemm_fusions/gemm_amax.md)
7+
- [GEMM + RoPE + MXFP8 Projection](gemm_fusions/gemm_proj_rope_mxfp8.md)
78
- [GEMM + SwiGLU](gemm_fusions/gemm_swiglu.md)
89
- [GEMM + sReLU](gemm_fusions/gemm_srelu.md)
910
- [GEMM + dsReLU](gemm_fusions/gemm_dsrelu.md)

python/cudnn/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,8 @@ def _dlopen_cudnn():
291291
"gemm_dsrelu_wrapper_sm100": (".gemm_dsrelu", "gemm_dsrelu_wrapper_sm100"),
292292
"GemmAmaxSm100": (".gemm_amax", "GemmAmaxSm100"),
293293
"gemm_amax_wrapper_sm100": (".gemm_amax", "gemm_amax_wrapper_sm100"),
294+
"GemmProjRopeMxfp8Sm100": (".gemm_proj_rope_mxfp8", "GemmProjRopeMxfp8Sm100"),
295+
"gemm_proj_rope_mxfp8_wrapper_sm100": (".gemm_proj_rope_mxfp8", "gemm_proj_rope_mxfp8_wrapper_sm100"),
294296
"RmsNormRhtAmaxSm100": (".rmsnorm_rht_amax", "RmsNormRhtAmaxSm100"),
295297
"rmsnorm_rht_amax_wrapper_sm100": (".rmsnorm_rht_amax", "rmsnorm_rht_amax_wrapper_sm100"),
296298
"grouped_gemm": (".grouped_gemm", None),
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from .api import (
2+
GemmProjRopeMxfp8Sm100,
3+
gemm_proj_rope_mxfp8_wrapper_sm100,
4+
)
5+
from .gemm_proj_rope_mxfp8 import (
6+
run,
7+
gemm_proj_rope_mxfp8_reference,
8+
)
9+
10+
__all__ = [
11+
"GemmProjRopeMxfp8Sm100",
12+
"gemm_proj_rope_mxfp8_reference",
13+
"gemm_proj_rope_mxfp8_wrapper_sm100",
14+
"run",
15+
]

0 commit comments

Comments
 (0)