| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | # Portions adapted from ROCm/aiter (https://github.com/ROCm/aiter/pull/5750): |
| 5 | # SPDX-License-Identifier: MIT |
| 6 | # Copyright (C) 2026, Advanced Micro Devices, Inc. All rights reserved. |
| 7 | # |
| 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy |
| 9 | # of this software and associated documentation files (the "Software"), to deal |
| 10 | # in the Software without restriction, including without limitation the rights |
| 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 12 | # copies of the Software, and to permit persons to whom the Software is |
| 13 | # furnished to do so, subject to the following conditions: |
| 14 | # |
| 15 | # The above copyright notice and this permission notice shall be included in |
| 16 | # all copies or substantial portions of the Software. |
| 17 | # |
| 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 24 | # SOFTWARE. |
| 25 | """MXFP8 GEMM on 32x32 block-scaled weights for gfx950 (``tl.dot_scaled``). |
| 26 | |
| 27 | ``y = x @ w.T`` with an MXFP8 activation (e4m3 values, one E8M0 scale per |
| 28 | row and 32 K, ``[M, K / 32]``) and an e4m3 weight whose E8M0 scales stay in |
| 29 | the checkpoint's 32x32 blocks, ``[N / 32, K / 32]``, instead of being |
| 30 | expanded to every row. Each weight scale byte is then read once per 32 output |
| 31 | rows, and small-M shapes can use the packed kernel below. |
| 32 | |
| 33 | Two kernels, picked per shape from a table tuned on MI355X: |
| 34 | |
| 35 | * a tiled kernel for larger M; |
| 36 | * a packed kernel for small M, which fills the MFMA's rows with K panels |
| 37 | instead of tokens and keeps only the matching-panel (block-diagonal) |
| 38 | products, so a handful of tokens still streams the weight at full width. |
| 39 | |
| 40 | Either can split K. The partials are normally summed in the same launch by |
| 41 | the last program of each output tile to finish, in split order, so the result |
| 42 | does not depend on scheduling. Otherwise a second launch reduces them. |
| 43 | |
| 44 | The packed kernel and the in-launch split-K reduction on one XCD |
| 45 | (``_split_tile``, ``_sum_splits``, ``_split_counters``, ``_k_partition``) are |
| 46 | adapted from the group32 GEMM in ROCm/aiter#5750. |
| 47 | """ |
| 48 | |
| 49 | from typing import NamedTuple |
| 50 | |
| 51 | import torch |
| 52 | |
| 53 | from vllm.triton_utils import tl, triton |
| 54 | from vllm.utils.torch_utils import direct_register_custom_op |
| 55 | |
| 56 | BLOCK_ROWS = 32 |
| 57 | |
| 58 | # gfx950 hands out workgroups to its XCDs round-robin by program id, and 8 is |
| 59 | # a multiple of the XCD count in every partition mode. _split_tile hardcodes |
| 60 | # the same stride. |
| 61 | _XCD_STRIDE = 8 |
| 62 | # One arrival counter per output tile of an in-launch split-K GEMM. |
| 63 | _SPLIT_COUNTERS = 1 << 16 |
| 64 | |
| 65 | |
| 66 | @triton.jit |
| 67 | def _split_tile(N: tl.constexpr, BLOCK_N: tl.constexpr, SPLITS: tl.constexpr): |
| 68 | """(pid_m, pid_n, tile, split) of a 1D grid that keeps a tile's splits on |
| 69 | one XCD, so they meet in that XCD's L2.""" |
| 70 | pid = tl.program_id(0) |
| 71 | tile = pid // (8 * SPLITS) * 8 + pid % 8 |
| 72 | grid_n: tl.constexpr = (N + BLOCK_N - 1) // BLOCK_N |
| 73 | return tile // grid_n, tile % grid_n, tile, pid // 8 % SPLITS |
| 74 | |
| 75 | |
| 76 | @triton.jit |
| 77 | def _sum_splits( |
| 78 | acc, out_ptrs, out_mask, slot_ptr, count_ptr, split, SPLITS: tl.constexpr |
| 79 | ): |
| 80 | """Publish this split's partial; the tile's last arrival sums all of them |
| 81 | in split order and re-arms the counter.""" |
| 82 | BM: tl.constexpr = acc.shape[0] |
| 83 | BN: tl.constexpr = acc.shape[1] |
| 84 | local = tl.arange(0, BM)[:, None] * BN + tl.arange(0, BN)[None, :] |
| 85 | tl.store(slot_ptr + split * BM * BN + local, acc) |
| 86 | # The partial must be in L2 before the arrival is counted. The splits |
| 87 | # share an XCD, so no device-scope release (an L2 writeback) is needed. |
| 88 | tl.inline_asm_elementwise( |
| 89 | "s_waitcnt vmcnt(0)", "=v,v", [split], dtype=tl.int32, is_pure=False, pack=1 |
| 90 | ) |
| 91 | tl.debug_barrier() |
| 92 | if tl.atomic_add(count_ptr, 1, sem="acq_rel", scope="cta") == SPLITS - 1: |
| 93 | total = tl.zeros((BM, BN), dtype=tl.float32) |
| 94 | for s in tl.static_range(SPLITS): |
| 95 | total += tl.load(slot_ptr + s * BM * BN + local, cache_modifier=".cv") |
| 96 | tl.store(out_ptrs, total.to(out_ptrs.dtype.element_ty), mask=out_mask) |
| 97 | tl.store(count_ptr, 0) |
| 98 | |
| 99 | |
| 100 | @triton.jit(do_not_specialize=["M"]) |
| 101 | def _block32_tiled_kernel( |
| 102 | x_ptr, |
| 103 | xs_ptr, |
| 104 | w_ptr, |
| 105 | ws_ptr, |
| 106 | out_ptr, |
| 107 | slot_ptr, |
| 108 | count_ptr, |
| 109 | M, |
| 110 | N: tl.constexpr, |
| 111 | K: tl.constexpr, |
| 112 | stride_om, |
| 113 | BLOCK_M: tl.constexpr, |
| 114 | BLOCK_N: tl.constexpr, |
| 115 | BLOCK_K: tl.constexpr, |
| 116 | K_PER_SPLIT: tl.constexpr, |
| 117 | N_FIRST: tl.constexpr, |
| 118 | EVEN_K: tl.constexpr, |
| 119 | FUSED_SPLITS: tl.constexpr, |
| 120 | ): |
| 121 | if FUSED_SPLITS > 1: |
| 122 | pid_m, pid_n, tile, pid_k = _split_tile(N, BLOCK_N, FUSED_SPLITS) |
| 123 | if pid_m * BLOCK_M >= M: |
| 124 | return |
| 125 | else: |
| 126 | # N_FIRST launches output-column tiles fastest, so consecutive |
| 127 | # programs share an activation tile. |
| 128 | if N_FIRST: |
| 129 | pid_n, pid_m = tl.program_id(0), tl.program_id(1) |
| 130 | else: |
| 131 | pid_m, pid_n = tl.program_id(0), tl.program_id(1) |
| 132 | pid_k = tl.program_id(2) |
| 133 | offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M) |
| 134 | offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N) |
| 135 | m_mask = offs_m < M |
| 136 | n_mask = offs_n < N |
| 137 | k0 = pid_k * K_PER_SPLIT |
| 138 | offs_k = k0 + tl.arange(0, BLOCK_K) |
| 139 | offs_sk = k0 // 32 + tl.arange(0, BLOCK_K // 32) |
| 140 | x_ptrs = x_ptr + offs_m[:, None] * K + offs_k[None, :] |
| 141 | xs_ptrs = xs_ptr + offs_m[:, None] * (K // 32) + offs_sk[None, :] |
| 142 | w_ptrs = w_ptr + offs_n[:, None] * K + offs_k[None, :] |
| 143 | ws_ptrs = ws_ptr + (offs_n[:, None] // 32) * (K // 32) + offs_sk[None, :] |
| 144 | acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32) |
| 145 | for kk in range(0, K_PER_SPLIT, BLOCK_K): |
| 146 | if EVEN_K: |
| 147 | x = tl.load(x_ptrs, mask=m_mask[:, None], other=0.0) |
| 148 | xs = tl.load(xs_ptrs, mask=m_mask[:, None], other=0) |
| 149 | w = tl.load(w_ptrs, mask=n_mask[:, None], other=0.0) |
| 150 | ws = tl.load(ws_ptrs, mask=n_mask[:, None], other=0) |
| 151 | else: |
| 152 | k_ok = (offs_k + kk) < K |
| 153 | s_ok = (offs_sk + kk // 32) < K // 32 |
| 154 | x = tl.load(x_ptrs, mask=m_mask[:, None] & k_ok[None, :], other=0.0) |
| 155 | xs = tl.load(xs_ptrs, mask=m_mask[:, None] & s_ok[None, :], other=0) |
| 156 | w = tl.load(w_ptrs, mask=n_mask[:, None] & k_ok[None, :], other=0.0) |
| 157 | ws = tl.load(ws_ptrs, mask=n_mask[:, None] & s_ok[None, :], other=0) |
| 158 | acc = tl.dot_scaled(x, xs, "e4m3", w.T, ws, "e4m3", acc=acc) |
| 159 | x_ptrs += BLOCK_K |
| 160 | w_ptrs += BLOCK_K |
| 161 | xs_ptrs += BLOCK_K // 32 |
| 162 | ws_ptrs += BLOCK_K // 32 |
| 163 | out_mask = m_mask[:, None] & n_mask[None, :] |
| 164 | if FUSED_SPLITS > 1: |
| 165 | _sum_splits( |
| 166 | acc, |
| 167 | out_ptr + offs_m[:, None] * stride_om + offs_n[None, :], |
| 168 | out_mask, |
| 169 | slot_ptr + tile * (FUSED_SPLITS * BLOCK_M * BLOCK_N), |
| 170 | count_ptr + tile, |
| 171 | pid_k, |
| 172 | FUSED_SPLITS, |
| 173 | ) |
| 174 | else: |
| 175 | out_ptrs = ( |
| 176 | out_ptr + pid_k * M * N + offs_m[:, None] * stride_om + offs_n[None, :] |
| 177 | ) |
| 178 | tl.store(out_ptrs, acc.to(out_ptr.dtype.element_ty), mask=out_mask) |
| 179 | |
| 180 | |
| 181 | @triton.jit(do_not_specialize=["M"]) |
| 182 | def _block32_packed_kernel( |
| 183 | x_ptr, |
| 184 | xs_ptr, |
| 185 | w_ptr, |
| 186 | ws_ptr, |
| 187 | out_ptr, |
| 188 | slot_ptr, |
| 189 | count_ptr, |
| 190 | M, |
| 191 | N: tl.constexpr, |
| 192 | K: tl.constexpr, |
| 193 | stride_om, |
| 194 | BLOCK_M: tl.constexpr, |
| 195 | BLOCK_N: tl.constexpr, |
| 196 | BLOCK_K: tl.constexpr, |
| 197 | K_PACK: tl.constexpr, |
| 198 | K_PER_SPLIT: tl.constexpr, |
| 199 | EVEN_K: tl.constexpr, |
| 200 | FUSED_SPLITS: tl.constexpr, |
| 201 | ): |
| 202 | if FUSED_SPLITS > 1: |
| 203 | pid_m, pid_n, tile, pid_k = _split_tile(N, BLOCK_N, FUSED_SPLITS) |
| 204 | if pid_m * BLOCK_M >= M: |
| 205 | return |
| 206 | k_start = pid_k * K_PER_SPLIT |
| 207 | k_stop = tl.minimum(k_start + K_PER_SPLIT, K) |
| 208 | else: |
| 209 | pid_m, pid_n, pid_k = tl.program_id(0), tl.program_id(1), tl.program_id(2) |
| 210 | if K_PER_SPLIT >= K: |
| 211 | k_start = 0 |
| 212 | k_stop = K |
| 213 | else: |
| 214 | k_start = pid_k * K_PER_SPLIT |
| 215 | k_stop = tl.minimum(k_start + K_PER_SPLIT, K) |
| 216 | # Tile row r is token r // K_PACK reading K panel r % K_PACK (and likewise |
| 217 | # for weight rows), so one dot covers K_PACK consecutive BLOCK_K slices. |
| 218 | rows = pid_m * BLOCK_M + tl.arange(0, BLOCK_M * K_PACK) // K_PACK |
| 219 | x_panel = tl.arange(0, BLOCK_M * K_PACK) % K_PACK |
| 220 | cols = pid_n * BLOCK_N + tl.arange(0, BLOCK_N * K_PACK) // K_PACK |
| 221 | w_panel = tl.arange(0, BLOCK_N * K_PACK) % K_PACK |
| 222 | ks = tl.arange(0, BLOCK_K) |
| 223 | gs = tl.arange(0, BLOCK_K // 32) |
| 224 | row_ok = rows < M |
| 225 | col_ok = cols < N |
| 226 | acc = tl.zeros((BLOCK_M * K_PACK, BLOCK_N * K_PACK), dtype=tl.float32) |
| 227 | for base in range(k_start, k_stop, BLOCK_K * K_PACK): |
| 228 | xk = base + x_panel[:, None] * BLOCK_K + ks[None, :] |
| 229 | wk = base + w_panel[:, None] * BLOCK_K + ks[None, :] |
| 230 | xg = base // 32 + x_panel[:, None] * (BLOCK_K // 32) + gs[None, :] |
| 231 | wg = base // 32 + w_panel[:, None] * (BLOCK_K // 32) + gs[None, :] |
| 232 | x_mask = row_ok[:, None] |
| 233 | w_mask = col_ok[:, None] |
| 234 | xs_mask = row_ok[:, None] |
| 235 | ws_mask = col_ok[:, None] |
| 236 | if not EVEN_K: |
| 237 | x_mask = x_mask & (xk < K) |
| 238 | w_mask = w_mask & (wk < K) |
| 239 | xs_mask = xs_mask & (xg < K // 32) |
| 240 | ws_mask = ws_mask & (wg < K // 32) |
| 241 | x = tl.load(x_ptr + rows[:, None] * K + xk, mask=x_mask, other=0.0) |
| 242 | w = tl.load(w_ptr + cols[:, None] * K + wk, mask=w_mask, other=0.0) |
| 243 | xs = tl.load(xs_ptr + rows[:, None] * (K // 32) + xg, mask=xs_mask, other=0) |
| 244 | ws = tl.load( |
| 245 | ws_ptr + (cols[:, None] // 32) * (K // 32) + wg, mask=ws_mask, other=0 |
| 246 | ) |
| 247 | acc = tl.dot_scaled(x, xs, "e4m3", w.T, ws, "e4m3", acc=acc) |
| 248 | # Keep only products of matching K panels: the block diagonal. |
| 249 | blocks = acc.reshape(BLOCK_M, K_PACK, BLOCK_N, K_PACK).trans(0, 2, 1, 3) |
| 250 | pair = tl.arange(0, K_PACK) |
| 251 | diagonal = tl.where( |
| 252 | pair[None, None, :, None] == pair[None, None, None, :], blocks, 0.0 |
| 253 | ) |
| 254 | y = tl.sum(tl.sum(diagonal, 3), 2) |
| 255 | out_rows = pid_m * BLOCK_M + tl.arange(0, BLOCK_M) |
| 256 | out_cols = pid_n * BLOCK_N + tl.arange(0, BLOCK_N) |
| 257 | out_ptrs = out_ptr + out_rows[:, None] * stride_om + out_cols[None, :] |
| 258 | out_mask = (out_rows[:, None] < M) & (out_cols[None, :] < N) |
| 259 | if FUSED_SPLITS > 1: |
| 260 | _sum_splits( |
| 261 | y, |
| 262 | out_ptrs, |
| 263 | out_mask, |
| 264 | slot_ptr + tile * (FUSED_SPLITS * BLOCK_M * BLOCK_N), |
| 265 | count_ptr + tile, |
| 266 | pid_k, |
| 267 | FUSED_SPLITS, |
| 268 | ) |
| 269 | else: |
| 270 | tl.store( |
| 271 | out_ptrs + pid_k * M * N, y.to(out_ptr.dtype.element_ty), mask=out_mask |
| 272 | ) |
| 273 | |
| 274 | |
| 275 | @triton.jit(do_not_specialize=["M"]) |
| 276 | def _block32_splitk_reduce_kernel( |
| 277 | partial_ptr, |
| 278 | out_ptr, |
| 279 | M, |
| 280 | N: tl.constexpr, |
| 281 | stride_om, |
| 282 | SPLIT_K: tl.constexpr, |
| 283 | BLOCK_M: tl.constexpr, |
| 284 | BLOCK_N: tl.constexpr, |
| 285 | ): |
| 286 | offs_m = tl.program_id(0) * BLOCK_M + tl.arange(0, BLOCK_M) |
| 287 | offs_n = tl.program_id(1) * BLOCK_N + tl.arange(0, BLOCK_N) |
| 288 | mask = (offs_m[:, None] < M) & (offs_n[None, :] < N) |
| 289 | acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32) |
| 290 | for split in tl.static_range(SPLIT_K): |
| 291 | acc += tl.load( |
| 292 | partial_ptr + split * M * N + offs_m[:, None] * N + offs_n[None, :], |
| 293 | mask=mask, |
| 294 | other=0.0, |
| 295 | ) |
| 296 | tl.store( |
| 297 | out_ptr + offs_m[:, None] * stride_om + offs_n[None, :], |
| 298 | acc.to(out_ptr.dtype.element_ty), |
| 299 | mask=mask, |
| 300 | ) |
| 301 | |
| 302 | |
| 303 | class _Config(NamedTuple): |
| 304 | packed: bool |
| 305 | block_m: int |
| 306 | block_n: int |
| 307 | block_k: int |
| 308 | # K panels per MFMA tile (packed) or K partitions reduced by a second |
| 309 | # launch (tiled). |
| 310 | k_split: int |
| 311 | n_first: bool |
| 312 | num_warps: int |
| 313 | num_stages: int |
| 314 | matrix_instr_nonkdim: int |
| 315 | waves_per_eu: int |
| 316 | # K partitions summed within the launch. |
| 317 | fused_splits: int = 1 |
| 318 | |
| 319 | |
| 320 | def _tiled( |
| 321 | bm, bn, bk, split_k, n_first, warps, stages, nonkdim, waves, fused=1 |
| 322 | ) -> _Config: |
| 323 | return _Config( |
| 324 | False, bm, bn, bk, split_k, bool(n_first), warps, stages, nonkdim, waves, fused |
| 325 | ) |
| 326 | |
| 327 | |
| 328 | def _packed(bm, bn, bk, k_pack, warps, stages, nonkdim, waves, fused=1) -> _Config: |
| 329 | return _Config( |
| 330 | True, bm, bn, bk, k_pack, False, warps, stages, nonkdim, waves, fused |
| 331 | ) |
| 332 | |
| 333 | |
| 334 | # (N, K) -> [(max M, config), ...] for the DeepSeek-V4.1-Flash MXFP8 linears at |
| 335 | # TP2/TP4, tuned on MI355X under HIP graphs with weights cold in the LLC. A |
| 336 | # tier serves every M up to its bound; larger M reuse the last tier. |
| 337 | _TUNED: dict[tuple[int, int], list[tuple[int, _Config]]] = { |
| 338 | # shared expert gate_up, TP4 |
| 339 | (1152, 5120): [ |
| 340 | (1, _packed(8, 16, 1024, 2, 2, 2, 16, 1, 3)), |
| 341 | (2, _packed(4, 32, 256, 4, 2, 2, 16, 1, 5)), |
| 342 | (4, _packed(4, 32, 256, 4, 2, 2, 16, 0, 5)), |
| 343 | (8, _packed(8, 16, 1024, 2, 2, 2, 16, 0, 3)), |
| 344 | (16, _tiled(16, 32, 512, 1, True, 2, 3, 16, 0, 5)), |
| 345 | (24, _tiled(32, 32, 512, 1, True, 4, 3, 16, 1, 5)), |
| 346 | (32, _packed(32, 32, 1024, 1, 4, 2, 16, 0, 5)), |
| 347 | (48, _tiled(64, 32, 512, 1, True, 4, 3, 16, 1, 5)), |
| 348 | (64, _tiled(64, 32, 512, 1, True, 8, 3, 16, 1, 5)), |
| 349 | (96, _tiled(32, 16, 1024, 1, True, 2, 3, 16, 1)), |
| 350 | (128, _tiled(64, 32, 512, 1, True, 8, 2, 16, 0, 3)), |
| 351 | (160, _tiled(64, 32, 512, 1, True, 8, 3, 16, 0, 2)), |
| 352 | (192, _tiled(32, 32, 1024, 1, True, 4, 2, 16, 0)), |
| 353 | (256, _tiled(64, 32, 256, 1, True, 4, 3, 16, 0, 3)), |
| 354 | (320, _tiled(64, 32, 256, 1, True, 8, 3, 16, 0, 2)), |
| 355 | (384, _tiled(32, 32, 512, 1, True, 4, 2, 16, 0)), |
| 356 | (512, _tiled(64, 64, 256, 1, True, 4, 2, 16, 0, 3)), |
| 357 | (768, _tiled(64, 64, 512, 1, True, 4, 2, 32, 0)), |
| 358 | (1024, _tiled(128, 128, 256, 2, False, 4, 2, 16, 1)), |
| 359 | (1536, _tiled(128, 128, 256, 2, False, 4, 2, 16, 0)), |
| 360 | (2048, _tiled(128, 128, 256, 1, False, 4, 2, 32, 0)), |
| 361 | (3072, _tiled(128, 128, 256, 1, False, 4, 2, 32, 1)), |
| 362 | (4096, _tiled(128, 128, 128, 1, True, 4, 2, 32, 0)), |
| 363 | (8192, _tiled(128, 128, 256, 1, False, 4, 2, 32, 1)), |
| 364 | ], |
| 365 | # fused wq_a|wkv (replicated) |
| 366 | (1792, 5120): [ |
| 367 | (1, _packed(8, 16, 1024, 2, 2, 3, 16, 0)), |
| 368 | (8, _packed(8, 16, 1024, 2, 2, 3, 16, 1)), |
| 369 | (16, _tiled(16, 64, 1024, 1, True, 4, 2, 16, 0, 5)), |
| 370 | (32, _packed(16, 16, 1024, 1, 2, 3, 16, 1)), |
| 371 | (48, _tiled(64, 32, 512, 1, True, 8, 2, 16, 0, 4)), |
| 372 | (64, _tiled(64, 32, 512, 1, True, 8, 3, 16, 1, 4)), |
| 373 | (96, _tiled(64, 32, 512, 1, True, 8, 3, 16, 0, 2)), |
| 374 | (128, _tiled(64, 32, 256, 1, True, 4, 3, 16, 0, 4)), |
| 375 | (192, _tiled(64, 32, 256, 1, True, 4, 3, 16, 0, 3)), |
| 376 | (256, _tiled(64, 64, 512, 1, True, 4, 2, 16, 1, 2)), |
| 377 | (384, _tiled(64, 64, 256, 1, True, 4, 2, 16, 0, 3)), |
| 378 | (512, _tiled(64, 64, 512, 1, True, 4, 2, 32, 1)), |
| 379 | (768, _tiled(128, 128, 256, 1, True, 4, 2, 16, 1, 2)), |
| 380 | (1024, _tiled(64, 64, 256, 1, True, 4, 2, 32, 0)), |
| 381 | (2048, _tiled(128, 128, 256, 1, False, 4, 2, 32, 1)), |
| 382 | (3072, _tiled(128, 128, 256, 1, False, 8, 2, 32, 1)), |
| 383 | (4096, _tiled(128, 128, 128, 1, True, 4, 2, 32, 0)), |
| 384 | (8192, _tiled(128, 128, 128, 1, False, 8, 2, 16, 1)), |
| 385 | ], |
| 386 | # shared expert gate_up, TP2 |
| 387 | (2304, 5120): [ |
| 388 | (1, _packed(8, 16, 1024, 2, 2, 3, 16, 1)), |
| 389 | (8, _packed(8, 16, 1024, 2, 2, 3, 16, 0)), |
| 390 | (16, _tiled(16, 64, 1024, 1, True, 4, 3, 16, 1, 5)), |
| 391 | (24, _packed(32, 32, 512, 1, 4, 2, 16, 0, 5)), |
| 392 | (32, _packed(32, 32, 1024, 1, 4, 2, 16, 1, 3)), |
| 393 | (48, _tiled(64, 32, 512, 1, True, 8, 3, 16, 0, 3)), |
| 394 | (64, _tiled(64, 64, 512, 1, True, 8, 2, 16, 0, 5)), |
| 395 | (128, _tiled(64, 32, 256, 1, True, 4, 3, 16, 0, 3)), |
| 396 | (160, _tiled(64, 64, 512, 1, True, 8, 2, 16, 1, 2)), |
| 397 | (192, _tiled(64, 64, 512, 1, True, 4, 2, 16, 0, 2)), |
| 398 | (256, _tiled(64, 64, 256, 1, True, 4, 2, 16, 0, 3)), |
| 399 | (320, _tiled(64, 64, 512, 1, True, 4, 2, 32, 0)), |
| 400 | (384, _tiled(64, 64, 512, 1, True, 4, 2, 32, 1)), |
| 401 | (512, _tiled(128, 128, 256, 1, True, 8, 2, 16, 0, 3)), |
| 402 | (768, _tiled(128, 128, 256, 1, True, 4, 2, 16, 1, 2)), |
| 403 | (1536, _tiled(128, 128, 256, 1, False, 4, 2, 32, 0)), |
| 404 | (2048, _tiled(128, 128, 128, 1, True, 4, 2, 32, 0)), |
| 405 | (3072, _tiled(128, 128, 128, 1, True, 8, 2, 16, 0)), |
| 406 | (4096, _tiled(128, 128, 256, 1, False, 4, 2, 32, 1)), |
| 407 | (8192, _tiled(128, 128, 128, 1, False, 8, 2, 32, 0)), |
| 408 | ], |
| 409 | # indexer wq_b |
| 410 | (4096, 1280): [ |
| 411 | (1, _packed(4, 16, 256, 4, 4, 2, 16, 1)), |
| 412 | (2, _packed(4, 16, 256, 4, 4, 2, 16, 0)), |
| 413 | (4, _packed(4, 16, 256, 4, 4, 2, 16, 1)), |
| 414 | (8, _packed(8, 16, 256, 4, 4, 3, 16, 1)), |
| 415 | (16, _packed(16, 16, 512, 2, 2, 3, 16, 1)), |
| 416 | (32, _packed(32, 16, 1024, 1, 2, 3, 16, 1)), |
| 417 | (64, _tiled(32, 32, 512, 1, True, 4, 3, 16, 1)), |
| 418 | (128, _tiled(64, 32, 512, 1, True, 8, 3, 16, 0)), |
| 419 | (192, _tiled(64, 32, 256, 1, True, 4, 3, 16, 0)), |
| 420 | (256, _tiled(64, 64, 512, 1, True, 4, 2, 32, 1)), |
| 421 | (384, _tiled(64, 64, 256, 1, True, 4, 2, 32, 0)), |
| 422 | (512, _tiled(128, 64, 256, 1, True, 8, 3, 32, 0)), |
| 423 | (1024, _tiled(128, 128, 256, 1, True, 4, 2, 32, 1)), |
| 424 | (2048, _tiled(128, 128, 128, 1, True, 4, 2, 32, 0)), |
| 425 | (3072, _tiled(128, 128, 128, 1, True, 8, 2, 32, 1)), |
| 426 | (4096, _tiled(128, 128, 128, 1, True, 4, 2, 32, 0)), |
| 427 | (8192, _tiled(128, 128, 128, 1, True, 4, 2, 16, 0)), |
| 428 | ], |
| 429 | # shared expert down, TP4 |
| 430 | (5120, 576): [ |
| 431 | (8, _packed(8, 32, 256, 2, 2, 2, 16, 1)), |
| 432 | (16, _packed(16, 32, 512, 1, 2, 2, 16, 0)), |
| 433 | (32, _packed(32, 32, 256, 1, 8, 3, 16, 1)), |
| 434 | (64, _tiled(32, 32, 128, 1, True, 4, 3, 16, 0)), |
| 435 | (96, _tiled(32, 64, 256, 1, True, 4, 3, 16, 0)), |
| 436 | (128, _tiled(32, 32, 256, 1, True, 2, 2, 16, 0)), |
| 437 | (192, _tiled(64, 64, 256, 1, True, 4, 3, 32, 0)), |
| 438 | (256, _tiled(64, 64, 256, 1, True, 2, 3, 32, 1)), |
| 439 | (320, _tiled(64, 64, 256, 1, True, 4, 2, 32, 0)), |
| 440 | (384, _tiled(128, 64, 256, 1, True, 8, 3, 32, 1)), |
| 441 | (512, _tiled(64, 64, 128, 1, True, 4, 2, 32, 0)), |
| 442 | (768, _tiled(128, 128, 128, 1, True, 8, 2, 32, 1)), |
| 443 | (1024, _tiled(128, 64, 128, 1, True, 4, 2, 32, 0)), |
| 444 | (3072, _tiled(128, 128, 128, 1, True, 8, 2, 32, 1)), |
| 445 | (4096, _tiled(128, 128, 128, 1, True, 8, 2, 32, 0)), |
| 446 | (8192, _tiled(128, 128, 128, 1, True, 4, 2, 32, 0)), |
| 447 | ], |
| 448 | # shared expert down, TP2 |
| 449 | (5120, 1152): [ |
| 450 | (1, _packed(4, 32, 128, 4, 2, 3, 16, 1)), |
| 451 | (8, _packed(8, 32, 256, 2, 4, 3, 16, 0)), |
| 452 | (32, _packed(32, 32, 512, 1, 2, 3, 16, 1)), |
| 453 | (128, _tiled(32, 32, 128, 1, True, 4, 3, 16, 0)), |
| 454 | (192, _tiled(64, 64, 512, 1, True, 4, 2, 32, 1)), |
| 455 | (320, _tiled(64, 64, 256, 1, True, 4, 2, 32, 0)), |
| 456 | (384, _tiled(128, 64, 256, 1, True, 8, 3, 32, 1)), |
| 457 | (512, _tiled(128, 128, 256, 1, True, 4, 2, 32, 1)), |
| 458 | (768, _tiled(128, 128, 256, 1, True, 4, 2, 32, 0)), |
| 459 | (1024, _tiled(128, 64, 128, 1, True, 8, 2, 32, 0)), |
| 460 | (1536, _tiled(128, 128, 128, 1, True, 8, 2, 32, 1)), |
| 461 | (2048, _tiled(128, 128, 128, 1, True, 8, 2, 32, 0)), |
| 462 | (3072, _tiled(128, 256, 128, 1, True, 2, 2, 32, 0)), |
| 463 | (4096, _tiled(128, 128, 128, 1, True, 8, 2, 32, 0)), |
| 464 | (8192, _tiled(128, 64, 128, 1, True, 8, 2, 32, 1)), |
| 465 | ], |
| 466 | # wo_b, TP4 |
| 467 | (5120, 2048): [ |
| 468 | (1, _packed(4, 32, 256, 4, 2, 2, 16, 0)), |
| 469 | (4, _packed(4, 32, 256, 4, 2, 2, 16, 1)), |
| 470 | (8, _packed(8, 32, 256, 4, 2, 3, 16, 0)), |
| 471 | (16, _tiled(16, 32, 1024, 1, True, 2, 3, 16, 0)), |
| 472 | (32, _tiled(32, 32, 512, 1, False, 4, 3, 16, 1)), |
| 473 | (64, _tiled(32, 32, 512, 1, True, 4, 2, 16, 0)), |
| 474 | (96, _tiled(32, 64, 512, 1, True, 8, 2, 16, 0)), |
| 475 | (128, _tiled(64, 64, 512, 1, True, 4, 2, 32, 0)), |
| 476 | (192, _tiled(64, 64, 512, 1, True, 4, 2, 32, 1)), |
| 477 | (320, _tiled(64, 64, 256, 1, True, 4, 2, 32, 0)), |
| 478 | (384, _tiled(128, 64, 256, 1, True, 8, 3, 32, 0)), |
| 479 | (768, _tiled(128, 128, 256, 1, True, 4, 2, 32, 0)), |
| 480 | (1024, _tiled(128, 128, 128, 1, True, 8, 2, 32, 1)), |
| 481 | (1536, _tiled(128, 128, 128, 1, True, 4, 2, 32, 0)), |
| 482 | (2048, _tiled(128, 128, 256, 1, True, 8, 2, 32, 1)), |
| 483 | (4096, _tiled(128, 128, 128, 1, True, 8, 2, 32, 1)), |
| 484 | (8192, _tiled(128, 128, 128, 1, True, 2, 2, 32, 1)), |
| 485 | ], |
| 486 | # wo_b, TP2 |
| 487 | (5120, 4096): [ |
| 488 | (4, _packed(4, 32, 512, 4, 2, 2, 16, 0)), |
| 489 | (8, _packed(8, 16, 128, 4, 2, 2, 16, 0, 2)), |
| 490 | (16, _tiled(16, 32, 256, 1, True, 2, 2, 16, 0, 4)), |
| 491 | (24, _tiled(32, 32, 256, 1, True, 4, 3, 16, 0, 4)), |
| 492 | (32, _tiled(32, 64, 512, 1, True, 8, 3, 16, 0, 3)), |
| 493 | (48, _tiled(64, 64, 512, 1, True, 8, 2, 16, 1, 3)), |
| 494 | (64, _tiled(64, 64, 512, 1, True, 8, 2, 16, 0, 3)), |
| 495 | (128, _tiled(64, 64, 256, 1, True, 4, 2, 16, 0, 3)), |
| 496 | (192, _tiled(64, 64, 512, 1, True, 4, 2, 32, 0)), |
| 497 | (256, _tiled(128, 128, 256, 1, True, 4, 2, 16, 1, 2)), |
| 498 | (320, _tiled(64, 64, 256, 1, True, 4, 2, 32, 0)), |
| 499 | (384, _tiled(128, 128, 256, 1, True, 4, 2, 16, 0, 2)), |
| 500 | (768, _tiled(128, 128, 256, 1, True, 4, 2, 32, 1)), |
| 501 | (1024, _tiled(128, 128, 128, 1, True, 8, 2, 32, 1)), |
| 502 | (1536, _tiled(128, 128, 128, 1, True, 4, 2, 32, 0)), |
| 503 | (2048, _tiled(128, 128, 256, 1, True, 8, 2, 32, 1)), |
| 504 | (3072, _tiled(128, 128, 128, 1, True, 4, 2, 32, 0)), |
| 505 | (4096, _tiled(128, 128, 128, 1, True, 8, 2, 16, 1)), |
| 506 | (8192, _tiled(128, 128, 128, 1, True, 4, 2, 32, 0)), |
| 507 | ], |
| 508 | # wq_b, TP4 |
| 509 | (8192, 1280): [ |
| 510 | (1, _packed(8, 32, 256, 2, 4, 3, 16, 1)), |
| 511 | (8, _packed(8, 32, 256, 2, 4, 3, 16, 0)), |
| 512 | (16, _packed(16, 32, 1024, 1, 2, 3, 16, 1)), |
| 513 | (32, _packed(32, 32, 512, 1, 2, 3, 16, 0)), |
| 514 | (48, _tiled(64, 32, 512, 1, True, 8, 3, 16, 1)), |
| 515 | (64, _packed(32, 64, 256, 1, 4, 3, 16, 1)), |
| 516 | (128, _tiled(64, 64, 512, 1, True, 4, 2, 32, 1)), |
| 517 | (192, _tiled(64, 64, 256, 1, True, 4, 2, 32, 0)), |
| 518 | (256, _tiled(128, 64, 256, 1, True, 8, 3, 32, 1)), |
| 519 | (384, _tiled(128, 128, 256, 1, True, 4, 2, 32, 1)), |
| 520 | (512, _tiled(128, 128, 256, 1, True, 4, 2, 32, 0)), |
| 521 | (1024, _tiled(128, 128, 128, 1, True, 4, 2, 32, 0)), |
| 522 | (1536, _tiled(128, 128, 128, 1, True, 8, 2, 16, 0)), |
| 523 | (4096, _tiled(128, 128, 128, 1, True, 4, 2, 32, 0)), |
| 524 | (8192, _tiled(128, 256, 128, 1, True, 2, 2, 32, 0)), |
| 525 | ], |
| 526 | # wq_b, TP2 |
| 527 | (16384, 1280): [ |
| 528 | (2, _packed(4, 32, 128, 4, 2, 3, 16, 1)), |
| 529 | (4, _packed(4, 32, 128, 4, 4, 2, 16, 0)), |
| 530 | (8, _packed(8, 32, 256, 2, 4, 2, 16, 0)), |
| 531 | (32, _packed(32, 64, 256, 1, 4, 3, 16, 1)), |
| 532 | (64, _tiled(64, 64, 256, 1, False, 4, 3, 32, 1)), |
| 533 | (128, _tiled(128, 64, 256, 1, True, 8, 3, 32, 0)), |
| 534 | (256, _tiled(128, 128, 256, 1, True, 4, 2, 32, 1)), |
| 535 | (384, _tiled(128, 128, 128, 1, True, 4, 2, 32, 0)), |
| 536 | (512, _tiled(128, 128, 128, 1, True, 8, 2, 32, 0)), |
| 537 | (768, _tiled(128, 128, 128, 1, True, 8, 2, 16, 1)), |
| 538 | (1024, _tiled(128, 128, 128, 1, True, 8, 2, 32, 1)), |
| 539 | (2048, _tiled(128, 128, 128, 1, True, 4, 2, 32, 0)), |
| 540 | (8192, _tiled(128, 256, 128, 1, True, 2, 2, 32, 0)), |
| 541 | ], |
| 542 | } |
| 543 | |
| 544 | |
| 545 | def _default_config(M: int, N: int, K: int) -> _Config: |
| 546 | """Untuned shapes: the tiers most tuned shapes settle on.""" |
| 547 | if M <= 8: |
| 548 | return _packed(8, 16, 256, 2, 2, 3, 16, 0) |
| 549 | if M <= 32: |
| 550 | return _packed(32, 32, 256, 1, 2, 3, 16, 0) |
| 551 | if M <= 256: |
| 552 | return _tiled(64, 64, 256, 1, True, 4, 2, 16, 0) |
| 553 | return _tiled(128, 128, 128, 1, True, 4, 2, 16, 0) |
| 554 | |
| 555 | |
| 556 | def _config(M: int, N: int, K: int) -> _Config: |
| 557 | tiers = _TUNED.get((N, K)) |
| 558 | if tiers is None: |
| 559 | return _default_config(M, N, K) |
| 560 | for max_m, cfg in tiers: |
| 561 | if max_m >= M: |
| 562 | return cfg |
| 563 | return tiers[-1][1] |
| 564 | |
| 565 | |
| 566 | _split_counters_by_stream: dict[tuple[int, int], torch.Tensor] = {} |
| 567 | |
| 568 | |
| 569 | def _split_counters(device: torch.device) -> torch.Tensor | None: |
| 570 | """Zeroed arrival counters, one set per stream so concurrent launches never |
| 571 | share a tile's counter. Each launch leaves the counters it used at zero. |
| 572 | |
| 573 | None when a stream first needs them inside a CUDA graph capture: that |
| 574 | allocation would come from the graph's pool, where it can take the address |
| 575 | of an intermediate that every replay overwrites. vLLM warms up on its |
| 576 | capture streams first, so this only affects captures without a warmup. |
| 577 | """ |
| 578 | key = (device.index, torch.accelerator.current_stream(device).stream_id) |
| 579 | counters = _split_counters_by_stream.get(key) |
| 580 | if counters is None: |
| 581 | if torch.cuda.is_current_stream_capturing(): |
| 582 | return None |
| 583 | counters = torch.zeros(_SPLIT_COUNTERS, dtype=torch.int32, device=device) |
| 584 | _split_counters_by_stream[key] = counters |
| 585 | return counters |
| 586 | |
| 587 | |
| 588 | def _k_partition(K: int, splits: int, step: int) -> tuple[int, int]: |
| 589 | """K per split, rounded up to whole steps, and the number of non-empty |
| 590 | splits.""" |
| 591 | size = triton.cdiv(triton.cdiv(K, splits), step) * step |
| 592 | return size, triton.cdiv(K, size) |
| 593 | |
| 594 | |
| 595 | def _launch( |
| 596 | x: torch.Tensor, |
| 597 | x_scale: torch.Tensor, |
| 598 | weight: torch.Tensor, |
| 599 | weight_scale: torch.Tensor, |
| 600 | out: torch.Tensor, |
| 601 | cfg: _Config, |
| 602 | ) -> None: |
| 603 | M, K = x.shape |
| 604 | N = weight.shape[0] |
| 605 | opts = dict( |
| 606 | num_warps=cfg.num_warps, |
| 607 | num_stages=cfg.num_stages, |
| 608 | matrix_instr_nonkdim=cfg.matrix_instr_nonkdim, |
| 609 | waves_per_eu=cfg.waves_per_eu, |
| 610 | ) |
| 611 | grid_m, grid_n = triton.cdiv(M, cfg.block_m), triton.cdiv(N, cfg.block_n) |
| 612 | tiles = grid_m * grid_n |
| 613 | step = cfg.block_k * cfg.k_split if cfg.packed else cfg.block_k |
| 614 | splits = cfg.fused_splits if cfg.fused_splits > 1 or cfg.packed else cfg.k_split |
| 615 | k_per_split, splits = _k_partition(K, splits, step) |
| 616 | counters = None |
| 617 | if cfg.fused_splits > 1 and splits > 1 and tiles <= _SPLIT_COUNTERS: |
| 618 | counters = _split_counters(out.device) |
| 619 | if counters is not None: |
| 620 | fused = splits |
| 621 | target = out |
| 622 | slots = torch.empty( |
| 623 | tiles * fused * cfg.block_m * cfg.block_n, |
| 624 | dtype=torch.float32, |
| 625 | device=out.device, |
| 626 | ) |
| 627 | grid: tuple[int, ...] = (triton.cdiv(tiles, _XCD_STRIDE) * _XCD_STRIDE * fused,) |
| 628 | else: |
| 629 | # Without counters, split K through a second launch. |
| 630 | fused = 1 |
| 631 | target = ( |
| 632 | out |
| 633 | if splits == 1 |
| 634 | else torch.empty((splits, M, N), dtype=torch.float32, device=x.device) |
| 635 | ) |
| 636 | slots = counters = target # Unused by the kernels. |
| 637 | if cfg.packed or not cfg.n_first: |
| 638 | grid = (grid_m, grid_n, splits) |
| 639 | else: |
| 640 | grid = (grid_n, grid_m, splits) |
| 641 | args = (x, x_scale, weight, weight_scale, target, slots, counters, M, N, K) |
| 642 | stride = out.stride(0) if splits == 1 or fused > 1 else N |
| 643 | if cfg.packed: |
| 644 | _block32_packed_kernel[grid]( |
| 645 | *args, |
| 646 | stride, |
| 647 | BLOCK_M=cfg.block_m, |
| 648 | BLOCK_N=cfg.block_n, |
| 649 | BLOCK_K=cfg.block_k, |
| 650 | K_PACK=cfg.k_split, |
| 651 | K_PER_SPLIT=k_per_split, |
| 652 | EVEN_K=K % step == 0, |
| 653 | FUSED_SPLITS=fused, |
| 654 | **opts, |
| 655 | ) |
| 656 | else: |
| 657 | _block32_tiled_kernel[grid]( |
| 658 | *args, |
| 659 | stride, |
| 660 | BLOCK_M=cfg.block_m, |
| 661 | BLOCK_N=cfg.block_n, |
| 662 | BLOCK_K=cfg.block_k, |
| 663 | K_PER_SPLIT=k_per_split, |
| 664 | N_FIRST=cfg.n_first, |
| 665 | EVEN_K=K % k_per_split == 0, |
| 666 | FUSED_SPLITS=fused, |
| 667 | **opts, |
| 668 | ) |
| 669 | if fused == 1 and splits > 1: |
| 670 | _block32_splitk_reduce_kernel[(triton.cdiv(M, 32), triton.cdiv(N, 64))]( |
| 671 | target, |
| 672 | out, |
| 673 | M, |
| 674 | N, |
| 675 | out.stride(0), |
| 676 | SPLIT_K=splits, |
| 677 | BLOCK_M=32, |
| 678 | BLOCK_N=64, |
| 679 | ) |
| 680 | |
| 681 | |
| 682 | def _rocm_mxfp8_block32_gemm_impl( |
| 683 | x: torch.Tensor, |
| 684 | x_scale: torch.Tensor, |
| 685 | weight: torch.Tensor, |
| 686 | weight_scale: torch.Tensor, |
| 687 | out_dtype: torch.dtype, |
| 688 | ) -> torch.Tensor: |
| 689 | M, K = x.shape |
| 690 | out = torch.empty((M, weight.shape[0]), dtype=out_dtype, device=x.device) |
| 691 | if M > 0: |
| 692 | _launch(x, x_scale, weight, weight_scale, out, _config(M, weight.shape[0], K)) |
| 693 | return out |
| 694 | |
| 695 | |
| 696 | def _rocm_mxfp8_block32_gemm_fake( |
| 697 | x: torch.Tensor, |
| 698 | x_scale: torch.Tensor, |
| 699 | weight: torch.Tensor, |
| 700 | weight_scale: torch.Tensor, |
| 701 | out_dtype: torch.dtype, |
| 702 | ) -> torch.Tensor: |
| 703 | return x.new_empty((x.shape[0], weight.shape[0]), dtype=out_dtype) |
| 704 | |
| 705 | |
| 706 | # An opaque op: the kernel and tile choice branch on the token count, which a |
| 707 | # compiled caller must not freeze at its trace-time value. |
| 708 | direct_register_custom_op( |
| 709 | op_name="rocm_mxfp8_block32_gemm", |
| 710 | op_func=_rocm_mxfp8_block32_gemm_impl, |
| 711 | fake_impl=_rocm_mxfp8_block32_gemm_fake, |
| 712 | ) |
| 713 | |
| 714 | |
| 715 | def rocm_mxfp8_block32_gemm( |
| 716 | x: torch.Tensor, |
| 717 | x_scale: torch.Tensor, |
| 718 | weight: torch.Tensor, |
| 719 | weight_scale: torch.Tensor, |
| 720 | out_dtype: torch.dtype, |
| 721 | ) -> torch.Tensor: |
| 722 | """``x @ weight.T`` for MXFP8 ``x`` and a 32x32 block-scaled MXFP8 weight. |
| 723 | |
| 724 | Args: |
| 725 | x: [M, K] e4m3 activation, contiguous. |
| 726 | x_scale: [M, K / 32] E8M0 (uint8) activation scales. |
| 727 | weight: [N, K] e4m3 weight, contiguous. |
| 728 | weight_scale: [ceil(N / 32), K / 32] E8M0 (uint8) weight block scales. |
| 729 | out_dtype: Output dtype. |
| 730 | |
| 731 | Returns: |
| 732 | The [M, N] product. |
| 733 | |
| 734 | """ |
| 735 | return torch.ops.vllm.rocm_mxfp8_block32_gemm( |
| 736 | x, x_scale, weight, weight_scale, out_dtype |
| 737 | ) |