main
cpp 1,238 lines 39.9 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 "vec.h"
9
10 namespace {
11
12 template <typename scalar_t>
13 inline void copy_stub(scalar_t* __restrict__ out, const float* __restrict__ input, int64_t size) {
14 using bVec = at::vec::Vectorized<scalar_t>;
15 using fVec = at::vec::Vectorized<float>;
16 constexpr int kVecSize = bVec::size();
17
18 int64_t d;
19 #pragma GCC unroll 4
20 for (d = 0; d <= size - kVecSize; d += kVecSize) {
21 auto [data0, data1] = load_float_vec2(input + d);
22 bVec out_vec = convert_from_float_ext<scalar_t>(data0, data1);
23 out_vec.store(out + d);
24 }
25 for (; d < size; ++d) {
26 out[d] = static_cast<scalar_t>(input[d]);
27 }
28 }
29
30 template <typename scalar_t>
31 inline void copy_add_stub(
32 scalar_t* __restrict__ out, const float* __restrict__ input, const float* __restrict__ bias, int64_t size) {
33 using bVec = at::vec::Vectorized<scalar_t>;
34 using fVec = at::vec::Vectorized<float>;
35 constexpr int kVecSize = bVec::size();
36
37 int64_t d;
38 #pragma GCC unroll 4
39 for (d = 0; d <= size - kVecSize; d += kVecSize) {
40 auto [data0, data1] = load_float_vec2(input + d);
41 auto [bias0, bias1] = load_float_vec2(bias + d);
42 bVec out_vec = convert_from_float_ext<scalar_t>(data0 + bias0, data1 + bias1);
43 out_vec.store(out + d);
44 }
45 for (; d < size; ++d) {
46 out[d] = static_cast<scalar_t>(input[d] + bias[d]);
47 }
48 }
49 template <typename scalar_t>
50 inline void copy_mul_stub(scalar_t* __restrict__ out, const float* __restrict__ input, int size, float scale) {
51 using bVec = at::vec::Vectorized<scalar_t>;
52 using fVec = at::vec::Vectorized<float>;
53 constexpr int kVecSize = bVec::size();
54 const fVec vscale = fVec(scale);
55
56 int d;
57 #pragma GCC unroll 4
58 for (d = 0; d <= size - kVecSize; d += kVecSize) {
59 auto [data0, data1] = load_float_vec2(input + d);
60 bVec out_vec = convert_from_float_ext<scalar_t>(data0 * vscale, data1 * vscale);
61 out_vec.store(out + d);
62 }
63 for (; d < size; ++d) {
64 out[d] = static_cast<scalar_t>(input[d] * scale);
65 }
66 }
67
68 template <>
69 inline void
70 copy_add_stub(float* __restrict__ out, const float* __restrict__ input, const float* __restrict__ bias, int64_t size) {
71 using fVec = at::vec::Vectorized<float>;
72 constexpr int kVecSize = fVec::size();
73
74 int64_t d;
75 #pragma GCC unroll 4
76 for (d = 0; d <= size - kVecSize; d += kVecSize) {
77 fVec data = fVec::loadu(input + d) + fVec::loadu(bias + d);
78 data.store(out + d);
79 }
80 for (; d < size; ++d) {
81 out[d] = input[d] + bias[d];
82 }
83 }
84
85 inline void unpack_B(
86 at::BFloat16* __restrict__ Btmp,
87 const at::Float8_e4m3fn* __restrict__ packed_B,
88 int64_t N,
89 int64_t K,
90 int64_t ldb,
91 int64_t ldb_tmp,
92 float scale) {
93 #if defined(CPU_CAPABILITY_AVX512)
94 // [K/2, N, 2]
95 const int64_t K2 = K >> 1;
96 const int64_t ldb2 = ldb; // ldb * 2 >> 1;
97 const uint16_t* b_ptr = reinterpret_cast<const uint16_t*>(packed_B);
98 const __m512 vexp = _mm512_castsi512_ps(_mm512_set1_epi32(kFP8_BIAS));
99 const __m512 vd = _mm512_mul_ps(_mm512_set1_ps(scale), vexp);
100
101 constexpr int BLOCK_N = block_size_n();
102 static_assert(BLOCK_N == 32);
103
104 // prefetch distance
105 constexpr int PREFETCH_SIZE_K = 64;
106
107 #pragma GCC unroll 4
108 for (int64_t k = 0; k < K2; ++k) {
109 __m512i b8 = _mm512_loadu_si512(b_ptr + k * ldb2);
110 if constexpr (PREFETCH_SIZE_K > 0) {
111 _mm_prefetch(b_ptr + (k + PREFETCH_SIZE_K) * ldb2, _MM_HINT_T0);
112 }
113
114 __m256i b8_0 = _mm512_extracti32x8_epi32(b8, 0);
115 __m256i b8_1 = _mm512_extracti32x8_epi32(b8, 1);
116
117 __m512bh bf16_0 = CVT_FP8_TO_BF16_EXT(b8_0);
118 __m512bh bf16_1 = CVT_FP8_TO_BF16_EXT(b8_1);
119
120 // Apply scale
121 __m512 f0_lo = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32((__m512i)bf16_0, 0));
122 __m512 f0_hi = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32((__m512i)bf16_0, 1));
123 __m512 f1_lo = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32((__m512i)bf16_1, 0));
124 __m512 f1_hi = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32((__m512i)bf16_1, 1));
125
126 f0_lo = _mm512_mul_ps(f0_lo, vd);
127 f0_hi = _mm512_mul_ps(f0_hi, vd);
128 f1_lo = _mm512_mul_ps(f1_lo, vd);
129 f1_hi = _mm512_mul_ps(f1_hi, vd);
130
131 bf16_0 = _mm512_cvtne2ps_pbh(f0_hi, f0_lo);
132 bf16_1 = _mm512_cvtne2ps_pbh(f1_hi, f1_lo);
133
134 _mm512_storeu_si512(Btmp + k * ldb_tmp * 2 + 0, (__m512i)bf16_0);
135 _mm512_storeu_si512(Btmp + k * ldb_tmp * 2 + 32, (__m512i)bf16_1);
136 }
137 #else
138 TORCH_CHECK(false, "unpack_B: scalar path not implemented!");
139 #endif
140 }
141
142 inline void unpack_B(
143 at::BFloat16* __restrict__ Btmp,
144 const at::Float8_e4m3fn* __restrict__ packed_B,
145 int N,
146 int K,
147 int ldb,
148 int ldb_tmp) {
149 #if defined(CPU_CAPABILITY_AVX512)
150 // [K/2, N, 2]
151 const int K2 = K >> 1;
152 const int ldb2 = ldb; // ldb * 2 >> 1;
153 const uint16_t* b_ptr = reinterpret_cast<const uint16_t*>(packed_B);
154
155 // prefetch distance
156 constexpr int PREFETCH_SIZE_K = 64;
157 #pragma GCC unroll 4
158 for (int k = 0; k < K2; ++k) {
159 __m512i b8 = _mm512_loadu_si512(b_ptr + k * ldb2);
160 if constexpr (PREFETCH_SIZE_K > 0) {
161 _mm_prefetch(b_ptr + (k + PREFETCH_SIZE_K) * ldb2, _MM_HINT_T0);
162 }
163
164 __m256i b8_0 = _mm512_extracti32x8_epi32(b8, 0);
165 __m256i b8_1 = _mm512_extracti32x8_epi32(b8, 1);
166
167 __m512bh bf16_0 = CVT_FP8_TO_BF16(b8_0);
168 __m512bh bf16_1 = CVT_FP8_TO_BF16(b8_1);
169 _mm512_storeu_si512(Btmp + k * ldb_tmp * 2 + 0, (__m512i)bf16_0);
170 _mm512_storeu_si512(Btmp + k * ldb_tmp * 2 + 32, (__m512i)bf16_1);
171 }
172 #else
173 TORCH_CHECK(false, "unpack_B: scalar path not implemented!");
174 #endif
175 }
176
177 // mxfp4
178 inline void unpack_B(
179 at::BFloat16* __restrict__ Btmp,
180 const uint8_t* __restrict__ packed_B,
181 int64_t N,
182 int64_t K,
183 int64_t ldb,
184 int64_t ldb_tmp,
185 const uint8_t* __restrict__ scale) {
186 #if defined(CPU_CAPABILITY_AVX512)
187 // [K/2, N, 2]
188 const int64_t K2 = K >> 1;
189 const int64_t ldb2 = ldb; // ldb * 2 >> 1;
190 const uint8_t* b_ptr = reinterpret_cast<const uint8_t*>(packed_B); // 2 * 4 bit = 8 bit
191
192 constexpr int BLOCK_N = block_size_n();
193 static_assert(BLOCK_N == 32);
194
195 // prefetch distance
196 constexpr int PREFETCH_SIZE_K = 64;
197
198 // exponent bias 127
199 const __m512i off = _mm512_set1_epi16(0x7F);
200
201 // load 32 bytes only once for each block
202 __m256i s8 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(scale));
203 __m512i s16 = _mm512_slli_epi16(_mm512_sub_epi16(_mm512_cvtepu8_epi16(s8), off), 0x7);
204
205 // holds Nx2(64) scales, interleaved as 2 belongs to K dimension
206 // e.g. vs0: { s0, s0, s1, s1, ..., s15, s15}
207 // vs1: {s16, s16, s17, s17, ..., s31, s31}
208 auto [vscale0, vscale1] = transpose_2x32_16bit(s16, s16);
209
210 #pragma GCC unroll 4
211 for (int64_t k = 0; k < K2; ++k) {
212 __m256i b4 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(b_ptr + k * ldb2));
213 if constexpr (PREFETCH_SIZE_K > 0) {
214 _mm_prefetch(b_ptr + (k + PREFETCH_SIZE_K) * ldb2, _MM_HINT_T0);
215 }
216 auto [vb0, vb1] = CVT_MXFP4_TO_BF16(b4, vscale0, vscale1);
217
218 _mm512_storeu_si512(Btmp + k * ldb_tmp * 2 + 0, (__m512i)vb0);
219 _mm512_storeu_si512(Btmp + k * ldb_tmp * 2 + 32, (__m512i)vb1);
220 }
221 #else
222 TORCH_CHECK(false, "unpack_B: scalar path not implemented!");
223 #endif
224 }
225
226 template <typename scalar_t, typename packed_t, typename param_t, bool has_bias, int BLOCK_M, int BLOCK_N>
227 struct tinygemm_kernel_nn {
228 static inline void apply(
229 const scalar_t* __restrict__ A,
230 const packed_t* __restrict__ B,
231 scalar_t* __restrict__ C,
232 const float* __restrict__ bias,
233 const param_t* __restrict__ scale,
234 int64_t K,
235 int64_t lda,
236 int64_t ldb,
237 int64_t ldc,
238 int64_t block_size_K) {
239 TORCH_CHECK(false, "tinygemm_kernel_nn: scalar path not implemented!");
240 }
241 };
242
243 template <typename scalar_t, int BLOCK_M, int BLOCK_N>
244 struct tinygemm_kernel_nn2 {
245 static inline void apply(
246 const scalar_t* __restrict__ A,
247 const at::Float8_e4m3fn* __restrict__ B,
248 scalar_t* __restrict__ C,
249 float scale,
250 int K,
251 int lda,
252 int ldb,
253 int ldc) {
254 TORCH_CHECK(false, "tinygemm_kernel_nn: scalar path not implemented!");
255 }
256 };
257 #if defined(CPU_CAPABILITY_AVX512)
258 template <bool has_bias, int BLOCK_M, int BLOCK_N>
259 struct tinygemm_kernel_nn<at::BFloat16, at::Float8_e4m3fn, float, has_bias, BLOCK_M, BLOCK_N> {
260 static inline void apply(
261 const at::BFloat16* __restrict__ A,
262 const at::Float8_e4m3fn* __restrict__ B,
263 at::BFloat16* __restrict__ C,
264 const float* __restrict__ bias,
265 const float* __restrict__ scale,
266 int64_t K,
267 int64_t lda,
268 int64_t ldb,
269 int64_t ldc,
270 int64_t block_size_K) {
271 constexpr int ROWS = BLOCK_M;
272 constexpr int COLS = BLOCK_N / 16;
273
274 const int64_t KB = div_up(K, (int64_t)BLOCK_K);
275
276 // prefetch distance
277 constexpr int PREFETCH_SIZE_K = 64;
278 constexpr int PREFETCH_SIZE_KB = 1;
279
280 __m512bh va;
281 __m512bh vb[COLS];
282 __m512 vc[ROWS * COLS];
283 __m512 vsum[ROWS * COLS];
284
285 // block quant scale
286 __m512 vscale;
287
288 const __m512 vexp = _mm512_castsi512_ps(_mm512_set1_epi32(kFP8_BIAS));
289
290 auto loadc = [&](auto i) {
291 constexpr int col = i % COLS;
292 if constexpr (has_bias) {
293 vc[i] = _mm512_loadu_ps(bias + col * 16);
294 } else {
295 vc[i] = _mm512_setzero_ps();
296 }
297 };
298 Unroll<ROWS * COLS>{}(loadc);
299
300 const int64_t lda2 = lda >> 1;
301 const int64_t ldb2 = ldb; // ldb * 2 >> 1;
302 const float* a_ptr = reinterpret_cast<const float*>(A);
303 const uint16_t* b_ptr = reinterpret_cast<const uint16_t*>(B);
304
305 auto compute = [&](auto i, int k) {
306 constexpr int row = i / COLS;
307 constexpr int col = i % COLS;
308
309 if constexpr (col == 0) {
310 va = (__m512bh)(_mm512_set1_ps(a_ptr[row * lda2 + k]));
311 if constexpr (PREFETCH_SIZE_K > 0) {
312 _mm_prefetch(a_ptr + row * lda2 + k + PREFETCH_SIZE_K, _MM_HINT_T0);
313 }
314 }
315 if constexpr (row == 0) {
316 if constexpr (col % 2 == 0) {
317 __m512i b8 = _mm512_loadu_si512(b_ptr + k * ldb2 + col * 16);
318 if constexpr (PREFETCH_SIZE_K > 0) {
319 _mm_prefetch(b_ptr + (k + PREFETCH_SIZE_K) * ldb2 + col * 16, _MM_HINT_T0);
320 }
321 vb[col + 0] = CVT_FP8_TO_BF16_EXT(_mm512_extracti32x8_epi32(b8, 0));
322 vb[col + 1] = CVT_FP8_TO_BF16_EXT(_mm512_extracti32x8_epi32(b8, 1));
323 }
324 }
325 vsum[i] = _mm512_dpbf16_ps(vsum[i], va, vb[col]);
326 };
327
328 constexpr int64_t BLOCK_K2 = BLOCK_K >> 1;
329 for (int64_t kb = 0; kb < KB; ++kb) {
330 int64_t kb_start = kb * BLOCK_K2;
331 int64_t kb_end = std::min(K >> 1, kb_start + BLOCK_K2);
332 // 1. load scale vector
333 vscale = _mm512_set1_ps(scale[kb]);
334 vscale = _mm512_mul_ps(vscale, vexp);
335 if constexpr (PREFETCH_SIZE_KB > 0) {
336 _mm_prefetch(scale + kb + PREFETCH_SIZE_KB, _MM_HINT_T0);
337 }
338 // 2. zero vsum for each block
339 Unroll<ROWS * COLS>{}([&](auto i) { vsum[i] = _mm512_setzero_ps(); });
340 // 3. accumulate across each block
341 for (int k = kb_start; k < kb_end; ++k) {
342 Unroll<ROWS * COLS>{}(compute, k);
343 }
344 // 4. apply scale
345 Unroll<ROWS * COLS>{}([&](auto i) { vc[i] = _mm512_fmadd_ps(vsum[i], vscale, vc[i]); });
346 }
347
348 auto storec = [&](auto i) {
349 constexpr int row = i / COLS;
350 constexpr int col = i % COLS;
351 // for COLS = 2,4 use 512bit store
352 if constexpr (col % 2 == 0) {
353 _mm512_storeu_si512(
354 reinterpret_cast<__m512i*>((C + row * ldc + col * 16)),
355 (__m512i)(_mm512_cvtne2ps_pbh(vc[row * COLS + col + 1], vc[row * COLS + col])));
356 }
357 };
358 Unroll<ROWS * COLS>{}(storec);
359 }
360 };
361 template <int BLOCK_M, int BLOCK_N>
362 struct tinygemm_kernel_nn2<at::BFloat16, BLOCK_M, BLOCK_N> {
363 static inline void apply(
364 const at::BFloat16* __restrict__ A,
365 const at::Float8_e4m3fn* __restrict__ B,
366 at::BFloat16* __restrict__ C,
367 float scale,
368 int K,
369 int lda,
370 int ldb,
371 int ldc) {
372 constexpr int ROWS = BLOCK_M;
373 constexpr int COLS = BLOCK_N / 16;
374
375 // prefetch distance
376 constexpr int PREFETCH_SIZE_K = 64;
377
378 __m512bh va;
379 __m512bh vb[COLS];
380 __m512 vc[ROWS * COLS];
381
382 const __m512 vscale = _mm512_set1_ps(scale);
383
384 auto loadc = [&](auto i) { vc[i] = _mm512_setzero_ps(); };
385 Unroll<ROWS * COLS>{}(loadc);
386
387 const int K2 = K >> 1;
388 const int lda2 = lda >> 1;
389 const int ldb2 = ldb; // ldb * 2 >> 1;
390 const float* a_ptr = reinterpret_cast<const float*>(A);
391 const uint16_t* b_ptr = reinterpret_cast<const uint16_t*>(B);
392
393 auto compute = [&](auto i, int k) {
394 constexpr int row = i / COLS;
395 constexpr int col = i % COLS;
396
397 if constexpr (col == 0) {
398 va = (__m512bh)(_mm512_set1_ps(a_ptr[row * lda2 + k]));
399 }
400 if constexpr (row == 0) {
401 if constexpr (col % 2 == 0) {
402 __m512i b8 = _mm512_loadu_si512(b_ptr + k * ldb2 + col * 16);
403 if constexpr (PREFETCH_SIZE_K > 0) {
404 _mm_prefetch(b_ptr + (k + PREFETCH_SIZE_K) * ldb2 + col * 16, _MM_HINT_T0);
405 }
406 vb[col + 0] = CVT_FP8_TO_BF16(_mm512_extracti32x8_epi32(b8, 0));
407 vb[col + 1] = CVT_FP8_TO_BF16(_mm512_extracti32x8_epi32(b8, 1));
408 }
409 }
410 vc[i] = _mm512_dpbf16_ps(vc[i], va, vb[col]);
411 };
412 for (int k = 0; k < K2; ++k) {
413 Unroll<ROWS * COLS>{}(compute, k);
414 }
415
416 auto storec = [&](auto i) {
417 constexpr int row = i / COLS;
418 constexpr int col = i % COLS;
419 // for COLS = 2, 4 use 512bit store
420 if constexpr (col % 2 == 0) {
421 __m512 vc0 = _mm512_mul_ps(vc[row * COLS + col + 0], vscale);
422 __m512 vc1 = _mm512_mul_ps(vc[row * COLS + col + 1], vscale);
423 _mm512_storeu_si512(
424 reinterpret_cast<__m512i*>((C + row * ldc + col * 16)), (__m512i)(_mm512_cvtne2ps_pbh(vc1, vc0)));
425 }
426 };
427 Unroll<ROWS * COLS>{}(storec);
428 }
429 };
430
431 template <bool has_bias, int BLOCK_M, int BLOCK_N>
432 struct tinygemm_kernel_nn<at::BFloat16, uint8_t, uint8_t, has_bias, BLOCK_M, BLOCK_N> {
433 static inline void apply(
434 const at::BFloat16* __restrict__ A,
435 const uint8_t* __restrict__ B,
436 at::BFloat16* __restrict__ C,
437 const float* __restrict__ bias,
438 const uint8_t* __restrict__ scale,
439 int K,
440 int lda,
441 int ldb,
442 int ldc,
443 int64_t block_size_K) {
444 // mxfp4 supports only group size of 32
445 // expect weight packed in 32-way, vnni2 format Nx2(64)
446 assert(block_size_K == 32);
447 assert(BLOCK_N == 32);
448
449 constexpr int ROWS = BLOCK_M;
450 constexpr int COLS = BLOCK_N / 16;
451
452 // prefetch distance
453 constexpr int PREFETCH_SIZE_K = 64;
454 constexpr int PREFETCH_SIZE_KB = 1;
455
456 __m512bh va;
457 __m512bh vb[COLS];
458 __m512 vc[ROWS * COLS];
459
460 // holds Nx2(64) scales, interleaved as 2 belongs to K dimension
461 // e.g. vs0: { s0, s0, s1, s1, ..., s15, s15}
462 // vs1: {s16, s16, s17, s17, ..., s31, s31}
463 __m512i vscale[COLS];
464
465 // exponent bias 127
466 const __m512i off = _mm512_set1_epi16(0x7F);
467
468 auto loadc = [&](auto i) {
469 constexpr int col = i % COLS;
470 if constexpr (has_bias) {
471 vc[i] = _mm512_loadu_ps(bias + col * 16);
472 } else {
473 vc[i] = _mm512_setzero_ps();
474 }
475 };
476 Unroll<ROWS * COLS>{}(loadc);
477
478 const int64_t K2 = K >> 1;
479 const int64_t lda2 = lda >> 1;
480 const int64_t ldb2 = ldb; // ldb * 2 >> 1;
481 const float* a_ptr = reinterpret_cast<const float*>(A);
482 const uint8_t* b_ptr = reinterpret_cast<const uint8_t*>(B);
483
484 auto compute = [&](auto i, int k) {
485 constexpr int row = i / COLS;
486 constexpr int col = i % COLS;
487
488 if constexpr (col == 0) {
489 va = (__m512bh)(_mm512_set1_ps(a_ptr[row * lda2 + k]));
490 if constexpr (PREFETCH_SIZE_K > 0) {
491 _mm_prefetch(a_ptr + row * lda2 + k + PREFETCH_SIZE_K, _MM_HINT_T0);
492 }
493 }
494 if constexpr (row == 0) {
495 // load 32 * 2 (64) int4 at a time
496 if constexpr (col % 2 == 0) {
497 __m256i b4 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(b_ptr + k * ldb2 + col * 16));
498 if constexpr (PREFETCH_SIZE_K > 0) {
499 _mm_prefetch(b_ptr + (k + PREFETCH_SIZE_K) * ldb2 + col * 16, _MM_HINT_T0);
500 }
501 std::tie(vb[col + 0], vb[col + 1]) = CVT_MXFP4_TO_BF16(b4, vscale[col + 0], vscale[col + 1]);
502 }
503 }
504 vc[i] = _mm512_dpbf16_ps(vc[i], va, vb[col]);
505 };
506
507 for (int64_t k = 0; k < K2; ++k) {
508 // update scales every 16x2 K
509 if ((k & 15) == 0) {
510 __m256i s8 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(scale + (k >> 4) * 32));
511 __m512i s16 = _mm512_slli_epi16(_mm512_sub_epi16(_mm512_cvtepu8_epi16(s8), off), 0x7);
512 std::tie(vscale[0], vscale[1]) = transpose_2x32_16bit(s16, s16);
513 }
514 Unroll<ROWS * COLS>{}(compute, k);
515 }
516
517 auto storec = [&](auto i) {
518 constexpr int row = i / COLS;
519 constexpr int col = i % COLS;
520 // for COLS = 2,4 use 512bit store
521 if constexpr (col % 2 == 0) {
522 _mm512_storeu_si512(
523 reinterpret_cast<__m512i*>((C + row * ldc + col * 16)),
524 (__m512i)(_mm512_cvtne2ps_pbh(vc[row * COLS + col + 1], vc[row * COLS + col])));
525 }
526 };
527 Unroll<ROWS * COLS>{}(storec);
528 }
529 };
530 #endif
531
532 #define LAUNCH_TINYGEMM_KERNEL_NN(MB_SIZE, NB_SIZE) \
533 tinygemm_kernel_nn<scalar_t, packed_t, param_t, has_bias, MB_SIZE, NB_SIZE>::apply( \
534 A + mb_start * lda, \
535 B + nb_start * 2, \
536 C + mb_start * ldc + nb_start, \
537 has_bias ? bias + nb_start : nullptr, \
538 scale, \
539 K, \
540 lda, \
541 ldb, \
542 ldc, \
543 block_size_K);
544
545 #define LAUNCH_TINYGEMM_KERNEL_NN2(MB_SIZE, NB_SIZE) \
546 tinygemm_kernel_nn2<scalar_t, MB_SIZE, NB_SIZE>::apply( \
547 A + mb_start * lda, B + nb_start * 2, C + mb_start * ldc + nb_start, scale, K, lda, ldb, ldc);
548
549 template <typename scalar_t, typename packed_t, typename param_t, bool has_bias>
550 struct brgemm {
551 static inline void apply(
552 const scalar_t* __restrict__ A,
553 const packed_t* __restrict__ B,
554 scalar_t* __restrict__ C,
555 scalar_t* __restrict__ Btmp,
556 float* __restrict__ Ctmp,
557 const float* __restrict__ bias,
558 const param_t* __restrict__ scale,
559 int M,
560 int N,
561 int K,
562 int lda,
563 int ldb,
564 int ldc,
565 bool do_unpack = true) {
566 TORCH_CHECK(false, "struct brgemm: primary template not implemented!");
567 }
568 };
569 template <typename scalar_t>
570 struct brgemm2 {};
571
572 template <bool has_bias>
573 struct brgemm<at::BFloat16, at::Float8_e4m3fn, float, has_bias> {
574 static inline void apply(
575 const at::BFloat16* __restrict__ A,
576 const at::Float8_e4m3fn* __restrict__ B,
577 at::BFloat16* __restrict__ C,
578 at::BFloat16* __restrict__ Btmp,
579 float* __restrict__ Ctmp,
580 const float* __restrict__ bias,
581 const float* __restrict__ scale,
582 int M,
583 int N,
584 int K,
585 int lda,
586 int ldb,
587 int ldc,
588 bool do_unpack = true) {
589 constexpr int BLOCK_N = block_size_n();
590
591 // [K, BLOCK_N] -> [K / 2, BLOCK_N * 2]
592 const int ldb_tmp = BLOCK_N;
593
594 if (do_unpack) {
595 for (int k = 0; k < K; k += BLOCK_K) {
596 int kb_size = std::min(BLOCK_K, K - k);
597
598 int idx = k >> 7; // k / BLOCK_K where BLOCK_K = 128
599 unpack_B(Btmp + k * ldb_tmp, B + k * ldb, N, kb_size, ldb, ldb_tmp, scale[idx]);
600 }
601 }
602
603 at::native::cpublas::brgemm(M, N, K, lda, ldb_tmp, BLOCK_N, /* add_C */ false, A, Btmp, Ctmp);
604
605 // copy from Ctmp to C
606 for (int m = 0; m < M; ++m) {
607 if constexpr (has_bias) {
608 copy_add_stub(C + m * ldc, Ctmp + m * BLOCK_N, bias, N);
609 } else {
610 copy_stub(C + m * ldc, Ctmp + m * BLOCK_N, N);
611 }
612 }
613 }
614 };
615
616 template <>
617 struct brgemm2<at::BFloat16> {
618 static inline void apply(
619 const at::BFloat16* __restrict__ A,
620 const at::Float8_e4m3fn* __restrict__ B,
621 at::BFloat16* __restrict__ C,
622 at::BFloat16* __restrict__ Btmp,
623 float* __restrict__ Ctmp,
624 float scale,
625 int M,
626 int N,
627 int K,
628 int lda,
629 int ldb,
630 int ldc) {
631 constexpr int BLOCK_N = block_size_n();
632
633 // [BLOCK_K, BLOCK_N] -> [BLOCK_K / 2, BLOCK_N * 2]
634 const int ldb_tmp = block_size_n();
635
636 // accumulate across K per BLOCK_K
637 for (int k = 0; k < K; k += BLOCK_K) {
638 int kb_size = std::min(BLOCK_K, K - k);
639 unpack_B(Btmp, B + k * ldb, N, kb_size, ldb, ldb_tmp);
640
641 const bool add_C = (k != 0);
642 at::native::cpublas::brgemm(M, N, kb_size, lda, ldb_tmp, BLOCK_N, add_C, A + k, Btmp, Ctmp);
643 }
644
645 // copy from Ctmp to C and mul scale
646 for (int m = 0; m < M; ++m) {
647 copy_mul_stub(C + m * ldc, Ctmp + m * BLOCK_N, N, scale);
648 }
649 }
650 };
651
652 template <bool has_bias>
653 struct brgemm<at::BFloat16, uint8_t, uint8_t, has_bias> {
654 static inline void apply(
655 const at::BFloat16* __restrict__ A,
656 const uint8_t* __restrict__ B,
657 at::BFloat16* __restrict__ C,
658 at::BFloat16* __restrict__ Btmp,
659 float* __restrict__ Ctmp,
660 const float* __restrict__ bias,
661 const uint8_t* __restrict__ scale,
662 int M,
663 int N,
664 int K,
665 int lda,
666 int ldb,
667 int ldc,
668 bool do_unpack = true) {
669 constexpr int BLOCK_N = block_size_n();
670
671 // [K, BLOCK_N] -> [K / 2, BLOCK_N * 2]
672 const int ldb_tmp = BLOCK_N;
673
674 if (do_unpack) {
675 // group size 32 for mxfp4
676 for (int k = 0; k < K; k += 32) {
677 unpack_B(Btmp + k * ldb_tmp, B + k * (ldb >> 1), N, 32, ldb, ldb_tmp, scale + (k >> 5) * BLOCK_N);
678 }
679 }
680
681 at::native::cpublas::brgemm(M, N, K, lda, ldb_tmp, BLOCK_N, /* add_C */ false, A, Btmp, Ctmp);
682
683 // copy from Ctmp to C
684 for (int m = 0; m < M; ++m) {
685 if constexpr (has_bias) {
686 copy_add_stub(C + m * ldc, Ctmp + m * BLOCK_N, bias, N);
687 } else {
688 copy_stub(C + m * ldc, Ctmp + m * BLOCK_N, N);
689 }
690 }
691 }
692 };
693
694 template <typename scalar_t, typename packed_t, typename param_t, bool has_bias>
695 void tinygemm_kernel(
696 const scalar_t* __restrict__ A,
697 const packed_t* __restrict__ B,
698 scalar_t* __restrict__ C,
699 scalar_t* __restrict__ Btmp,
700 float* __restrict__ Ctmp,
701 const param_t* __restrict__ scale,
702 const float* __restrict__ bias,
703 int64_t M,
704 int64_t N,
705 int64_t K,
706 int64_t lda,
707 int64_t ldb,
708 int64_t ldc,
709 bool brg,
710 int64_t block_size_K,
711 bool do_unpack = true) {
712 if (brg) {
713 brgemm<scalar_t, packed_t, param_t, has_bias>::apply(
714 A, B, C, Btmp, Ctmp, bias, scale, M, N, K, lda, ldb, ldc, do_unpack);
715 return;
716 }
717
718 // pattern: 1-4-16
719 constexpr int64_t BLOCK_M = 4;
720 constexpr int64_t BLOCK_N = 64;
721 const int64_t MB = div_up(M, BLOCK_M);
722 const int64_t NB = div_up(N, BLOCK_N);
723 for (int mb = 0; mb < MB; ++mb) {
724 int64_t mb_start = mb * BLOCK_M;
725 int64_t mb_size = std::min(BLOCK_M, M - mb_start);
726 for (int64_t nb = 0; nb < NB; ++nb) {
727 int64_t nb_start = nb * BLOCK_N;
728 int64_t nb_size = std::min(BLOCK_N, N - nb_start);
729
730 switch (mb_size << 4 | nb_size >> 4) {
731 case 0x12:
732 LAUNCH_TINYGEMM_KERNEL_NN(1, 32);
733 break;
734 case 0x22:
735 LAUNCH_TINYGEMM_KERNEL_NN(2, 32);
736 break;
737 case 0x32:
738 LAUNCH_TINYGEMM_KERNEL_NN(3, 32);
739 break;
740 case 0x42:
741 LAUNCH_TINYGEMM_KERNEL_NN(4, 32);
742 break;
743 default:
744 TORCH_CHECK(false, "Unexpected block size, ", mb_size, "x", "nb_size");
745 }
746 }
747 }
748 }
749
750 template <typename scalar_t>
751 void tinygemm_kernel2(
752 const scalar_t* __restrict__ A,
753 const at::Float8_e4m3fn* __restrict__ B,
754 scalar_t* __restrict__ C,
755 scalar_t* __restrict__ Btmp,
756 float* __restrict__ Ctmp,
757 float scale,
758 int64_t M,
759 int64_t N,
760 int64_t K,
761 int64_t lda,
762 int64_t ldb,
763 int64_t ldc,
764 bool brg) {
765 if (brg) {
766 brgemm2<scalar_t>::apply(A, B, C, Btmp, Ctmp, scale, M, N, K, lda, ldb, ldc);
767 return;
768 }
769
770 // pattern: 1-8-8
771 if (M == 1) {
772 constexpr int64_t BLOCK_N = 128;
773 const int64_t NB = div_up(N, BLOCK_N);
774 int64_t mb_start = 0;
775
776 for (int64_t nb = 0; nb < NB; ++nb) {
777 int64_t nb_start = nb * BLOCK_N;
778 int64_t nb_size = std::min(BLOCK_N, N - nb_start);
779
780 switch (nb_size >> 4) {
781 case 2:
782 LAUNCH_TINYGEMM_KERNEL_NN2(1, 32);
783 break;
784 case 4:
785 LAUNCH_TINYGEMM_KERNEL_NN2(1, 64);
786 break;
787 case 6:
788 LAUNCH_TINYGEMM_KERNEL_NN2(1, 96);
789 break;
790 case 8:
791 LAUNCH_TINYGEMM_KERNEL_NN2(1, 128);
792 break;
793 default:
794 TORCH_CHECK(false, "Unexpected block size, 1x", "nb_size");
795 }
796 }
797 return;
798 }
799
800 // pattern: 1-4-16
801 constexpr int64_t BLOCK_M = 4;
802 constexpr int64_t BLOCK_N = 64;
803 const int64_t MB = div_up(M, BLOCK_M);
804 const int64_t NB = div_up(N, BLOCK_N);
805 for (int64_t mb = 0; mb < MB; ++mb) {
806 int64_t mb_start = mb * BLOCK_M;
807 int64_t mb_size = std::min(BLOCK_M, M - mb_start);
808 for (int64_t nb = 0; nb < NB; ++nb) {
809 int64_t nb_start = nb * BLOCK_N;
810 int64_t nb_size = std::min(BLOCK_N, N - nb_start);
811
812 switch (mb_size << 4 | nb_size >> 4) {
813 // mb_size = 1
814 case 0x12:
815 LAUNCH_TINYGEMM_KERNEL_NN2(1, 32);
816 break;
817 case 0x14:
818 LAUNCH_TINYGEMM_KERNEL_NN2(1, 64);
819 break;
820 // mb_size = 2
821 case 0x22:
822 LAUNCH_TINYGEMM_KERNEL_NN2(2, 32);
823 break;
824 case 0x24:
825 LAUNCH_TINYGEMM_KERNEL_NN2(2, 64);
826 break;
827 // mb_size = 3
828 case 0x32:
829 LAUNCH_TINYGEMM_KERNEL_NN2(3, 32);
830 break;
831 case 0x34:
832 LAUNCH_TINYGEMM_KERNEL_NN2(3, 64);
833 break;
834 // mb_size = 4
835 case 0x42:
836 LAUNCH_TINYGEMM_KERNEL_NN2(4, 32);
837 break;
838 case 0x44:
839 LAUNCH_TINYGEMM_KERNEL_NN2(4, 64);
840 break;
841 default:
842 TORCH_CHECK(false, "Unexpected block size, ", mb_size, "x", "nb_size");
843 }
844 }
845 }
846 }
847
848 // NB: fp8/fp4 scaled mm kernel implementation
849 //
850 // scalar_t packed_t param_t
851 // FP8 BF16 FP8 FP32
852 // MXFP4 BF16 U8 U8
853 //
854 template <typename scalar_t, typename packed_t, typename param_t, typename func_t>
855 void fp_scaled_mm_kernel_impl(
856 scalar_t* __restrict__ out,
857 const scalar_t* __restrict__ mat1,
858 const packed_t* __restrict__ mat2,
859 const param_t* __restrict__ scales2,
860 const float* __restrict__ bias,
861 scalar_t* __restrict__ buffer,
862 int64_t M,
863 int64_t N,
864 int64_t K,
865 int64_t mat1_strideM,
866 int64_t out_strideM,
867 int64_t block_size_N,
868 int64_t block_size_K,
869 int64_t buffer_size_per_thread,
870 const func_t& scale_offset_per_block) {
871 constexpr int64_t BLOCK_M = block_size_m();
872 constexpr int64_t BLOCK_N = block_size_n();
873 const int64_t MB = div_up(M, BLOCK_M);
874 const int64_t NB = div_up(N, BLOCK_N);
875
876 const bool use_brgemm = can_use_brgemm<packed_t>(M);
877
878 // use K/2 for mxfp4 and K for fp8
879 const int64_t packed_K = get_row_size<packed_t>(K);
880
881 // parallel on [MB, NB]
882 AT_DISPATCH_BOOL(bias != nullptr, has_bias, [&] {
883 parallel_2d(MB, NB, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) {
884 int tid = get_thread_num();
885 scalar_t* __restrict__ Btmp = buffer + tid * buffer_size_per_thread;
886 float* __restrict__ Ctmp = (float*)((void*)(Btmp + MAX_CACHE_BLOCK_SIZE * BLOCK_N * K));
887
888 loop_2d<packed_t>(mb0, mb1, nb0, nb1, BLOCK_N * K, [&](int64_t mb, int64_t nb, int64_t nb_offset) {
889 const param_t* scale_ptr = scales2 + scale_offset_per_block(nb);
890
891 int64_t mb_start = mb * BLOCK_M;
892 int64_t mb_size = std::min(M - mb_start, BLOCK_M);
893 int64_t nb_start = nb * BLOCK_N;
894 int64_t nb_size = std::min(N - nb_start, BLOCK_N);
895
896 // only do unpacking for the first row
897 bool do_unpack = (mb == mb0);
898
899 tinygemm_kernel<scalar_t, packed_t, param_t, has_bias>(
900 /* A */ mat1 + mb_start * mat1_strideM,
901 /* B */ mat2 + nb_start * packed_K, // nb * BLOCK_N * K
902 /* C */ out + mb_start * out_strideM + nb_start,
903 /* Btmp */ Btmp + nb_offset * BLOCK_N * K,
904 /* Ctmp */ Ctmp,
905 /* scale */ scale_ptr,
906 /* bias */ bias + nb_start,
907 /* M */ mb_size,
908 /* N */ nb_size,
909 /* K */ K,
910 /* lda */ mat1_strideM,
911 /* ldb */ nb_size,
912 /* ldc */ out_strideM,
913 /* brg */ use_brgemm,
914 /* block_size_K */ block_size_K,
915 /* do_unpack */ do_unpack);
916 });
917
918 if (use_brgemm) {
919 at::native::cpublas::brgemm_release();
920 }
921 });
922 });
923 }
924
925 } // anonymous namespace
926
927 // tinygemm interface
928 template <typename scalar_t>
929 void tinygemm_kernel(
930 const scalar_t* __restrict__ A,
931 const at::Float8_e4m3fn* __restrict__ B,
932 scalar_t* __restrict__ C,
933 scalar_t* __restrict__ Btmp,
934 float* __restrict__ Ctmp,
935 const float* __restrict__ Bbias,
936 const float* __restrict__ scale,
937 int64_t M,
938 int64_t N,
939 int64_t K,
940 int64_t lda,
941 int64_t ldb,
942 int64_t ldc,
943 bool brg,
944 int64_t block_size_K,
945 bool do_unpack) {
946 if (Bbias != nullptr) {
947 tinygemm_kernel<scalar_t, at::Float8_e4m3fn, float, true>(
948 A, B, C, Btmp, Ctmp, scale, Bbias, M, N, K, lda, ldb, ldc, brg, block_size_K, do_unpack);
949 return;
950 }
951 tinygemm_kernel<scalar_t, at::Float8_e4m3fn, float, false>(
952 A, B, C, Btmp, Ctmp, scale, nullptr, M, N, K, lda, ldb, ldc, brg, block_size_K, do_unpack);
953 }
954
955 template <typename scalar_t>
956 void tinygemm_kernel(
957 const scalar_t* __restrict__ A,
958 const at::Float8_e4m3fn* __restrict__ B,
959 scalar_t* __restrict__ C,
960 scalar_t* __restrict__ Btmp,
961 float* __restrict__ Ctmp,
962 float scale,
963 int64_t M,
964 int64_t N,
965 int64_t K,
966 int64_t lda,
967 int64_t ldb,
968 int64_t ldc,
969 bool brg) {
970 tinygemm_kernel2<scalar_t>(A, B, C, Btmp, Ctmp, scale, M, N, K, lda, ldb, ldc, brg);
971 }
972 template <typename scalar_t>
973 void tinygemm_kernel(
974 const scalar_t* __restrict__ A,
975 const uint8_t* __restrict__ B,
976 scalar_t* __restrict__ C,
977 scalar_t* __restrict__ Btmp,
978 float* __restrict__ Ctmp,
979 const float* __restrict__ Bbias,
980 const uint8_t* __restrict__ scale,
981 int64_t M,
982 int64_t N,
983 int64_t K,
984 int64_t lda,
985 int64_t ldb,
986 int64_t ldc,
987 bool brg,
988 int64_t block_size_K,
989 bool do_unpack) {
990 if (Bbias != nullptr) {
991 tinygemm_kernel<scalar_t, uint8_t, uint8_t, true>(
992 A, B, C, Btmp, Ctmp, scale, Bbias, M, N, K, lda, ldb, ldc, brg, block_size_K, do_unpack);
993 return;
994 }
995 tinygemm_kernel<scalar_t, uint8_t, uint8_t, false>(
996 A, B, C, Btmp, Ctmp, scale, nullptr, M, N, K, lda, ldb, ldc, brg, block_size_K, do_unpack);
997 }
998
999 // tinygemm interface
1000 template <typename scalar_t>
1001 void tinygemm_kernel(
1002 const scalar_t* __restrict__ A,
1003 const at::Float8_e4m3fn* __restrict__ B,
1004 float* __restrict__ C,
1005 scalar_t* __restrict__ Btmp,
1006 const float* __restrict__ Bbias,
1007 const float* __restrict__ scale,
1008 int64_t M,
1009 int64_t N,
1010 int64_t K,
1011 int64_t lda,
1012 int64_t ldb,
1013 int64_t ldc,
1014 bool brg,
1015 int64_t block_size_K,
1016 bool do_unpack) {
1017 if (Bbias != nullptr) {
1018 tinygemm_kernel<scalar_t, at::Float8_e4m3fn, float, true>(
1019 A, B, C, Btmp, scale, Bbias, M, N, K, lda, ldb, ldc, brg, block_size_K, do_unpack);
1020 return;
1021 }
1022 tinygemm_kernel<scalar_t, at::Float8_e4m3fn, float, false>(
1023 A, B, C, Btmp, scale, nullptr, M, N, K, lda, ldb, ldc, brg, block_size_K, do_unpack);
1024 }
1025
1026 template <typename scalar_t>
1027 void tinygemm_kernel(
1028 const scalar_t* __restrict__ A,
1029 const uint8_t* __restrict__ B,
1030 float* __restrict__ C,
1031 scalar_t* __restrict__ Btmp,
1032 const float* __restrict__ Bbias,
1033 const uint8_t* __restrict__ scale,
1034 int64_t M,
1035 int64_t N,
1036 int64_t K,
1037 int64_t lda,
1038 int64_t ldb,
1039 int64_t ldc,
1040 bool brg,
1041 int64_t block_size_K,
1042 bool do_unpack) {
1043 if (Bbias != nullptr) {
1044 tinygemm_kernel<scalar_t, uint8_t, uint8_t, true>(
1045 A, B, C, Btmp, scale, Bbias, M, N, K, lda, ldb, ldc, brg, block_size_K, do_unpack);
1046 return;
1047 }
1048 tinygemm_kernel<scalar_t, uint8_t, uint8_t, false>(
1049 A, B, C, Btmp, scale, nullptr, M, N, K, lda, ldb, ldc, brg, block_size_K, do_unpack);
1050 }
1051
1052 #define INSTANTIATE_TINYGEMM_TEMPLATE(TYPE_A, TYPE_B, TYPE_S) \
1053 template void tinygemm_kernel<TYPE_A>( \
1054 const TYPE_A* __restrict__ A, \
1055 const TYPE_B* __restrict__ B, \
1056 TYPE_A* __restrict__ C, \
1057 TYPE_A* __restrict__ Btmp, \
1058 float* __restrict__ Ctmp, \
1059 const float* __restrict__ Bbias, \
1060 const TYPE_S* __restrict__ scale, \
1061 int64_t M, \
1062 int64_t N, \
1063 int64_t K, \
1064 int64_t lda, \
1065 int64_t ldb, \
1066 int64_t ldc, \
1067 bool brg, \
1068 int64_t block_size_K, \
1069 bool do_unpack)
1070
1071 INSTANTIATE_TINYGEMM_TEMPLATE(at::BFloat16, at::Float8_e4m3fn, float);
1072 INSTANTIATE_TINYGEMM_TEMPLATE(at::Half, at::Float8_e4m3fn, float);
1073 INSTANTIATE_TINYGEMM_TEMPLATE(at::BFloat16, uint8_t, uint8_t);
1074 INSTANTIATE_TINYGEMM_TEMPLATE(at::Half, uint8_t, uint8_t);
1075
1076 #define INSTANTIATE_TINYGEMM_TEMPLATE2(TYPE) \
1077 template void tinygemm_kernel<TYPE>( \
1078 const TYPE* __restrict__ A, \
1079 const at::Float8_e4m3fn* __restrict__ B, \
1080 TYPE* __restrict__ C, \
1081 TYPE* __restrict__ Btmp, \
1082 float* __restrict__ Ctmp, \
1083 float scale, \
1084 int64_t M, \
1085 int64_t N, \
1086 int64_t K, \
1087 int64_t lda, \
1088 int64_t ldb, \
1089 int64_t ldc, \
1090 bool brg)
1091
1092 INSTANTIATE_TINYGEMM_TEMPLATE2(at::BFloat16);
1093
1094 inline const float* get_bias_data(const std::optional<at::Tensor>& bias, int64_t N) {
1095 if (bias.has_value()) {
1096 const auto& bias_ref = bias.value();
1097 CHECK_EQ(bias_ref.size(0), N);
1098 return bias_ref.data_ptr<float>();
1099 }
1100 return nullptr;
1101 }
1102 // FP8 and MXFP4 WoQ uses the same pattern:
1103 // Btmp : [T, BLOCK_N * K]
1104 // Ctmp : [T, BLOCK_M * BLOCK_N]
1105 inline at::Tensor alloc_thread_buffer(const at::TensorOptions& options, int64_t K) {
1106 constexpr int64_t BLOCK_M = block_size_m();
1107 constexpr int64_t BLOCK_N = block_size_n();
1108 int num_threads = at::get_num_threads();
1109 int64_t size_per_thread = MAX_CACHE_BLOCK_SIZE * BLOCK_N * K + BLOCK_M * BLOCK_N * 2;
1110 return at::empty({num_threads, size_per_thread}, options);
1111 }
1112
1113 at::Tensor fp8_scaled_mm_cpu(
1114 at::Tensor& mat1,
1115 at::Tensor& mat2,
1116 at::Tensor& scales2,
1117 std::vector<int64_t> block_size,
1118 const std::optional<at::Tensor>& bias,
1119 at::ScalarType out_dtype,
1120 bool is_vnni) {
1121 auto packed_w = is_vnni ? mat2 : convert_weight_packed(mat2);
1122
1123 CHECK_LAST_DIM_CONTIGUOUS_INPUT(mat1);
1124 CHECK_INPUT(mat2);
1125 CHECK_INPUT(scales2);
1126 TORCH_CHECK(scales2.scalar_type() == at::kFloat, "fp8_scaled_mm_cpu: expect scales2 to be float32.");
1127
1128 int64_t M = mat1.size(0);
1129 int64_t N = mat2.size(0);
1130 int64_t K = mat2.size(1);
1131
1132 CHECK_EQ(mat1.size(1), K);
1133 CHECK_DIM(2, mat1);
1134 CHECK_DIM(2, mat2);
1135
1136 TORCH_CHECK(block_size.size() == 2, "fp8_scaled_mm_cpu: expect block_size.size() to be 2.");
1137 int64_t block_size_N = block_size[0];
1138 int64_t block_size_K = block_size[1];
1139
1140 constexpr int64_t BLOCK_N = block_size_n();
1141 TORCH_CHECK(block_size_N % BLOCK_N == 0, "fp8_scaled_mm_cpu: expect block_size_N to be multiples of BLOCK_N");
1142 TORCH_CHECK(block_size_K == BLOCK_K, "fp8_scaled_mm_cpu: expect block_size_K equals to BLOCK_K");
1143 CHECK_EQ(scales2.size(0), div_up(N, block_size_N));
1144 CHECK_EQ(scales2.size(1), div_up(K, block_size_K));
1145
1146 const auto st = mat1.scalar_type();
1147 TORCH_CHECK(st == at::kBFloat16 || st == at::kHalf, "fp8_scaled_mm_cpu: expect A to be bfloat16 or half.");
1148 TORCH_CHECK(st == out_dtype, "fp8_scaled_mm_cpu: expect A has same dtype with out_dtype.");
1149 TORCH_CHECK(mat2.scalar_type() == at::kFloat8_e4m3fn, "fp8_scaled_mm_cpu: expect mat2 to be fp8_e4m3.");
1150 TORCH_CHECK(scales2.scalar_type() == at::kFloat, "fp8_scaled_mm_cpu: expect scales to be float32.");
1151 auto out = at::empty({M, N}, mat1.options().dtype(out_dtype));
1152
1153 auto buffer = alloc_thread_buffer(mat1.options(), K);
1154
1155 AT_DISPATCH_REDUCED_FLOATING_TYPES(out_dtype, "fp8_scaled_mm_kernel_impl", [&] {
1156 // used for lambda computing scale offset for each block
1157 // fp8 block gemm sale shape: [N/128, K/128]
1158 // for each block: [1, K/128]
1159 const int64_t scale_size_K = div_up(K, block_size_K);
1160 const int64_t blocks_n_per_group = block_size_N / BLOCK_N;
1161
1162 fp_scaled_mm_kernel_impl<scalar_t, at::Float8_e4m3fn, float>(
1163 out.data_ptr<scalar_t>(),
1164 mat1.data_ptr<scalar_t>(),
1165 packed_w.data_ptr<at::Float8_e4m3fn>(),
1166 scales2.data_ptr<float>(),
1167 get_bias_data(bias, N),
1168 buffer.data_ptr<scalar_t>(),
1169 M,
1170 N,
1171 K,
1172 mat1.stride(0),
1173 out.stride(0),
1174 block_size_N,
1175 block_size_K,
1176 buffer.size(-1),
1177 [&](int64_t nb) { return (nb / blocks_n_per_group) * scale_size_K; });
1178 });
1179
1180 return out;
1181 }
1182
1183 // mat1 : [M, K] bfloat16
1184 // mat2 : [N, K / 2] uint8, actual layout: [N / BLOCK_N, K / 2, BLOCK_N, 2]
1185 // scales2: [N, K / G], actual layout: [N / BLOCK_N, K / G, BLOCK_N]
1186 at::Tensor mxfp4_scaled_mm_cpu(
1187 at::Tensor& mat1, at::Tensor& mat2, at::Tensor& scales2, const std::optional<at::Tensor>& bias, bool is_vnni) {
1188 auto packed_w = is_vnni ? mat2 : convert_weight_packed(mat2);
1189
1190 CHECK_INPUT(mat1);
1191 CHECK_INPUT(mat2);
1192 CHECK_INPUT(scales2);
1193
1194 int64_t M = mat1.size(0);
1195 int64_t N = mat2.size(0);
1196 int64_t K = mat2.size(1) * 2;
1197
1198 // mxfp4 supports only group size of 32 (2^5)
1199 constexpr int64_t group_size = 32;
1200 constexpr int64_t BLOCK_N = block_size_n();
1201
1202 CHECK_EQ(mat1.size(1), K);
1203 CHECK_EQ(scales2.numel(), N * K >> 5);
1204
1205 const auto st = mat1.scalar_type();
1206 TORCH_CHECK(st == at::kBFloat16 || st == at::kHalf, "mxfp4_scaled_mm_cpu: expect A to be bfloat16 or half.");
1207 TORCH_CHECK(mat2.scalar_type() == at::kByte, "mxfp4_scaled_mm_cpu: expect mat2 to be uint8.");
1208 TORCH_CHECK(scales2.scalar_type() == at::kByte, "mxfp4_scaled_mm_cpu: expect scales to be uint8.");
1209 auto out = at::empty({M, N}, mat1.options());
1210
1211 auto buffer = alloc_thread_buffer(mat1.options(), K);
1212
1213 AT_DISPATCH_REDUCED_FLOATING_TYPES(st, "mxfp4_scaled_mm_kernel_impl", [&] {
1214 // used for lambda computing scale offset for each block
1215 // mxfp4 block gemm sale shape: [N/BLOCK_N, K/32, BLOCK_N]
1216 // for each block: [K/32, BLOCK_N]
1217 const int64_t s_strideN = (K >> 5) * BLOCK_N;
1218
1219 fp_scaled_mm_kernel_impl<scalar_t, uint8_t, uint8_t>(
1220 out.data_ptr<scalar_t>(),
1221 mat1.data_ptr<scalar_t>(),
1222 packed_w.data_ptr<uint8_t>(),
1223 scales2.data_ptr<uint8_t>(),
1224 get_bias_data(bias, N),
1225 buffer.data_ptr<scalar_t>(),
1226 M,
1227 N,
1228 K,
1229 mat1.stride(0),
1230 out.stride(0),
1231 /* block_size_N */ 1,
1232 /* block_size_K */ group_size,
1233 buffer.size(-1),
1234 [&](int64_t nb) { return nb * s_strideN; });
1235 });
1236
1237 return out;
1238 }