| 1 | // Adapted from |
| 2 | // https://github.com/sgl-project/sglang/tree/main/sgl-kernel/csrc/cpu |
| 3 | |
| 4 | // clang-format off |
| 5 | |
| 6 | #include "common.h" |
| 7 | #include "gemm.h" |
| 8 | #include "moe.h" |
| 9 | |
| 10 | template <typename scalar_t, typename packed_t, typename param_t, bool is_mxfp4> |
| 11 | void fused_experts_fp_kernel_impl( |
| 12 | scalar_t* __restrict__ output, |
| 13 | scalar_t* __restrict__ ic0, |
| 14 | scalar_t* __restrict__ ic1, |
| 15 | scalar_t* __restrict__ ic2, |
| 16 | scalar_t* __restrict__ A_tmp, |
| 17 | scalar_t* __restrict__ B_tmp, |
| 18 | float* __restrict__ C_tmp, |
| 19 | const scalar_t* __restrict__ input, |
| 20 | const packed_t* __restrict__ packed_w1, |
| 21 | const packed_t* __restrict__ packed_w2, |
| 22 | const float* __restrict__ w1_bias, |
| 23 | const float* __restrict__ w2_bias, |
| 24 | const param_t* __restrict__ w1s, |
| 25 | const param_t* __restrict__ w2s, |
| 26 | int64_t block_size_N, |
| 27 | int64_t block_size_K, |
| 28 | const float* __restrict__ topk_weights, |
| 29 | const int32_t* __restrict__ sorted_ids, |
| 30 | const int32_t* __restrict__ expert_ids, |
| 31 | const int32_t* __restrict__ offsets, |
| 32 | int64_t M, |
| 33 | int64_t N, |
| 34 | int64_t K, |
| 35 | int64_t E, |
| 36 | int64_t topk, |
| 37 | int64_t num_tokens_post_pad, |
| 38 | float alpha, |
| 39 | float limit, |
| 40 | CPUActMethod act_func, |
| 41 | bool with_bias) { |
| 42 | constexpr int64_t BLOCK_M = block_size_m(); |
| 43 | constexpr int64_t BLOCK_N = block_size_n(); |
| 44 | |
| 45 | // stage 1: intermediate_cache0 = hidden_states @ w1 |
| 46 | const int64_t MB = div_up(num_tokens_post_pad, BLOCK_M); |
| 47 | const int64_t NB = div_up(2 * N, BLOCK_N); |
| 48 | int64_t scale_size_N = div_up(2 * N, block_size_N); |
| 49 | int64_t scale_size_K = div_up(K, block_size_K); |
| 50 | int64_t blocks_n_per_group = block_size_N / BLOCK_N; |
| 51 | std::function<int64_t(int64_t)> scale_offset_per_block; |
| 52 | if constexpr (is_mxfp4) { |
| 53 | scale_offset_per_block = [&](int64_t a) { return a * BLOCK_N; }; |
| 54 | } else { |
| 55 | scale_offset_per_block = [&](int64_t a) { return a / blocks_n_per_group; }; |
| 56 | } |
| 57 | |
| 58 | const int64_t packed_K = get_row_size<packed_t>(K); |
| 59 | |
| 60 | const int64_t stride_e = 2 * N * packed_K; |
| 61 | const int64_t stride_n = packed_K; |
| 62 | |
| 63 | int64_t B_tmp_size_per_thread = MAX_CACHE_BLOCK_SIZE * BLOCK_N * std::max(K, N); |
| 64 | |
| 65 | // here we only parallel on half of 2N to fuse silu_and_mul with gemm |
| 66 | parallel_2d(MB, NB, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) { |
| 67 | // get local pointers |
| 68 | int tid = get_thread_num(); |
| 69 | scalar_t* __restrict__ A = A_tmp + tid * BLOCK_M * K; |
| 70 | bool use_brgemm = false; |
| 71 | |
| 72 | loop_2d<packed_t>(mb0, mb1, nb0, nb1, BLOCK_N * K, [&](int64_t mb, int64_t nb, int64_t nb_offset) { |
| 73 | int64_t n_size = std::min(2 * N - nb * BLOCK_N, BLOCK_N); |
| 74 | |
| 75 | // B shape [K, n_size] in vnni format |
| 76 | int32_t expert_id = expert_ids[mb]; |
| 77 | const packed_t* __restrict__ B = packed_w1 + expert_id * stride_e + nb * BLOCK_N * stride_n; |
| 78 | const param_t* __restrict__ Bs = |
| 79 | w1s + expert_id * scale_size_N * scale_size_K + scale_offset_per_block(nb) * scale_size_K; |
| 80 | const float* __restrict__ B_bias = with_bias ? w1_bias + expert_id * 2 * N + nb * BLOCK_N : nullptr; |
| 81 | |
| 82 | // do unpacking for the first row or a new expert |
| 83 | int32_t pre_expert_id = mb == 0 ? -1 : expert_ids[mb - 1]; |
| 84 | bool do_unpack = (mb == mb0) || (expert_id != pre_expert_id); |
| 85 | |
| 86 | int64_t m_size = offsets[mb + 1] - offsets[mb]; |
| 87 | if (m_size == 0) { |
| 88 | return; |
| 89 | } |
| 90 | const bool brg = can_use_brgemm<packed_t>(m_size, n_size); |
| 91 | use_brgemm |= brg; |
| 92 | |
| 93 | if (nb_offset == 0) { |
| 94 | // 1.a load A |
| 95 | const int32_t* A_ids = sorted_ids + mb * BLOCK_M; |
| 96 | for (int64_t m = 0; m < m_size; ++m) { |
| 97 | int32_t index = A_ids[m] / topk; |
| 98 | copy_stub(A + m * K, input + index * K, K); |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | const int64_t offset = offsets[mb]; |
| 103 | tinygemm_kernel<scalar_t>( |
| 104 | /* A */ A, |
| 105 | /* B */ B, |
| 106 | /* C */ ic0 + offset * 2 * N + nb * BLOCK_N, |
| 107 | /* Btmp */ B_tmp + tid * B_tmp_size_per_thread + nb_offset * BLOCK_N * K, |
| 108 | /* Ctmp */ C_tmp + tid * 2 * BLOCK_M * BLOCK_N, |
| 109 | /* Bbias */ B_bias, |
| 110 | /* scale */ Bs, |
| 111 | /* M */ m_size, |
| 112 | /* N */ n_size, |
| 113 | /* K */ K, |
| 114 | /* lda */ K, |
| 115 | /* ldb */ n_size, |
| 116 | /* ldc */ 2 * N, |
| 117 | /* brg */ brg, |
| 118 | /* block_size_K */ block_size_K, |
| 119 | /* do_unpack */ do_unpack); |
| 120 | }); |
| 121 | |
| 122 | if (use_brgemm) { |
| 123 | at::native::cpublas::brgemm_release(); |
| 124 | } |
| 125 | }); |
| 126 | |
| 127 | // stage 1.5: intermediate_cache1 = silu(intermediate_cache0) |
| 128 | if (act_func == CPUActMethod::silu_and_mul) { |
| 129 | at::parallel_for(0, M * topk, 0, [&](int64_t begin, int64_t end) { |
| 130 | for (int64_t m = begin; m < end; ++m) { |
| 131 | silu_and_mul_stub(ic1 + m * N, ic0 + m * 2 * N, ic0 + m * 2 * N + N, N); |
| 132 | } |
| 133 | }); |
| 134 | } else if (act_func == CPUActMethod::swiglu) { |
| 135 | at::parallel_for(0, M * topk, 0, [&](int64_t begin, int64_t end) { |
| 136 | for (int64_t m = begin; m < end; ++m) { |
| 137 | clamp_sigmoid_and_mul_stub(ic1 + m * N, ic0 + m * 2 * N, N / 2, alpha, limit); |
| 138 | clamp_sigmoid_and_mul_stub(ic1 + m * N + N / 2, ic0 + m * 2 * N + N, N / 2, alpha, limit); |
| 139 | } |
| 140 | }); |
| 141 | } |
| 142 | // stage 2: intermediate_cache2 = intermediate_cache1 @ w2 |
| 143 | // w2 : [E, K, N] as [E, OC, IC] |
| 144 | const int64_t OC = K; // rename K as OC |
| 145 | const int64_t IC = N; // rename N as IC |
| 146 | const int64_t MB2 = MB; |
| 147 | const int64_t NB2 = div_up(OC, BLOCK_N); |
| 148 | scale_size_N = div_up(K, block_size_N); |
| 149 | scale_size_K = div_up(N, block_size_K); |
| 150 | const int64_t packed_IC = get_row_size<packed_t>(IC); |
| 151 | const int64_t stride_e2 = OC * packed_IC; |
| 152 | const int64_t stride_oc = packed_IC; |
| 153 | |
| 154 | // parallel on [MB2, NB2] |
| 155 | parallel_2d(MB2, NB2, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) { |
| 156 | int tid = get_thread_num(); |
| 157 | alignas(64) scalar_t C[BLOCK_M * BLOCK_K]; |
| 158 | bool use_brgemm = false; |
| 159 | |
| 160 | loop_2d<packed_t>(mb0, mb1, nb0, nb1, BLOCK_N * IC, [&](int64_t mb, int64_t nb, int64_t nb_offset) { |
| 161 | int64_t m_size = offsets[mb + 1] - offsets[mb]; |
| 162 | if (m_size == 0) { |
| 163 | return; |
| 164 | } |
| 165 | int64_t n_size = std::min(OC - nb * BLOCK_N, BLOCK_N); |
| 166 | const bool brg = can_use_brgemm<packed_t>(m_size, n_size); |
| 167 | use_brgemm |= brg; |
| 168 | |
| 169 | // A ptr from ic1 of [M * topk, N] in sorted order |
| 170 | // so as to avoid copy A to tmp buffer again |
| 171 | const scalar_t* __restrict__ A = ic1 + offsets[mb] * N; |
| 172 | const int32_t* A_ids = sorted_ids + mb * BLOCK_M; |
| 173 | |
| 174 | // B shape [IC, n_size] in vnni format |
| 175 | int32_t expert_id = expert_ids[mb]; |
| 176 | const packed_t* __restrict__ B = packed_w2 + expert_id * stride_e2 + nb * BLOCK_N * stride_oc; |
| 177 | const param_t* __restrict__ Bs = |
| 178 | w2s + expert_id * scale_size_N * scale_size_K + scale_offset_per_block(nb) * scale_size_K; |
| 179 | const float* __restrict__ B_bias = with_bias ? w2_bias + expert_id * OC + nb * BLOCK_N : nullptr; |
| 180 | |
| 181 | // do unpacking for the first row or a new expert |
| 182 | int32_t pre_expert_id = mb == 0 ? -1 : expert_ids[mb - 1]; |
| 183 | bool do_unpack = (mb == mb0) || (expert_id != pre_expert_id); |
| 184 | |
| 185 | tinygemm_kernel<scalar_t>( |
| 186 | /* A */ A, |
| 187 | /* B */ B, |
| 188 | /* C */ C, |
| 189 | /* Btmp */ B_tmp + tid * B_tmp_size_per_thread + nb_offset * BLOCK_N * IC, |
| 190 | /* Ctmp */ C_tmp + tid * 2 * BLOCK_M * BLOCK_N, |
| 191 | /* Bbias */ B_bias, |
| 192 | /* scale */ Bs, |
| 193 | /* M */ m_size, |
| 194 | /* N */ n_size, |
| 195 | /* K */ IC, |
| 196 | /* lda */ IC, |
| 197 | /* ldb */ n_size, |
| 198 | /* ldc */ BLOCK_N, |
| 199 | /* brg */ brg, |
| 200 | /* block_size_K */ block_size_K, |
| 201 | /* do_unpack */ do_unpack); |
| 202 | |
| 203 | // 2.b copy from C to ic2 in original order |
| 204 | // and also mul topk_weights in float32 |
| 205 | for (int64_t m = 0; m < m_size; ++m) { |
| 206 | int32_t index = A_ids[m]; |
| 207 | float weight = topk_weights[index]; |
| 208 | copy_mul_stub(ic2 + index * K + nb * BLOCK_N, C + m * BLOCK_N, weight, n_size); |
| 209 | } |
| 210 | }); |
| 211 | |
| 212 | if (use_brgemm) { |
| 213 | at::native::cpublas::brgemm_release(); |
| 214 | } |
| 215 | }); |
| 216 | // stage 3: out = intermediate_cache2.sum(dim=1) |
| 217 | // from [M, topk, K] to [M, K] |
| 218 | at::parallel_for(0, M, 0, [&](int64_t begin, int64_t end) { |
| 219 | for (int64_t m = begin; m < end; ++m) { |
| 220 | sum_stub(output + m * K, ic2 + m * topk * K, topk, K); |
| 221 | } |
| 222 | }); |
| 223 | } |
| 224 | |
| 225 | #define INSTANTIATE_MOE_FP_TEMPLATE(TYPE1, TYPE2, TYPE3, IS_MXFP4) \ |
| 226 | template void fused_experts_fp_kernel_impl<TYPE1, TYPE2, TYPE3, IS_MXFP4>( \ |
| 227 | TYPE1* __restrict__ output, \ |
| 228 | TYPE1* __restrict__ ic0, \ |
| 229 | TYPE1* __restrict__ ic1, \ |
| 230 | TYPE1* __restrict__ ic2, \ |
| 231 | TYPE1* __restrict__ A_tmp, \ |
| 232 | TYPE1* __restrict__ B_tmp, \ |
| 233 | float* __restrict__ C_tmp, \ |
| 234 | const TYPE1* __restrict__ input, \ |
| 235 | const TYPE2* __restrict__ packed_w1, \ |
| 236 | const TYPE2* __restrict__ packed_w2, \ |
| 237 | const float* __restrict__ w1_bias, \ |
| 238 | const float* __restrict__ w2_bias, \ |
| 239 | const TYPE3* __restrict__ w1s, \ |
| 240 | const TYPE3* __restrict__ w2s, \ |
| 241 | int64_t block_size_N, \ |
| 242 | int64_t block_size_K, \ |
| 243 | const float* __restrict__ topk_weights, \ |
| 244 | const int32_t* __restrict__ sorted_ids, \ |
| 245 | const int32_t* __restrict__ expert_ids, \ |
| 246 | const int32_t* __restrict__ offsets, \ |
| 247 | int64_t M, \ |
| 248 | int64_t N, \ |
| 249 | int64_t K, \ |
| 250 | int64_t E, \ |
| 251 | int64_t topk, \ |
| 252 | int64_t num_tokens_post_pad, \ |
| 253 | float alpha, \ |
| 254 | float limit, \ |
| 255 | CPUActMethod act_func, \ |
| 256 | bool with_bias) |
| 257 | |
| 258 | INSTANTIATE_MOE_FP_TEMPLATE(at::BFloat16, at::Float8_e4m3fn, float, false); |
| 259 | INSTANTIATE_MOE_FP_TEMPLATE(at::Half, at::Float8_e4m3fn, float, false); |
| 260 | INSTANTIATE_MOE_FP_TEMPLATE(at::BFloat16, uint8_t, uint8_t, true); |
| 261 | INSTANTIATE_MOE_FP_TEMPLATE(at::Half, uint8_t, uint8_t, true); |
| 262 | |
| 263 | template <typename scalar_t> |
| 264 | void shared_expert_fp8_kernel_impl( |
| 265 | scalar_t* __restrict__ output, |
| 266 | scalar_t* __restrict__ ic0, |
| 267 | scalar_t* __restrict__ ic1, |
| 268 | scalar_t* __restrict__ B_tmp, |
| 269 | float* __restrict__ C_tmp, |
| 270 | const scalar_t* __restrict__ input, |
| 271 | const at::Float8_e4m3fn* __restrict__ packed_w1, |
| 272 | const at::Float8_e4m3fn* __restrict__ packed_w2, |
| 273 | const float* __restrict__ w1s, |
| 274 | const float* __restrict__ w2s, |
| 275 | int64_t block_size_N, |
| 276 | int64_t block_size_K, |
| 277 | const scalar_t* __restrict__ fused_experts_out, |
| 278 | float routed_scaling_factor, |
| 279 | int64_t M, |
| 280 | int64_t N, |
| 281 | int64_t K) { |
| 282 | constexpr int64_t BLOCK_M = block_size_m(); |
| 283 | constexpr int64_t BLOCK_N = block_size_n(); |
| 284 | |
| 285 | // stage 1: intermediate_cache0 = hidden_states @ w1 |
| 286 | const int64_t MB = div_up(M, BLOCK_M); |
| 287 | const int64_t NB = div_up(2 * N, BLOCK_N); |
| 288 | int64_t scale_size_K = div_up(K, block_size_K); |
| 289 | int64_t blocks_n_per_group = block_size_N / BLOCK_N; |
| 290 | |
| 291 | const bool use_brgemm = can_use_brgemm<at::Float8_e4m3fn>(M); |
| 292 | const bool apply_scaling_factor = fused_experts_out != nullptr; |
| 293 | |
| 294 | int64_t B_tmp_size_per_thread = MAX_CACHE_BLOCK_SIZE * BLOCK_N * std::max(K, N); |
| 295 | |
| 296 | parallel_2d(MB, NB, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) { |
| 297 | int tid = get_thread_num(); |
| 298 | |
| 299 | loop_2d<at::Float8_e4m3fn>(mb0, mb1, nb0, nb1, BLOCK_N * K, [&](int64_t mb, int64_t nb, int64_t nb_offset) { |
| 300 | int64_t m_size = std::min(M - mb * BLOCK_M, BLOCK_M); |
| 301 | int64_t n_size = std::min(2 * N - nb * BLOCK_N, BLOCK_N); |
| 302 | |
| 303 | // do unpacking for the first row |
| 304 | bool do_unpack = (mb == mb0); |
| 305 | |
| 306 | tinygemm_kernel<scalar_t>( |
| 307 | /* A */ input + mb * BLOCK_M * K, |
| 308 | /* B */ packed_w1 + nb * BLOCK_N * K, |
| 309 | /* C */ ic0 + mb * BLOCK_M * 2 * N + nb * BLOCK_N, |
| 310 | /* Btmp */ B_tmp + tid * B_tmp_size_per_thread + nb_offset * BLOCK_N * K, |
| 311 | /* Ctmp */ C_tmp + tid * 2 * BLOCK_M * BLOCK_N, |
| 312 | /* Bbias */ nullptr, |
| 313 | /* scale */ w1s + (nb / blocks_n_per_group) * scale_size_K, |
| 314 | /* M */ m_size, |
| 315 | /* N */ n_size, |
| 316 | /* K */ K, |
| 317 | /* lda */ K, |
| 318 | /* ldb */ n_size, |
| 319 | /* ldc */ 2 * N, |
| 320 | /* brg */ use_brgemm, |
| 321 | /* block_size_K */ block_size_K, |
| 322 | /* do_unpack */ do_unpack); |
| 323 | }); |
| 324 | |
| 325 | if (use_brgemm) { |
| 326 | at::native::cpublas::brgemm_release(); |
| 327 | } |
| 328 | }); |
| 329 | |
| 330 | // stage 1.5: intermediate_cache1 = silu(intermediate_cache0) |
| 331 | at::parallel_for(0, M, 0, [&](int64_t begin, int64_t end) { |
| 332 | for (int64_t m = begin; m < end; ++m) { |
| 333 | silu_and_mul_stub(ic1 + m * N, ic0 + m * 2 * N, ic0 + m * 2 * N + N, N); |
| 334 | } |
| 335 | }); |
| 336 | |
| 337 | // stage 2: intermediate_cache2 = intermediate_cache1 @ w2 |
| 338 | // w2 : [K, N] as [OC, IC] |
| 339 | const int64_t OC = K; // rename K as OC |
| 340 | const int64_t IC = N; // rename N as IC |
| 341 | const int64_t MB2 = MB; |
| 342 | const int64_t NB2 = div_up(K, BLOCK_N); |
| 343 | scale_size_K = div_up(N, block_size_K); |
| 344 | |
| 345 | // parallel on [MB2, NB2] |
| 346 | parallel_2d(MB2, NB2, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) { |
| 347 | int tid = get_thread_num(); |
| 348 | alignas(64) scalar_t C[BLOCK_M * BLOCK_K]; |
| 349 | |
| 350 | loop_2d<at::Float8_e4m3fn>(mb0, mb1, nb0, nb1, BLOCK_N * IC, [&](int64_t mb, int64_t nb, int64_t nb_offset) { |
| 351 | int64_t m_size = std::min(M - mb * BLOCK_M, BLOCK_M); |
| 352 | int64_t n_size = std::min(OC - nb * BLOCK_N, BLOCK_N); |
| 353 | |
| 354 | // do unpacking for the first row |
| 355 | bool do_unpack = (mb == mb0); |
| 356 | |
| 357 | // 2.a gemm: C = A @ B |
| 358 | tinygemm_kernel<scalar_t>( |
| 359 | /* A */ ic1 + mb * BLOCK_M * N, |
| 360 | /* B */ packed_w2 + nb * BLOCK_N * N, |
| 361 | /* C */ C, |
| 362 | /* Btmp */ B_tmp + tid * B_tmp_size_per_thread + nb_offset * BLOCK_N * IC, |
| 363 | /* Ctmp */ C_tmp + tid * 2 * BLOCK_M * BLOCK_N, |
| 364 | /* Bbias */ nullptr, |
| 365 | /* scale */ w2s + (nb / blocks_n_per_group) * scale_size_K, |
| 366 | /* M */ m_size, |
| 367 | /* N */ n_size, |
| 368 | /* K */ IC, |
| 369 | /* lda */ IC, |
| 370 | /* ldb */ n_size, |
| 371 | /* ldc */ BLOCK_N, |
| 372 | /* brg */ use_brgemm, |
| 373 | /* block_size_K */ block_size_K, |
| 374 | /* do_unpack */ do_unpack); |
| 375 | |
| 376 | // 2.b copy from C to output and add fused_experts_out |
| 377 | scalar_t* __restrict__ out = output + mb * BLOCK_M * K + nb * BLOCK_N; |
| 378 | const scalar_t* __restrict__ fused_out = |
| 379 | apply_scaling_factor ? fused_experts_out + mb * BLOCK_M * K + nb * BLOCK_N : nullptr; |
| 380 | for (int64_t m = 0; m < m_size; ++m) { |
| 381 | const scalar_t* __restrict__ fused_out_row = apply_scaling_factor ? (fused_out + m * K) : nullptr; |
| 382 | add_mul_stub(out + m * K, C + m * BLOCK_N, fused_out_row, routed_scaling_factor, n_size); |
| 383 | } |
| 384 | }); |
| 385 | }); |
| 386 | |
| 387 | if (use_brgemm) { |
| 388 | at::native::cpublas::brgemm_release(); |
| 389 | } |
| 390 | } |
| 391 | |
| 392 | #define INSTANTIATE_SHARED_EXPERT_FP8_TEMPLATE(TYPE) \ |
| 393 | template void shared_expert_fp8_kernel_impl<TYPE>( \ |
| 394 | TYPE* __restrict__ output, \ |
| 395 | TYPE* __restrict__ ic0, \ |
| 396 | TYPE* __restrict__ ic1, \ |
| 397 | TYPE* __restrict__ B_tmp, \ |
| 398 | float* __restrict__ C_tmp, \ |
| 399 | const TYPE* __restrict__ input, \ |
| 400 | const at::Float8_e4m3fn* __restrict__ packed_w1, \ |
| 401 | const at::Float8_e4m3fn* __restrict__ packed_w2, \ |
| 402 | const float* __restrict__ w1s, \ |
| 403 | const float* __restrict__ w2s, \ |
| 404 | int64_t block_size_N, \ |
| 405 | int64_t block_size_K, \ |
| 406 | const TYPE* __restrict__ fused_experts_out, \ |
| 407 | float routed_scaling_factor, \ |
| 408 | int64_t M, \ |
| 409 | int64_t N, \ |
| 410 | int64_t K) |
| 411 | |
| 412 | INSTANTIATE_SHARED_EXPERT_FP8_TEMPLATE(at::BFloat16); |
| 413 | INSTANTIATE_SHARED_EXPERT_FP8_TEMPLATE(at::Half); |