main
cpp 1,821 lines 59.4 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 // [NOTE] TODO list for this kernel:
13 // 1. tune the value for BLOCK_N
14 // 2. planning for {batches, num_heads, num_kv_splits}
15 // and use actual num_kv_splits for small seq length
16 // 3. try fast impl of `.tanh()`
17 // 4. provide amx kernel for index_gemm_kernel_nn when M = 16
18 //
19
20 #if defined(CPU_CAPABILITY_AVX512)
21 // key: from [N, 32] to [32/2, N, 2]
22 // val: from [N, 32] to [N/2, 32, 2]
23 template <typename scalar_t, typename index_t>
24 inline void pack_vnni_Nx32(
25 scalar_t* __restrict__ dst0,
26 scalar_t* __restrict__ dst1,
27 const scalar_t* __restrict__ src,
28 const index_t* __restrict__ ind,
29 int N,
30 int ld_src,
31 int ld_dst0,
32 int ld_dst1,
33 bool convert_v) {
34 __m512i vinputs[16];
35 int n = 0;
36 for (; n < N; ++n) {
37 vinputs[n] = _mm512_loadu_si512(src + ind[n] * ld_src);
38 }
39 // padding with zero to avoid uninitialized vectors
40 for (; n < 16; ++n) {
41 vinputs[n] = _mm512_set1_epi32(0);
42 }
43
44 // pack value, skip 64 elems for deepseek
45 // handle 2 vectors at a time from [2, 32] to [32, 2]
46 if (convert_v) {
47 for (int n = 0; n < 16; n += 2) {
48 __m512i d0, d1;
49 std::tie(d0, d1) = transpose_2x32_16bit(vinputs[n], vinputs[n + 1]);
50 _mm512_storeu_si512(dst1 + (n >> 1) * ld_dst1 * 2, d0);
51 _mm512_storeu_si512(dst1 + (n >> 1) * ld_dst1 * 2 + 32, d1);
52 }
53 }
54
55 // pack key
56 transpose_16x16_32bit(vinputs);
57
58 const __mmask16 vmask = (1 << N) - 1;
59 for (int k = 0; k < 16; ++k) {
60 _mm512_mask_storeu_epi32(dst0 + k * ld_dst0 * 2, vmask, vinputs[k]);
61 }
62 }
63 #endif
64
65 // [NOTE]: MLA vnni format conversion
66 //
67 // here we apply same strategy as `FlashMLA`:
68 // each kv_cache is loaded once and packed twice (L2 cache hit)
69 //
70 // * for key: from [N, K/2, 2] to [K/2, N, 2]
71 // * for value: from [N/2, 2, Kv] to [N/2, Kv, 2]
72 //
73 template <typename scalar_t, typename index_t>
74 void pack_vnni(
75 scalar_t* __restrict__ dst0,
76 scalar_t* __restrict__ dst1,
77 const scalar_t* __restrict__ src,
78 const index_t* __restrict__ ind,
79 int N,
80 int K,
81 int Kv,
82 int ld_src,
83 int ld_dst0,
84 int ld_dst1) {
85 #if defined(CPU_CAPABILITY_AVX512)
86 const int NB = div_up(N, 16);
87 const int KB = K / 32; // no remainder
88 const int KBv = Kv / 32; // no remainder
89
90 for (int nb = 0; nb < NB; ++nb) {
91 for (int kb = 0; kb < KB; ++kb) {
92 // handle 16x512bits each block
93 int nb_size = std::min(N - nb * 16, 16);
94 pack_vnni_Nx32<scalar_t, index_t>(
95 /* dst0 */ dst0 + ((kb * 32) >> 1) * ld_dst0 * 2 + nb * 16 * 2,
96 /* dst1 */ dst1 + ((nb * 16) >> 1) * ld_dst1 * 2 + kb * 32 * 2,
97 /* src */ src + kb * 32,
98 /* ind */ ind + nb * 16,
99 /* N */ nb_size,
100 /* ld_src */ ld_src,
101 /* ld_dst0 */ ld_dst0,
102 /* ld_dst1 */ ld_dst1,
103 /* cvt_v */ kb < KBv);
104 }
105 }
106 #else
107 for (int n = 0; n < N; ++n) {
108 index_t index = ind[n];
109 for (int k = 0; k < K / 2; ++k) {
110 for (int d = 0; d < 2; ++d) {
111 dst0[k * ld_dst0 * 2 + n * 2 + d] = src[index * ld_src + k * 2 + d];
112 }
113 }
114 }
115 // from [N/2, 2, K] to [N/2, K, 2]
116 for (int n = 0; n < (N >> 1) * 2; n += 2) {
117 index_t index0 = ind[n + 0];
118 index_t index1 = ind[n + 1];
119 for (int k = 0; k < Kv; ++k) {
120 dst1[(n >> 1) * ld_dst1 * 2 + k * 2 + 0] = src[index0 * ld_src + k];
121 dst1[(n >> 1) * ld_dst1 * 2 + k * 2 + 1] = src[index1 * ld_src + k];
122 }
123 }
124 if (N % 2 != 0) {
125 index_t index = ind[N - 1];
126 for (int k = 0; k < Kv; ++k) {
127 dst1[(N >> 1) * ld_dst1 * 2 + k * 2 + 0] = src[index * ld_src + k];
128 dst1[(N >> 1) * ld_dst1 * 2 + k * 2 + 1] = 0;
129 }
130 }
131 #endif
132 }
133
134 template <typename scalar_t>
135 inline void fill_stub(scalar_t* __restrict__ out, float val, int64_t size) {
136 using Vec = at::vec::Vectorized<scalar_t>;
137 constexpr int kVecSize = Vec::size();
138 const Vec data_vec = Vec(static_cast<scalar_t>(val));
139 int64_t d = 0;
140 #pragma GCC unroll 4
141 for (; d <= size - kVecSize; d += kVecSize) {
142 data_vec.store(out + d);
143 }
144 if (size - d > 0) {
145 data_vec.store(out + d, size - d);
146 }
147 }
148
149 template <typename scalar_t>
150 inline void copy_stub(scalar_t* __restrict__ out, const float* __restrict__ acc, float s, int64_t size) {
151 using bVec = at::vec::Vectorized<scalar_t>;
152 using fVec = at::vec::Vectorized<float>;
153 constexpr int kVecSize = bVec::size();
154 const fVec s_fvec = fVec(s);
155 int64_t d = 0;
156 #pragma GCC unroll 4
157 for (; d <= size - kVecSize; d += kVecSize) {
158 auto [a_fvec0, a_fvec1] = load_float_vec2(acc + d);
159 a_fvec0 = a_fvec0 * s_fvec;
160 a_fvec1 = a_fvec1 * s_fvec;
161 bVec out_bvec = convert_from_float_ext<scalar_t>(a_fvec0, a_fvec1);
162 out_bvec.store(out + d);
163 }
164 for (; d < size; ++d) {
165 out[d] = static_cast<scalar_t>(acc[d] * s);
166 }
167 }
168
169 template <typename scalar_t>
170 inline void copy_stub(scalar_t* __restrict__ out, const scalar_t* __restrict__ src, int64_t size) {
171 using bVec = at::vec::Vectorized<scalar_t>;
172 constexpr int kVecSize = bVec::size();
173 int64_t d = 0;
174 #pragma GCC unroll 4
175 for (; d <= size - kVecSize; d += kVecSize) {
176 bVec out_bvec = bVec::loadu(src + d);
177 out_bvec.store(out + d);
178 }
179 for (; d < size; ++d) {
180 out[d] = src[d];
181 }
182 }
183
184 template <typename scalar_t, int BLOCK_N>
185 inline void copy_stub(scalar_t* __restrict__ out, const float* __restrict__ input) {
186 static_assert(BLOCK_N % 32 == 0);
187 using bVec = at::vec::Vectorized<scalar_t>;
188 using fVec = at::vec::Vectorized<float>;
189
190 constexpr int COLS = BLOCK_N / 16;
191 auto store = [&](auto i) {
192 constexpr int col = i % COLS;
193 // for COLS = 2, 4 use 512bit store
194 if constexpr (col % 2 == 0) {
195 auto [a_fvec0, a_fvec1] = load_float_vec2(input + col * 16);
196 bVec out_bvec = convert_from_float_ext<scalar_t>(a_fvec0, a_fvec1);
197 out_bvec.store(out + col * 16);
198 }
199 };
200 Unroll<COLS>{}(store);
201 }
202
203 // GEMM handles query @ key (indexed) x scale
204 // A : [M, K]
205 // B : [N, K] indexed
206 // C : [M, N]
207 //
208 template <typename scalar_t, typename index_t, int BLOCK_M, int BLOCK_N>
209 struct tinygemm_kernel_nt {
210 static inline void apply(
211 const scalar_t* __restrict__ A,
212 const scalar_t* __restrict__ B,
213 float* __restrict__ C,
214 const index_t* __restrict__ indices,
215 float scale,
216 int64_t lda,
217 int64_t ldb,
218 int64_t ldc,
219 int64_t K,
220 int64_t max_tokens) {
221 for (int64_t m = 0; m < BLOCK_M; ++m) {
222 for (int64_t n = 0; n < BLOCK_N; ++n) {
223 float sum = 0.f;
224 int64_t b_idx = indices[n];
225 TORCH_CHECK(b_idx < max_tokens, "token index out of scope!");
226 for (int64_t k = 0; k < K; ++k) {
227 sum += scale * static_cast<float>(A[m * lda + k]) * static_cast<float>(B[b_idx * ldb + k]);
228 }
229 C[m * ldc + n] = sum;
230 }
231 }
232 }
233 };
234
235 #if defined(CPU_CAPABILITY_AVX512)
236 template <typename index_t, int BLOCK_M, int BLOCK_N>
237 struct tinygemm_kernel_nt<at::BFloat16, index_t, BLOCK_M, BLOCK_N> {
238 static inline void apply(
239 const at::BFloat16* __restrict__ A,
240 const at::BFloat16* __restrict__ B,
241 float* __restrict__ C,
242 const index_t* __restrict__ indices,
243 float scale,
244 int64_t lda,
245 int64_t ldb,
246 int64_t ldc,
247 int64_t K,
248 int64_t max_tokens) {
249 constexpr int ROWS = BLOCK_M;
250 constexpr int COLS = BLOCK_N;
251
252 __m512bh va;
253 __m512bh vb[COLS];
254 __m512 vc[ROWS * COLS];
255 __m512 vscale = _mm512_set1_ps(scale);
256
257 auto loadc = [&](auto i) { vc[i] = _mm512_setzero_ps(); };
258 Unroll<ROWS * COLS>{}(loadc);
259
260 // for main loop
261 auto compute = [&](auto i, int64_t k) {
262 constexpr int row = i / COLS;
263 constexpr int col = i % COLS;
264
265 if constexpr (col == 0) {
266 va = (__m512bh)(_mm512_loadu_si512(A + row * lda + k));
267 }
268 if constexpr (row == 0) {
269 if constexpr (col + 1 < COLS) {
270 int64_t b_idx_prefetch = indices[col + 1];
271 _mm_prefetch(B + b_idx_prefetch * ldb + k, _MM_HINT_T0);
272 }
273 int64_t b_idx = indices[col];
274 TORCH_CHECK(b_idx < max_tokens, "token index out of scope!");
275 vb[col] = (__m512bh)(_mm512_loadu_si512(B + b_idx * ldb + k));
276 }
277 vc[i] = _mm512_dpbf16_ps(vc[i], va, vb[col]);
278 };
279
280 // for remainder
281 auto compute2 = [&](auto i, int64_t k, __mmask32 mask) {
282 constexpr int row = i / COLS;
283 constexpr int col = i % COLS;
284
285 if constexpr (col == 0) {
286 va = (__m512bh)(_mm512_maskz_loadu_epi16(mask, A + row * lda + k));
287 }
288 if constexpr (row == 0) {
289 int64_t b_idx = indices[col];
290 TORCH_CHECK(b_idx < max_tokens, "token index out of scope!");
291 vb[col] = (__m512bh)(_mm512_maskz_loadu_epi16(mask, B + b_idx * ldb + k));
292 }
293 vc[i] = _mm512_dpbf16_ps(vc[i], va, vb[col]);
294 };
295
296 int64_t k = 0;
297 for (; k <= K - 32; k += 32) {
298 Unroll<ROWS * COLS>{}(compute, k);
299 }
300 int64_t count = K - k;
301 if (count > 0) {
302 __mmask32 mask = (1ULL << count) - 1;
303 Unroll<ROWS * COLS>{}(compute2, k, mask);
304 }
305
306 auto storec = [&](auto i) {
307 constexpr int row = i / COLS;
308 constexpr int col = i % COLS;
309 C[row * ldc + col] = _mm512_reduce_add_ps(_mm512_mul_ps(vc[i], vscale));
310 };
311 Unroll<ROWS * COLS>{}(storec);
312 }
313 };
314 #endif
315
316 #if defined(CPU_CAPABILITY_AVX512)
317 template <typename index_t, int BLOCK_M, int BLOCK_N>
318 struct tinygemm_kernel_nt<at::Half, index_t, BLOCK_M, BLOCK_N> {
319 static inline void apply(
320 const at::Half* __restrict__ A,
321 const at::Half* __restrict__ B,
322 float* __restrict__ C,
323 const index_t* __restrict__ indices,
324 float scale,
325 int64_t lda,
326 int64_t ldb,
327 int64_t ldc,
328 int64_t K,
329 int64_t max_tokens) {
330 constexpr int ROWS = BLOCK_M;
331 constexpr int COLS = BLOCK_N;
332
333 __m512 va0, va1;
334 __m512 vb0[COLS], vb1[COLS];
335 __m512 vc[ROWS * COLS];
336 __m512 vscale = _mm512_set1_ps(scale);
337
338 auto loadc = [&](auto i) { vc[i] = _mm512_setzero_ps(); };
339 Unroll<ROWS * COLS>{}(loadc);
340
341 auto compute = [&](auto i, int64_t k) {
342 constexpr int row = i / COLS;
343 constexpr int col = i % COLS;
344
345 if constexpr (col == 0) {
346 __m512i a16 = _mm512_loadu_si512((__m512i const*)(A + row * lda + k));
347 va0 = CVT_FP16_TO_FP32(_mm512_extracti32x8_epi32(a16, 0));
348 va1 = CVT_FP16_TO_FP32(_mm512_extracti32x8_epi32(a16, 1));
349 }
350
351 if constexpr (row == 0) {
352 int64_t b_idx = indices[col];
353 TORCH_CHECK(b_idx < max_tokens, "token index out of scope!");
354 __m512i b16 = _mm512_loadu_si512((__m512i const*)(B + b_idx * ldb + k));
355 vb0[col] = CVT_FP16_TO_FP32(_mm512_extracti32x8_epi32(b16, 0));
356 vb1[col] = CVT_FP16_TO_FP32(_mm512_extracti32x8_epi32(b16, 1));
357 }
358
359 vc[i] = _mm512_fmadd_ps(va0, vb0[col], _mm512_fmadd_ps(va1, vb1[col], vc[i]));
360 };
361
362 auto compute2 = [&](auto i, int64_t k, __mmask32 mask) {
363 constexpr int row = i / COLS;
364 constexpr int col = i % COLS;
365
366 if constexpr (col == 0) {
367 __m512i a16 = _mm512_maskz_loadu_epi16(mask, (const void*)(A + row * lda + k));
368 va0 = CVT_FP16_TO_FP32(_mm512_extracti32x8_epi32(a16, 0));
369 va1 = CVT_FP16_TO_FP32(_mm512_extracti32x8_epi32(a16, 1));
370 }
371
372 if constexpr (row == 0) {
373 int64_t b_idx = indices[col];
374 TORCH_CHECK(b_idx < max_tokens, "token index out of scope!");
375 __m512i b16 = _mm512_maskz_loadu_epi16(mask, (const void*)(B + b_idx * ldb + k));
376 vb0[col] = CVT_FP16_TO_FP32(_mm512_extracti32x8_epi32(b16, 0));
377 vb1[col] = CVT_FP16_TO_FP32(_mm512_extracti32x8_epi32(b16, 1));
378 }
379
380 vc[i] = _mm512_fmadd_ps(va0, vb0[col], _mm512_fmadd_ps(va1, vb1[col], vc[i]));
381 };
382
383 int64_t k = 0;
384 for (; k <= K - 32; k += 32) {
385 Unroll<ROWS * COLS>{}(compute, k);
386 }
387 int64_t count = K - k;
388 if (count > 0) {
389 __mmask32 mask = (1ULL << count) - 1;
390 Unroll<ROWS * COLS>{}(compute2, k, mask);
391 }
392
393 auto storec = [&](auto i) {
394 constexpr int row = i / COLS;
395 constexpr int col = i % COLS;
396 C[row * ldc + col] = _mm512_reduce_add_ps(_mm512_mul_ps(vc[i], vscale));
397 };
398 Unroll<ROWS * COLS>{}(storec);
399 }
400 };
401 #endif
402
403 #define LAUNCH_TINYGEMM_KERNEL_NT(MB_SIZE, NB_SIZE) \
404 tinygemm_kernel_nt<scalar_t, index_t, MB_SIZE, NB_SIZE>::apply( \
405 A + mb_start * lda, B, C + mb_start * ldc + nb_start, indices + nb_start, scale, lda, ldb, ldc, K, max_tokens);
406
407 // this is used when N isn't multiple of 16,
408 // N corresponds to `head_size_v` which should be 16x
409 template <typename scalar_t, typename index_t>
410 inline void tinygemm_kernel_nn_scalar(
411 const float* __restrict__ A,
412 const scalar_t* __restrict__ B,
413 float* __restrict__ C,
414 const index_t* __restrict__ indices,
415 const float* __restrict__ scale,
416 int64_t M,
417 int64_t N,
418 int64_t K,
419 int64_t lda,
420 int64_t ldb,
421 int64_t ldc,
422 int64_t max_tokens) {
423 for (int64_t m = 0; m < M; ++m) {
424 for (int64_t n = 0; n < N; ++n) {
425 C[m * ldc + n] *= scale[m];
426 for (int64_t k = 0; k < K; ++k) {
427 int64_t b_idx = indices[k];
428 TORCH_CHECK(b_idx < max_tokens, "token index out of scope!");
429 C[m * ldc + n] += A[m * lda + k] * static_cast<float>(B[b_idx * ldb + n]);
430 }
431 }
432 }
433 }
434
435 // GEMM handles v' * scale + attn @ value (indexed)
436 // A : [M, K]
437 // B : [K, N] indexed
438 // C :[M, N]
439 //
440 template <typename scalar_t, typename index_t, int BLOCK_M, int BLOCK_N>
441 struct tinygemm_kernel_nn {
442 static inline void apply(
443 const float* __restrict__ A,
444 const scalar_t* __restrict__ B,
445 float* __restrict__ C,
446 const index_t* __restrict__ indices,
447 const float* __restrict__ scale,
448 int64_t lda,
449 int64_t ldb,
450 int64_t ldc,
451 int64_t K,
452 int64_t max_tokens) {
453 tinygemm_kernel_nn_scalar(A, B, C, indices, scale, BLOCK_M, BLOCK_N, K, lda, ldb, ldc, max_tokens);
454 }
455 };
456
457 #if defined(CPU_CAPABILITY_AVX512)
458 template <typename index_t, int BLOCK_M, int BLOCK_N>
459 struct tinygemm_kernel_nn<at::BFloat16, index_t, BLOCK_M, BLOCK_N> {
460 static inline void apply(
461 const float* __restrict__ A,
462 const at::BFloat16* __restrict__ B,
463 float* __restrict__ C,
464 const index_t* __restrict__ indices,
465 const float* __restrict__ scale,
466 int64_t lda,
467 int64_t ldb,
468 int64_t ldc,
469 int64_t K,
470 int64_t max_tokens) {
471 constexpr int ROWS = BLOCK_M;
472 constexpr int COLS = BLOCK_N / 16;
473
474 __m512 va;
475 __m512 vb[COLS];
476 __m512 vc[ROWS * COLS];
477 __m512 vscale;
478
479 auto loadc = [&](auto i) {
480 constexpr int row = i / COLS;
481 constexpr int col = i % COLS;
482 #pragma GCC diagnostic push
483 #pragma GCC diagnostic ignored "-Warray-bounds"
484 if constexpr (col == 0) {
485 vscale = _mm512_set1_ps(scale[row]);
486 }
487 #pragma GCC diagnostic pop
488 vc[i] = _mm512_loadu_ps(C + row * ldc + col * 16);
489 vc[i] = _mm512_mul_ps(vc[i], vscale);
490 };
491 Unroll<ROWS * COLS>{}(loadc);
492
493 auto compute = [&](auto i, int64_t k) {
494 constexpr int row = i / COLS;
495 constexpr int col = i % COLS;
496
497 if constexpr (col == 0) {
498 va = _mm512_set1_ps(A[row * lda + k]);
499 }
500 if constexpr (row == 0) {
501 if (k + 1 < K) {
502 int64_t b_idx_prefetch = indices[k + 1];
503 _mm_prefetch(B + b_idx_prefetch * ldb + col * 16, _MM_HINT_T0);
504 }
505 int64_t b_idx = indices[k];
506 TORCH_CHECK(b_idx < max_tokens, "token index out of scope!");
507
508 // for COLS = 2, 4, 6, 8 use 512 bit load
509 // for COLS = 1, 3, 5, 7 use 256 bit load
510 if constexpr (COLS % 2 == 0) {
511 if constexpr (col % 2 == 0) {
512 __m512i b16 = _mm512_loadu_si512(reinterpret_cast<const __m512i*>(B + b_idx * ldb + col * 16));
513 vb[col + 0] = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(b16, 0));
514 vb[col + 1] = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(b16, 1));
515 }
516 } else {
517 __m256i b16 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(B + b_idx * ldb + col * 16));
518 vb[col] = CVT_BF16_TO_FP32(b16);
519 }
520 }
521 vc[i] = _mm512_fmadd_ps(va, vb[col], vc[i]);
522 };
523
524 for (int64_t k = 0; k < K; ++k) {
525 Unroll<ROWS * COLS>{}(compute, k);
526 }
527
528 auto storec = [&](auto i) {
529 constexpr int row = i / COLS;
530 constexpr int col = i % COLS;
531 _mm512_storeu_ps(C + row * ldc + col * 16, vc[i]);
532 };
533 Unroll<ROWS * COLS>{}(storec);
534 }
535 };
536 #endif
537
538 #if defined(CPU_CAPABILITY_AVX512)
539 template <typename index_t, int BLOCK_M, int BLOCK_N>
540 struct tinygemm_kernel_nn<at::Half, index_t, BLOCK_M, BLOCK_N> {
541 static inline void apply(
542 const float* __restrict__ A,
543 const at::Half* __restrict__ B,
544 float* __restrict__ C,
545 const index_t* __restrict__ indices,
546 const float* __restrict__ scale,
547 int64_t lda,
548 int64_t ldb,
549 int64_t ldc,
550 int64_t K,
551 int64_t max_tokens) {
552 constexpr int ROWS = BLOCK_M;
553 constexpr int COLS = BLOCK_N / 16;
554
555 __m512 va;
556 __m512 vb[COLS];
557 __m512 vc[ROWS * COLS];
558 __m512 vscale;
559
560 auto loadc = [&](auto i) {
561 constexpr int row = i / COLS;
562 constexpr int col = i % COLS;
563 #pragma GCC diagnostic push
564 #pragma GCC diagnostic ignored "-Warray-bounds"
565 if constexpr (col == 0) {
566 vscale = _mm512_set1_ps(scale[row]);
567 }
568 #pragma GCC diagnostic pop
569 vc[i] = _mm512_loadu_ps(C + row * ldc + col * 16);
570 vc[i] = _mm512_mul_ps(vc[i], vscale);
571 };
572 Unroll<ROWS * COLS>{}(loadc);
573
574 auto compute = [&](auto i, int64_t k) {
575 constexpr int row = i / COLS;
576 constexpr int col = i % COLS;
577
578 if constexpr (col == 0) {
579 va = _mm512_set1_ps(A[row * lda + k]);
580 }
581 if constexpr (row == 0) {
582 if (k + 1 < K) {
583 int64_t b_idx_prefetch = indices[k + 1];
584 _mm_prefetch(B + b_idx_prefetch * ldb + col * 16, _MM_HINT_T0);
585 }
586 int64_t b_idx = indices[k];
587 TORCH_CHECK(b_idx < max_tokens, "token index out of scope!");
588
589 // for COLS = 2, 4, 6, 8 use 512 bit load
590 // for COLS = 1, 3, 5, 7 use 256 bit load
591 if constexpr (COLS % 2 == 0) {
592 if constexpr (col % 2 == 0) {
593 __m512i b16 = _mm512_loadu_si512(reinterpret_cast<const __m512i*>(B + b_idx * ldb + col * 16));
594 vb[col + 0] = CVT_FP16_TO_FP32(_mm512_extracti32x8_epi32(b16, 0));
595 vb[col + 1] = CVT_FP16_TO_FP32(_mm512_extracti32x8_epi32(b16, 1));
596 }
597 } else {
598 __m256i b16 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(B + b_idx * ldb + col * 16));
599 vb[col] = CVT_FP16_TO_FP32(b16);
600 }
601 }
602 vc[i] = _mm512_fmadd_ps(va, vb[col], vc[i]);
603 };
604
605 for (int64_t k = 0; k < K; ++k) {
606 Unroll<ROWS * COLS>{}(compute, k);
607 }
608
609 auto storec = [&](auto i) {
610 constexpr int row = i / COLS;
611 constexpr int col = i % COLS;
612 _mm512_storeu_ps(C + row * ldc + col * 16, vc[i]);
613 };
614 Unroll<ROWS * COLS>{}(storec);
615 }
616 };
617 #endif
618
619 #define LAUNCH_TINYGEMM_KERNEL_NN(MB_SIZE, NB_SIZE) \
620 tinygemm_kernel_nn<scalar_t, index_t, MB_SIZE, NB_SIZE>::apply( \
621 A + mb_start * lda, \
622 B + nb_start, \
623 C + mb_start * ldc + nb_start, \
624 indices, \
625 scale + mb_start, \
626 lda, \
627 ldb, \
628 ldc, \
629 K, \
630 max_tokens);
631
632 template <typename scalar_t, typename index_t>
633 void index_gemm_kernel_nt(
634 const scalar_t* __restrict__ A,
635 const scalar_t* __restrict__ B,
636 float* __restrict__ C,
637 const index_t* __restrict__ indices,
638 float scale,
639 int64_t M,
640 int64_t N,
641 int64_t K,
642 int64_t lda,
643 int64_t ldb,
644 int64_t ldc,
645 int64_t max_tokens) {
646 // pattern: 1-8-8
647 if (M == 1) {
648 constexpr int64_t BLOCK_N = 8;
649 const int64_t NB = div_up(N, BLOCK_N);
650 int64_t mb_start = 0, lda = 1, ldc = 1;
651
652 for (int64_t nb = 0; nb < NB; ++nb) {
653 int64_t nb_start = nb * BLOCK_N;
654 int64_t nb_size = std::min(BLOCK_N, N - nb_start);
655
656 switch (nb_size) {
657 case 1:
658 LAUNCH_TINYGEMM_KERNEL_NT(1, 1);
659 break;
660 case 2:
661 LAUNCH_TINYGEMM_KERNEL_NT(1, 2);
662 break;
663 case 3:
664 LAUNCH_TINYGEMM_KERNEL_NT(1, 3);
665 break;
666 case 4:
667 LAUNCH_TINYGEMM_KERNEL_NT(1, 4);
668 break;
669 case 5:
670 LAUNCH_TINYGEMM_KERNEL_NT(1, 5);
671 break;
672 case 6:
673 LAUNCH_TINYGEMM_KERNEL_NT(1, 6);
674 break;
675 case 7:
676 LAUNCH_TINYGEMM_KERNEL_NT(1, 7);
677 break;
678 case 8:
679 LAUNCH_TINYGEMM_KERNEL_NT(1, 8);
680 break;
681 default:
682 TORCH_CHECK(false, "Unexpected block size, 1x", "nb_size");
683 }
684 }
685 return;
686 }
687
688 // default pattern: 1-6-24
689 // FP16 pattern: 2-8-16
690 constexpr int64_t BLOCK_M = 4;
691 constexpr int64_t BLOCK_N = std::is_same_v<scalar_t, at::Half> ? 4 : 6;
692 const int64_t MB = div_up(M, BLOCK_M);
693 const int64_t NB = div_up(N, BLOCK_N);
694
695 for (int64_t mb = 0; mb < MB; ++mb) {
696 int64_t mb_start = mb * BLOCK_M;
697 int64_t mb_size = std::min(BLOCK_M, M - mb_start);
698 for (int64_t nb = 0; nb < NB; ++nb) {
699 int64_t nb_start = nb * BLOCK_N;
700 int64_t nb_size = std::min(BLOCK_N, N - nb_start);
701
702 switch (mb_size << 4 | nb_size) {
703 // mb_size = 1
704 case 0x11:
705 LAUNCH_TINYGEMM_KERNEL_NT(1, 1);
706 break;
707 case 0x12:
708 LAUNCH_TINYGEMM_KERNEL_NT(1, 2);
709 break;
710 case 0x13:
711 LAUNCH_TINYGEMM_KERNEL_NT(1, 3);
712 break;
713 case 0x14:
714 LAUNCH_TINYGEMM_KERNEL_NT(1, 4);
715 break;
716 case 0x15:
717 LAUNCH_TINYGEMM_KERNEL_NT(1, 5);
718 break;
719 case 0x16:
720 LAUNCH_TINYGEMM_KERNEL_NT(1, 6);
721 break;
722 // mb_size = 2
723 case 0x21:
724 LAUNCH_TINYGEMM_KERNEL_NT(2, 1);
725 break;
726 case 0x22:
727 LAUNCH_TINYGEMM_KERNEL_NT(2, 2);
728 break;
729 case 0x23:
730 LAUNCH_TINYGEMM_KERNEL_NT(2, 3);
731 break;
732 case 0x24:
733 LAUNCH_TINYGEMM_KERNEL_NT(2, 4);
734 break;
735 case 0x25:
736 LAUNCH_TINYGEMM_KERNEL_NT(2, 5);
737 break;
738 case 0x26:
739 LAUNCH_TINYGEMM_KERNEL_NT(2, 6);
740 break;
741 // mb_size = 3
742 case 0x31:
743 LAUNCH_TINYGEMM_KERNEL_NT(3, 1);
744 break;
745 case 0x32:
746 LAUNCH_TINYGEMM_KERNEL_NT(3, 2);
747 break;
748 case 0x33:
749 LAUNCH_TINYGEMM_KERNEL_NT(3, 3);
750 break;
751 case 0x34:
752 LAUNCH_TINYGEMM_KERNEL_NT(3, 4);
753 break;
754 case 0x35:
755 LAUNCH_TINYGEMM_KERNEL_NT(3, 5);
756 break;
757 case 0x36:
758 LAUNCH_TINYGEMM_KERNEL_NT(3, 6);
759 break;
760 // mb_size = 4
761 case 0x41:
762 LAUNCH_TINYGEMM_KERNEL_NT(4, 1);
763 break;
764 case 0x42:
765 LAUNCH_TINYGEMM_KERNEL_NT(4, 2);
766 break;
767 case 0x43:
768 LAUNCH_TINYGEMM_KERNEL_NT(4, 3);
769 break;
770 case 0x44:
771 LAUNCH_TINYGEMM_KERNEL_NT(4, 4);
772 break;
773 case 0x45:
774 LAUNCH_TINYGEMM_KERNEL_NT(4, 5);
775 break;
776 case 0x46:
777 LAUNCH_TINYGEMM_KERNEL_NT(4, 6);
778 break;
779 default:
780 TORCH_CHECK(false, "Unexpected block size, ", mb_size, "x", "nb_size");
781 }
782 }
783 }
784 }
785
786 template <typename scalar_t, typename index_t>
787 void index_gemm_kernel_nn(
788 const float* __restrict__ A,
789 const scalar_t* __restrict__ B,
790 float* __restrict__ C,
791 const index_t* __restrict__ indices,
792 float* __restrict__ scale,
793 int64_t M,
794 int64_t N,
795 int64_t K,
796 int64_t lda,
797 int64_t ldb,
798 int64_t ldc,
799 int64_t max_tokens) {
800 constexpr int kVecSize = 16;
801 if ((N & (kVecSize - 1)) != 0) {
802 tinygemm_kernel_nn_scalar(A, B, C, indices, scale, M, N, K, lda, ldb, ldc, max_tokens);
803 return;
804 }
805
806 // pattern: 1-8-8
807 if (M == 1) {
808 constexpr int64_t BLOCK_N = 8 * kVecSize;
809 const int64_t NB = div_up(N, BLOCK_N);
810 int64_t mb_start = 0, lda = 1, ldc = 1;
811
812 for (int64_t nb = 0; nb < NB; ++nb) {
813 int64_t nb_start = nb * BLOCK_N;
814 int64_t nb_size = std::min(BLOCK_N, N - nb_start);
815
816 switch (nb_size >> 4) {
817 case 1:
818 LAUNCH_TINYGEMM_KERNEL_NN(1, 16);
819 break;
820 case 2:
821 LAUNCH_TINYGEMM_KERNEL_NN(1, 32);
822 break;
823 case 3:
824 LAUNCH_TINYGEMM_KERNEL_NN(1, 48);
825 break;
826 case 4:
827 LAUNCH_TINYGEMM_KERNEL_NN(1, 64);
828 break;
829 case 5:
830 LAUNCH_TINYGEMM_KERNEL_NN(1, 80);
831 break;
832 case 6:
833 LAUNCH_TINYGEMM_KERNEL_NN(1, 96);
834 break;
835 case 7:
836 LAUNCH_TINYGEMM_KERNEL_NN(1, 112);
837 break;
838 case 8:
839 LAUNCH_TINYGEMM_KERNEL_NN(1, 128);
840 break;
841 default:
842 TORCH_CHECK(false, "Unexpected block size, 1x", "nb_size");
843 }
844 }
845 return;
846 }
847
848 constexpr int64_t BLOCK_M = 4;
849 constexpr int64_t BLOCK_N = 6 * kVecSize;
850 const int64_t MB = div_up(M, BLOCK_M);
851 const int64_t NB = div_up(N, BLOCK_N);
852
853 for (int64_t mb = 0; mb < MB; ++mb) {
854 int64_t mb_start = mb * BLOCK_M;
855 int64_t mb_size = std::min(BLOCK_M, M - mb_start);
856 for (int64_t nb = 0; nb < NB; ++nb) {
857 int64_t nb_start = nb * BLOCK_N;
858 int64_t nb_size = std::min(BLOCK_N, N - nb_start);
859
860 switch (mb_size << 4 | nb_size >> 4) {
861 // mb_size = 1
862 case 0x11:
863 LAUNCH_TINYGEMM_KERNEL_NN(1, 16);
864 break;
865 case 0x12:
866 LAUNCH_TINYGEMM_KERNEL_NN(1, 32);
867 break;
868 case 0x13:
869 LAUNCH_TINYGEMM_KERNEL_NN(1, 48);
870 break;
871 case 0x14:
872 LAUNCH_TINYGEMM_KERNEL_NN(1, 64);
873 break;
874 case 0x15:
875 LAUNCH_TINYGEMM_KERNEL_NN(1, 80);
876 break;
877 case 0x16:
878 LAUNCH_TINYGEMM_KERNEL_NN(1, 96);
879 break;
880 // mb_size = 2
881 case 0x21:
882 LAUNCH_TINYGEMM_KERNEL_NN(2, 16);
883 break;
884 case 0x22:
885 LAUNCH_TINYGEMM_KERNEL_NN(2, 32);
886 break;
887 case 0x23:
888 LAUNCH_TINYGEMM_KERNEL_NN(2, 48);
889 break;
890 case 0x24:
891 LAUNCH_TINYGEMM_KERNEL_NN(2, 64);
892 break;
893 case 0x25:
894 LAUNCH_TINYGEMM_KERNEL_NN(2, 80);
895 break;
896 case 0x26:
897 LAUNCH_TINYGEMM_KERNEL_NN(2, 96);
898 break;
899 // mb_size = 3
900 case 0x31:
901 LAUNCH_TINYGEMM_KERNEL_NN(3, 16);
902 break;
903 case 0x32:
904 LAUNCH_TINYGEMM_KERNEL_NN(3, 32);
905 break;
906 case 0x33:
907 LAUNCH_TINYGEMM_KERNEL_NN(3, 48);
908 break;
909 case 0x34:
910 LAUNCH_TINYGEMM_KERNEL_NN(3, 64);
911 break;
912 case 0x35:
913 LAUNCH_TINYGEMM_KERNEL_NN(3, 80);
914 break;
915 case 0x36:
916 LAUNCH_TINYGEMM_KERNEL_NN(3, 96);
917 break;
918 // mb_size = 4
919 case 0x41:
920 LAUNCH_TINYGEMM_KERNEL_NN(4, 16);
921 break;
922 case 0x42:
923 LAUNCH_TINYGEMM_KERNEL_NN(4, 32);
924 break;
925 case 0x43:
926 LAUNCH_TINYGEMM_KERNEL_NN(4, 48);
927 break;
928 case 0x44:
929 LAUNCH_TINYGEMM_KERNEL_NN(4, 64);
930 break;
931 case 0x45:
932 LAUNCH_TINYGEMM_KERNEL_NN(4, 80);
933 break;
934 case 0x46:
935 LAUNCH_TINYGEMM_KERNEL_NN(4, 96);
936 break;
937 default:
938 TORCH_CHECK(false, "Unexpected block size, ", mb_size, "x", "nb_size");
939 }
940 }
941 }
942 }
943
944 template <typename scalar_t>
945 void decode_set_kv_buffer(
946 scalar_t* __restrict__ k_buffer,
947 scalar_t* __restrict__ v_buffer,
948 const scalar_t* __restrict__ key,
949 const scalar_t* __restrict__ value,
950 const int64_t* __restrict__ loc,
951 int64_t batches,
952 int64_t num_heads_kv,
953 int64_t head_size,
954 int64_t head_size_v,
955 int64_t k_strideN,
956 int64_t k_strideH,
957 int64_t v_strideN,
958 int64_t v_strideH,
959 int64_t nk_strideN,
960 int64_t nk_strideH,
961 int64_t nv_strideN,
962 int64_t nv_strideH,
963 bool is_mla) {
964 at::parallel_for(0, batches * num_heads_kv, 0, [&](int64_t begin, int64_t end) {
965 int64_t bs{0}, head_kv_id{0};
966 data_index_init(begin, bs, batches, head_kv_id, num_heads_kv);
967
968 for (int64_t i = begin; i < end; i++) {
969 int64_t loc_val = loc[bs];
970 scalar_t* k_buffer_ptr = k_buffer + loc_val * k_strideN + head_kv_id * k_strideH;
971 const scalar_t* new_key_ptr = key + bs * nk_strideN + head_kv_id * nk_strideH;
972 copy_stub<scalar_t>(k_buffer_ptr, new_key_ptr, head_size);
973 if (!is_mla) {
974 scalar_t* v_buffer_ptr = v_buffer + loc_val * v_strideN + head_kv_id * v_strideH;
975 const scalar_t* new_value_ptr = value + bs * nv_strideN + head_kv_id * nv_strideH;
976 copy_stub<scalar_t>(v_buffer_ptr, new_value_ptr, head_size_v);
977 }
978
979 // move to the next index
980 data_index_step(bs, batches, head_kv_id, num_heads_kv);
981 }
982 });
983 }
984
985 template <typename scalar_t>
986 void decode_accumulate_kv_splits(
987 scalar_t* __restrict__ output,
988 float* __restrict__ attn_logits,
989 const scalar_t* __restrict__ sinks_ptr,
990 int64_t batches,
991 int64_t num_heads,
992 int64_t head_size_v,
993 int64_t num_kv_splits,
994 int64_t l_stride1,
995 int64_t l_stride2,
996 bool has_sink) {
997 using Vec = at::vec::Vectorized<float>;
998
999 // parallel on [batches, num_heads]
1000 at::parallel_for(0, batches * num_heads, 0, [&](int64_t begin, int64_t end) {
1001 int64_t bi{0}, ni{0};
1002 data_index_init(begin, bi, batches, ni, num_heads);
1003 // NB: here we use logits[b][h][0] as acc, since
1004 // for the first kv split (kv_id == 0):
1005 // m_delta = std::exp(-inf) = 0
1006 // e_logic = std::exp(0) = 1
1007 // acc = acc * m_delta + tv * e_logic = tv
1008 for (int64_t i = begin; i < end; ++i) {
1009 float* __restrict__ acc = attn_logits + i * l_stride1;
1010
1011 float s_prime = 0.f;
1012 float m_prime = -std::numeric_limits<scalar_t>::infinity();
1013
1014 // update acc with from each kv_split
1015 for (int64_t kv_id = 0; kv_id < num_kv_splits; ++kv_id) {
1016 float* __restrict__ tv = acc + kv_id * l_stride2;
1017 const float tlogic = (acc + kv_id * l_stride2)[head_size_v];
1018
1019 float m_i = std::max(tlogic, m_prime);
1020 float m_delta = std::exp(m_prime - m_i);
1021 float e_logic = std::exp(tlogic - m_i);
1022 if (kv_id != 0) {
1023 at::vec::map2<float>(
1024 [m_delta, e_logic](Vec x, Vec y) { return x * Vec(m_delta) + y * Vec(e_logic); },
1025 acc,
1026 acc,
1027 tv,
1028 head_size_v);
1029 }
1030
1031 s_prime = s_prime * m_delta + e_logic;
1032 m_prime = m_i;
1033 }
1034 if (has_sink) {
1035 s_prime += std::exp(sinks_ptr[ni] - m_prime);
1036 }
1037 copy_stub<scalar_t>(output + i * head_size_v, acc, 1 / s_prime, head_size_v);
1038 // move to the next index
1039 data_index_step(bi, batches, ni, num_heads);
1040 }
1041 });
1042 }
1043
1044 template <typename scalar_t, typename index_t, int64_t BLOCK_N>
1045 void decode_attention_kernel_impl(
1046 scalar_t* __restrict__ output,
1047 float* __restrict__ attn_logits,
1048 const scalar_t* __restrict__ query,
1049 const scalar_t* __restrict__ k_buffer,
1050 const scalar_t* __restrict__ v_buffer,
1051 const index_t* __restrict__ req_to_token,
1052 const int64_t* __restrict__ req_pool_indices,
1053 const int64_t* __restrict__ seq_lens,
1054 const int64_t* __restrict__ encoder_lens,
1055 const scalar_t* __restrict__ sinks,
1056 int64_t batches,
1057 int64_t num_heads,
1058 int64_t head_size,
1059 int64_t head_size_v,
1060 int64_t num_kv_splits,
1061 int64_t q_strideM,
1062 int64_t q_strideH,
1063 int64_t k_strideN,
1064 int64_t k_strideH,
1065 int64_t v_strideN,
1066 int64_t v_strideH,
1067 float sm_scale,
1068 float logit_cap,
1069 int64_t max_num_reqs,
1070 int64_t max_context_len,
1071 int64_t max_total_num_tokens,
1072 int64_t sliding_window_size,
1073 bool is_cross_attn,
1074 bool has_encoder_lens,
1075 bool has_sink) {
1076 using Vec = at::vec::Vectorized<float>;
1077
1078 // strides
1079 const int64_t l_stride1 = num_kv_splits * (head_size_v + 1);
1080 const int64_t l_stride2 = head_size_v + 1;
1081
1082 const bool has_logit_cap = logit_cap > 0;
1083 float rlogit_cap = has_logit_cap ? 1 / logit_cap : 0.f;
1084
1085 // parallel on [batches, num_heads, num_kv_splits]
1086 at::parallel_for(0, batches * num_heads * num_kv_splits, 0, [&](int64_t begin, int64_t end) {
1087 int64_t bs{0}, head_id{0}, kv_id{0};
1088 data_index_init(begin, bs, batches, head_id, num_heads, kv_id, num_kv_splits);
1089
1090 // s_prime and s_delta
1091 alignas(64) float s_i[BLOCK_N];
1092 float* __restrict__ s_delta = s_i;
1093
1094 for (int64_t i = begin; i < end; ++i) {
1095 // get query
1096 const scalar_t* __restrict__ q_ptr = query + bs * q_strideM + head_id * q_strideH;
1097
1098 // get key/value
1099 int64_t seq_len_kv = is_cross_attn ? encoder_lens[bs] : seq_lens[bs];
1100 int64_t req_pool_id = req_pool_indices[bs];
1101 int64_t kv_offset = (has_encoder_lens && (!is_cross_attn)) ? encoder_lens[bs] : 0;
1102 if (sliding_window_size > 0 && seq_len_kv > sliding_window_size) {
1103 kv_offset = seq_len_kv - sliding_window_size;
1104 seq_len_kv = sliding_window_size;
1105 }
1106 TORCH_CHECK(seq_len_kv <= max_context_len, "seq_len_kv out of scope!");
1107 TORCH_CHECK(req_pool_id < max_num_reqs, "req_pool_id out of scope!");
1108
1109 const int64_t SPLIT_SIZE = div_up(seq_len_kv, num_kv_splits);
1110 const int64_t kv_start = kv_id * SPLIT_SIZE;
1111 const int64_t kv_end = std::min(kv_start + SPLIT_SIZE, seq_len_kv);
1112
1113 float m_prime = -std::numeric_limits<float>::infinity();
1114 float s_prime = 0.f;
1115
1116 // get v_prime, and init to zero
1117 float* __restrict__ v_prime = attn_logits + i * (head_size_v + 1);
1118 fill_stub(v_prime, 0.f, head_size_v);
1119
1120 // loop over K and V sequence with BLOCK_N
1121 for (int64_t n = kv_start; n < kv_end; n += BLOCK_N) {
1122 int64_t n_size = std::min(BLOCK_N, kv_end - n);
1123
1124 // calculate s_i <- scale * Q @ K
1125 index_gemm_kernel_nt<scalar_t, index_t>(
1126 /* A */ q_ptr,
1127 /* B */ k_buffer + head_id * k_strideH,
1128 /* C */ s_i,
1129 /* ind */ req_to_token + req_pool_id * max_context_len + n + kv_offset,
1130 /* scl */ sm_scale,
1131 /* M */ 1,
1132 /* N */ n_size,
1133 /* K */ head_size,
1134 /* lda */ 1,
1135 /* ldb */ k_strideN,
1136 /* ldc */ 1,
1137 /* mtt */ max_total_num_tokens);
1138
1139 // TODO: `tanh` from torch uses sleef u10, going to be slow
1140 if (has_logit_cap) {
1141 at::vec::map<float>(
1142 [logit_cap, rlogit_cap](Vec x) { return Vec(logit_cap) * (x * Vec(rlogit_cap)).tanh(); },
1143 s_i,
1144 s_i,
1145 n_size);
1146 }
1147
1148 // m_i: max value per row
1149 float m_i = at::vec::reduce_all<float>([](Vec& x, Vec& y) { return at::vec::maximum(x, y); }, s_i, n_size);
1150 m_i = std::max(m_i, m_prime);
1151
1152 // m_delta <- exp(m' - m_i)
1153 float m_delta = std::exp(m_prime - m_i);
1154
1155 // s_delta <- exp(s_i - m_i)
1156 at::vec::map<float>([m_i](Vec x) { return (x - Vec(m_i)).exp_u20(); }, s_delta, s_i, n_size);
1157
1158 // s' <- s' * m_delta + sum(s_delta)
1159 s_prime *= m_delta;
1160 s_prime += at::vec::reduce_all<float>([](Vec& x, Vec& y) { return x + y; }, s_delta, n_size);
1161
1162 m_prime = m_i;
1163
1164 // calculate V' <- s_delta @ V + V' * m_delta
1165 index_gemm_kernel_nn<scalar_t, index_t>(
1166 /* A */ s_delta,
1167 /* B */ v_buffer + head_id * v_strideH,
1168 /* C */ v_prime,
1169 /* ind */ req_to_token + req_pool_id * max_context_len + n + kv_offset,
1170 /* scl */ &m_delta,
1171 /* M */ 1,
1172 /* N */ head_size_v,
1173 /* K */ n_size,
1174 /* lda */ 1,
1175 /* ldb */ v_strideN,
1176 /* ldc */ 1,
1177 /* mtt */ max_total_num_tokens);
1178 } // loop with KV blocks
1179
1180 // only update v' when kv_split_size > 0
1181 if (kv_end > kv_start) {
1182 float s = 1 / s_prime;
1183 at::vec::map<float>([s](Vec out) { return out * Vec(s); }, v_prime, v_prime, head_size_v);
1184
1185 v_prime[head_size_v] = m_prime + std::log(s_prime);
1186 } else {
1187 v_prime[head_size_v] = -std::numeric_limits<float>::infinity();
1188 }
1189
1190 // move to the next index
1191 data_index_step(bs, batches, head_id, num_heads, kv_id, num_kv_splits);
1192 }
1193 });
1194
1195 decode_accumulate_kv_splits(
1196 output, attn_logits, sinks, batches, num_heads, head_size_v, num_kv_splits, l_stride1, l_stride2, has_sink);
1197 } // MHA
1198
1199 template <typename scalar_t, typename index_t, int64_t BLOCK_N>
1200 void decode_attention_mla_kernel_impl(
1201 scalar_t* __restrict__ output,
1202 float* __restrict__ attn_logits,
1203 const scalar_t* __restrict__ query,
1204 const scalar_t* __restrict__ k_buffer,
1205 const scalar_t* __restrict__ v_buffer,
1206 const index_t* __restrict__ req_to_token,
1207 const int64_t* __restrict__ req_pool_indices,
1208 const int64_t* __restrict__ seq_lens,
1209 scalar_t* __restrict__ buffer,
1210 const scalar_t* __restrict__ sinks,
1211 int64_t batches,
1212 int64_t num_heads,
1213 int64_t head_size,
1214 int64_t head_size_v,
1215 int64_t num_kv_splits,
1216 int64_t q_strideM,
1217 int64_t q_strideH,
1218 int64_t k_strideN,
1219 int64_t k_strideH,
1220 int64_t v_strideN,
1221 int64_t v_strideH,
1222 float sm_scale,
1223 float logit_cap,
1224 int64_t max_num_reqs,
1225 int64_t max_context_len,
1226 int64_t max_total_num_tokens,
1227 int64_t buffer_size_per_thread,
1228 bool has_sink) {
1229 using Vec = at::vec::Vectorized<float>;
1230
1231 // block length for heads
1232 const int64_t BLOCK_H = batches == 1 ? 6 : (batches > 16 ? 22 : 11);
1233
1234 // strides
1235 const int64_t l_stride0 = num_heads * num_kv_splits * (head_size_v + 1);
1236 const int64_t l_stride1 = num_kv_splits * (head_size_v + 1);
1237 const int64_t l_stride2 = head_size_v + 1;
1238
1239 TORCH_CHECK(logit_cap == 0.f, "decode MLA: expect no logit_cap.");
1240
1241 // partition the heads into blocks for parallel
1242 const int64_t num_blocks = div_up(num_heads, BLOCK_H);
1243
1244 // parallel on [batches, num_blocks, num_kv_splits]
1245 at::parallel_for(0, batches * num_blocks * num_kv_splits, 0, [&](int64_t begin, int64_t end) {
1246 int64_t bs{0}, block_id{0}, kv_id{0};
1247 data_index_init(begin, bs, batches, block_id, num_blocks, kv_id, num_kv_splits);
1248
1249 int tid = at::get_thread_num();
1250 scalar_t* __restrict__ Btmp0 = buffer + tid * buffer_size_per_thread;
1251 scalar_t* __restrict__ Btmp1 = Btmp0 + BLOCK_N * head_size;
1252
1253 // init Btmp1 just once for each thread to prevent NaN
1254 // Btmp0 is not needed as it computes full K every single time
1255 fill_stub(Btmp1, 0.f, BLOCK_N * head_size_v);
1256
1257 alignas(64) float s_i[BLOCK_H * BLOCK_N];
1258 float* __restrict__ s_delta = s_i;
1259 alignas(64) scalar_t s_delta2[BLOCK_H * BLOCK_N];
1260
1261 alignas(64) float s_prime[BLOCK_H];
1262 alignas(64) float m_prime[BLOCK_H];
1263 alignas(64) float m_delta[BLOCK_H];
1264
1265 for (int64_t i = begin; i < end; ++i) {
1266 const int64_t h_start = block_id * BLOCK_H;
1267 const int64_t h_end = std::min(block_id * BLOCK_H + BLOCK_H, num_heads);
1268 const int64_t h_size = h_end - h_start;
1269
1270 // get query
1271 const scalar_t* __restrict__ q_ptr = query + bs * q_strideM + h_start * q_strideH;
1272
1273 int64_t seq_len_kv = seq_lens[bs];
1274 int64_t req_pool_id = req_pool_indices[bs];
1275 TORCH_CHECK(seq_len_kv <= max_context_len, "seq_len_kv out of scope!");
1276 TORCH_CHECK(req_pool_id < max_num_reqs, "req_pool_id out of scope!");
1277
1278 const int64_t SPLIT_SIZE = div_up(seq_len_kv, num_kv_splits);
1279 const int64_t kv_start = kv_id * SPLIT_SIZE;
1280 const int64_t kv_end = std::min(kv_start + SPLIT_SIZE, seq_len_kv);
1281
1282 fill_stub(s_prime, 0.f, BLOCK_H);
1283 fill_stub(m_prime, -std::numeric_limits<float>::infinity(), BLOCK_H);
1284
1285 // get v_prime, and init to zero
1286 float* __restrict__ v_prime = attn_logits + bs * l_stride0 + h_start * l_stride1 + kv_id * l_stride2;
1287 for (int64_t h = 0; h < h_size; ++h) {
1288 fill_stub(v_prime + h * l_stride1, 0.f, head_size_v);
1289 }
1290
1291 // loop over K and V sequence with BLOCK_N
1292 for (int64_t n = kv_start; n < kv_end; n += BLOCK_N) {
1293 int64_t n_size = std::min(BLOCK_N, kv_end - n);
1294 const int64_t padded_n_size = div_up(int(n_size), TILE_K) * TILE_K;
1295
1296 // get key and pack
1297 pack_vnni<scalar_t, index_t>(
1298 /* dst0 */ Btmp0,
1299 /* dst1 */ Btmp1,
1300 /* src */ k_buffer + /* head_kv_id */ 0 * k_strideH,
1301 /* ind */ req_to_token + req_pool_id * max_context_len + n,
1302 /* N */ n_size,
1303 /* K */ head_size,
1304 /* Kv */ head_size_v,
1305 /* ld_src */ k_strideN,
1306 /* ld_dst0 */ BLOCK_N,
1307 /* ld_dst1 */ head_size_v);
1308
1309 // calculate s_i <- Q @ K
1310 at::native::cpublas::brgemm(
1311 /* M */ h_size,
1312 /* N */ n_size,
1313 /* K */ head_size,
1314 /* lda */ q_strideH,
1315 /* ldb */ BLOCK_N,
1316 /* ldc */ BLOCK_N,
1317 /* add_C */ false,
1318 /* A */ q_ptr,
1319 /* B */ Btmp0,
1320 /* C */ s_i);
1321
1322 const Vec scale_vec = Vec(sm_scale);
1323 for (int64_t h = 0; h < h_size; ++h) {
1324 // s_i <- s_i * scale
1325 at::vec::map<float>(
1326 [scale_vec](Vec x) { return x * scale_vec; }, s_i + h * BLOCK_N, s_i + h * BLOCK_N, n_size);
1327
1328 // m_i: max value per row
1329 float m_i = at::vec::reduce_all<float>(
1330 [](Vec& x, Vec& y) { return at::vec::maximum(x, y); }, s_i + h * BLOCK_N, n_size);
1331 m_i = std::max(m_i, m_prime[h]);
1332
1333 // m_delta <- exp(m' - m_i)
1334 m_delta[h] = std::exp(m_prime[h] - m_i);
1335
1336 // s_delta <- exp(s_i - m_i)
1337 at::vec::map<float>(
1338 [m_i](Vec x) { return (x - Vec(m_i)).exp_u20(); }, s_delta + h * BLOCK_N, s_i + h * BLOCK_N, n_size);
1339
1340 // s' <- s' * m_delta + sum(s_delta)
1341 s_prime[h] *= m_delta[h];
1342 s_prime[h] += at::vec::reduce_all<float>([](Vec& x, Vec& y) { return x + y; }, s_delta + h * BLOCK_N, n_size);
1343
1344 m_prime[h] = m_i;
1345
1346 // v' <- v' * m_delta
1347 float scale_m = m_delta[h];
1348 at::vec::map<float>(
1349 [scale_m](Vec x) { return x * Vec(scale_m); },
1350 v_prime + h * l_stride1,
1351 v_prime + h * l_stride1,
1352 head_size_v);
1353
1354 // pad s_delta with 0 first and then convert to scalar_t
1355 fill_stub(s_delta + h * BLOCK_N + n_size, 0.f, padded_n_size - n_size);
1356 copy_stub<scalar_t, BLOCK_N>(s_delta2 + h * BLOCK_N, s_delta + h * BLOCK_N);
1357 }
1358
1359 // calculate V' <- s_delta @ V + V'
1360 at::native::cpublas::brgemm(
1361 /* M */ h_size,
1362 /* N */ head_size_v,
1363 /* K */ padded_n_size, // n_size
1364 /* lda */ BLOCK_N,
1365 /* ldb */ head_size_v,
1366 /* ldc */ l_stride1,
1367 /* add_C */ true,
1368 /* A */ s_delta2,
1369 /* B */ Btmp1,
1370 /* C */ v_prime);
1371 } // loop with KV blocks
1372
1373 // only update v' when kv_split_size > 0
1374 if (kv_end > kv_start) {
1375 for (int64_t h = 0; h < h_size; ++h) {
1376 float s = 1 / s_prime[h];
1377 at::vec::map<float>(
1378 [s](Vec out) { return out * Vec(s); }, v_prime + h * l_stride1, v_prime + h * l_stride1, head_size_v);
1379 (v_prime + h * l_stride1)[head_size_v] = m_prime[h] + std::log(s_prime[h]);
1380 }
1381 } else {
1382 for (int64_t h = 0; h < h_size; ++h) {
1383 (v_prime + h * l_stride1)[head_size_v] = -std::numeric_limits<float>::infinity();
1384 }
1385 }
1386
1387 // move to the next index
1388 data_index_step(bs, batches, block_id, num_blocks, kv_id, num_kv_splits);
1389 }
1390 at::native::cpublas::brgemm_release();
1391 });
1392
1393 decode_accumulate_kv_splits(
1394 output, attn_logits, sinks, batches, num_heads, head_size_v, num_kv_splits, l_stride1, l_stride2, has_sink);
1395 } // MLA
1396
1397 template <typename scalar_t, typename index_t, int64_t BLOCK_N>
1398 void decode_attention_grouped_kernel_impl(
1399 scalar_t* __restrict__ output,
1400 float* __restrict__ attn_logits,
1401 const scalar_t* __restrict__ query,
1402 const scalar_t* __restrict__ k_buffer,
1403 const scalar_t* __restrict__ v_buffer,
1404 const index_t* __restrict__ req_to_token,
1405 const int64_t* __restrict__ req_pool_indices,
1406 const int64_t* __restrict__ seq_lens,
1407 const int64_t* __restrict__ encoder_lens,
1408 const scalar_t* __restrict__ sinks,
1409 int64_t batches,
1410 int64_t num_heads,
1411 int64_t num_heads_kv,
1412 int64_t head_size,
1413 int64_t head_size_v,
1414 int64_t num_kv_splits,
1415 int64_t q_strideM,
1416 int64_t q_strideH,
1417 int64_t k_strideN,
1418 int64_t k_strideH,
1419 int64_t v_strideN,
1420 int64_t v_strideH,
1421 float sm_scale,
1422 float logit_cap,
1423 int64_t max_num_reqs,
1424 int64_t max_context_len,
1425 int64_t max_total_num_tokens,
1426 int64_t sliding_window_size,
1427 bool is_cross_attn,
1428 bool has_encoder_lens,
1429 bool has_sink) {
1430 using Vec = at::vec::Vectorized<float>;
1431
1432 // block length for heads
1433 // we parallel on [batches, divup(num_heads, BLOCK_H), num_kv_splits]
1434 // use smaller BLOCK_H when batches is small to utilize all cores
1435 constexpr int64_t kBLOCK_H = 16;
1436 const int64_t BLOCK_H = std::min(4 * batches, kBLOCK_H);
1437
1438 // strides
1439 const int64_t l_stride0 = num_heads * num_kv_splits * (head_size_v + 1);
1440 const int64_t l_stride1 = num_kv_splits * (head_size_v + 1);
1441 const int64_t l_stride2 = head_size_v + 1;
1442
1443 const bool has_logit_cap = logit_cap > 0;
1444 float rlogit_cap = has_logit_cap ? 1 / logit_cap : 0.f;
1445
1446 // partition the heads into blocks for parallel
1447 const int64_t num_groups = num_heads / num_heads_kv;
1448 const int64_t num_blocks = div_up(num_groups, BLOCK_H);
1449
1450 // parallel on [batches, num_heads_kv, num_blocks, num_kv_splits]
1451 at::parallel_for(0, batches * num_heads_kv * num_blocks * num_kv_splits, 0, [&](int64_t begin, int64_t end) {
1452 int64_t bs{0}, head_kv_id{0}, block_id{0}, kv_id{0};
1453 data_index_init(begin, bs, batches, head_kv_id, num_heads_kv, block_id, num_blocks, kv_id, num_kv_splits);
1454
1455 alignas(64) float s_i[BLOCK_H * BLOCK_N];
1456 float* __restrict__ s_delta = s_i;
1457
1458 alignas(64) float s_prime[BLOCK_H];
1459 alignas(64) float m_prime[BLOCK_H];
1460 alignas(64) float m_delta[BLOCK_H];
1461
1462 for (int64_t i = begin; i < end; ++i) {
1463 const int64_t h_start = head_kv_id * num_groups + block_id * BLOCK_H;
1464 const int64_t h_end = head_kv_id * num_groups + std::min(block_id * BLOCK_H + BLOCK_H, num_groups);
1465 const int64_t h_size = h_end - h_start;
1466
1467 // get query
1468 const scalar_t* __restrict__ q_ptr = query + bs * q_strideM + h_start * q_strideH;
1469
1470 int64_t seq_len_kv = is_cross_attn ? encoder_lens[bs] : seq_lens[bs];
1471 int64_t req_pool_id = req_pool_indices[bs];
1472 int64_t kv_offset = (has_encoder_lens && (!is_cross_attn)) ? encoder_lens[bs] : 0;
1473 TORCH_CHECK(seq_len_kv <= max_context_len, "seq_len_kv out of scope!");
1474 TORCH_CHECK(req_pool_id < max_num_reqs, "req_pool_id out of scope!");
1475 if (sliding_window_size > 0 && seq_len_kv > sliding_window_size) {
1476 kv_offset = seq_len_kv - sliding_window_size;
1477 seq_len_kv = sliding_window_size;
1478 }
1479 const int64_t SPLIT_SIZE = div_up(seq_len_kv, num_kv_splits);
1480 const int64_t kv_start = kv_id * SPLIT_SIZE;
1481 const int64_t kv_end = std::min(kv_start + SPLIT_SIZE, seq_len_kv);
1482
1483 fill_stub(s_prime, 0.f, BLOCK_H);
1484 fill_stub(m_prime, -std::numeric_limits<float>::infinity(), BLOCK_H);
1485
1486 // get v_prime, and init to zero
1487 float* __restrict__ v_prime = attn_logits + bs * l_stride0 + h_start * l_stride1 + kv_id * l_stride2;
1488 for (int64_t h = 0; h < h_size; ++h) {
1489 fill_stub(v_prime + h * l_stride1, 0.f, head_size_v);
1490 }
1491
1492 // loop over K and V sequence with BLOCK_N
1493 for (int64_t n = kv_start; n < kv_end; n += BLOCK_N) {
1494 int64_t n_size = std::min(BLOCK_N, kv_end - n);
1495
1496 // calculate Q @ K
1497 index_gemm_kernel_nt<scalar_t, index_t>(
1498 /* A */ q_ptr,
1499 /* B */ k_buffer + head_kv_id * k_strideH,
1500 /* C */ s_i,
1501 /* ind */ req_to_token + req_pool_id * max_context_len + n + kv_offset,
1502 /* scl */ sm_scale,
1503 /* M */ h_size,
1504 /* N */ n_size,
1505 /* K */ head_size,
1506 /* lda */ q_strideH,
1507 /* ldb */ k_strideN,
1508 /* ldc */ BLOCK_N,
1509 /* mtt */ max_total_num_tokens);
1510
1511 if (has_logit_cap) {
1512 at::vec::map<float>(
1513 [logit_cap, rlogit_cap](Vec x) { return Vec(logit_cap) * (x * Vec(rlogit_cap)).tanh(); },
1514 s_i,
1515 s_i,
1516 BLOCK_H * BLOCK_N);
1517 }
1518
1519 // update the sm_scale coefficients
1520 for (int64_t h = 0; h < h_size; ++h) {
1521 // m_i: max value per row
1522 float m_i = at::vec::reduce_all<float>(
1523 [](Vec& x, Vec& y) { return at::vec::maximum(x, y); }, s_i + h * BLOCK_N, n_size);
1524 m_i = std::max(m_i, m_prime[h]);
1525
1526 // m_delta <- exp(m' - m_i)
1527 m_delta[h] = std::exp(m_prime[h] - m_i);
1528
1529 // s_delta <- exp(s_i - m_i)
1530 at::vec::map<float>(
1531 [m_i](Vec x) { return (x - Vec(m_i)).exp_u20(); }, s_delta + h * BLOCK_N, s_i + h * BLOCK_N, n_size);
1532
1533 // s' <- s' * m_delta + sum(s_delta)
1534 s_prime[h] *= m_delta[h];
1535 s_prime[h] += at::vec::reduce_all<float>([](Vec& x, Vec& y) { return x + y; }, s_delta + h * BLOCK_N, n_size);
1536
1537 m_prime[h] = m_i;
1538 }
1539
1540 // calculate V' <- s_delta @ V + V' * m_delta
1541 index_gemm_kernel_nn<scalar_t, index_t>(
1542 /* A */ s_delta,
1543 /* B */ v_buffer + head_kv_id * v_strideH,
1544 /* C */ v_prime,
1545 /* ind */ req_to_token + req_pool_id * max_context_len + n + kv_offset,
1546 /* scl */ m_delta,
1547 /* M */ h_size,
1548 /* N */ head_size_v,
1549 /* K */ n_size,
1550 /* lda */ BLOCK_N,
1551 /* ldb */ v_strideN,
1552 /* ldc */ l_stride1,
1553 /* mtt */ max_total_num_tokens);
1554 } // loop with KV blocks
1555
1556 // only update v' when kv_split_size > 0
1557 if (kv_end > kv_start) {
1558 for (int64_t h = 0; h < h_size; ++h) {
1559 float s = 1 / s_prime[h];
1560 at::vec::map<float>(
1561 [s](Vec out) { return out * Vec(s); }, v_prime + h * l_stride1, v_prime + h * l_stride1, head_size_v);
1562 (v_prime + h * l_stride1)[head_size_v] = m_prime[h] + std::log(s_prime[h]);
1563 }
1564 } else {
1565 for (int64_t h = 0; h < h_size; ++h) {
1566 (v_prime + h * l_stride1)[head_size_v] = -std::numeric_limits<float>::infinity();
1567 }
1568 }
1569
1570 // move to the next index
1571 data_index_step(bs, batches, head_kv_id, num_heads_kv, block_id, num_blocks, kv_id, num_kv_splits);
1572 }
1573 });
1574
1575 decode_accumulate_kv_splits(
1576 output, attn_logits, sinks, batches, num_heads, head_size_v, num_kv_splits, l_stride1, l_stride2, has_sink);
1577 } // GQA/MQA
1578
1579 } // anonymous namespace
1580
1581 // query: [num_tokens, num_heads, head_size]
1582 // output: [num_tokens, num_heads, head_size]
1583 // k_buffer: [max_total_num_tokens, num_heads, head_size]
1584 // v_buffer: [max_total_num_tokens, num_heads, head_size_v]
1585 // attn_logits: [num_seqs, num_heads, num_kv_splits, head_size_v + 1]
1586 // req_to_token: [max_num_reqs, max_context_len] int32 or int64
1587 // req_pool_indices: [num_seqs] int64
1588 // seq_lens: [num_seqs] int64
1589 // encoder_lens: [num_seqs] int64 or None
1590 // sinks: [num_heads] or None
1591 void decode_attention_cpu(
1592 at::Tensor& query,
1593 at::Tensor& k_buffer,
1594 at::Tensor& v_buffer,
1595 at::Tensor& output,
1596 const std::optional<at::Tensor>& key,
1597 const std::optional<at::Tensor>& value,
1598 const std::optional<at::Tensor>& loc,
1599 at::Tensor& attn_logits,
1600 at::Tensor& req_to_token,
1601 at::Tensor& req_pool_indices,
1602 at::Tensor& seq_lens,
1603 double sm_scale,
1604 double logit_cap,
1605 bool is_cross_attn,
1606 int64_t sliding_window_size,
1607 std::optional<at::Tensor> encoder_lens,
1608 std::optional<at::Tensor> sinks) {
1609 CHECK_LAST_DIM_CONTIGUOUS_INPUT(query);
1610 CHECK_LAST_DIM_CONTIGUOUS_INPUT(k_buffer);
1611 CHECK_LAST_DIM_CONTIGUOUS_INPUT(v_buffer);
1612 CHECK_DIM(3, query);
1613 CHECK_DIM(3, k_buffer);
1614 CHECK_DIM(3, v_buffer);
1615
1616 int64_t num_seqs = seq_lens.size(0);
1617 int64_t max_num_reqs = req_to_token.size(0);
1618 int64_t max_context_len = req_to_token.size(1);
1619 int64_t max_total_num_tokens = k_buffer.size(0);
1620
1621 int64_t num_heads = query.size(1);
1622 int64_t num_heads_kv = k_buffer.size(1);
1623 int64_t head_size = query.size(2);
1624 int64_t head_size_v = v_buffer.size(2);
1625
1626 int64_t num_kv_splits = attn_logits.size(2);
1627
1628 CHECK_EQ(attn_logits.size(0), num_seqs);
1629 CHECK_EQ(attn_logits.size(1), num_heads);
1630 CHECK_EQ(attn_logits.size(3), head_size_v + 1);
1631 CHECK_EQ(attn_logits.scalar_type(), at::kFloat);
1632
1633 // strides for query
1634 int64_t q_strideM = query.stride(0);
1635 int64_t q_strideH = query.stride(1);
1636
1637 // strides for k_buffer and v_buffer
1638 int64_t k_strideN = k_buffer.stride(0);
1639 int64_t k_strideH = k_buffer.stride(1);
1640 int64_t v_strideN = v_buffer.stride(0);
1641 int64_t v_strideH = v_buffer.stride(1);
1642
1643 // check index data types
1644 const auto index_dtype = req_to_token.scalar_type();
1645 TORCH_CHECK(
1646 index_dtype == at::kInt || index_dtype == at::kLong,
1647 "decode: expect req_to_token to be int32 or int64, got ",
1648 index_dtype);
1649 TORCH_CHECK(seq_lens.scalar_type() == at::kLong, "decode: expect req_lens to be int64, got ", seq_lens.scalar_type());
1650 TORCH_CHECK(
1651 req_pool_indices.scalar_type() == at::kLong,
1652 "decode: expect req_pool_indices to be int64, got ",
1653 req_pool_indices.scalar_type());
1654
1655 // check if we have MLA here
1656 void* k_buffer_data = k_buffer.data_ptr();
1657 void* v_buffer_data = v_buffer.data_ptr();
1658 const bool is_mla = (k_buffer_data == v_buffer_data) && (num_heads_kv == 1) && (head_size == head_size_v + 64);
1659
1660 // block length for k_buffer and v_buffer
1661 constexpr int BLOCK_N = 256;
1662
1663 // buffer for packing k_cache and v_cache
1664 int num_threads = at::get_num_threads();
1665 int64_t size_per_thread = is_mla ? BLOCK_N * head_size + BLOCK_N * head_size_v : 0;
1666 auto buffer = at::empty({num_threads, size_per_thread}, k_buffer.options());
1667 bool has_encoder_lens = encoder_lens.has_value();
1668 // Since encoder_lens is not used when it is None, encoder_lens_t can be initialized as any tensor of int64_t dtype.
1669 at::Tensor encoder_lens_t = seq_lens;
1670 if (has_encoder_lens) {
1671 encoder_lens_t = encoder_lens.value();
1672 CHECK_EQ(encoder_lens_t.size(0), num_seqs);
1673 }
1674 bool has_sink = sinks.has_value();
1675 at::Tensor sinks_tensor = has_sink ? sinks.value() : at::empty({num_heads}, query.options());
1676 CHECK_DIM(1, sinks_tensor);
1677 CHECK_EQ(sinks_tensor.size(0), num_heads);
1678 AT_DISPATCH_REDUCED_FLOATING_TYPES(query.scalar_type(), "decode_attention_kernel", [&] {
1679 AT_DISPATCH_INDEX_TYPES(index_dtype, "decode_attention_indices", [&] {
1680 if (key.has_value()) {
1681 TORCH_CHECK(value.has_value(), "key and value should have values at the same time")
1682 TORCH_CHECK(loc.has_value(), "loc must be given when key/value are given")
1683 auto loc_tensor = loc.value();
1684 CHECK_DIM(1, loc_tensor);
1685 CHECK_EQ(loc_tensor.numel(), num_seqs);
1686 auto key_tensor = key.value();
1687 auto value_tensor = value.value();
1688 // for MLA, key and value shares the same storage and value could be non-contiguous
1689 CHECK_LAST_DIM_CONTIGUOUS_INPUT(key_tensor);
1690 CHECK_LAST_DIM_CONTIGUOUS_INPUT(value_tensor);
1691 CHECK_DIM(3, key_tensor);
1692 CHECK_DIM(3, value_tensor);
1693 // strides for new key and value
1694 int64_t nk_strideN = key_tensor.stride(0);
1695 int64_t nk_strideH = key_tensor.stride(1);
1696 int64_t nv_strideN = value_tensor.stride(0);
1697 int64_t nv_strideH = value_tensor.stride(1);
1698 // update the kv buffer
1699 decode_set_kv_buffer(
1700 (scalar_t*)k_buffer_data,
1701 (scalar_t*)v_buffer_data,
1702 key_tensor.data_ptr<scalar_t>(),
1703 value_tensor.data_ptr<scalar_t>(),
1704 loc_tensor.data_ptr<int64_t>(),
1705 num_seqs,
1706 num_heads_kv,
1707 head_size,
1708 head_size_v,
1709 k_strideN,
1710 k_strideH,
1711 v_strideN,
1712 v_strideH,
1713 nk_strideN,
1714 nk_strideH,
1715 nv_strideN,
1716 nv_strideH,
1717 is_mla);
1718 }
1719
1720 if (num_heads == num_heads_kv) {
1721 // MHA
1722 decode_attention_kernel_impl<scalar_t, index_t, BLOCK_N>(
1723 output.data_ptr<scalar_t>(),
1724 attn_logits.data_ptr<float>(),
1725 query.data_ptr<scalar_t>(),
1726 (const scalar_t*)k_buffer_data,
1727 (const scalar_t*)v_buffer_data,
1728 req_to_token.data_ptr<index_t>(),
1729 req_pool_indices.data_ptr<int64_t>(),
1730 seq_lens.data_ptr<int64_t>(),
1731 encoder_lens_t.data_ptr<int64_t>(),
1732 sinks_tensor.data_ptr<scalar_t>(),
1733 num_seqs,
1734 num_heads,
1735 head_size,
1736 head_size_v,
1737 num_kv_splits,
1738 q_strideM,
1739 q_strideH,
1740 k_strideN,
1741 k_strideH,
1742 v_strideN,
1743 v_strideH,
1744 sm_scale,
1745 logit_cap,
1746 max_num_reqs,
1747 max_context_len,
1748 max_total_num_tokens,
1749 sliding_window_size,
1750 is_cross_attn,
1751 has_encoder_lens,
1752 has_sink);
1753 } else if (is_mla) {
1754 // MLA
1755 decode_attention_mla_kernel_impl<scalar_t, index_t, BLOCK_N>(
1756 output.data_ptr<scalar_t>(),
1757 attn_logits.data_ptr<float>(),
1758 query.data_ptr<scalar_t>(),
1759 (const scalar_t*)k_buffer_data,
1760 (const scalar_t*)v_buffer_data,
1761 req_to_token.data_ptr<index_t>(),
1762 req_pool_indices.data_ptr<int64_t>(),
1763 seq_lens.data_ptr<int64_t>(),
1764 buffer.data_ptr<scalar_t>(),
1765 sinks_tensor.data_ptr<scalar_t>(),
1766 num_seqs,
1767 num_heads,
1768 head_size,
1769 head_size_v,
1770 num_kv_splits,
1771 q_strideM,
1772 q_strideH,
1773 k_strideN,
1774 k_strideH,
1775 v_strideN,
1776 v_strideH,
1777 sm_scale,
1778 logit_cap,
1779 max_num_reqs,
1780 max_context_len,
1781 max_total_num_tokens,
1782 size_per_thread,
1783 has_sink);
1784 } else {
1785 // GQA/MQA
1786 decode_attention_grouped_kernel_impl<scalar_t, index_t, BLOCK_N>(
1787 output.data_ptr<scalar_t>(),
1788 attn_logits.data_ptr<float>(),
1789 query.data_ptr<scalar_t>(),
1790 (const scalar_t*)k_buffer_data,
1791 (const scalar_t*)v_buffer_data,
1792 req_to_token.data_ptr<index_t>(),
1793 req_pool_indices.data_ptr<int64_t>(),
1794 seq_lens.data_ptr<int64_t>(),
1795 encoder_lens_t.data_ptr<int64_t>(),
1796 sinks_tensor.data_ptr<scalar_t>(),
1797 num_seqs,
1798 num_heads,
1799 num_heads_kv,
1800 head_size,
1801 head_size_v,
1802 num_kv_splits,
1803 q_strideM,
1804 q_strideH,
1805 k_strideN,
1806 k_strideH,
1807 v_strideN,
1808 v_strideH,
1809 sm_scale,
1810 logit_cap,
1811 max_num_reqs,
1812 max_context_len,
1813 max_total_num_tokens,
1814 sliding_window_size,
1815 is_cross_attn,
1816 has_encoder_lens,
1817 has_sink);
1818 }
1819 });
1820 });
1821 }