main
cpp 894 lines 32.2 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__ y, const scalar_t* __restrict__ x, int64_t size) {
14 using Vec = at::vec::Vectorized<scalar_t>;
15 const bool is_padding = (x == nullptr);
16 for (int64_t d = 0; d < size; d += Vec::size()) {
17 Vec data_vec = is_padding ? Vec(0.f) : Vec::loadu(x + d);
18 data_vec.store(y + d);
19 }
20 }
21
22 // no remainder
23 template <typename scalar_t>
24 void inline update_conv_state(
25 scalar_t* __restrict__ conv_states,
26 const scalar_t* __restrict__ input,
27 int64_t width,
28 int64_t dim,
29 int64_t seqlen,
30 bool has_initial_states) {
31 // width for `conv_states`
32 int64_t width1 = width - 1;
33 int64_t w = 0;
34 for (; w < width1 - seqlen; ++w) {
35 scalar_t* y = conv_states + w * dim;
36 const scalar_t* x = has_initial_states ? conv_states + (w + seqlen) * dim : nullptr;
37 copy_stub(y, x, dim);
38 }
39 for (; w < width1; ++w) {
40 scalar_t* y = conv_states + w * dim;
41 const scalar_t* x = input + (w + seqlen - width1) * dim;
42 copy_stub(y, x, dim);
43 }
44 }
45
46 // A : [M, BLOCK_N]
47 // B : [BLOCK_N, K], prepacked as [K/2, BLOCK_N, 2]
48 // C : [M, BLOCK_N]
49 // bias : [BLOCK_N]
50 //
51 // lda : leading dimension of `input` and `out`
52 //
53 template <typename scalar_t, int K, int BLOCK_N, bool has_bias, bool has_silu>
54 struct tinygemm_kernel {
55 static inline void apply(
56 const scalar_t* __restrict__ A,
57 const scalar_t* __restrict__ B,
58 scalar_t* __restrict__ C,
59 const scalar_t* __restrict__ bias,
60 const scalar_t* __restrict__ conv_states,
61 bool has_initial_state,
62 int64_t M,
63 int64_t lda,
64 bool is_first_token) {
65 TORCH_CHECK(false, "tinygemm_kernel_nn: scalar path not implemented!");
66 }
67 };
68
69 #if defined(ARM_BF16_SUPPORT)
70 #include "conv_arm.h"
71 #endif
72
73 #if defined(CPU_CAPABILITY_AVX512)
74 template <int K, int BLOCK_N, bool has_bias, bool has_silu>
75 struct tinygemm_kernel<at::BFloat16, K, BLOCK_N, has_bias, has_silu> {
76 static inline void apply(
77 const at::BFloat16* __restrict__ A,
78 const at::BFloat16* __restrict__ B,
79 at::BFloat16* __restrict__ C,
80 const at::BFloat16* __restrict__ bias,
81 const at::BFloat16* __restrict__ conv_states,
82 bool has_initial_state,
83 int64_t M,
84 int64_t lda,
85 bool is_first_token) {
86 assert(K == 4);
87 constexpr int ROWS = K;
88 constexpr int COLS = BLOCK_N / block_size_n();
89
90 // leading dimension size for b for next block [K/2, 32, 2]
91 constexpr int ldb = block_size_n() * K;
92
93 __m512bh va[ROWS * COLS];
94 __m512bh vb[ROWS * COLS];
95 __m512 vc[COLS * 2];
96
97 // k: {-3, -2, -1} -> {0, 1, 2}
98 auto set_conv_states = [&](int k, int col) -> __m512i {
99 return has_initial_state ? _mm512_loadu_si512(conv_states + (k + K - 1) * lda + col * 32)
100 : _mm512_setzero_si512();
101 };
102
103 #define MM512_LOAD_A(idx) \
104 ((idx) < 0 && is_first_token) ? (__m512bh)(set_conv_states((idx), col)) \
105 : (__m512bh)(_mm512_loadu_si512(A + (idx) * lda + col * 32))
106
107 #define MM512_PACK_A(ap, bp, a, b) \
108 do { \
109 __m512i r0 = (__m512i)(a); \
110 __m512i r1 = (__m512i)(b); \
111 __m512i d0 = _mm512_unpacklo_epi16(r0, r1); \
112 __m512i d1 = _mm512_unpackhi_epi16(r0, r1); \
113 r0 = _mm512_shuffle_i32x4(d0, d1, 0x88); \
114 r1 = _mm512_shuffle_i32x4(d0, d1, 0xdd); \
115 (ap) = (__m512bh)_mm512_shuffle_i32x4(r0, r1, 0x88); \
116 (bp) = (__m512bh)_mm512_shuffle_i32x4(r0, r1, 0xdd); \
117 } while (0)
118
119 // step 0 : preload a at time step [-3][-2][-1]
120 auto preloada = [&](auto i) {
121 constexpr int col = i;
122 int64_t m = 0;
123 va[1 * COLS + col] = MM512_LOAD_A(m - 3);
124 va[2 * COLS + col] = MM512_LOAD_A(m - 2);
125 va[3 * COLS + col] = MM512_LOAD_A(m - 1);
126 };
127 Unroll<COLS>{}(preloada);
128
129 auto loada = [&](auto i, int64_t m) {
130 constexpr int col = i;
131 // update previous time step
132 va[0 * COLS + col] = va[1 * COLS + col];
133 va[1 * COLS + col] = va[2 * COLS + col];
134 va[2 * COLS + col] = va[3 * COLS + col];
135 // load current time step
136 va[3 * COLS + col] = MM512_LOAD_A(m);
137 };
138
139 // step 1 : load weight for just once
140 auto loadb = [&](auto i) {
141 constexpr int row = i / COLS;
142 constexpr int col = i % COLS;
143 vb[row * COLS + col] = (__m512bh)(_mm512_loadu_si512(B + col * ldb + row * 32));
144 };
145 Unroll<ROWS * COLS>{}(loadb);
146
147 // [NB] accumulates 4x32 bfloat16 blocks
148 //
149 // +------------+------------+
150 // | col0 | col1 |
151 // +------------+------------+
152 // | va0 va1 | va0 va1 |
153 // | va2 va3 | va2 va3 |
154 // +------------+------------+
155 // | vc0 vc1 | vc0 vc1 |
156 // +------------+------------+
157 //
158 // * va and vb shares the same memory layout
159 // * block_n 32 with 4 rows equals to 4 registers
160 // * 37 uops with avx512bf16 v.s. 57 uops with avx512f
161 //
162 auto compute = [&](auto i) {
163 constexpr int col = i;
164
165 // init accumulators
166 if constexpr (has_bias) {
167 __m512i b16 = _mm512_loadu_si512(reinterpret_cast<const __m512i*>(bias + col * 32));
168 vc[col * 2 + 0] = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(b16, 0));
169 vc[col * 2 + 1] = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(b16, 1));
170 } else {
171 vc[col * 2 + 0] = _mm512_set1_ps(0.f);
172 vc[col * 2 + 1] = _mm512_set1_ps(0.f);
173 }
174
175 // convert to vnni2 format
176 __m512bh va0, va1, va2, va3;
177 MM512_PACK_A(va0, va1, va[0 * COLS + col], va[1 * COLS + col]);
178 MM512_PACK_A(va2, va3, va[2 * COLS + col], va[3 * COLS + col]);
179
180 // accumulate
181 vc[col * 2 + 0] = _mm512_dpbf16_ps(vc[col * 2 + 0], va0, vb[0 * COLS + col]);
182 vc[col * 2 + 0] = _mm512_dpbf16_ps(vc[col * 2 + 0], va2, vb[2 * COLS + col]);
183 vc[col * 2 + 1] = _mm512_dpbf16_ps(vc[col * 2 + 1], va1, vb[1 * COLS + col]);
184 vc[col * 2 + 1] = _mm512_dpbf16_ps(vc[col * 2 + 1], va3, vb[3 * COLS + col]);
185 };
186
187 using fVec = at::vec::Vectorized<float>;
188 using bVec = at::vec::Vectorized<at::BFloat16>;
189 auto storec = [&](auto i, int64_t m) {
190 constexpr int col = i;
191 fVec x0 = fVec(vc[col * 2 + 0]);
192 fVec x1 = fVec(vc[col * 2 + 1]);
193 if constexpr (has_silu) {
194 x0 = fast_silu(x0);
195 x1 = fast_silu(x1);
196 }
197 bVec out_vec = convert_from_float_ext<at::BFloat16>(x0, x1);
198 out_vec.store(C + m * lda + col * 32);
199 };
200
201 for (int64_t m = 0; m < M; ++m) {
202 // step 3.a : load a at current time step
203 Unroll<COLS>{}(loada, m);
204 // step 3.b : accumulate for window size (4)
205 Unroll<COLS>{}(compute);
206 // step 3.c : store c at current time step
207 Unroll<COLS>{}(storec, m);
208 }
209 }
210 };
211 #endif
212
213 #define LAUNCH_TINYGEMM_KERNEL(K, NB_SIZE) \
214 tinygemm_kernel<scalar_t, K, NB_SIZE, has_bias, has_silu>::apply( \
215 input + bs * seqlen * dim + mb_start * dim + nb_start, \
216 weight + nb_start * width, \
217 out + bs * seqlen * dim + mb_start * dim + nb_start, \
218 has_bias ? bias + nb_start : nullptr, \
219 has_conv_states ? conv_states + conv_state_index * conv_state_slot_stride + nb_start : nullptr, \
220 has_initial_states_value, \
221 mb_size, \
222 dim, \
223 mb_start == 0);
224
225 template <typename scalar_t>
226 void causal_conv1d_fwd_kernel_impl(
227 scalar_t* __restrict__ out,
228 const scalar_t* __restrict__ input,
229 const scalar_t* __restrict__ weight,
230 const scalar_t* __restrict__ bias,
231 scalar_t* __restrict__ conv_states,
232 const int32_t* __restrict__ conv_indices,
233 const bool* __restrict__ has_initial_state,
234 bool silu_activation,
235 int64_t batch,
236 int64_t dim,
237 int64_t seqlen,
238 int64_t width,
239 int64_t num_seq_blocks,
240 int64_t conv_state_slot_stride) {
241 // handle 32 x 64 per block
242 constexpr int64_t BLOCK_M = block_size_m();
243 constexpr int64_t BLOCK_N = block_size_n() * 2;
244 const int64_t NB = div_up(dim, BLOCK_N);
245
246 const int64_t num_blocks_per_seq = div_up(seqlen, BLOCK_M);
247 const bool has_conv_states = conv_states != nullptr;
248 const bool has_conv_indices = conv_indices != nullptr;
249
250 // parallel on [batch, seq, NB]
251 AT_DISPATCH_BOOL2(bias != nullptr, has_bias, silu_activation, has_silu, [&] {
252 at::parallel_for(0, num_seq_blocks * NB, 0, [&](int64_t begin, int64_t end) {
253 int64_t mb{0}, nb{0};
254 data_index_init(begin, mb, num_seq_blocks, nb, NB);
255
256 for (int64_t i = begin; i < end; ++i) {
257 int64_t bs = mb / num_blocks_per_seq;
258
259 int64_t mb_start = (mb % num_blocks_per_seq) * BLOCK_M;
260 int64_t mb_size = std::min(seqlen - mb_start, BLOCK_M);
261 int64_t nb_start = nb * BLOCK_N;
262 int64_t nb_size = std::min(dim - nb_start, BLOCK_N);
263
264 const bool has_initial_states_value = has_conv_states ? has_initial_state[bs] : false;
265 int32_t conv_state_index = has_conv_indices ? conv_indices[bs] : bs;
266
267 switch (width << 4 | nb_size >> 4) {
268 case 0x42:
269 LAUNCH_TINYGEMM_KERNEL(4, 32);
270 break;
271 case 0x44:
272 LAUNCH_TINYGEMM_KERNEL(4, 64);
273 break;
274 default:
275 TORCH_CHECK(false, "Unexpected block size, ", width, " x ", nb_size);
276 }
277
278 // move to the next index
279 data_index_step(mb, num_seq_blocks, nb, NB);
280 }
281 });
282 });
283
284 // update conv_states if necessary
285 if (has_conv_states) {
286 at::parallel_for(0, batch, 0, [&](int64_t begin, int64_t end) {
287 for (int64_t bs = begin; bs < end; ++bs) {
288 update_conv_state(
289 conv_states + bs * conv_state_slot_stride, input + bs * seqlen * dim, width, dim, seqlen, has_initial_state[bs]);
290 }
291 });
292 }
293 }
294
295 #define LAUNCH_TINYGEMM_VARLEN_KERNEL(K, NB_SIZE) \
296 tinygemm_kernel<scalar_t, K, NB_SIZE, has_bias, has_silu>::apply( \
297 input + batch_offset * dim + mb_start * dim + nb_start, \
298 weight + nb_start * width, \
299 out + batch_offset * dim + mb_start * dim + nb_start, \
300 has_bias ? bias + nb_start : nullptr, \
301 has_conv_states ? conv_states + conv_state_index * conv_state_slot_stride + nb_start : nullptr, \
302 has_initial_states_value, \
303 mb_size, \
304 dim, \
305 mb_start == 0);
306
307 template <typename scalar_t>
308 void causal_conv1d_fwd_varlen_kernel_impl(
309 scalar_t* __restrict__ out,
310 const scalar_t* __restrict__ input,
311 const scalar_t* __restrict__ weight,
312 const scalar_t* __restrict__ bias,
313 scalar_t* __restrict__ conv_states,
314 const int32_t* __restrict__ query_start_loc,
315 const int32_t* __restrict__ conv_indices,
316 const bool* __restrict__ has_initial_state,
317 const int32_t* __restrict__ block_indices,
318 bool silu_activation,
319 int64_t batch,
320 int64_t dim,
321 int64_t width,
322 int64_t num_seq_blocks,
323 int64_t conv_state_slot_stride) {
324 // handle 32 x 64 per block
325 constexpr int64_t BLOCK_M = block_size_m();
326 constexpr int64_t BLOCK_N = block_size_n() * 2;
327 const int64_t NB = div_up(dim, BLOCK_N);
328
329 const bool has_conv_states = conv_states != nullptr;
330 const bool has_conv_indices = conv_indices != nullptr;
331
332 // parallel on [batch, seq, NB]
333 AT_DISPATCH_BOOL2(bias != nullptr, has_bias, silu_activation, has_silu, [&] {
334 at::parallel_for(0, num_seq_blocks * NB, 0, [&](int64_t begin, int64_t end) {
335 int64_t mb{0}, nb{0};
336 data_index_init(begin, mb, num_seq_blocks, nb, NB);
337
338 for (int64_t i = begin; i < end; ++i) {
339 int32_t bs = block_indices[mb * 2 + 0];
340 int32_t batch_offset = query_start_loc[bs];
341 int32_t seqlen = query_start_loc[bs + 1] - query_start_loc[bs];
342
343 int64_t mb_start = block_indices[mb * 2 + 1] * BLOCK_M;
344 int64_t mb_size = std::min(seqlen - mb_start, BLOCK_M);
345 int64_t nb_start = nb * BLOCK_N;
346 int64_t nb_size = std::min(dim - nb_start, BLOCK_N);
347
348 const bool has_initial_states_value = has_conv_states ? has_initial_state[bs] : false;
349 int32_t conv_state_index = has_conv_indices ? conv_indices[bs] : bs;
350
351 switch (width << 4 | nb_size >> 4) {
352 case 0x42:
353 LAUNCH_TINYGEMM_VARLEN_KERNEL(4, 32);
354 break;
355 case 0x44:
356 LAUNCH_TINYGEMM_VARLEN_KERNEL(4, 64);
357 break;
358 default:
359 TORCH_CHECK(false, "Unexpected block size, ", width, " x ", nb_size);
360 }
361
362 // move to the next index
363 data_index_step(mb, num_seq_blocks, nb, NB);
364 }
365 });
366 });
367
368 // update conv_states if necessary
369 if (has_conv_states) {
370 at::parallel_for(0, batch, 0, [&](int64_t begin, int64_t end) {
371 for (int64_t bs = begin; bs < end; ++bs) {
372 int32_t conv_state_index = has_conv_indices ? conv_indices[bs] : bs;
373 int32_t seqlen = query_start_loc[bs + 1] - query_start_loc[bs];
374 int32_t batch_offset = query_start_loc[bs];
375 update_conv_state(
376 conv_states + conv_state_index * conv_state_slot_stride,
377 input + batch_offset * dim,
378 width,
379 dim,
380 seqlen,
381 has_initial_state[bs]);
382 }
383 });
384 }
385 }
386
387 template <typename scalar_t>
388 void causal_conv1d_update_kernel_impl(
389 scalar_t* __restrict__ out,
390 const scalar_t* __restrict__ input,
391 scalar_t* __restrict__ conv_states,
392 const scalar_t* __restrict__ weight,
393 const scalar_t* __restrict__ bias,
394 const int32_t* __restrict__ conv_indices,
395 bool silu_activation,
396 int64_t batch,
397 int64_t dim,
398 int64_t seqlen,
399 int64_t width,
400 int64_t conv_state_slot_stride) {
401 // handle 32 x 64 per block
402 constexpr int64_t BLOCK_M = block_size_m();
403 constexpr int64_t BLOCK_N = block_size_n() * 2;
404 const int64_t NB = div_up(dim, BLOCK_N);
405
406 const bool has_conv_states = conv_states != nullptr;
407 const bool has_conv_indices = conv_indices != nullptr;
408
409 // parallel on [batch, NB]
410 AT_DISPATCH_BOOL2(bias != nullptr, has_bias, silu_activation, has_silu, [&] {
411 at::parallel_for(0, batch * NB, 0, [&](int64_t begin, int64_t end) {
412 int64_t bs{0}, nb{0};
413 data_index_init(begin, bs, batch, nb, NB);
414
415 for (int64_t i = begin; i < end; ++i) {
416 int64_t mb_start = 0;
417 int64_t mb_size = 1;
418 int64_t nb_start = nb * BLOCK_N;
419 int64_t nb_size = std::min(dim - nb_start, BLOCK_N);
420
421 const bool has_initial_states_value = true;
422 int32_t conv_state_index = has_conv_indices ? conv_indices[bs] : bs;
423
424 switch (width << 4 | nb_size >> 4) {
425 case 0x42:
426 LAUNCH_TINYGEMM_KERNEL(4, 32);
427 break;
428 case 0x44:
429 LAUNCH_TINYGEMM_KERNEL(4, 64);
430 break;
431 default:
432 TORCH_CHECK(false, "Unexpected block size, ", width, " x ", nb_size);
433 }
434
435 // move to the next index
436 data_index_step(bs, batch, nb, NB);
437 }
438 });
439 });
440
441 #define CONV_STATE_INDEXR(w) conv_states + conv_state_index*conv_state_slot_stride + (w) * dim
442
443 // update conv_states
444 at::parallel_for(0, batch, 0, [&](int64_t begin, int64_t end) {
445 for (int64_t bs = begin; bs < end; ++bs) {
446 // update old states, range [1, width - 1)
447 int32_t conv_state_index = has_conv_indices ? conv_indices[bs] : bs;
448 for (int64_t w = 1; w < width - 1; ++w) {
449 std::memcpy(CONV_STATE_INDEXR(w - 1), CONV_STATE_INDEXR(w), dim * sizeof(scalar_t));
450 }
451 // copy new states
452 std::memcpy(CONV_STATE_INDEXR(width - 2), input + bs * dim, dim * sizeof(scalar_t));
453 }
454 });
455 }
456
457 template <typename scalar_t>
458 void causal_conv1d_update_multi_kernel_impl(
459 scalar_t* __restrict__ out,
460 const scalar_t* __restrict__ input,
461 scalar_t* __restrict__ conv_states,
462 const scalar_t* __restrict__ weight,
463 const scalar_t* __restrict__ bias,
464 const int32_t* __restrict__ num_accepted_tokens,
465 const int32_t* __restrict__ conv_indices,
466 bool silu_activation,
467 int64_t batch,
468 int64_t dim,
469 int64_t seqlen,
470 int64_t width,
471 int64_t state_len,
472 int64_t conv_state_slot_stride) {
473 constexpr int64_t BLOCK_N = block_size_n() * 2;
474 const int64_t NB = div_up(dim, BLOCK_N);
475
476 AT_DISPATCH_BOOL2(bias != nullptr, has_bias, silu_activation, has_silu, [&] {
477 at::parallel_for(0, batch * NB, 0, [&](int64_t begin, int64_t end) {
478 int64_t bs{0}, nb{0};
479 data_index_init(begin, bs, batch, nb, NB);
480
481 for (int64_t i = begin; i < end; ++i) {
482 const int64_t nb_start = nb * BLOCK_N;
483 const int64_t nb_size = std::min(dim - nb_start, BLOCK_N);
484 const int32_t conv_state_index = conv_indices[bs];
485 const int32_t history_offset = num_accepted_tokens[bs] - 1;
486
487 switch (width << 4 | nb_size >> 4) {
488 case 0x42:
489 tinygemm_kernel<scalar_t, 4, 32, has_bias, has_silu>::apply(
490 input + bs * seqlen * dim + nb_start,
491 weight + nb_start * width,
492 out + bs * seqlen * dim + nb_start,
493 has_bias ? bias + nb_start : nullptr,
494 conv_states + conv_state_index * conv_state_slot_stride +
495 history_offset * dim + nb_start,
496 true,
497 seqlen,
498 dim,
499 true);
500 break;
501 case 0x44:
502 tinygemm_kernel<scalar_t, 4, 64, has_bias, has_silu>::apply(
503 input + bs * seqlen * dim + nb_start,
504 weight + nb_start * width,
505 out + bs * seqlen * dim + nb_start,
506 has_bias ? bias + nb_start : nullptr,
507 conv_states + conv_state_index * conv_state_slot_stride +
508 history_offset * dim + nb_start,
509 true,
510 seqlen,
511 dim,
512 true);
513 break;
514 default:
515 TORCH_CHECK(false, "Unexpected block size, ", width, " x ", nb_size);
516 }
517
518 data_index_step(bs, batch, nb, NB);
519 }
520 });
521 });
522
523 at::parallel_for(0, batch, 0, [&](int64_t begin, int64_t end) {
524 for (int64_t bs = begin; bs < end; ++bs) {
525 const int32_t conv_state_index = conv_indices[bs];
526 const int32_t num_accepted = num_accepted_tokens[bs];
527 scalar_t* state = conv_states + conv_state_index * conv_state_slot_stride;
528
529 std::memmove(
530 state,
531 state + num_accepted * dim,
532 (state_len - seqlen) * dim * sizeof(scalar_t));
533 std::memcpy(
534 state + (state_len - seqlen) * dim,
535 input + bs * seqlen * dim,
536 seqlen * dim * sizeof(scalar_t));
537 }
538 });
539 }
540
541 } // anonymous namespace
542
543 // from [dim, width] or [N, K]
544 // to [N/BLOCK_N, K/2, BLOCK_N, 2]
545 at::Tensor causal_conv1d_weight_pack(const at::Tensor& weight) {
546 CHECK_INPUT(weight);
547
548 int64_t dim = weight.size(0);
549 int64_t width = weight.size(1);
550 constexpr int64_t BLOCK_N =
551 #if defined(ARM_BF16_SUPPORT)
552 8;// 8 channel blocks for microtile
553 #else
554 block_size_n();
555 #endif
556 TORCH_CHECK(width == 4, "causal_conv1d_weight_pack: support only width of 4");
557 TORCH_CHECK(
558 dim % block_size_n() == 0,
559 "causal_conv1d_weight_pack: invalid dim size ",
560 dim);
561
562 const int64_t N = dim, K2 = width >> 1;
563 const int64_t NB = div_up(N, BLOCK_N);
564
565 auto packed_weight = at::empty_like(weight);
566 AT_DISPATCH_REDUCED_FLOATING_TYPES(weight.scalar_type(), "causal_conv1d_fwd_kernel_impl", [&] {
567 // Copy each pair as an opaque 32-bit word. This is not a bf16->fp32 conversion.
568 const float* w_data = reinterpret_cast<float*>(weight.data_ptr<scalar_t>());
569 float* packed_data = reinterpret_cast<float*>(packed_weight.data_ptr<scalar_t>());
570
571 at::parallel_for(0, NB * K2 * BLOCK_N, 0, [&](int64_t begin, int64_t end) {
572 int64_t nb{0}, k2{0}, n{0};
573 data_index_init(begin, nb, NB, k2, K2, n, BLOCK_N);
574
575 // TODO: optimize this if we need to online prepacking.
576 for (int64_t i = begin; i < end; ++i) {
577 packed_data[i] = w_data[nb * BLOCK_N * K2 + n * K2 + k2];
578
579 // move to the next index
580 data_index_step(nb, NB, k2, K2, n, BLOCK_N);
581 }
582 });
583 });
584 return packed_weight;
585 }
586
587 #define CHECK_OPTIONAL_SHAPE_DTYPE(OPT, SIZE, DTYPE) \
588 if (OPT.has_value()) { \
589 const auto tensor = OPT.value(); \
590 CHECK_CONTIGUOUS(tensor); \
591 CHECK_EQ(tensor.size(0), SIZE); \
592 CHECK_EQ(tensor.scalar_type(), DTYPE); \
593 }
594
595 template <int BLOCK_M>
596 int64_t get_block_count(const std::optional<at::Tensor>& offsets, int64_t batch, int64_t seqlen) {
597 if (offsets.has_value()) {
598 const int32_t* offsets_data = offsets.value().data_ptr<int32_t>();
599 int32_t num_seq_blocks = 0;
600 for (int64_t row = 0; row < batch; ++row) {
601 num_seq_blocks += div_up(offsets_data[row + 1] - offsets_data[row], BLOCK_M);
602 }
603 return num_seq_blocks;
604 }
605 return batch * div_up(seqlen, int64_t(BLOCK_M));
606 }
607
608 template <int BLOCK_M>
609 at::Tensor get_block_indices(const std::optional<at::Tensor>& offsets, int64_t num_seq_blocks) {
610 if (!offsets.has_value()) {
611 return at::Tensor();
612 }
613
614 const at::Tensor& offsets_ = offsets.value();
615 at::Tensor indices = at::empty({num_seq_blocks, 2}, offsets_.options());
616
617 int64_t batch = offsets_.size(0) - 1;
618
619 const int32_t* offsets_data = offsets_.data_ptr<int32_t>();
620 int32_t* indices_data = indices.data_ptr<int32_t>();
621
622 int64_t idx = 0;
623 for (int32_t row = 0; row < batch; ++row) {
624 int32_t blocks = div_up(offsets_data[row + 1] - offsets_data[row], BLOCK_M);
625
626 for (int32_t col = 0; col < blocks; ++col) {
627 indices_data[idx * 2 + 0] = row;
628 indices_data[idx * 2 + 1] = col;
629 idx++;
630 }
631 }
632 return indices;
633 }
634
635 // API aligned with GPUs
636 //
637 // x: (batch, dim, seqlen) or (dim, cu_seq_len) for varlen
638 // weight: (dim, width)
639 // bias: (dim,)
640 // query_start_loc: (batch + 1) int32
641 // cache_indices: (batch) int32
642 // has_initial_state: (batch) bool
643 // conv_states: (..., dim, state_len) itype, where state_len >= width - 1
644 // activation: either None or "silu" or "swish"
645 // pad_slot_id: int
646 //
647 at::Tensor causal_conv1d_fwd_cpu(
648 const at::Tensor& x,
649 const at::Tensor& weight,
650 const std::optional<at::Tensor>& bias,
651 const std::optional<at::Tensor>& conv_states,
652 const std::optional<at::Tensor>& query_start_loc,
653 const std::optional<at::Tensor>& conv_state_indices,
654 const std::optional<at::Tensor>& has_initial_state,
655 bool silu_activation,
656 int64_t pad_slot_id,
657 bool is_weight_packed) {
658 CHECK_CONTIGUOUS(weight);
659 auto packed_w = is_weight_packed ? weight : causal_conv1d_weight_pack(weight);
660
661 const bool is_var_seqlen = query_start_loc.has_value();
662 const int64_t input_ndim = is_var_seqlen ? 2 : 3;
663 TORCH_CHECK(x.dim() == input_ndim, "causal_conv1d_fwd_cpu: expect x to be ", input_ndim, "D tensor.");
664 TORCH_CHECK(x.stride(-2) == 1 && x.stride(-1) == x.size(-2), "causal_conv1d_fwd_cpu: expect x to be transposed.");
665
666 const int64_t batch = is_var_seqlen ? query_start_loc.value().size(0) - 1 : x.size(0);
667 const int64_t dim = x.size(-2);
668 const int64_t seqlen = x.size(-1);
669 const int64_t width = weight.size(-1);
670
671 const auto scalar_type = x.scalar_type();
672 CHECK_EQ(weight.scalar_type(), scalar_type);
673 CHECK_OPTIONAL_SHAPE_DTYPE(bias, dim, scalar_type);
674 CHECK_OPTIONAL_SHAPE_DTYPE(query_start_loc, batch + 1, at::kInt);
675 CHECK_OPTIONAL_SHAPE_DTYPE(conv_state_indices, batch, at::kInt);
676 CHECK_OPTIONAL_SHAPE_DTYPE(has_initial_state, batch, at::kBool);
677
678 if (conv_states.has_value()) {
679 auto& conv_states_val = conv_states.value();
680 int64_t padded_batch = conv_states_val.size(0);
681 CHECK_EQ(conv_states_val.scalar_type(), scalar_type);
682 CHECK_GE(padded_batch, batch);
683 CHECK_EQ(conv_states_val.size(1), dim);
684 const int64_t state_len = conv_states_val.size(2);
685 CHECK_GE(state_len, width - 1);
686
687 // adjust `conv_states` to be contiguous on `dim`
688 // should happen only once
689 if (conv_states_val.stride(-2) != 1) {
690 TORCH_CHECK(state_len == width - 1,
691 "causal_conv1d_fwd_cpu: wide conv_states must be contiguous on dim.");
692 auto conv_states_copy = conv_states_val.clone();
693 conv_states_val.as_strided_({padded_batch, dim, width - 1}, {(width - 1) * dim, 1, dim});
694 conv_states_val.copy_(conv_states_copy);
695 }
696 }
697
698 // IMPORTANT: To make the kernal compatible with vLLM KV cache layout
699 int64_t conv_state_slot_stride = conv_states->stride(0);
700
701 // block size for sequence blocks, 32
702 constexpr int64_t BLOCK_M = block_size_m();
703
704 // total number of sequence blocks
705 int64_t num_seq_blocks = get_block_count<BLOCK_M>(query_start_loc, batch, seqlen);
706
707 at::Tensor out = at::empty_like(x);
708 AT_DISPATCH_REDUCED_FLOATING_TYPES(scalar_type, "causal_conv1d_fwd_kernel_impl", [&] {
709 if (is_var_seqlen) {
710 // record seq blocks in Coordinate format, aka [num_seq_blocks, 2]
711 at::Tensor block_indices = get_block_indices<BLOCK_M>(query_start_loc, num_seq_blocks);
712
713 causal_conv1d_fwd_varlen_kernel_impl(
714 out.data_ptr<scalar_t>(),
715 x.data_ptr<scalar_t>(),
716 packed_w.data_ptr<scalar_t>(),
717 conditional_data_ptr<scalar_t>(bias),
718 conditional_data_ptr<scalar_t>(conv_states),
719 conditional_data_ptr<int32_t>(query_start_loc),
720 conditional_data_ptr<int32_t>(conv_state_indices),
721 conditional_data_ptr<bool>(has_initial_state),
722 block_indices.data_ptr<int32_t>(),
723 silu_activation,
724 batch,
725 dim,
726 width,
727 num_seq_blocks,
728 conv_state_slot_stride);
729 } else {
730 causal_conv1d_fwd_kernel_impl<scalar_t>(
731 out.data_ptr<scalar_t>(),
732 x.data_ptr<scalar_t>(),
733 packed_w.data_ptr<scalar_t>(),
734 conditional_data_ptr<scalar_t>(bias),
735 conditional_data_ptr<scalar_t>(conv_states),
736 conditional_data_ptr<int32_t>(conv_state_indices),
737 conditional_data_ptr<bool>(has_initial_state),
738 silu_activation,
739 batch,
740 dim,
741 seqlen,
742 width,
743 num_seq_blocks,
744 conv_state_slot_stride);
745 }
746 });
747 return out;
748 }
749
750 // API aligned with GPUs
751 //
752 // x: (batch, dim) or (batch, seqlen, dim)
753 // conv_state: (..., dim, state_len), where state_len >= width - 1
754 // weight: (dim, width)
755 // bias: (dim,)
756 // num_accepted_tokens: (batch,), dtype int32.
757 // conv_state_indices: (batch,), dtype int32
758 // pad_slot_id: int
759 // out: (batch, dim) or (batch, seqlen, dim)
760 //
761 at::Tensor causal_conv1d_update_cpu(
762 const at::Tensor& x,
763 const at::Tensor& conv_states,
764 const at::Tensor& weight,
765 const std::optional<at::Tensor>& bias,
766 bool silu_activation,
767 const std::optional<at::Tensor>& num_accepted_tokens,
768 const std::optional<at::Tensor>& conv_state_indices,
769 int64_t pad_slot_id,
770 bool is_weight_packed) {
771 CHECK_CONTIGUOUS(x);
772 CHECK_CONTIGUOUS(weight);
773 auto packed_w = is_weight_packed ? weight : causal_conv1d_weight_pack(weight);
774
775 TORCH_CHECK(
776 x.dim() == 2 || x.dim() == 3,
777 "causal_conv1d_update_cpu: expect x to be 2D or 3D tensor.");
778
779 int64_t batch = x.size(0);
780 int64_t dim = x.dim() == 2 ? x.size(1) : x.size(2);
781 int64_t seqlen = x.dim() == 2 ? 1 : x.size(1);
782 int64_t width = weight.size(-1);
783
784 const auto scalar_type = x.scalar_type();
785 CHECK_EQ(weight.scalar_type(), scalar_type);
786 CHECK_OPTIONAL_SHAPE_DTYPE(bias, dim, scalar_type);
787 CHECK_OPTIONAL_SHAPE_DTYPE(conv_state_indices, batch, at::kInt);
788
789 CHECK_EQ(conv_states.scalar_type(), scalar_type);
790 CHECK_EQ(conv_states.size(1), dim);
791 const int64_t state_len = conv_states.size(2);
792 CHECK_GE(state_len, width - 1);
793
794 if (x.dim() == 3) {
795 TORCH_CHECK(
796 num_accepted_tokens.has_value(),
797 "causal_conv1d_update_cpu: num_accepted_tokens is required for 3D x.");
798 TORCH_CHECK(
799 conv_state_indices.has_value(),
800 "causal_conv1d_update_cpu: conv_state_indices is required for 3D x.");
801 CHECK_OPTIONAL_SHAPE_DTYPE(num_accepted_tokens, batch, at::kInt);
802 TORCH_CHECK(
803 width == 4,
804 "causal_conv1d_update_cpu: support only width of 4 for 3D x.");
805 TORCH_CHECK(
806 seqlen > 0,
807 "causal_conv1d_update_cpu: expect non-empty sequence for 3D x.");
808 TORCH_CHECK(
809 state_len >= seqlen,
810 "causal_conv1d_update_cpu: state_len must be >= seqlen for 3D x.");
811 TORCH_CHECK(
812 conv_states.stride(-2) == 1 && conv_states.stride(-1) == dim,
813 "causal_conv1d_update_cpu: 3D x requires SD conv_states layout.");
814
815 const int32_t* accepted_counts =
816 num_accepted_tokens.value().data_ptr<int32_t>();
817 const int32_t* indices = conv_state_indices.value().data_ptr<int32_t>();
818 const int64_t num_slots = conv_states.size(0);
819 for (int64_t bs = 0; bs < batch; ++bs) {
820 const int32_t num_accepted = accepted_counts[bs];
821 const int32_t conv_state_index = indices[bs];
822 TORCH_CHECK(
823 conv_state_index != pad_slot_id,
824 "causal_conv1d_update_cpu: 3D x does not support pad slots.");
825 TORCH_CHECK(
826 conv_state_index >= 0 && conv_state_index < num_slots,
827 "causal_conv1d_update_cpu: conv_state_indices out of range.");
828 TORCH_CHECK(
829 num_accepted >= 1 && num_accepted <= seqlen,
830 "causal_conv1d_update_cpu: num_accepted_tokens must be in [1, "
831 "seqlen].");
832 TORCH_CHECK(
833 num_accepted - 1 + width - 1 <= state_len,
834 "causal_conv1d_update_cpu: history window exceeds conv_states.");
835 }
836
837 int64_t conv_state_slot_stride = conv_states.stride(0);
838 at::Tensor out = at::empty_like(x);
839 AT_DISPATCH_REDUCED_FLOATING_TYPES(
840 scalar_type, "causal_conv1d_update_multi_kernel_impl", [&] {
841 causal_conv1d_update_multi_kernel_impl<scalar_t>(
842 out.data_ptr<scalar_t>(),
843 x.data_ptr<scalar_t>(),
844 conv_states.data_ptr<scalar_t>(),
845 packed_w.data_ptr<scalar_t>(),
846 conditional_data_ptr<scalar_t>(bias),
847 accepted_counts,
848 indices,
849 silu_activation,
850 batch,
851 dim,
852 seqlen,
853 width,
854 state_len,
855 conv_state_slot_stride);
856 });
857 return out;
858 }
859
860 TORCH_CHECK(
861 !num_accepted_tokens.has_value(),
862 "causal_conv1d_update_cpu: num_accepted_tokens is only supported for 3D "
863 "x.");
864
865 // adjust `conv_states` to be contiguous on `dim`
866 if (conv_states.stride(-2) != 1) {
867 TORCH_CHECK(state_len == width - 1,
868 "causal_conv1d_update_cpu: wide conv_states must be contiguous on dim.");
869 int64_t num_cache_lines = conv_states.size(0);
870 auto conv_states_copy = conv_states.clone();
871 conv_states.as_strided_({num_cache_lines, dim, width - 1}, {(width - 1) * dim, 1, dim});
872 conv_states.copy_(conv_states_copy);
873 }
874
875 // IMPORTANT: To make the kernal compatible with vLLM KV cache layout
876 int64_t conv_state_slot_stride = conv_states.stride(0);
877 at::Tensor out = at::empty_like(x);
878 AT_DISPATCH_REDUCED_FLOATING_TYPES(scalar_type, "causal_conv1d_update_kernel_impl", [&] {
879 causal_conv1d_update_kernel_impl<scalar_t>(
880 out.data_ptr<scalar_t>(),
881 x.data_ptr<scalar_t>(),
882 conv_states.data_ptr<scalar_t>(),
883 packed_w.data_ptr<scalar_t>(),
884 conditional_data_ptr<scalar_t>(bias),
885 conditional_data_ptr<int32_t>(conv_state_indices),
886 silu_activation,
887 batch,
888 dim,
889 seqlen,
890 width,
891 conv_state_slot_stride);
892 });
893 return out;
894 }