| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | """Triton input-staging kernel for DeepSeek V4 MegaMoE. |
| 4 | |
| 5 | Quantizes hidden states to fp8 with E8M0 group scales and repacks the |
| 6 | routing top-k tensors into the int64/float32 layout that the DeepGEMM |
| 7 | MegaMoE kernels consume. |
| 8 | """ |
| 9 | |
| 10 | import torch |
| 11 | |
| 12 | from vllm.triton_utils import tl, triton |
| 13 | |
| 14 | |
| 15 | @triton.jit(do_not_specialize=["num_tokens"]) |
| 16 | def _prepare_megamoe_inputs_kernel( |
| 17 | hidden_states, |
| 18 | x_fp8, |
| 19 | x_sf, |
| 20 | shared_x_sf, |
| 21 | topk_ids, |
| 22 | topk_weights, |
| 23 | is_padding, |
| 24 | topk_idx_out, |
| 25 | topk_weights_out, |
| 26 | hidden_stride_m: tl.constexpr, |
| 27 | hidden_stride_k: tl.constexpr, |
| 28 | x_stride_m: tl.constexpr, |
| 29 | x_stride_k: tl.constexpr, |
| 30 | x_sf_stride_m: tl.constexpr, |
| 31 | x_sf_stride_k: tl.constexpr, |
| 32 | shared_x_sf_stride_m: tl.constexpr, |
| 33 | shared_x_sf_stride_k: tl.constexpr, |
| 34 | topk_ids_stride_m: tl.constexpr, |
| 35 | topk_ids_stride_k: tl.constexpr, |
| 36 | topk_weights_stride_m: tl.constexpr, |
| 37 | topk_weights_stride_k: tl.constexpr, |
| 38 | is_padding_stride_m: tl.constexpr, |
| 39 | topk_idx_stride_m: tl.constexpr, |
| 40 | topk_idx_stride_k: tl.constexpr, |
| 41 | topk_weights_out_stride_m: tl.constexpr, |
| 42 | topk_weights_out_stride_k: tl.constexpr, |
| 43 | num_tokens, |
| 44 | hidden_size: tl.constexpr, |
| 45 | top_k: tl.constexpr, |
| 46 | BLOCK_M: tl.constexpr, |
| 47 | BLOCK_K: tl.constexpr, |
| 48 | GROUP_K: tl.constexpr, |
| 49 | BLOCK_TOPK: tl.constexpr, |
| 50 | SHARED_BLOCK_M: tl.constexpr, |
| 51 | ) -> None: |
| 52 | token_id = tl.program_id(0) * BLOCK_M + tl.arange(0, BLOCK_M) |
| 53 | token_mask = token_id < num_tokens |
| 54 | k_block_id = tl.program_id(1) |
| 55 | |
| 56 | k_offsets = k_block_id * BLOCK_K + tl.arange(0, BLOCK_K) |
| 57 | k_mask = token_mask[:, None] & (k_offsets[None, :] < hidden_size) |
| 58 | hidden = tl.load( |
| 59 | hidden_states |
| 60 | + token_id[:, None] * hidden_stride_m |
| 61 | + k_offsets[None, :] * hidden_stride_k, |
| 62 | mask=k_mask, |
| 63 | other=0.0, |
| 64 | ).to(tl.float32) |
| 65 | |
| 66 | num_groups: tl.constexpr = BLOCK_K // GROUP_K |
| 67 | hidden_groups = tl.reshape(tl.abs(hidden), [BLOCK_M, num_groups, GROUP_K]) |
| 68 | amax = tl.max(hidden_groups, axis=2) |
| 69 | amax = tl.maximum(amax, 1.0e-4) |
| 70 | |
| 71 | scale = amax / 448.0 |
| 72 | scale_bits = scale.to(tl.uint32, bitcast=True) |
| 73 | scale_exp = ((scale_bits >> 23) & 0xFF) + ((scale_bits & 0x7FFFFF) != 0).to( |
| 74 | tl.uint32 |
| 75 | ) |
| 76 | scale_exp = tl.minimum(tl.maximum(scale_exp, 1), 254) |
| 77 | rounded_scale = (scale_exp << 23).to(tl.float32, bitcast=True) |
| 78 | |
| 79 | hidden_groups = tl.reshape(hidden, [BLOCK_M, num_groups, GROUP_K]) |
| 80 | scaled = hidden_groups * (1.0 / rounded_scale)[:, :, None] |
| 81 | scaled = tl.reshape(scaled, [BLOCK_M, BLOCK_K]) |
| 82 | fp8 = scaled.to(tl.float8e4nv) |
| 83 | tl.store( |
| 84 | x_fp8 + token_id[:, None] * x_stride_m + k_offsets[None, :] * x_stride_k, |
| 85 | fp8, |
| 86 | mask=k_mask, |
| 87 | ) |
| 88 | |
| 89 | scale_offsets = tl.arange(0, num_groups) |
| 90 | packed_scale = tl.sum(scale_exp << (scale_offsets[None, :] * 8), axis=1).to( |
| 91 | tl.int32 |
| 92 | ) |
| 93 | tl.store( |
| 94 | x_sf + token_id * x_sf_stride_m + k_block_id * x_sf_stride_k, |
| 95 | packed_scale, |
| 96 | mask=token_mask, |
| 97 | ) |
| 98 | |
| 99 | # DeepGEMM's SM100 shared-expert TMA loads require the activation scales |
| 100 | # in an MN-major layout whose row permutation depends on the MegaMoE |
| 101 | # scheduler's runtime BLOCK_M. Write that view while the packed UE8M0 scale |
| 102 | # is already resident, avoiding another kernel and temporary tensor. |
| 103 | if shared_x_sf is not None: |
| 104 | m_block_id = token_id // SHARED_BLOCK_M |
| 105 | m_in_block = token_id % SHARED_BLOCK_M |
| 106 | aligned_block_m: tl.constexpr = triton.cdiv(SHARED_BLOCK_M, 128) * 128 |
| 107 | transposed_m = ( |
| 108 | (m_in_block // 128) * 128 + (m_in_block % 32) * 4 + (m_in_block % 128) // 32 |
| 109 | ) |
| 110 | shared_row = m_block_id * aligned_block_m + transposed_m |
| 111 | tl.store( |
| 112 | shared_x_sf |
| 113 | + shared_row * shared_x_sf_stride_m |
| 114 | + k_block_id * shared_x_sf_stride_k, |
| 115 | packed_scale, |
| 116 | mask=token_mask, |
| 117 | ) |
| 118 | |
| 119 | if k_block_id == 0: |
| 120 | topk_offsets = tl.arange(0, BLOCK_TOPK) |
| 121 | topk_mask = token_mask[:, None] & (topk_offsets[None, :] < top_k) |
| 122 | token_is_padding = tl.full((BLOCK_M,), False, tl.int1) |
| 123 | if is_padding is not None: |
| 124 | token_is_padding = tl.load( |
| 125 | is_padding + token_id * is_padding_stride_m, |
| 126 | mask=token_mask, |
| 127 | other=True, |
| 128 | ) |
| 129 | |
| 130 | ids = tl.load( |
| 131 | topk_ids |
| 132 | + token_id[:, None] * topk_ids_stride_m |
| 133 | + topk_offsets[None, :] * topk_ids_stride_k, |
| 134 | mask=topk_mask, |
| 135 | other=0, |
| 136 | ).to(tl.int64) |
| 137 | ids = tl.where(token_is_padding[:, None], -1, ids) |
| 138 | tl.store( |
| 139 | topk_idx_out |
| 140 | + token_id[:, None] * topk_idx_stride_m |
| 141 | + topk_offsets[None, :] * topk_idx_stride_k, |
| 142 | ids, |
| 143 | mask=topk_mask, |
| 144 | ) |
| 145 | |
| 146 | weights = tl.load( |
| 147 | topk_weights |
| 148 | + token_id[:, None] * topk_weights_stride_m |
| 149 | + topk_offsets[None, :] * topk_weights_stride_k, |
| 150 | mask=topk_mask, |
| 151 | other=0.0, |
| 152 | ) |
| 153 | weights = tl.where(token_is_padding[:, None], 0.0, weights) |
| 154 | tl.store( |
| 155 | topk_weights_out |
| 156 | + token_id[:, None] * topk_weights_out_stride_m |
| 157 | + topk_offsets[None, :] * topk_weights_out_stride_k, |
| 158 | weights, |
| 159 | mask=topk_mask, |
| 160 | ) |
| 161 | |
| 162 | |
| 163 | def prepare_megamoe_inputs( |
| 164 | hidden_states: torch.Tensor, |
| 165 | topk_weights: torch.Tensor, |
| 166 | topk_ids: torch.Tensor, |
| 167 | x_fp8: torch.Tensor, |
| 168 | x_sf: torch.Tensor, |
| 169 | topk_idx_out: torch.Tensor, |
| 170 | topk_weights_out: torch.Tensor, |
| 171 | is_padding: torch.Tensor | None = None, |
| 172 | shared_x_sf: torch.Tensor | None = None, |
| 173 | shared_block_m: int | None = None, |
| 174 | ) -> None: |
| 175 | num_tokens, hidden_size = hidden_states.shape |
| 176 | if num_tokens == 0: |
| 177 | return |
| 178 | if hidden_size % 128 != 0: |
| 179 | raise ValueError( |
| 180 | "DeepSeek V4 MegaMoE input staging requires hidden_size to be " |
| 181 | "a multiple of 128." |
| 182 | ) |
| 183 | top_k = topk_ids.shape[1] |
| 184 | if topk_weights.shape != topk_ids.shape: |
| 185 | raise ValueError( |
| 186 | "DeepSeek V4 MegaMoE input staging requires topk_weights and " |
| 187 | "topk_ids to have the same shape." |
| 188 | ) |
| 189 | if (shared_x_sf is None) != (shared_block_m is None): |
| 190 | raise ValueError( |
| 191 | "DeepSeek V4 MegaMoE shared input staging requires both " |
| 192 | "shared_x_sf and shared_block_m." |
| 193 | ) |
| 194 | if shared_x_sf is not None: |
| 195 | assert shared_block_m is not None |
| 196 | if shared_block_m <= 0: |
| 197 | raise ValueError("MegaMoE shared_block_m must be positive.") |
| 198 | expected_sf_k = hidden_size // 128 |
| 199 | if shared_x_sf.ndim != 2 or shared_x_sf.shape[1] != expected_sf_k: |
| 200 | raise ValueError( |
| 201 | "MegaMoE shared_x_sf must have shape " |
| 202 | f"(*, {expected_sf_k}), got {tuple(shared_x_sf.shape)}." |
| 203 | ) |
| 204 | aligned_block_m = triton.cdiv(shared_block_m, 128) * 128 |
| 205 | required_rows = triton.cdiv(num_tokens, shared_block_m) * aligned_block_m |
| 206 | if shared_x_sf.shape[0] < required_rows: |
| 207 | raise ValueError( |
| 208 | "MegaMoE shared_x_sf has insufficient rows: requires " |
| 209 | f"{required_rows}, got {shared_x_sf.shape[0]}." |
| 210 | ) |
| 211 | |
| 212 | block_k = 128 |
| 213 | # On GB200, eight-row tiles win from 64 tokens; keep smaller batches untiled. |
| 214 | block_m = 8 if num_tokens >= 64 else 1 |
| 215 | grid = (triton.cdiv(num_tokens, block_m), triton.cdiv(hidden_size, block_k)) |
| 216 | block_topk = triton.next_power_of_2(top_k) |
| 217 | padding_stride_m = is_padding.stride(0) if is_padding is not None else 0 |
| 218 | _prepare_megamoe_inputs_kernel[grid]( |
| 219 | hidden_states, |
| 220 | x_fp8, |
| 221 | x_sf, |
| 222 | shared_x_sf, |
| 223 | topk_ids, |
| 224 | topk_weights, |
| 225 | is_padding, |
| 226 | topk_idx_out, |
| 227 | topk_weights_out, |
| 228 | hidden_states.stride(0), |
| 229 | hidden_states.stride(1), |
| 230 | x_fp8.stride(0), |
| 231 | x_fp8.stride(1), |
| 232 | x_sf.stride(0), |
| 233 | x_sf.stride(1), |
| 234 | shared_x_sf.stride(0) if shared_x_sf is not None else 0, |
| 235 | shared_x_sf.stride(1) if shared_x_sf is not None else 0, |
| 236 | topk_ids.stride(0), |
| 237 | topk_ids.stride(1), |
| 238 | topk_weights.stride(0), |
| 239 | topk_weights.stride(1), |
| 240 | padding_stride_m, |
| 241 | topk_idx_out.stride(0), |
| 242 | topk_idx_out.stride(1), |
| 243 | topk_weights_out.stride(0), |
| 244 | topk_weights_out.stride(1), |
| 245 | num_tokens, |
| 246 | hidden_size, |
| 247 | top_k, |
| 248 | BLOCK_M=block_m, |
| 249 | BLOCK_K=block_k, |
| 250 | GROUP_K=32, |
| 251 | BLOCK_TOPK=block_topk, |
| 252 | SHARED_BLOCK_M=shared_block_m or 1, |
| 253 | num_warps=4, |
| 254 | ) |