|
| 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`. |
0 commit comments