main
cpp 965 lines 32.6 KB
Raw
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 namespace {
11
12 template <typename scalar_t, int BLOCK_N>
13 inline void silu_and_mul(
14 scalar_t* __restrict__ C,
15 const int32_t* __restrict__ C0, // x: x0, x1
16 const int32_t* __restrict__ C1, // y: y0, y1
17 const float* __restrict__ As,
18 const float* __restrict__ Bs0,
19 const float* __restrict__ Bs1,
20 const int32_t* __restrict__ Bcomp0,
21 const int32_t* __restrict__ Bcomp1,
22 int64_t m_size,
23 int64_t N) {
24 #if defined(CPU_CAPABILITY_AVX512)
25 constexpr int COLS = BLOCK_N / 16;
26 static_assert(COLS % 2 == 0);
27
28 __m512 vc0[COLS];
29 __m512 vc1[COLS];
30 __m512i vcomp0[COLS];
31 __m512i vcomp1[COLS];
32 __m512 vas;
33 __m512 vbs0[COLS];
34 __m512 vbs1[COLS];
35
36 auto load_scale_and_comp = [&](auto col) {
37 vcomp0[col] = _mm512_loadu_si512(Bcomp0 + col * 16);
38 vcomp1[col] = _mm512_loadu_si512(Bcomp1 + col * 16);
39 vbs0[col] = _mm512_loadu_ps(Bs0 + col * 16);
40 vbs1[col] = _mm512_loadu_ps(Bs1 + col * 16);
41 };
42 Unroll<COLS>{}(load_scale_and_comp);
43
44 auto scalec = [&](auto col, int64_t m) {
45 // update As
46 vas = _mm512_set1_ps(As[m]);
47 // C = As * (C - Bcomp) * Bs
48 __m512i vc32_0 = _mm512_loadu_si512(C0 + m * BLOCK_N + col * 16);
49 __m512i vc32_1 = _mm512_loadu_si512(C1 + m * BLOCK_N + col * 16);
50 vc0[col] = _mm512_cvtepi32_ps(_mm512_sub_epi32(vc32_0, vcomp0[col]));
51 vc1[col] = _mm512_cvtepi32_ps(_mm512_sub_epi32(vc32_1, vcomp1[col]));
52 vc0[col] = _mm512_mul_ps(_mm512_mul_ps(vc0[col], vas), vbs0[col]);
53 vc1[col] = _mm512_mul_ps(_mm512_mul_ps(vc1[col], vas), vbs1[col]);
54 };
55
56 auto silu_and_mul = [&](auto col) {
57 __m512 x = vc0[col];
58 __m512 y = vc1[col];
59 vc0[col] = _mm512_mul_ps(_mm512_rcp14_silu_ps(x), y);
60 };
61
62 using bVec = at::vec::Vectorized<scalar_t>;
63 using fVec = at::vec::Vectorized<float>;
64 auto storec = [&](auto col, int64_t m) {
65 if constexpr (col % 2 == 0) {
66 fVec x0 = fVec(vc0[col + 0]);
67 fVec x1 = fVec(vc0[col + 1]);
68 bVec out_vec = convert_from_float_ext<scalar_t>(x0, x1);
69 out_vec.store(C + m * N + col * 16);
70 }
71 };
72
73 for (int64_t m = 0; m < m_size; ++m) {
74 Unroll<COLS>{}(scalec, m);
75 Unroll<COLS>{}(silu_and_mul);
76 Unroll<COLS>{}(storec, m);
77 }
78 #else
79 TORCH_CHECK(false, "silu_and_mul: scalar path not implemented!");
80 #endif
81 }
82
83 template <int BLOCK_N>
84 inline void scale_C(
85 float* __restrict__ C,
86 const int32_t* __restrict__ Ctmp,
87 const float* __restrict__ As,
88 const float* __restrict__ Bs,
89 const int32_t* __restrict__ Bcomp,
90 int64_t m_size) {
91 #if defined(CPU_CAPABILITY_AVX512)
92 constexpr int COLS = BLOCK_N / 16;
93 static_assert(COLS % 2 == 0);
94
95 __m512 vc[COLS];
96 __m512i vcomp[COLS];
97 __m512 vas;
98 __m512 vbs[COLS];
99
100 auto load_scale_and_comp = [&](auto col) {
101 vcomp[col] = _mm512_loadu_si512(Bcomp + col * 16);
102 vbs[col] = _mm512_loadu_ps(Bs + col * 16);
103 };
104 Unroll<COLS>{}(load_scale_and_comp);
105
106 auto scalec = [&](auto col, int64_t m) {
107 // update As
108 vas = _mm512_set1_ps(As[m]);
109 // C = As * (C - Bcomp) * Bs
110 __m512i vc32 = _mm512_loadu_si512(Ctmp + m * BLOCK_N + col * 16);
111 vc[col] = _mm512_cvtepi32_ps(_mm512_sub_epi32(vc32, vcomp[col]));
112 vc[col] = _mm512_mul_ps(_mm512_mul_ps(vc[col], vas), vbs[col]);
113 _mm512_storeu_ps(C + m * BLOCK_N + col * 16, vc[col]);
114 };
115
116 for (int64_t m = 0; m < m_size; ++m) {
117 Unroll<COLS>{}(scalec, m);
118 }
119 #else
120 TORCH_CHECK(false, "scale_C: scalar path not implemented!");
121 #endif
122 }
123
124 /// gemm for w13
125 template <typename scalar_t, int BLOCK_M, int BLOCK_N>
126 struct tinygemm_kernel_vnni {
127 static inline void apply(
128 const uint8_t* __restrict__ A,
129 const int8_t* __restrict__ B0,
130 const int8_t* __restrict__ B1,
131 scalar_t* __restrict__ C,
132 const float* __restrict__ As,
133 const float* __restrict__ Bs0,
134 const float* __restrict__ Bs1,
135 const int32_t* __restrict__ Bcomp0,
136 const int32_t* __restrict__ Bcomp1,
137 int64_t K,
138 int64_t lda,
139 int64_t ldb,
140 int64_t ldc) {
141 TORCH_CHECK(false, "tinygemm_kernel_nn: scalar path not implemented!");
142 }
143 };
144
145 #if defined(CPU_CAPABILITY_AVX512)
146 template <int BLOCK_M, int BLOCK_N>
147 struct tinygemm_kernel_vnni<at::BFloat16, BLOCK_M, BLOCK_N> {
148 static inline void apply(
149 const uint8_t* __restrict__ A,
150 const int8_t* __restrict__ B0,
151 const int8_t* __restrict__ B1,
152 at::BFloat16* __restrict__ C,
153 const float* __restrict__ As,
154 const float* __restrict__ Bs0,
155 const float* __restrict__ Bs1,
156 const int32_t* __restrict__ Bcomp0,
157 const int32_t* __restrict__ Bcomp1,
158 int64_t K,
159 int64_t lda,
160 int64_t ldb,
161 int64_t ldc) {
162 constexpr int ROWS = BLOCK_M;
163 constexpr int COLS = BLOCK_N / 16;
164 static_assert(COLS % 2 == 0);
165
166 __m512i va;
167 __m512i vb0[COLS];
168 __m512i vb1[COLS];
169 __m512i vc0[ROWS * COLS];
170 __m512i vc1[ROWS * COLS];
171 __m512i vcomp0[COLS];
172 __m512i vcomp1[COLS];
173 __m512 vas;
174 __m512 vbs0[COLS];
175 __m512 vbs1[COLS];
176
177 auto loadc = [&](auto i) {
178 vc0[i] = _mm512_set1_epi32(0);
179 vc1[i] = _mm512_set1_epi32(0);
180 };
181 Unroll<ROWS * COLS>{}(loadc);
182
183 const int64_t K4 = K >> 2;
184 const int64_t lda4 = lda >> 2;
185 const int64_t ldb4 = ldb; // ldb * 4 >> 2;
186 const int32_t* a_ptr = reinterpret_cast<const int32_t*>(A);
187 const int32_t* b0_ptr = reinterpret_cast<const int32_t*>(B0);
188 const int32_t* b1_ptr = reinterpret_cast<const int32_t*>(B1);
189
190 auto compute = [&](auto i, int64_t k) {
191 constexpr int row = i / COLS;
192 constexpr int col = i % COLS;
193
194 if constexpr (col == 0) {
195 va = _mm512_set1_epi32(a_ptr[row * lda4 + k]);
196 }
197 if constexpr (row == 0) {
198 vb0[col] = _mm512_loadu_si512(b0_ptr + k * ldb4 + col * 16);
199 vb1[col] = _mm512_loadu_si512(b1_ptr + k * ldb4 + col * 16);
200 }
201 vc0[i] = _mm512_dpbusd_epi32(vc0[i], va, vb0[col]);
202 vc1[i] = _mm512_dpbusd_epi32(vc1[i], va, vb1[col]);
203 };
204 for (int64_t k = 0; k < K4; ++k) {
205 Unroll<ROWS * COLS>{}(compute, k);
206 }
207
208 auto scalec = [&](auto i) {
209 constexpr int row = i / COLS;
210 constexpr int col = i % COLS;
211
212 // load a scale
213 if constexpr (col == 0) {
214 vas = _mm512_set1_ps(As[row]);
215 }
216 // load b scale and vcomp
217 if constexpr (row == 0) {
218 vbs0[col] = _mm512_loadu_ps(Bs0 + col * 16);
219 vbs1[col] = _mm512_loadu_ps(Bs1 + col * 16);
220 vcomp0[col] = _mm512_loadu_si512(Bcomp0 + col * 16);
221 vcomp1[col] = _mm512_loadu_si512(Bcomp1 + col * 16);
222 }
223 __m512 c0 = _mm512_cvtepi32_ps(_mm512_sub_epi32(vc0[i], vcomp0[col]));
224 __m512 c1 = _mm512_cvtepi32_ps(_mm512_sub_epi32(vc1[i], vcomp1[col]));
225 vc0[i] = _mm512_castps_si512(_mm512_mul_ps(_mm512_mul_ps(c0, vas), vbs0[col]));
226 vc1[i] = _mm512_castps_si512(_mm512_mul_ps(_mm512_mul_ps(c1, vas), vbs1[col]));
227 };
228 Unroll<ROWS * COLS>{}(scalec);
229
230 auto storec = [&](auto i) {
231 constexpr int row = i / COLS;
232 constexpr int col = i % COLS;
233 // for COLS = 2, 4 use 512bit store
234 if constexpr (col % 2 == 0) {
235 __m512 x0 = _mm512_castsi512_ps(vc0[row * COLS + col + 0]);
236 __m512 x1 = _mm512_castsi512_ps(vc0[row * COLS + col + 1]);
237 __m512 y0 = _mm512_castsi512_ps(vc1[row * COLS + col + 0]);
238 __m512 y1 = _mm512_castsi512_ps(vc1[row * COLS + col + 1]);
239 x0 = _mm512_mul_ps(_mm512_rcp14_silu_ps(x0), y0);
240 x1 = _mm512_mul_ps(_mm512_rcp14_silu_ps(x1), y1);
241
242 _mm512_storeu_si512(
243 reinterpret_cast<__m512i*>((C + row * ldc + col * 16)),
244 (__m512i)(_mm512_cvtne2ps_pbh(__m512(x1), __m512(x0))));
245 }
246 };
247 Unroll<ROWS * COLS>{}(storec);
248 }
249 };
250 #endif
251
252 #define LAUNCH_TINYGEMM_KERNEL_VNNI(MB_SIZE, NB_SIZE) \
253 tinygemm_kernel_vnni<scalar_t, MB_SIZE, NB_SIZE>::apply( \
254 A + mb_start * lda, \
255 B0 + nb_start * 4, \
256 B1 + nb_start * 4, \
257 C + mb_start * ldc + nb_start, \
258 As + mb_start, \
259 Bs0 + nb_start, \
260 Bs1 + nb_start, \
261 Bcomp0 + nb_start, \
262 Bcomp1 + nb_start, \
263 K, \
264 lda, \
265 ldb, \
266 ldc);
267
268 template <typename scalar_t>
269 void tinygemm_kernel(
270 const uint8_t* __restrict__ A,
271 const int8_t* __restrict__ B0,
272 const int8_t* __restrict__ B1,
273 scalar_t* __restrict__ C,
274 const float* __restrict__ As,
275 const float* __restrict__ Bs0,
276 const float* __restrict__ Bs1,
277 int64_t M,
278 int64_t N,
279 int64_t K,
280 int64_t lda,
281 int64_t ldb,
282 int64_t ldc) {
283 const int32_t* Bcomp0 = reinterpret_cast<const int32_t*>(B0 + block_size_n() * K);
284 const int32_t* Bcomp1 = reinterpret_cast<const int32_t*>(B1 + block_size_n() * K);
285
286 // pattern: 1-(2+2)-(8+8)
287 constexpr int64_t BLOCK_M = 4;
288 constexpr int64_t BLOCK_N = 32;
289 const int64_t MB = div_up(M, BLOCK_M);
290 const int64_t NB = div_up(N, BLOCK_N);
291 for (int mb = 0; mb < MB; ++mb) {
292 int64_t mb_start = mb * BLOCK_M;
293 int64_t mb_size = std::min(BLOCK_M, M - mb_start);
294 for (int64_t nb = 0; nb < NB; ++nb) {
295 int64_t nb_start = nb * BLOCK_N;
296 int64_t nb_size = std::min(BLOCK_N, N - nb_start);
297
298 switch (mb_size << 4 | nb_size >> 4) {
299 case 0x12:
300 LAUNCH_TINYGEMM_KERNEL_VNNI(1, 32);
301 break;
302 case 0x22:
303 LAUNCH_TINYGEMM_KERNEL_VNNI(2, 32);
304 break;
305 case 0x32:
306 LAUNCH_TINYGEMM_KERNEL_VNNI(3, 32);
307 break;
308 case 0x42:
309 LAUNCH_TINYGEMM_KERNEL_VNNI(4, 32);
310 break;
311 default:
312 TORCH_CHECK(false, "Unexpected block size, ", mb_size, "x", "nb_size");
313 }
314 }
315 }
316 }
317
318 /// gemm for w2
319 template <typename scalar_t, int BLOCK_M, int BLOCK_N>
320 struct tinygemm_kernel_vnni2 {
321 static inline void apply(
322 const uint8_t* __restrict__ A,
323 const int8_t* __restrict__ B,
324 float* __restrict__ C,
325 const float* __restrict__ As,
326 const float* __restrict__ Bs,
327 const int32_t* __restrict__ Bcomp,
328 int64_t K,
329 int64_t lda,
330 int64_t ldb,
331 int64_t ldc) {
332 TORCH_CHECK(false, "tinygemm_kernel_nn: scalar path not implemented!");
333 }
334 };
335
336 #if defined(CPU_CAPABILITY_AVX512)
337 template <int BLOCK_M, int BLOCK_N>
338 struct tinygemm_kernel_vnni2<at::BFloat16, BLOCK_M, BLOCK_N> {
339 static inline void apply(
340 const uint8_t* __restrict__ A,
341 const int8_t* __restrict__ B,
342 float* __restrict__ C,
343 const float* __restrict__ As,
344 const float* __restrict__ Bs,
345 const int32_t* __restrict__ Bcomp,
346 int64_t K,
347 int64_t lda,
348 int64_t ldb,
349 int64_t ldc) {
350 constexpr int ROWS = BLOCK_M;
351 constexpr int COLS = BLOCK_N / 16;
352 static_assert(COLS % 2 == 0);
353
354 __m512i va;
355 __m512i vb[COLS];
356 __m512i vc[ROWS * COLS];
357 __m512i vcomp[COLS];
358 __m512 vas;
359 __m512 vbs[COLS];
360
361 auto loadc = [&](auto i) { vc[i] = _mm512_set1_epi32(0); };
362 Unroll<ROWS * COLS>{}(loadc);
363
364 const int64_t K4 = K >> 2;
365 const int64_t lda4 = lda >> 2;
366 const int64_t ldb4 = ldb; // ldb * 4 >> 2;
367 const int32_t* a_ptr = reinterpret_cast<const int32_t*>(A);
368 const int32_t* b_ptr = reinterpret_cast<const int32_t*>(B);
369
370 auto compute = [&](auto i, int64_t k) {
371 constexpr int row = i / COLS;
372 constexpr int col = i % COLS;
373
374 if constexpr (col == 0) {
375 va = _mm512_set1_epi32(a_ptr[row * lda4 + k]);
376 }
377 if constexpr (row == 0) {
378 vb[col] = _mm512_loadu_si512(b_ptr + k * ldb4 + col * 16);
379 }
380 vc[i] = _mm512_dpbusd_epi32(vc[i], va, vb[col]);
381 };
382 for (int64_t k = 0; k < K4; ++k) {
383 Unroll<ROWS * COLS>{}(compute, k);
384 }
385
386 auto storec = [&](auto i) {
387 constexpr int row = i / COLS;
388 constexpr int col = i % COLS;
389
390 // load a scale
391 if constexpr (col == 0) {
392 vas = _mm512_set1_ps(As[row]);
393 }
394 // load b scale and vcomp per 2 vectors
395 // also load bias if any
396 if constexpr (row == 0) {
397 if constexpr (col % 2 == 0) {
398 vbs[col + 0] = _mm512_loadu_ps(Bs + col * 16);
399 vbs[col + 1] = _mm512_loadu_ps(Bs + col * 16 + 16);
400 vcomp[col + 0] = _mm512_loadu_si512(Bcomp + col * 16);
401 vcomp[col + 1] = _mm512_loadu_si512(Bcomp + col * 16 + 16);
402 }
403 }
404 __m512 x = _mm512_cvtepi32_ps(_mm512_sub_epi32(vc[i], vcomp[col]));
405 x = _mm512_mul_ps(_mm512_mul_ps(x, vas), vbs[col]);
406 _mm512_storeu_ps(reinterpret_cast<__m512*>(C + row * ldc + col * 16), x);
407 };
408 Unroll<ROWS * COLS>{}(storec);
409 }
410 };
411 #endif
412
413 #define LAUNCH_TINYGEMM_KERNEL_VNNI2(MB_SIZE, NB_SIZE) \
414 tinygemm_kernel_vnni2<scalar_t, MB_SIZE, NB_SIZE>::apply( \
415 A + mb_start * lda, \
416 B + nb_start * 4, \
417 C + mb_start * ldc + nb_start, \
418 As + mb_start, \
419 Bs + nb_start, \
420 Bcomp + nb_start, \
421 K, \
422 lda, \
423 ldb, \
424 ldc);
425
426 template <typename scalar_t>
427 void tinygemm_kernel(
428 const uint8_t* __restrict__ A,
429 const int8_t* __restrict__ B,
430 float* __restrict__ C,
431 const float* __restrict__ As,
432 const float* __restrict__ Bs,
433 int64_t M,
434 int64_t N,
435 int64_t K,
436 int64_t lda,
437 int64_t ldb,
438 int64_t ldc) {
439 // B compensation
440 const int32_t* Bcomp = reinterpret_cast<const int32_t*>(B + block_size_n() * K);
441
442 // pattern: 1-4-16
443 constexpr int64_t BLOCK_M = 4;
444 constexpr int64_t BLOCK_N = 64;
445 const int64_t MB = div_up(M, BLOCK_M);
446 const int64_t NB = div_up(N, BLOCK_N);
447 for (int64_t mb = 0; mb < MB; ++mb) {
448 int64_t mb_start = mb * BLOCK_M;
449 int64_t mb_size = std::min(BLOCK_M, M - mb_start);
450 for (int64_t nb = 0; nb < NB; ++nb) {
451 int64_t nb_start = nb * BLOCK_N;
452 int64_t nb_size = std::min(BLOCK_N, N - nb_start);
453
454 switch (mb_size << 4 | nb_size >> 4) {
455 case 0x12:
456 LAUNCH_TINYGEMM_KERNEL_VNNI2(1, 32);
457 break;
458 case 0x22:
459 LAUNCH_TINYGEMM_KERNEL_VNNI2(2, 32);
460 break;
461 case 0x32:
462 LAUNCH_TINYGEMM_KERNEL_VNNI2(3, 32);
463 break;
464 case 0x42:
465 LAUNCH_TINYGEMM_KERNEL_VNNI2(4, 32);
466 break;
467 default:
468 TORCH_CHECK(false, "Unexpected block size, ", mb_size, "x", "nb_size");
469 }
470 }
471 }
472 }
473
474 } // anonymous namespace
475
476 template <typename scalar_t>
477 void fused_experts_int8_kernel_impl(
478 scalar_t* __restrict__ output,
479 scalar_t* __restrict__ ic1,
480 scalar_t* __restrict__ ic2,
481 uint8_t* __restrict__ A_tmp,
482 float* __restrict__ C_tmp,
483 uint8_t* __restrict__ Aq_tmp,
484 float* __restrict__ As_tmp,
485 const scalar_t* __restrict__ input,
486 const int8_t* __restrict__ packed_w1,
487 const int8_t* __restrict__ packed_w2,
488 const float* __restrict__ w1s,
489 const float* __restrict__ w2s,
490 const float* __restrict__ topk_weights,
491 const int32_t* __restrict__ sorted_ids,
492 const int32_t* __restrict__ expert_ids,
493 const int32_t* __restrict__ offsets,
494 int64_t M,
495 int64_t N,
496 int64_t K,
497 int64_t E,
498 int64_t topk,
499 int64_t num_tokens_post_pad) {
500 // handle 2 tiles per block
501 constexpr int64_t BLOCK_M = block_size_m();
502 constexpr int64_t BLOCK_N = block_size_n();
503
504 // stage 0: quantize input to uint8, [M, K]
505 at::parallel_for(0, M, 0, [&](int64_t begin, int64_t end) {
506 for (int64_t m = begin; m < end; ++m) {
507 quantize_row_int8<scalar_t>(Aq_tmp + m * K, As_tmp[m], input + m * K, K);
508 }
509 });
510
511 // stage 1: intermediate_cache1 = silu(hidden_states @ w1)
512 const int64_t MB = div_up(num_tokens_post_pad, BLOCK_M);
513 const int64_t NB = div_up(N, BLOCK_N);
514
515 // strides for w1: [E, 2N, K]
516 TORCH_CHECK(N % BLOCK_N == 0, "Fixme when N is not multiples of ", BLOCK_N);
517
518 // K and N are packed for int8
519 const int64_t packed_K = get_row_size<int8_t>(K);
520 const int64_t packed_N = get_row_size<int8_t>(N);
521
522 const int64_t stride_e = 2 * N * packed_K;
523 const int64_t stride_n = packed_K;
524
525 int64_t avg_M = std::max(int64_t(1), M * topk / E);
526 const bool use_brgemm = can_use_brgemm<int8_t>(avg_M);
527
528 // here we only parallel on half of 2N to fuse silu_and_mul with gemm
529 parallel_2d(MB, NB, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) {
530 // get local pointers
531 int tid = get_thread_num();
532 uint8_t* __restrict__ A = A_tmp + tid * BLOCK_M * K;
533 int32_t* __restrict__ C0 = reinterpret_cast<int32_t*>(C_tmp) + tid * 2 * BLOCK_M * BLOCK_N;
534 int32_t* __restrict__ C1 = C0 + BLOCK_M * BLOCK_N;
535
536 alignas(64) float As[BLOCK_M];
537
538 loop_2d<int8_t>(mb0, mb1, nb0, nb1, BLOCK_N * K * 2, [&](int64_t mb, int64_t nb, int64_t nb_offset) {
539 // nb_upper from top half and nb_lower from bottom half
540 int64_t nb_upper = nb, nb_lower = nb + NB;
541 int64_t n_size = std::min(N - nb * BLOCK_N, BLOCK_N);
542
543 // B shape [K, n_size] in vnni format
544 int32_t expert_id = expert_ids[mb];
545 const int8_t* __restrict__ B0 = packed_w1 + expert_id * stride_e + nb_upper * BLOCK_N * stride_n;
546 const int8_t* __restrict__ B1 = packed_w1 + expert_id * stride_e + nb_lower * BLOCK_N * stride_n;
547 const float* __restrict__ Bs0 = w1s + expert_id * 2 * N + nb_upper * BLOCK_N;
548 const float* __restrict__ Bs1 = w1s + expert_id * 2 * N + nb_lower * BLOCK_N;
549
550 int64_t m_size = offsets[mb + 1] - offsets[mb];
551
552 if (nb_offset == 0) {
553 // 1.a load A
554 const int32_t* A_ids = sorted_ids + mb * BLOCK_M;
555 for (int64_t m = 0; m < m_size; ++m) {
556 int32_t index = A_ids[m] / topk;
557 copy_stub(A + m * K, Aq_tmp + index * K, K);
558 As[m] = As_tmp[index];
559 }
560 }
561
562 if (use_brgemm) {
563 // 1.b gemm: C0 = A @ B0
564 at::native::cpublas::brgemm(
565 /* M */ m_size,
566 /* N */ n_size,
567 /* K */ K,
568 /* lda */ K,
569 /* ldb */ n_size,
570 /* ldc */ BLOCK_N,
571 /* add_C */ false,
572 /* A */ A,
573 /* B */ B0,
574 /* C */ C0);
575
576 // 1.c gemm: C1 = A @ B1
577 at::native::cpublas::brgemm(
578 /* M */ m_size,
579 /* N */ n_size,
580 /* K */ K,
581 /* lda */ K,
582 /* ldb */ n_size,
583 /* ldc */ BLOCK_N,
584 /* add_C */ false,
585 /* A */ A,
586 /* B */ B1,
587 /* C */ C1);
588
589 const int32_t* Bcomp0 = reinterpret_cast<const int32_t*>(B0 + block_size_n() * K);
590 const int32_t* Bcomp1 = reinterpret_cast<const int32_t*>(B1 + block_size_n() * K);
591
592 // 1.d silu and mul
593 const int64_t offset = offsets[mb];
594 silu_and_mul<scalar_t, BLOCK_N>(
595 ic1 + offset * N + nb * BLOCK_N, C0, C1, As, Bs0, Bs1, Bcomp0, Bcomp1, m_size, N);
596 } else {
597 // fused 1.bcd: silu_and_mul(A @ B0, A @ B1)
598 const int64_t offset = offsets[mb];
599 tinygemm_kernel(
600 /* A */ A,
601 /* B0 */ B0,
602 /* B1 */ B1,
603 /* C */ ic1 + offset * N + nb * BLOCK_N,
604 /* As */ As,
605 /* Bs0 */ Bs0,
606 /* Bs1 */ Bs1,
607 /* M */ m_size,
608 /* N */ n_size,
609 /* K */ K,
610 /* lda */ K,
611 /* ldb */ n_size,
612 /* ldc */ N);
613 }
614 });
615
616 if (use_brgemm) {
617 at::native::cpublas::brgemm_release();
618 }
619 });
620
621 // stage 1.5: quantize ic1 to uint8, [M * topk, N]
622 at::parallel_for(0, M * topk, 0, [&](int64_t begin, int64_t end) {
623 for (int64_t m = begin; m < end; ++m) {
624 quantize_row_int8<scalar_t>(Aq_tmp + m * N, As_tmp[m], ic1 + m * N, N);
625 }
626 });
627
628 // stage 2: intermediate_cache2 = intermediate_cache1 @ w2
629 // w2 : [E, K, N] as [E, OC, IC]
630 const int64_t OC = K; // rename K as OC
631 const int64_t IC = N; // rename N as IC
632 const int64_t MB2 = MB;
633 const int64_t NB2 = div_up(OC, BLOCK_N);
634 const int64_t stride_e2 = OC * packed_N;
635 const int64_t stride_oc = packed_N;
636
637 // parallel on [MB2, NB2]
638 parallel_2d(MB2, NB2, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) {
639 // get local pointers
640 int tid = get_thread_num();
641 float* __restrict__ C = C_tmp + tid * 2 * BLOCK_M * BLOCK_N;
642 int32_t* __restrict__ C32 = reinterpret_cast<int32_t*>(C + BLOCK_M * BLOCK_N);
643
644 loop_2d<int8_t>(mb0, mb1, nb0, nb1, BLOCK_N * IC, [&](int64_t mb, int64_t nb, int64_t nb_offset) {
645 int64_t m_size = offsets[mb + 1] - offsets[mb];
646 int64_t n_size = std::min(OC - nb * BLOCK_N, BLOCK_N);
647
648 // A ptr from ic1 of [M * topk, N] in sorted order
649 // so as to avoid copy A to tmp buffer again
650 const uint8_t* __restrict__ A = Aq_tmp + offsets[mb] * N;
651 const float* __restrict__ As = As_tmp + offsets[mb];
652 const int32_t* A_ids = sorted_ids + mb * BLOCK_M;
653
654 // B shape [IC, n_size] in vnni format
655 int32_t expert_id = expert_ids[mb];
656 const int8_t* __restrict__ B = packed_w2 + expert_id * stride_e2 + nb * BLOCK_N * stride_oc;
657 const float* __restrict__ Bs = w2s + expert_id * K + nb * BLOCK_N;
658
659 // 2.a gemm: C = A @ B
660 if (use_brgemm) {
661 at::native::cpublas::brgemm(
662 /* M */ m_size,
663 /* N */ n_size,
664 /* K */ IC,
665 /* lda */ IC,
666 /* ldb */ n_size,
667 /* ldc */ BLOCK_N,
668 /* add_C */ false,
669 /* A */ A,
670 /* B */ B,
671 /* C */ C32);
672
673 // apply scales
674 const int32_t* Bcomp = reinterpret_cast<const int32_t*>(B + block_size_n() * IC);
675 scale_C<BLOCK_N>(C, C32, As, Bs, Bcomp, m_size);
676 } else {
677 tinygemm_kernel<scalar_t>(
678 /* A */ A,
679 /* B */ B,
680 /* C */ C,
681 /* As */ As,
682 /* Bs */ Bs,
683 /* M */ m_size,
684 /* N */ n_size,
685 /* K */ IC,
686 /* lda */ IC,
687 /* ldb */ n_size,
688 /* ldc */ BLOCK_N);
689 }
690
691 // 2.b copy from C to ic2 in original order
692 // and also mul topk_weights in float32
693 for (int64_t m = 0; m < m_size; ++m) {
694 int32_t index = A_ids[m];
695 float weight = topk_weights[index];
696 copy_mul_stub(ic2 + index * K + nb * BLOCK_N, C + m * BLOCK_N, weight, n_size);
697 }
698 });
699
700 if (use_brgemm) {
701 at::native::cpublas::brgemm_release();
702 }
703 });
704
705 // stage 3: out = intermediate_cache2.sum(dim=1)
706 // from [M, topk, K] to [M, K]
707 at::parallel_for(0, M, 0, [&](int64_t begin, int64_t end) {
708 for (int64_t m = begin; m < end; ++m) {
709 sum_stub(output + m * K, ic2 + m * topk * K, topk, K);
710 }
711 });
712 }
713
714 #define INSTANTIATE_MOE_INT8_TEMPLATE(TYPE) \
715 template void fused_experts_int8_kernel_impl<TYPE>( \
716 TYPE* __restrict__ output, \
717 TYPE* __restrict__ ic1, \
718 TYPE* __restrict__ ic2, \
719 uint8_t* __restrict__ A_tmp, \
720 float* __restrict__ C_tmp, \
721 uint8_t* __restrict__ Aq_tmp, \
722 float* __restrict__ As_tmp, \
723 const TYPE* __restrict__ input, \
724 const int8_t* __restrict__ packed_w1, \
725 const int8_t* __restrict__ packed_w2, \
726 const float* __restrict__ w1s, \
727 const float* __restrict__ w2s, \
728 const float* __restrict__ topk_weights, \
729 const int32_t* __restrict__ sorted_ids, \
730 const int32_t* __restrict__ expert_ids, \
731 const int32_t* __restrict__ offsets, \
732 int64_t M, \
733 int64_t N, \
734 int64_t K, \
735 int64_t E, \
736 int64_t topk, \
737 int64_t num_tokens_post_pad)
738
739 INSTANTIATE_MOE_INT8_TEMPLATE(at::BFloat16);
740 INSTANTIATE_MOE_INT8_TEMPLATE(at::Half);
741
742 template <typename scalar_t>
743 void shared_expert_int8_kernel_impl(
744 scalar_t* __restrict__ output,
745 scalar_t* __restrict__ ic1,
746 float* __restrict__ C_tmp,
747 uint8_t* __restrict__ Aq_tmp,
748 float* __restrict__ As_tmp,
749 const scalar_t* __restrict__ input,
750 const int8_t* __restrict__ packed_w1,
751 const int8_t* __restrict__ packed_w2,
752 const float* __restrict__ w1s,
753 const float* __restrict__ w2s,
754 const scalar_t* __restrict__ fused_experts_out,
755 float routed_scaling_factor,
756 int64_t M,
757 int64_t N,
758 int64_t K) {
759 // handle 2 tiles per block
760 constexpr int64_t BLOCK_M = block_size_m();
761 constexpr int64_t BLOCK_N = block_size_n();
762
763 // stage 0: quantize input to uint8, [M, K]
764 at::parallel_for(0, M, 0, [&](int64_t begin, int64_t end) {
765 for (int64_t m = begin; m < end; ++m) {
766 quantize_row_int8<scalar_t>(Aq_tmp + m * K, As_tmp[m], input + m * K, K);
767 }
768 });
769
770 // stage 1: intermediate_cache1 = silu(hidden_states @ w1)
771 const int64_t MB = div_up(M, BLOCK_M);
772 const int64_t NB = div_up(N, BLOCK_N);
773
774 TORCH_CHECK(N % BLOCK_N == 0, "Fixme when N is not multiples of ", BLOCK_N);
775
776 // K and N are packed for int8
777 const int64_t packed_K = get_row_size<int8_t>(K);
778 const int64_t packed_N = get_row_size<int8_t>(N);
779 const int64_t stride_n = packed_K;
780
781 const bool use_brgemm = can_use_brgemm<int8_t>(M);
782 const bool apply_scaling_factor = fused_experts_out != nullptr;
783
784 // here we only parallel on half of 2N to fuse silu_and_mul with gemm
785 parallel_2d(MB, NB, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) {
786 // get local pointers
787 int tid = get_thread_num();
788 int32_t* __restrict__ C0 = reinterpret_cast<int32_t*>(C_tmp) + tid * 2 * BLOCK_M * BLOCK_N;
789 int32_t* __restrict__ C1 = C0 + BLOCK_M * BLOCK_N;
790
791 loop_2d<int8_t>(mb0, mb1, nb0, nb1, BLOCK_N * K * 2, [&](int64_t mb, int64_t nb, int64_t nb_offset) {
792 // nb_upper from top half and nb_lower from bottom half
793 int64_t nb_upper = nb, nb_lower = nb + NB;
794 int64_t n_size = std::min(N - nb * BLOCK_N, BLOCK_N);
795 int64_t m_size = std::min(M - mb * BLOCK_M, BLOCK_M);
796
797 // A shape [m_size, K]
798 const uint8_t* A = Aq_tmp + mb * BLOCK_M * K;
799 const float* As = As_tmp + mb * BLOCK_M;
800
801 // B shape [K, n_size] in vnni format
802 const int8_t* __restrict__ B0 = packed_w1 + nb_upper * BLOCK_N * stride_n;
803 const int8_t* __restrict__ B1 = packed_w1 + nb_lower * BLOCK_N * stride_n;
804 const float* __restrict__ Bs0 = w1s + nb_upper * BLOCK_N;
805 const float* __restrict__ Bs1 = w1s + nb_lower * BLOCK_N;
806
807 if (use_brgemm) {
808 // 1.b gemm: C0 = A @ B0
809 at::native::cpublas::brgemm(
810 /* M */ m_size,
811 /* N */ n_size,
812 /* K */ K,
813 /* lda */ K,
814 /* ldb */ n_size,
815 /* ldc */ BLOCK_N,
816 /* add_C */ false,
817 /* A */ A,
818 /* B */ B0,
819 /* C */ C0);
820
821 // 1.c gemm: C1 = A @ B1
822 at::native::cpublas::brgemm(
823 /* M */ m_size,
824 /* N */ n_size,
825 /* K */ K,
826 /* lda */ K,
827 /* ldb */ n_size,
828 /* ldc */ BLOCK_N,
829 /* add_C */ false,
830 /* A */ A,
831 /* B */ B1,
832 /* C */ C1);
833
834 const int32_t* Bcomp0 = reinterpret_cast<const int32_t*>(B0 + block_size_n() * K);
835 const int32_t* Bcomp1 = reinterpret_cast<const int32_t*>(B1 + block_size_n() * K);
836
837 // 1.d silu and mul
838 silu_and_mul<scalar_t, BLOCK_N>(
839 ic1 + mb * BLOCK_M * N + nb * BLOCK_N, C0, C1, As, Bs0, Bs1, Bcomp0, Bcomp1, m_size, N);
840 } else {
841 // fused 1.bcd: silu_and_mul(A @ B0, A @ B1)
842 tinygemm_kernel(
843 /* A */ A,
844 /* B0 */ B0,
845 /* B1 */ B1,
846 /* C */ ic1 + mb * BLOCK_M * N + nb * BLOCK_N,
847 /* As */ As,
848 /* Bs0 */ Bs0,
849 /* Bs1 */ Bs1,
850 /* M */ m_size,
851 /* N */ n_size,
852 /* K */ K,
853 /* lda */ K,
854 /* ldb */ n_size,
855 /* ldc */ N);
856 }
857 });
858
859 if (use_brgemm) {
860 at::native::cpublas::brgemm_release();
861 }
862 });
863
864 // stage 1.5: quantize ic1 to uint8, [M * topk, N]
865 at::parallel_for(0, M, 0, [&](int64_t begin, int64_t end) {
866 for (int64_t m = begin; m < end; ++m) {
867 quantize_row_int8<scalar_t>(Aq_tmp + m * N, As_tmp[m], ic1 + m * N, N);
868 }
869 });
870
871 // stage 2: intermediate_cache2 = intermediate_cache1 @ w2
872 // w2 : [K, N] as [OC, IC]
873 const int64_t OC = K; // rename K as OC
874 const int64_t IC = N; // rename N as IC
875 const int64_t MB2 = MB;
876 const int64_t NB2 = div_up(OC, BLOCK_N);
877 const int64_t stride_oc = packed_N;
878
879 // parallel on [MB2, NB2]
880 parallel_2d(MB2, NB2, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) {
881 // get local pointers
882 int tid = get_thread_num();
883 float* __restrict__ C = C_tmp + tid * 2 * BLOCK_M * BLOCK_N;
884 int32_t* __restrict__ C32 = reinterpret_cast<int32_t*>(C + BLOCK_M * BLOCK_N);
885
886 loop_2d<int8_t>(mb0, mb1, nb0, nb1, BLOCK_N * IC, [&](int64_t mb, int64_t nb, int64_t nb_offset) {
887 int64_t m_size = std::min(M - mb * BLOCK_M, BLOCK_M);
888 int64_t n_size = std::min(OC - nb * BLOCK_N, BLOCK_N);
889
890 // A shape [m_size, IC]
891 const uint8_t* __restrict__ A = Aq_tmp + mb * BLOCK_M * N;
892 const float* __restrict__ As = As_tmp + mb * BLOCK_M;
893
894 // B shape [IC, n_size] in vnni format
895 const int8_t* __restrict__ B = packed_w2 + nb * BLOCK_N * stride_oc;
896 const float* __restrict__ Bs = w2s + nb * BLOCK_N;
897
898 if (use_brgemm) {
899 at::native::cpublas::brgemm(
900 /* M */ m_size,
901 /* N */ n_size,
902 /* K */ IC,
903 /* lda */ IC,
904 /* ldb */ n_size,
905 /* ldc */ BLOCK_N,
906 /* add_C */ false,
907 /* A */ A,
908 /* B */ B,
909 /* C */ C32);
910
911 // apply scales
912 const int32_t* Bcomp = reinterpret_cast<const int32_t*>(B + block_size_n() * IC);
913 scale_C<BLOCK_N>(C, C32, As, Bs, Bcomp, m_size);
914 } else {
915 // 2.a gemm: C = A @ B
916 tinygemm_kernel<scalar_t>(
917 /* A */ A,
918 /* B */ B,
919 /* C */ C,
920 /* As */ As,
921 /* Bs */ Bs,
922 /* M */ m_size,
923 /* N */ n_size,
924 /* K */ IC,
925 /* lda */ IC,
926 /* ldb */ n_size,
927 /* ldc */ BLOCK_N);
928 }
929
930 // 2.b copy from C to output and add fused_experts_out
931 scalar_t* __restrict__ out = output + mb * BLOCK_M * K + nb * BLOCK_N;
932 const scalar_t* __restrict__ fused_out =
933 apply_scaling_factor ? fused_experts_out + mb * BLOCK_M * K + nb * BLOCK_N : nullptr;
934 for (int64_t m = 0; m < m_size; ++m) {
935 const scalar_t* __restrict__ fused_out_row = apply_scaling_factor ? (fused_out + m * K) : nullptr;
936 add_mul_stub(out + m * K, C + m * BLOCK_N, fused_out_row, routed_scaling_factor, n_size);
937 }
938 });
939
940 if (use_brgemm) {
941 at::native::cpublas::brgemm_release();
942 }
943 });
944 }
945
946 #define INSTANTIATE_SHARED_EXPERT_INT8_TEMPLATE(TYPE) \
947 template void shared_expert_int8_kernel_impl<TYPE>( \
948 TYPE* __restrict__ output, \
949 TYPE* __restrict__ ic1, \
950 float* __restrict__ C_tmp, \
951 uint8_t* __restrict__ Aq_tmp, \
952 float* __restrict__ As_tmp, \
953 const TYPE* __restrict__ input, \
954 const int8_t* __restrict__ packed_w1, \
955 const int8_t* __restrict__ packed_w2, \
956 const float* __restrict__ w1s, \
957 const float* __restrict__ w2s, \
958 const TYPE* __restrict__ fused_experts_out, \
959 float routed_scaling_factor, \
960 int64_t M, \
961 int64_t N, \
962 int64_t K)
963
964 INSTANTIATE_SHARED_EXPERT_INT8_TEMPLATE(at::BFloat16);
965 INSTANTIATE_SHARED_EXPERT_INT8_TEMPLATE(at::Half);