main
cpp 2,674 lines 99.5 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 #include "vec_pack.h"
10
11 namespace {
12
13 // [NOTE] GDN Optimizations on AMX CPU
14 // * intra loop: fuse `kkt_solve` and `recompute_w_u` so as to avoid materialize `A`.
15 // * inter loop: fuse `recompute_w_u` and `update_v` so as to avoid materialize `h` and `v_new`.
16 // * intra loop parallel on H instead of Hv, remove duplicated key @ key.T
17 // * fuse format pack with elemwise OP as much as possible.
18 // * update state (FP32) with amx-bf16 where C(FP32) += A(BF16) * B(BF16)
19 // * compile time mask out upper triangular part in decay mask and tril solve, reduce fma needed.
20
21 // * convert to vnni format, expect contiguous input and output
22 // from [K/2, 2, N] FP32 to [K/2, N, 2] BF16
23 // * update src = src * exp(g_last)
24 template <typename scalar_t, int K, int N>
25 void pack_vnni2(scalar_t* __restrict__ dst, float* __restrict__ src, const float g_last, int ld_src, int ld_dst) {
26 static_assert(K % 32 == 0);
27 static_assert(N % 32 == 0);
28
29 const float scale = std::exp(g_last);
30 #if defined(CPU_CAPABILITY_AVX512)
31 constexpr int KB = K / 2;
32 constexpr int NB = N / 32;
33
34 __m512i s0, s1, d0, d1;
35 __m512 vd = _mm512_set1_ps(scale);
36
37 const auto trans = [&](auto i) {
38 constexpr int kb = i / NB;
39 constexpr int nb = i % NB;
40
41 // [K/2, 2, N/32, 32] -> [K/2, N/32, 32, 2]
42 constexpr int k0 = kb * 2 + 0;
43 constexpr int k1 = kb * 2 + 1;
44 __m512 v00 = _mm512_loadu_ps(src + k0 * ld_src + nb * 32);
45 __m512 v01 = _mm512_loadu_ps(src + k0 * ld_src + nb * 32 + 16);
46 __m512 v10 = _mm512_loadu_ps(src + k1 * ld_src + nb * 32);
47 __m512 v11 = _mm512_loadu_ps(src + k1 * ld_src + nb * 32 + 16);
48 s0 = (__m512i)_mm512_cvtne2ps_pbh(v01, v00);
49 s1 = (__m512i)_mm512_cvtne2ps_pbh(v11, v10);
50
51 std::tie(d0, d1) = transpose_2x32_16bit(s0, s1);
52 _mm512_storeu_si512(dst + kb * ld_dst * 2 + nb * 32 * 2, d0);
53 _mm512_storeu_si512(dst + kb * ld_dst * 2 + nb * 32 * 2 + 32, d1);
54
55 // update src = src * exp(g_last)
56 _mm512_storeu_ps(src + k0 * ld_src + nb * 32, _mm512_mul_ps(v00, vd));
57 _mm512_storeu_ps(src + k0 * ld_src + nb * 32 + 16, _mm512_mul_ps(v01, vd));
58 _mm512_storeu_ps(src + k1 * ld_src + nb * 32, _mm512_mul_ps(v10, vd));
59 _mm512_storeu_ps(src + k1 * ld_src + nb * 32 + 16, _mm512_mul_ps(v11, vd));
60 };
61 Unroll<KB * NB>{}(trans);
62 #else
63 // [K/2, 2, N] -> [K/2, N, 2]
64 for (int k = 0; k < K; k += 2) {
65 for (int n = 0; n < N; ++n) {
66 const float v0 = src[(k + 0) * ld_src + n];
67 const float v1 = src[(k + 1) * ld_src + n];
68 dst[(k >> 1) * ld_dst * 2 + n * 2 + 0] = static_cast<scalar_t>(v0);
69 dst[(k >> 1) * ld_dst * 2 + n * 2 + 1] = static_cast<scalar_t>(v1);
70 src[(k + 0) * ld_src + n] = v0 * scale;
71 src[(k + 1) * ld_src + n] = v1 * scale;
72 }
73 }
74 #endif
75 }
76
77 template <typename state_t>
78 inline void store_state_vec2(
79 state_t* __restrict__ dst,
80 const at::vec::Vectorized<float>& src0,
81 const at::vec::Vectorized<float>& src1) {
82 if constexpr (std::is_same_v<state_t, float>) {
83 src0.store(dst);
84 src1.store(dst + at::vec::Vectorized<float>::size());
85 } else {
86 auto state_vec = convert_from_float_ext<state_t>(src0, src1);
87 state_vec.store(dst);
88 }
89 }
90
91 template <typename scalar_t, int SIZE>
92 inline void fill_stub(scalar_t* __restrict__ out, float val) {
93 using Vec = at::vec::Vectorized<scalar_t>;
94 constexpr int kVecSize = Vec::size();
95 static_assert(SIZE % kVecSize == 0);
96 const Vec data_vec = Vec(static_cast<scalar_t>(val));
97 #pragma GCC unroll 8
98 for (int d = 0; d < SIZE; d += kVecSize) {
99 data_vec.store(out + d);
100 }
101 }
102
103 // Portable fallback for non-AVX512 builds (ARM/NEON, old x86 without
104 // AVX512BF16), vectorized via at::vec::Vectorized<T> (portable across
105 // AVX2/NEON/generic) mirroring the idioms used elsewhere in this file (see
106 // l2norm_fwd_kernel_impl's predecessor and fused_gdn_gating_kernel_impl):
107 // bVec/fVec pairs with convert_to_float/convert_from_float for bf16<->float,
108 // plain scalar tails for remainders. Two kernels (cumsum_kernel,
109 // update_key_kernel) write a transposed layout relative to their vectorized
110 // read axis; those vectorize the load/compute and unpack lanes for the
111 // (unavoidably strided) store.
112 template <typename scalar_t, int D, bool has_scale>
113 struct l2norm_kernel {
114 static inline void apply(scalar_t* __restrict__ out, const scalar_t* __restrict__ input, float eps) {
115 using bVec = at::vec::Vectorized<scalar_t>;
116 using fVec = at::vec::Vectorized<float>;
117 constexpr int bVecSize = bVec::size();
118 const float scale = 1.f / std::sqrt(static_cast<float>(D));
119
120 fVec sum_fvec0(0.f), sum_fvec1(0.f);
121 int d = 0;
122 for (; d <= D - bVecSize; d += bVecSize) {
123 bVec in_bvec = bVec::loadu(input + d);
124 fVec in0, in1;
125 std::tie(in0, in1) = at::vec::convert_to_float(in_bvec);
126 sum_fvec0 = sum_fvec0 + in0 * in0;
127 sum_fvec1 = sum_fvec1 + in1 * in1;
128 }
129 float sqsum = vec_reduce_sum(sum_fvec0 + sum_fvec1);
130 for (; d < D; ++d) {
131 float v = static_cast<float>(input[d]);
132 sqsum += v * v;
133 }
134
135 float rscale = 1.f / std::sqrt(sqsum + eps);
136 fVec rscale_fvec(rscale);
137 fVec scale_fvec(scale);
138 d = 0;
139 for (; d <= D - bVecSize; d += bVecSize) {
140 bVec in_bvec = bVec::loadu(input + d);
141 fVec in0, in1;
142 std::tie(in0, in1) = at::vec::convert_to_float(in_bvec);
143 in0 = in0 * rscale_fvec;
144 in1 = in1 * rscale_fvec;
145 if constexpr (has_scale) {
146 in0 = in0 * scale_fvec;
147 in1 = in1 * scale_fvec;
148 }
149 bVec out_bvec = at::vec::convert_from_float<scalar_t>(in0, in1);
150 out_bvec.store(out + d);
151 }
152 for (; d < D; ++d) {
153 float v = static_cast<float>(input[d]) * rscale;
154 if constexpr (has_scale) {
155 v *= scale;
156 }
157 out[d] = static_cast<scalar_t>(v);
158 }
159 }
160 };
161
162 #if defined(CPU_CAPABILITY_AVX512)
163 template <int D, bool has_scale>
164 struct l2norm_kernel<at::BFloat16, D, has_scale> {
165 static inline void apply(at::BFloat16* __restrict__ out, const at::BFloat16* __restrict__ input, float eps) {
166 static_assert(D % 32 == 0);
167 constexpr int COLS = D / 32;
168
169 __m512bh va[COLS];
170 __m512 vrscale;
171
172 const float scale = 1.f / std::sqrt(D);
173 __m512 vscale = _mm512_set1_ps(scale);
174
175 // step 1: load input and do reduce with avx512-bf16
176 __m512 vsum = _mm512_set1_ps(0.f);
177 auto reduce = [&](auto col) {
178 va[col] = (__m512bh)(_mm512_loadu_si512(input + col * 32));
179 vsum = _mm512_dpbf16_ps(vsum, va[col], va[col]);
180 };
181 Unroll<COLS>{}(reduce);
182
183 float sqsum = _mm512_reduce_add_ps(vsum);
184 float rscale = 1.f / std::sqrt(sqsum + eps);
185 vrscale = _mm512_set1_ps(rscale);
186
187 // step 2: apply scale to output
188 auto map = [&](auto col) {
189 __m512i a16 = (__m512i)va[col];
190 __m512 va0 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(a16, 0));
191 __m512 va1 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(a16, 1));
192 va0 = _mm512_mul_ps(va0, vrscale);
193 va1 = _mm512_mul_ps(va1, vrscale);
194 // keep the mul order same as torch code:
195 // query = l2norm(query) * scale
196 if constexpr (has_scale) {
197 va0 = _mm512_mul_ps(va0, vscale);
198 va1 = _mm512_mul_ps(va1, vscale);
199 }
200 _mm512_storeu_si512(out + col * 32, (__m512i)(_mm512_cvtne2ps_pbh(va1, va0)));
201 };
202 Unroll<COLS>{}(map);
203 }
204 };
205 #endif
206
207 template <typename scalar_t, int CHUNK_SIZE, int BLOCK_H>
208 struct cumsum_kernel {
209 static inline void apply(
210 scalar_t* __restrict__ out,
211 const scalar_t* __restrict__ input,
212 int mb_size,
213 int hb_size,
214 int ld_src,
215 int ld_dst) {
216 // out: [hb_size valid rows, CHUNK_SIZE] within a [BLOCK_H, ld_dst] buffer
217 // input: [mb_size valid rows, hb_size] within a [CHUNK_SIZE(padded), ld_src] buffer
218 // input is contiguous along j (vectorize the load/accumulate); out is
219 // contiguous along i instead (transposed), so the store is per-lane.
220 using Vec = at::vec::Vectorized<scalar_t>;
221 constexpr int VecSize = Vec::size();
222 alignas(64) scalar_t lane_buf[VecSize];
223 int j = 0;
224 for (; j <= hb_size - VecSize; j += VecSize) {
225 Vec running(static_cast<scalar_t>(0));
226 for (int i = 0; i < CHUNK_SIZE; ++i) {
227 if (i < mb_size) {
228 running = running + Vec::loadu(input + i * ld_src + j);
229 }
230 running.store(lane_buf);
231 for (int lane = 0; lane < VecSize; ++lane) {
232 out[(j + lane) * ld_dst + i] = lane_buf[lane];
233 }
234 }
235 }
236 for (; j < hb_size; ++j) {
237 float running = 0.f;
238 for (int i = 0; i < CHUNK_SIZE; ++i) {
239 if (i < mb_size) {
240 running += static_cast<float>(input[i * ld_src + j]);
241 }
242 out[j * ld_dst + i] = static_cast<scalar_t>(running);
243 }
244 }
245 }
246 };
247
248 #if defined(CPU_CAPABILITY_AVX512)
249 template <int CHUNK_SIZE, int BLOCK_H>
250 struct cumsum_kernel<float, CHUNK_SIZE, BLOCK_H> {
251 static inline void
252 apply(float* __restrict__ out, const float* __restrict__ input, int mb_size, int hb_size, int ld_src, int ld_dst) {
253 // vector length of fp32 for avx512
254 static_assert(BLOCK_H == 16);
255 TORCH_CHECK(hb_size > 0 && hb_size <= BLOCK_H);
256 const __mmask16 vmask = static_cast<__mmask16>((1u << hb_size) - 1u);
257
258 __m512i va[16];
259 __m512 vsum = _mm512_set1_ps(0.f);
260
261 for (int i = 0; i < CHUNK_SIZE; i += 16) {
262 // load input data
263 Unroll<16>{}([&](auto j) {
264 __m512 v;
265 if (i + j < mb_size) {
266 v = _mm512_maskz_loadu_ps(vmask, input + (i + j) * ld_src);
267 } else {
268 v = _mm512_setzero_ps();
269 }
270 vsum = _mm512_add_ps(vsum, v);
271 va[j] = _mm512_castps_si512(vsum);
272 });
273 // transpose
274 transpose_16x16_32bit(va);
275 // store output data
276 Unroll<16>{}([&](auto j) {
277 if (j < hb_size) {
278 _mm512_storeu_si512(out + j * ld_dst + i, va[j]);
279 }
280 });
281 }
282 }
283 };
284 #endif
285
286 template <typename scalar_t, int CHUNK_SIZE>
287 struct decay_mask_kernel {
288 // decay_mask = ((g.unsqueeze(-1) - g.unsqueeze(-2)).tril().exp().float()).tril()
289 static inline void apply(scalar_t* __restrict__ out, const scalar_t* __restrict__ input) {
290 using Vec = at::vec::Vectorized<scalar_t>;
291 constexpr int VecSize = Vec::size();
292 const Vec zero(static_cast<scalar_t>(0));
293 for (int row = 0; row < CHUNK_SIZE; ++row) {
294 Vec g_row(input[row]);
295 Vec limit_vec(static_cast<scalar_t>(row));
296 int col = 0;
297 for (; col <= CHUNK_SIZE - VecSize; col += VecSize) {
298 Vec g_col = Vec::loadu(input + col);
299 Vec vc = (g_row - g_col).exp_u20();
300 Vec idx = Vec::arange(static_cast<scalar_t>(col), static_cast<scalar_t>(1));
301 Vec result = Vec::blendv(zero, vc, idx <= limit_vec);
302 result.store(out + row * CHUNK_SIZE + col);
303 }
304 for (; col < CHUNK_SIZE; ++col) {
305 out[row * CHUNK_SIZE + col] =
306 col <= row
307 ? static_cast<scalar_t>(std::exp(static_cast<float>(input[row]) - static_cast<float>(input[col])))
308 : static_cast<scalar_t>(0);
309 }
310 }
311 }
312 };
313
314 #if defined(CPU_CAPABILITY_AVX512)
315 template <int CHUNK_SIZE>
316 struct decay_mask_kernel<float, CHUNK_SIZE> {
317 static inline void apply(float* __restrict__ out, const float* __restrict__ input) {
318 static_assert(CHUNK_SIZE % 16 == 0);
319
320 constexpr int ROWS = CHUNK_SIZE;
321 constexpr int COLS = CHUNK_SIZE / 16;
322
323 __m512 va;
324 __m512 vb[COLS];
325
326 // step 1: load g[j]
327 auto loadb = [&](auto i) { vb[i] = _mm512_loadu_ps(input + i * 16); };
328 Unroll<COLS>{}(loadb);
329
330 // step2: exp(g[i] - g[j])
331 auto compute = [&](auto i) {
332 constexpr int row = i / COLS;
333 constexpr int col = i % COLS;
334
335 if constexpr (col == 0) {
336 va = _mm512_set1_ps(input[row]);
337 }
338
339 // mask vb[col] (already loaded in step 1) for the lower-triangular region
340 constexpr int len = std::max(0, std::min(row + 1 - col * 16, 16));
341
342 __m512 vc;
343 if constexpr (len == 16) {
344 vc = _mm512_fexp_u20_ps(va - vb[col]);
345 } else if constexpr (len == 0) {
346 vc = _mm512_setzero_ps();
347 } else {
348 vc = _mm512_fexp_u20_ps(va - vb[col]);
349 // do mask for vc
350 constexpr __mmask16 vmask = (1 << len) - 1;
351 vc = _mm512_mask_blend_ps(vmask, _mm512_setzero_ps(), vc);
352 }
353 _mm512_storeu_ps(out + row * CHUNK_SIZE + col * 16, vc);
354 };
355 Unroll<ROWS * COLS>{}(compute);
356 }
357 };
358 #endif
359
360 template <typename scalar_t, int CHUNK_SIZE, bool has_beta>
361 struct apply_mask_kernel {
362 // has_beta: attn2 = -attn * beta * d (strict lower, col < row)
363 // !has_beta: attn2 = attn * d (lower incl. diagonal, col <= row)
364 static inline void apply(
365 scalar_t* __restrict__ attn2,
366 const float* __restrict__ attn,
367 const scalar_t* __restrict__ beta,
368 const float* __restrict__ d,
369 int size,
370 int b_stride = 0) {
371 using bVec = at::vec::Vectorized<scalar_t>;
372 using fVec = at::vec::Vectorized<float>;
373 constexpr int fVecSize = fVec::size();
374 constexpr int bVecSize = bVec::size();
375 const fVec zero(0.f);
376 for (int row = 0; row < size; ++row) {
377 int col_limit = has_beta ? row : row + 1;
378 float beta_val = 1.f;
379 if constexpr (has_beta) {
380 beta_val = -static_cast<float>(beta[row * b_stride]);
381 }
382 fVec beta_fvec(beta_val);
383 fVec limit_fvec(static_cast<float>(col_limit));
384 int col = 0;
385 for (; col <= CHUNK_SIZE - bVecSize; col += bVecSize) {
386 fVec a0 = fVec::loadu(attn + row * CHUNK_SIZE + col);
387 fVec a1 = fVec::loadu(attn + row * CHUNK_SIZE + col + fVecSize);
388 fVec d0 = fVec::loadu(d + row * CHUNK_SIZE + col);
389 fVec d1 = fVec::loadu(d + row * CHUNK_SIZE + col + fVecSize);
390 fVec v0 = a0 * beta_fvec * d0;
391 fVec v1 = a1 * beta_fvec * d1;
392 fVec idx0 = fVec::arange(static_cast<float>(col), 1.f);
393 fVec idx1 = fVec::arange(static_cast<float>(col + fVecSize), 1.f);
394 v0 = fVec::blendv(zero, v0, idx0 < limit_fvec);
395 v1 = fVec::blendv(zero, v1, idx1 < limit_fvec);
396 bVec out_bvec = at::vec::convert_from_float<scalar_t>(v0, v1);
397 out_bvec.store(attn2 + row * CHUNK_SIZE + col);
398 }
399 for (; col < CHUNK_SIZE; ++col) {
400 float v = 0.f;
401 if (col < col_limit) {
402 v = attn[row * CHUNK_SIZE + col] * beta_val * d[row * CHUNK_SIZE + col];
403 }
404 attn2[row * CHUNK_SIZE + col] = static_cast<scalar_t>(v);
405 }
406 }
407 }
408 };
409
410 #if defined(CPU_CAPABILITY_AVX512)
411 template <int CHUNK_SIZE, bool has_beta>
412 struct apply_mask_kernel<at::BFloat16, CHUNK_SIZE, has_beta> {
413 static inline void apply(
414 at::BFloat16* __restrict__ attn2,
415 const float* __restrict__ attn,
416 const at::BFloat16* __restrict__ beta,
417 const float* __restrict__ d,
418 int size,
419 int b_stride = 0) {
420 static_assert(CHUNK_SIZE % 16 == 0);
421
422 constexpr int ROWS = CHUNK_SIZE;
423 constexpr int COLS = CHUNK_SIZE / 16;
424
425 __m512 vbeta;
426
427 // has_beta: attn2 = -attn * beta * d (strict lower)
428 // !has_beta: attn2 = attn * d (lower incl. diagonal)
429 auto compute = [&](auto i) {
430 constexpr int row = i / COLS;
431 constexpr int col = i % COLS;
432
433 constexpr int len =
434 has_beta ? std::max(0, std::min(row - col * 16, 16)) : std::max(0, std::min(row + 1 - col * 16, 16));
435 if (row < size) {
436 if constexpr (has_beta) {
437 if constexpr (col == 0) {
438 vbeta = _mm512_set1_ps(-static_cast<float>(beta[row * b_stride]));
439 }
440 }
441
442 __m512 vc;
443 if constexpr (len == 0) {
444 vc = _mm512_setzero_ps();
445 } else {
446 constexpr __mmask16 vmask = (1 << len) - 1;
447 __m512 va = _mm512_maskz_loadu_ps(vmask, attn + row * CHUNK_SIZE + col * 16);
448 __m512 vd = _mm512_maskz_loadu_ps(vmask, d + row * CHUNK_SIZE + col * 16);
449 if constexpr (has_beta) {
450 vc = _mm512_mul_ps(_mm512_mul_ps(va, vbeta), vd);
451 } else {
452 vc = _mm512_mul_ps(va, vd);
453 }
454 }
455 _mm256_storeu_si256(
456 reinterpret_cast<__m256i*>(attn2 + row * CHUNK_SIZE + col * 16), (__m256i)(_mm512_cvtneps_pbh(vc)));
457 }
458 };
459 Unroll<ROWS * COLS>{}(compute);
460 }
461 };
462 #endif
463
464 template <typename scalar_t, int CHUNK_SIZE>
465 struct solve_tril_kernel {
466 // (I + L)^{-1} via forward substitution, L = strict-lower part of attn2.
467 static inline void apply(scalar_t* __restrict__ attn2, int size) {
468 using bVec = at::vec::Vectorized<scalar_t>;
469 using fVec = at::vec::Vectorized<float>;
470 constexpr int fVecSize = fVec::size();
471 constexpr int bVecSize = bVec::size();
472
473 // for len == 0 and row < size, we don't have to write back zero again
474 // as in `apply_mask_kernel`, we already set zero for the upper-triangular region
475 for (int i = 1; i < size; ++i) {
476 scalar_t* __restrict__ row_ptr = attn2 + i * CHUNK_SIZE;
477 float vsum[CHUNK_SIZE];
478 int j = 0;
479 for (; j <= i - bVecSize; j += bVecSize) {
480 bVec row_bvec = bVec::loadu(row_ptr + j);
481 fVec f0, f1;
482 std::tie(f0, f1) = at::vec::convert_to_float(row_bvec);
483 f0.store(vsum + j);
484 f1.store(vsum + j + fVecSize);
485 }
486 for (; j < i; ++j) {
487 vsum[j] = static_cast<float>(row_ptr[j]);
488 }
489
490 // row = attn[..., i, :i].clone()
491 // sub = attn[..., :i, :i].clone()
492 // vsum = row + (row.unsqueeze(-1) * sub).sum(-2)
493 for (int k = 0; k < i; ++k) {
494 // read BEFORE row_ptr is written back below (row k was finalized in
495 // an earlier outer iteration; row i itself is untouched until the
496 // final write-back after this loop)
497 float va = static_cast<float>(row_ptr[k]);
498 fVec va_vec(va);
499 const scalar_t* __restrict__ row_k_ptr = attn2 + k * CHUNK_SIZE;
500 int jj = 0;
501 for (; jj <= k - bVecSize; jj += bVecSize) {
502 bVec rk_bvec = bVec::loadu(row_k_ptr + jj);
503 fVec rk0, rk1;
504 std::tie(rk0, rk1) = at::vec::convert_to_float(rk_bvec);
505 fVec vsum0 = fVec::loadu(vsum + jj);
506 fVec vsum1 = fVec::loadu(vsum + jj + fVecSize);
507 vsum0 = vsum0 + va_vec * rk0;
508 vsum1 = vsum1 + va_vec * rk1;
509 vsum0.store(vsum + jj);
510 vsum1.store(vsum + jj + fVecSize);
511 }
512 for (; jj < k; ++jj) {
513 vsum[jj] += va * static_cast<float>(row_k_ptr[jj]);
514 }
515 }
516
517 j = 0;
518 for (; j <= i - bVecSize; j += bVecSize) {
519 fVec f0 = fVec::loadu(vsum + j);
520 fVec f1 = fVec::loadu(vsum + j + fVecSize);
521 bVec out_bvec = at::vec::convert_from_float<scalar_t>(f0, f1);
522 out_bvec.store(row_ptr + j);
523 }
524 for (; j < i; ++j) {
525 row_ptr[j] = static_cast<scalar_t>(vsum[j]);
526 }
527 }
528
529 // attn = attn + torch.eye(chunk_size)
530 for (int i = 0; i < size; ++i) {
531 attn2[i * CHUNK_SIZE + i] = static_cast<scalar_t>(static_cast<float>(attn2[i * CHUNK_SIZE + i]) + 1.f);
532 }
533 }
534 };
535
536 #if defined(CPU_CAPABILITY_AVX512)
537 template <int CHUNK_SIZE>
538 struct solve_tril_kernel<at::BFloat16, CHUNK_SIZE> {
539 static inline void apply(at::BFloat16* __restrict__ attn2, int size) {
540 static_assert(CHUNK_SIZE % 16 == 0);
541
542 constexpr int COLS = CHUNK_SIZE / 16;
543
544 __m512 va;
545 __m512 vb[COLS];
546 __m512 vsum[COLS];
547
548 // for len == 0 and row < size, we don't have to write back zero again
549 // as in `apply_mask_kernel`, we already set zero for the upper-triangular region
550 for (int i = 1; i < size; ++i) {
551 // load row attn[..., i, :i]
552 at::BFloat16* __restrict__ row_ptr = attn2 + i * CHUNK_SIZE;
553 Unroll<COLS>{}([&](auto col) {
554 int len = std::min(i - col * 16, 16);
555 if (len > 0) {
556 const __mmask16 vmask = (1 << len) - 1;
557 vsum[col] = CVT_BF16_TO_FP32(_mm256_maskz_loadu_epi16(vmask, row_ptr + col * 16));
558 }
559 });
560
561 // row = attn[..., i, :i].clone()
562 // sub = attn[..., :i, :i].clone()
563 // vsum = row + (row.unsqueeze(-1) * sub).sum(-2)
564 for (int k = 0; k < i; ++k) {
565 va = _mm512_set1_ps(static_cast<float>(row_ptr[k]));
566
567 const at::BFloat16* __restrict__ row_k_ptr = attn2 + k * CHUNK_SIZE;
568 Unroll<COLS>{}([&](auto col) {
569 int len = std::min(k - col * 16, 16);
570 if (len > 0) {
571 const __mmask16 vmask = (1 << len) - 1;
572 vb[col] = CVT_BF16_TO_FP32(_mm256_maskz_loadu_epi16(vmask, row_k_ptr + col * 16));
573 vsum[col] = _mm512_fmadd_ps(va, vb[col], vsum[col]);
574 }
575 });
576 }
577
578 // attn[..., i, :i] = vsum
579 Unroll<COLS>{}([&](auto col) {
580 int len = std::min(i - col * 16, 16);
581 if (len > 0) {
582 const __mmask16 vmask = (1 << len) - 1;
583 _mm256_mask_storeu_epi16(row_ptr + col * 16, vmask, (__m256i)(_mm512_cvtneps_pbh(vsum[col])));
584 }
585 });
586 }
587
588 // attn = attn + torch.eye(chunk_size)
589 for (int i = 0; i < size; ++i) {
590 attn2[i * CHUNK_SIZE + i] += 1.f;
591 }
592 }
593 };
594 #endif
595
596 template <typename scalar_t, int CHUNK_SIZE, int D, bool has_beta, bool has_g>
597 struct apply_beta_kernel {
598 static inline void apply(
599 scalar_t* __restrict__ out,
600 const scalar_t* __restrict__ input,
601 const scalar_t* __restrict__ beta,
602 const float* __restrict__ g,
603 int size,
604 int ld_src,
605 int ld_dst,
606 int b_stride) {
607 using bVec = at::vec::Vectorized<scalar_t>;
608 using fVec = at::vec::Vectorized<float>;
609 constexpr int fVecSize = fVec::size();
610 constexpr int bVecSize = bVec::size();
611
612 for (int i = 0; i < size; ++i) {
613 float scale = 1.f;
614 if constexpr (has_beta) {
615 scale *= static_cast<float>(beta[i * b_stride]);
616 }
617 if constexpr (has_g) {
618 scale *= std::exp(g[i]);
619 }
620 fVec scale_fvec(scale);
621 int d = 0;
622 for (; d <= D - bVecSize; d += bVecSize) {
623 bVec in_bvec = bVec::loadu(input + i * ld_src + d);
624 fVec in0, in1;
625 std::tie(in0, in1) = at::vec::convert_to_float(in_bvec);
626 in0 = in0 * scale_fvec;
627 in1 = in1 * scale_fvec;
628 bVec out_bvec = at::vec::convert_from_float<scalar_t>(in0, in1);
629 out_bvec.store(out + i * ld_dst + d);
630 }
631 for (; d < D; ++d) {
632 out[i * ld_dst + d] = static_cast<scalar_t>(static_cast<float>(input[i * ld_src + d]) * scale);
633 }
634 }
635 }
636 };
637
638 #if defined(CPU_CAPABILITY_AVX512)
639 template <int CHUNK_SIZE, int D, bool has_beta, bool has_g>
640 struct apply_beta_kernel<at::BFloat16, CHUNK_SIZE, D, has_beta, has_g> {
641 static inline void apply(
642 at::BFloat16* __restrict__ out,
643 const at::BFloat16* __restrict__ input,
644 const at::BFloat16* __restrict__ beta,
645 const float* __restrict__ g,
646 int size,
647 int ld_src,
648 int ld_dst,
649 int b_stride) {
650 static_assert(D % 32 == 0);
651 constexpr int COLS = D / 16;
652
653 // get g.exp() and g is padded to CHUNK_SIZE
654 alignas(64) float g_arr[CHUNK_SIZE];
655 if constexpr (has_g) {
656 Unroll<CHUNK_SIZE / 16>{}([&](auto col) {
657 __m512 vg = _mm512_loadu_ps(g + col * 16);
658 __m512 vg_exp = _mm512_fexp_u20_ps(vg);
659 _mm512_storeu_ps(g_arr + col * 16, vg_exp);
660 });
661 }
662
663 for (int i = 0; i < size; ++i) {
664 __m512 vbeta;
665 if constexpr (has_beta) {
666 vbeta = _mm512_set1_ps(static_cast<float>(beta[i * b_stride]));
667 }
668 __m512 vg;
669 if constexpr (has_g) {
670 vg = _mm512_set1_ps(g_arr[i]);
671 }
672
673 Unroll<COLS>{}([&](auto col) {
674 // load for 0, 2, 4, 6
675 if constexpr (col % 2 == 0) {
676 __m512i a16 = _mm512_loadu_si512(input + i * ld_src + col * 16);
677 __m512 va0 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(a16, 0));
678 __m512 va1 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(a16, 1));
679 if constexpr (has_beta) {
680 va0 = _mm512_mul_ps(va0, vbeta);
681 va1 = _mm512_mul_ps(va1, vbeta);
682 }
683 if constexpr (has_g) {
684 va0 = _mm512_mul_ps(va0, vg);
685 va1 = _mm512_mul_ps(va1, vg);
686 }
687 _mm512_storeu_si512(out + i * ld_dst + col * 16, (__m512i)(_mm512_cvtne2ps_pbh(va1, va0)));
688 }
689 });
690 }
691 }
692 };
693 #endif
694
695 template <typename scalar_t, int D>
696 struct update_kernel {
697 static inline void
698 apply(scalar_t* __restrict__ out, const float* __restrict__ input, int size, int ld_src, int ld_dst) {
699 using bVec = at::vec::Vectorized<scalar_t>;
700 using fVec = at::vec::Vectorized<float>;
701 constexpr int fVecSize = fVec::size();
702 constexpr int bVecSize = bVec::size();
703
704 for (int i = 0; i < size; ++i) {
705 int d = 0;
706 for (; d <= D - bVecSize; d += bVecSize) {
707 fVec f0 = fVec::loadu(input + i * ld_src + d);
708 fVec f1 = fVec::loadu(input + i * ld_src + d + fVecSize);
709 bVec out_bvec = at::vec::convert_from_float<scalar_t>(f0, f1);
710 out_bvec.store(out + i * ld_dst + d);
711 }
712 for (; d < D; ++d) {
713 out[i * ld_dst + d] = static_cast<scalar_t>(input[i * ld_src + d]);
714 }
715 }
716 }
717 };
718
719 #if defined(CPU_CAPABILITY_AVX512)
720 template <int D>
721 struct update_kernel<at::BFloat16, D> {
722 static inline void
723 apply(at::BFloat16* __restrict__ out, const float* __restrict__ input, int size, int ld_src, int ld_dst) {
724 static_assert(D % 32 == 0);
725 constexpr int COLS = D / 16;
726
727 for (int i = 0; i < size; ++i) {
728 Unroll<COLS>{}([&](auto col) {
729 if constexpr (col % 2 == 0) {
730 __m512 va0 = _mm512_loadu_ps(input + i * ld_src + (col + 0) * 16);
731 __m512 va1 = _mm512_loadu_ps(input + i * ld_src + (col + 1) * 16);
732 __m512i a16 = (__m512i)(_mm512_cvtne2ps_pbh(va1, va0));
733 _mm512_storeu_si512(out + i * ld_dst + col * 16, a16);
734 }
735 });
736 }
737 }
738 };
739 #endif
740
741 template <typename scalar_t, int D>
742 struct update_value_kernel {
743 static inline void apply(
744 scalar_t* __restrict__ v_prime2,
745 const scalar_t* __restrict__ v,
746 const float* __restrict__ v_prime,
747 int size,
748 int padded_size,
749 int v_strideT) {
750 using bVec = at::vec::Vectorized<scalar_t>;
751 using fVec = at::vec::Vectorized<float>;
752 constexpr int fVecSize = fVec::size();
753 constexpr int bVecSize = bVec::size();
754
755 // v2' = v - v'
756 for (int i = 0; i < size; ++i) {
757 int d = 0;
758 for (; d <= D - bVecSize; d += bVecSize) {
759 bVec v_bvec = bVec::loadu(v + i * v_strideT + d);
760 fVec v0, v1;
761 std::tie(v0, v1) = at::vec::convert_to_float(v_bvec);
762 fVec vp0 = fVec::loadu(v_prime + i * D + d);
763 fVec vp1 = fVec::loadu(v_prime + i * D + d + fVecSize);
764 v0 = v0 - vp0;
765 v1 = v1 - vp1;
766 bVec out_bvec = at::vec::convert_from_float<scalar_t>(v0, v1);
767 out_bvec.store(v_prime2 + i * D + d);
768 }
769 for (; d < D; ++d) {
770 float val = static_cast<float>(v[i * v_strideT + d]) - v_prime[i * D + d];
771 v_prime2[i * D + d] = static_cast<scalar_t>(val);
772 }
773 }
774 // pad the last chunk
775 const bVec zero_bvec(static_cast<scalar_t>(0));
776 for (int i = size; i < padded_size; ++i) {
777 int d = 0;
778 for (; d <= D - bVecSize; d += bVecSize) {
779 zero_bvec.store(v_prime2 + i * D + d);
780 }
781 for (; d < D; ++d) {
782 v_prime2[i * D + d] = static_cast<scalar_t>(0);
783 }
784 }
785 }
786 };
787
788 #if defined(CPU_CAPABILITY_AVX512)
789 template <int D>
790 struct update_value_kernel<at::BFloat16, D> {
791 static inline void apply(
792 at::BFloat16* __restrict__ v_prime2,
793 const at::BFloat16* __restrict__ v,
794 const float* __restrict__ v_prime,
795 int size,
796 int padded_size,
797 int v_strideT) {
798 static_assert(D % 32 == 0);
799 constexpr int COLS = D / 16;
800
801 // v2' = v - v'
802 for (int i = 0; i < size; ++i) {
803 Unroll<COLS>{}([&](auto col) {
804 // load for 0, 2, 4, 6
805 if constexpr (col % 2 == 0) {
806 __m512i v16 = _mm512_loadu_si512(v + i * v_strideT + col * 16);
807 __m512 va0 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(v16, 0));
808 __m512 va1 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(v16, 1));
809
810 __m512 v_prime0 = _mm512_loadu_ps(v_prime + i * D + col * 16);
811 __m512 v_prime1 = _mm512_loadu_ps(v_prime + i * D + col * 16 + 16);
812 va0 = _mm512_sub_ps(va0, v_prime0);
813 va1 = _mm512_sub_ps(va1, v_prime1);
814 __m512i o16 = (__m512i)(_mm512_cvtne2ps_pbh(va1, va0));
815 _mm512_storeu_si512(v_prime2 + i * D + col * 16, o16);
816 }
817 });
818 }
819
820 // pad the last chunk
821 for (int i = size; i < padded_size; ++i) {
822 Unroll<COLS>{}([&](auto col) {
823 if constexpr (col % 2 == 0) {
824 __m512i v16 = _mm512_setzero_si512();
825 _mm512_storeu_si512(v_prime2 + i * D + col * 16, v16);
826 }
827 });
828 }
829 }
830 };
831 #endif
832
833 template <typename scalar_t, int CHUNK_SIZE, int D>
834 struct update_key_kernel {
835 static inline void apply(
836 scalar_t* __restrict__ k_updated,
837 const scalar_t* __restrict__ k,
838 const float* __restrict__ g,
839 int size,
840 int k_strideT) {
841 // k_updated is transposed: [D, CHUNK_SIZE], k_updated[d, t] = k[t, d] * exp(g_last - g[t]).
842 // k's D dim is contiguous (vectorize load/compute); k_updated's D dim is
843 // strided (CHUNK_SIZE apart), so the store is unpacked per lane.
844 using bVec = at::vec::Vectorized<scalar_t>;
845 using fVec = at::vec::Vectorized<float>;
846 constexpr int fVecSize = fVec::size();
847 constexpr int bVecSize = bVec::size();
848 alignas(64) scalar_t lane_buf[bVecSize];
849
850 const float g_last = g[size - 1];
851 for (int t = 0; t < size; ++t) {
852 float scale = std::exp(g_last - g[t]);
853 fVec scale_fvec(scale);
854 int d = 0;
855 for (; d <= D - bVecSize; d += bVecSize) {
856 bVec k_bvec = bVec::loadu(k + t * k_strideT + d);
857 fVec k0, k1;
858 std::tie(k0, k1) = at::vec::convert_to_float(k_bvec);
859 k0 = k0 * scale_fvec;
860 k1 = k1 * scale_fvec;
861 bVec out_bvec = at::vec::convert_from_float<scalar_t>(k0, k1);
862 out_bvec.store(lane_buf);
863 for (int lane = 0; lane < bVecSize; ++lane) {
864 k_updated[(d + lane) * CHUNK_SIZE + t] = lane_buf[lane];
865 }
866 }
867 for (; d < D; ++d) {
868 k_updated[d * CHUNK_SIZE + t] = static_cast<scalar_t>(static_cast<float>(k[t * k_strideT + d]) * scale);
869 }
870 }
871 const bVec zero_bvec(static_cast<scalar_t>(0));
872 for (int t = size; t < CHUNK_SIZE; ++t) {
873 int d = 0;
874 for (; d <= D - bVecSize; d += bVecSize) {
875 zero_bvec.store(lane_buf);
876 for (int lane = 0; lane < bVecSize; ++lane) {
877 k_updated[(d + lane) * CHUNK_SIZE + t] = lane_buf[lane];
878 }
879 }
880 for (; d < D; ++d) {
881 k_updated[d * CHUNK_SIZE + t] = static_cast<scalar_t>(0);
882 }
883 }
884 }
885 };
886
887 #if defined(CPU_CAPABILITY_AVX512)
888 template <int CHUNK_SIZE, int D>
889 struct update_key_kernel<at::BFloat16, CHUNK_SIZE, D> {
890 static inline void apply(
891 at::BFloat16* __restrict__ k_updated,
892 const at::BFloat16* __restrict__ k,
893 const float* __restrict__ g,
894 int size,
895 int k_strideT) {
896 static_assert(D % 32 == 0);
897 const int MB = div_up(size, 16);
898 const int KB = D / 16;
899
900 const float g_last = g[size - 1];
901 const __m512 vg_last = _mm512_set1_ps(g_last);
902
903 float scale_arr[16];
904 __m256i va[16];
905
906 // from [C, D](MB, KB) to [D, C](KB, MB)
907 // pad size to 16 in this kernel so that transpose can be done in one loop
908 for (int mb = 0; mb < MB; ++mb) {
909 const int mb_size = std::min(size - mb * 16, 16);
910 // prepare exp(g_last - g)
911 __m512 vg = _mm512_loadu_ps(g + mb * 16);
912 _mm512_storeu_ps(scale_arr, _mm512_fexp_u20_ps(_mm512_sub_ps(vg_last, vg)));
913 for (int kb = 0; kb < KB; ++kb) {
914 const at::BFloat16* __restrict__ k_ptr = k + mb * 16 * k_strideT + kb * 16;
915 at::BFloat16* __restrict__ k_updated_ptr = k_updated + kb * 16 * CHUNK_SIZE + mb * 16;
916 // load 16 regs
917 Unroll<16>{}([&](auto m) {
918 if (m < mb_size) {
919 __m256i v16 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(k_ptr + m * k_strideT));
920 __m512 v32 = _mm512_mul_ps(CVT_BF16_TO_FP32(v16), _mm512_set1_ps(scale_arr[m]));
921 va[m] = (__m256i)_mm512_cvtneps_pbh(v32);
922 } else {
923 va[m] = _mm256_setzero_si256();
924 }
925 });
926 // transpose 16x16
927 transpose_16x16_16bit(va);
928 // store 16 regs
929 Unroll<16>{}(
930 [&](auto k) { _mm256_storeu_si256(reinterpret_cast<__m256i*>(k_updated_ptr + k * CHUNK_SIZE), va[k]); });
931 }
932 }
933 }
934 };
935 #endif
936
937
938 // template head_dim here to reduce extra read
939 // * normal approach: read inputs 2 times:
940 // - reduce: 1R
941 // - scale: 1R + 1W
942 // * keep input data in register:
943 // - reduce: 1R
944 // - scale: 1W
945 template <typename scalar_t, int D>
946 void l2norm_fwd_kernel_impl(
947 scalar_t* __restrict__ query_norm,
948 scalar_t* __restrict__ key_norm,
949 const scalar_t* __restrict__ query,
950 const scalar_t* __restrict__ key,
951 float eps,
952 int64_t T,
953 int64_t H,
954 int64_t q_strideT,
955 int64_t q_strideH,
956 int64_t k_strideT,
957 int64_t k_strideH) {
958 // expected to be contuguous
959 int64_t qn_strideH = D;
960 int64_t kn_strideH = D;
961
962 // parallel on [B, T, H]
963 at::parallel_for(0, T * H, 0, [&](int64_t begin, int64_t end) {
964 int64_t t{0}, h{0};
965 data_index_init(begin, t, T, h, H);
966
967 for (int64_t i = begin; i < end; ++i) {
968 const scalar_t* __restrict__ q_ptr = query + t * q_strideT + h * q_strideH;
969 const scalar_t* __restrict__ k_ptr = key + t * k_strideT + h * k_strideH;
970 scalar_t* __restrict__ qn_ptr = query_norm + i * qn_strideH;
971 scalar_t* __restrict__ kn_ptr = key_norm + i * kn_strideH;
972
973 l2norm_kernel<scalar_t, D, true>::apply(qn_ptr, q_ptr, eps);
974 l2norm_kernel<scalar_t, D, false>::apply(kn_ptr, k_ptr, eps);
975
976 // move to the next index
977 data_index_step(t, T, h, H);
978 }
979 });
980 }
981
982 // g : [B, T, Hv]
983 // g_ : [B, Hv, NT, C] -> [B, NT, HB, BLOCK_H, C]
984 // cu_seqlens : [num_seqs + 1]
985 // chunk_indices : [NT * 2]
986 template <typename scalar_t, int CHUNK_SIZE>
987 void chunk_local_cumsum_kernel_impl(
988 scalar_t* __restrict__ g_,
989 const scalar_t* __restrict__ g,
990 const int32_t* __restrict__ cu_seqlens,
991 const int32_t* __restrict__ chunk_indices,
992 int64_t Hv,
993 int64_t NT) {
994 constexpr int BLOCK_H = 16;
995 int64_t HB = div_up(Hv, int64_t(BLOCK_H));
996
997 // parallel on [NT * HB] to increase parallelism
998 at::parallel_for(0, NT * HB, 0, [&](int64_t begin, int64_t end) {
999 int64_t nt{0}, hb{0};
1000 data_index_init(begin, nt, NT, hb, HB);
1001
1002 for (int64_t i = begin; i < end; ++i) {
1003 int32_t bs = chunk_indices[nt * 2 + 0];
1004 int32_t batch_offset = cu_seqlens[bs];
1005 int32_t seqlen = cu_seqlens[bs + 1] - cu_seqlens[bs];
1006 int64_t mb_start = chunk_indices[nt * 2 + 1] * CHUNK_SIZE;
1007 int64_t mb_size = std::min(seqlen - mb_start, int64_t(CHUNK_SIZE));
1008 int64_t hb_size = std::min(Hv - hb * BLOCK_H, int64_t(BLOCK_H));
1009
1010 const scalar_t* __restrict__ g_ptr = g + (batch_offset + mb_start) * Hv + hb * BLOCK_H;
1011 scalar_t* __restrict__ gsum_ptr = g_ + nt * (Hv * CHUNK_SIZE) + hb * (BLOCK_H * CHUNK_SIZE);
1012 cumsum_kernel<scalar_t, CHUNK_SIZE, BLOCK_H>::apply(gsum_ptr, g_ptr, mb_size, hb_size, Hv, CHUNK_SIZE);
1013
1014 // move to the next index
1015 data_index_step(nt, NT, hb, HB);
1016 }
1017 });
1018 }
1019
1020 #define DECL_BUF(type, name, size_expr) alignas(64) type name[(size_expr)]
1021 #define DECL_ZERO_BUF(type, name, size_expr) \
1022 DECL_BUF(type, name, size_expr); \
1023 fill_stub<type, (size_expr)>(name, 0.f)
1024
1025 // w : [B, T, Hv, D]
1026 // u : [B, T, Hv, Dv]
1027 // d : [B, NT, Hv, C, C]
1028 // k : [B, T, H, D]
1029 // v : [B, T, Hv, Dv]
1030 // g : [B, NT, Hv, C]
1031 // beta : [B, T, Hv]
1032 // cu_seqlens : [num_seqs + 1]
1033 // chunk_indices : [NT * 2]
1034 template <typename scalar_t, int D, int CHUNK_SIZE>
1035 void chunk_gated_delta_rule_fwd_intra_kernel_impl(
1036 scalar_t* __restrict__ w,
1037 scalar_t* __restrict__ u,
1038 float* __restrict__ d,
1039 const scalar_t* __restrict__ k,
1040 const scalar_t* __restrict__ v,
1041 const float* __restrict__ g,
1042 const scalar_t* __restrict__ beta,
1043 const int32_t* __restrict__ cu_seqlens,
1044 const int32_t* __restrict__ chunk_indices,
1045 int64_t H,
1046 int64_t Hv,
1047 int64_t NT,
1048 int64_t k_strideT,
1049 int64_t k_strideH,
1050 int64_t v_strideT,
1051 int64_t v_strideH) {
1052 // head group, expect to be 1,2,4 for qwen3.5
1053 const int64_t HG = Hv / H;
1054
1055 // strides
1056 const int64_t w_strideT = Hv * D;
1057 const int64_t w_strideH = D;
1058 const int64_t u_strideT = Hv * D;
1059 const int64_t u_strideH = D;
1060
1061 // [NB]: parallel on [NT, H]
1062 // * parallel on num_heads and go sequential on num_heads_v,
1063 // * avoid instantialize k_beta (beta * k)
1064 // * compute key @ key^T * beta instead of k_beta @ key^T, same as triton impl
1065 // * compute key @ key^T once for each k head index and reuse for v head index
1066 at::parallel_for(0, NT * H, 0, [&](int64_t begin, int64_t end) {
1067 int64_t nt{0}, h{0};
1068 data_index_init(begin, nt, NT, h, H);
1069
1070 // thread local temp buffer
1071 DECL_ZERO_BUF(scalar_t, tmp, CHUNK_SIZE * D);
1072 DECL_ZERO_BUF(scalar_t, tmp2, CHUNK_SIZE * D);
1073 DECL_ZERO_BUF(float, attn, CHUNK_SIZE* CHUNK_SIZE);
1074 DECL_ZERO_BUF(scalar_t, attn2, CHUNK_SIZE * CHUNK_SIZE);
1075 DECL_ZERO_BUF(float, tmp3, CHUNK_SIZE* D);
1076
1077 // alias
1078 scalar_t* __restrict__ k_packed = tmp;
1079 scalar_t* __restrict__ k_beta = tmp;
1080 scalar_t* __restrict__ v_beta = tmp;
1081 scalar_t* __restrict__ k_beta_packed = tmp2;
1082 scalar_t* __restrict__ v_beta_packed = tmp2;
1083 float* __restrict__ k_updated = tmp3;
1084 float* __restrict__ v_updated = tmp3;
1085
1086 for (int64_t i = begin; i < end; ++i) {
1087 int32_t bs = chunk_indices[nt * 2 + 0];
1088 int32_t batch_offset = cu_seqlens[bs];
1089 int32_t seqlen = cu_seqlens[bs + 1] - cu_seqlens[bs];
1090 int64_t mb_start = chunk_indices[nt * 2 + 1] * CHUNK_SIZE;
1091 int64_t mb_size = std::min(seqlen - mb_start, int64_t(CHUNK_SIZE));
1092
1093 // mb_size` is K in 5.c, 5.g, pad to TILE_K;
1094 const int64_t padded_mb_size = div_up((int)mb_size, TILE_K) * TILE_K;
1095
1096 // step 1: decay_mask = ((g.unsqueeze(-1) - g.unsqueeze(-2)).tril().exp().float()).tril()
1097 for (int64_t hv = h * HG; hv < h * HG + HG; ++hv) {
1098 const float* __restrict__ g_ptr = g + nt * (Hv * CHUNK_SIZE) + hv * CHUNK_SIZE;
1099 float* __restrict__ d_ptr = d + nt * (Hv * CHUNK_SIZE * CHUNK_SIZE) + hv * (CHUNK_SIZE * CHUNK_SIZE);
1100 decay_mask_kernel<float, CHUNK_SIZE>::apply(d_ptr, g_ptr);
1101 }
1102
1103 // step 2: attn = key @ key^T
1104 const scalar_t* __restrict__ k_ptr = k + (batch_offset + mb_start) * k_strideT + h * k_strideH;
1105 if constexpr (brgemm_supported()) {
1106 pack_vnni<scalar_t>(
1107 /* dst */ k_packed,
1108 /* src */ k_ptr,
1109 /* N */ mb_size,
1110 /* K */ D,
1111 /* ld_src */ k_strideT,
1112 /* ld_dst */ CHUNK_SIZE);
1113
1114 at::native::cpublas::brgemm(
1115 /* M */ mb_size,
1116 /* N */ mb_size,
1117 /* K */ D,
1118 /* lda */ k_strideT,
1119 /* ldb */ CHUNK_SIZE,
1120 /* ldc */ CHUNK_SIZE,
1121 /* add_C */ false,
1122 /* A */ k_ptr,
1123 /* B */ k_packed,
1124 /* C */ attn);
1125 } else {
1126 blas_gemm(
1127 at::native::TransposeType::Transpose,
1128 at::native::TransposeType::NoTranspose,
1129 mb_size,
1130 mb_size,
1131 D,
1132 1.0f,
1133 k_ptr,
1134 k_strideT,
1135 k_ptr,
1136 k_strideT,
1137 0.0f,
1138 attn,
1139 CHUNK_SIZE);
1140 }
1141
1142 for (int64_t hv = h * HG; hv < h * HG + HG; ++hv) {
1143 // step 3: attn2 = -attn * beta * d
1144 const scalar_t* __restrict__ beta_ptr = beta + (batch_offset + mb_start) * Hv + hv;
1145 const float* __restrict__ d_ptr = d + nt * (Hv * CHUNK_SIZE * CHUNK_SIZE) + hv * (CHUNK_SIZE * CHUNK_SIZE);
1146 apply_mask_kernel<scalar_t, CHUNK_SIZE, true>::apply(attn2, attn, beta_ptr, d_ptr, mb_size, Hv);
1147
1148 // step 4: solve_tril(attn2) -> (I + L)^{-1}, L = strict-lower from step 3
1149 // for i in 1..C-1: attn2[i, :i] += (attn2[i, :i] * attn2[:i, :i]).sum(-1)
1150 // attn2 += eye(C)
1151 solve_tril_kernel<scalar_t, CHUNK_SIZE>::apply(attn2, mb_size);
1152
1153 // step 5: recompute_w_u
1154 // w = attn2 @ (k_beta * g.exp().unsqueeze(-1))
1155 // u = attn2 @ value * beta.unsqueeze(-1)
1156 const float* __restrict__ g_ptr = g + nt * (Hv * CHUNK_SIZE) + hv * CHUNK_SIZE;
1157 const scalar_t* __restrict__ v_ptr = v + (batch_offset + mb_start) * v_strideT + hv * v_strideH;
1158
1159 // 5.a key = key * beta * g.exp
1160 apply_beta_kernel<scalar_t, CHUNK_SIZE, D, true, true>::apply(
1161 k_beta, k_ptr, beta_ptr, g_ptr, mb_size, k_strideT, D, Hv);
1162
1163 // 5.b pack key
1164 if constexpr (brgemm_supported()) {
1165 pack_vnni2<scalar_t>(
1166 /* dst */ k_beta_packed,
1167 /* src */ k_beta,
1168 /* K */ mb_size,
1169 /* N */ D,
1170 /* ld_src */ D,
1171 /* ld_dst */ D);
1172
1173 // 5.c w = attn2 @ k_beta
1174 at::native::cpublas::brgemm(
1175 /* M */ mb_size,
1176 /* N */ D,
1177 /* K */ padded_mb_size, // mb_size
1178 /* lda */ CHUNK_SIZE,
1179 /* ldb */ D,
1180 /* ldc */ D,
1181 /* add_C */ false,
1182 /* A */ attn2,
1183 /* B */ k_beta_packed,
1184 /* C */ k_updated);
1185 } else {
1186 blas_gemm(
1187 at::native::TransposeType::NoTranspose,
1188 at::native::TransposeType::NoTranspose,
1189 D,
1190 mb_size,
1191 padded_mb_size,
1192 1.0f,
1193 k_beta,
1194 D,
1195 attn2,
1196 CHUNK_SIZE,
1197 0.0f,
1198 k_updated,
1199 D);
1200 }
1201
1202 // 5.d k_updated -> w
1203 scalar_t* __restrict__ w_ptr = w + (batch_offset + mb_start) * w_strideT + hv * w_strideH;
1204 update_kernel<scalar_t, D>::apply(w_ptr, k_updated, mb_size, D, w_strideT);
1205
1206 // 5.e value = value * beta
1207 apply_beta_kernel<scalar_t, CHUNK_SIZE, D, true, false>::apply(
1208 v_beta, v_ptr, beta_ptr, nullptr, mb_size, v_strideT, D, Hv);
1209
1210 // 5.f pack value
1211 if constexpr (brgemm_supported()) {
1212 pack_vnni2<scalar_t>(
1213 /* dst */ v_beta_packed,
1214 /* src */ v_beta,
1215 /* K */ mb_size,
1216 /* N */ D,
1217 /* ld_src */ D,
1218 /* ld_dst */ D);
1219
1220 // 5.g u = attn2 @ v_beta
1221 at::native::cpublas::brgemm(
1222 /* M */ mb_size,
1223 /* N */ D,
1224 /* K */ padded_mb_size, // mb_size
1225 /* lda */ CHUNK_SIZE,
1226 /* ldb */ D,
1227 /* ldc */ D,
1228 /* add_C */ false,
1229 /* A */ attn2,
1230 /* B */ v_beta_packed,
1231 /* C */ v_updated);
1232 } else {
1233 blas_gemm(
1234 at::native::TransposeType::NoTranspose,
1235 at::native::TransposeType::NoTranspose,
1236 D,
1237 mb_size,
1238 padded_mb_size,
1239 1.0f,
1240 v_beta,
1241 D,
1242 attn2,
1243 CHUNK_SIZE,
1244 0.0f,
1245 v_updated,
1246 D);
1247 }
1248
1249 // 5.h v_updated -> u
1250 scalar_t* __restrict__ u_ptr = u + (batch_offset + mb_start) * u_strideT + hv * u_strideH;
1251 update_kernel<scalar_t, D>::apply(u_ptr, v_updated, mb_size, D, u_strideT);
1252 }
1253
1254 // move to the next index
1255 data_index_step(nt, NT, h, H);
1256 }
1257 at::native::cpublas::brgemm_release();
1258 });
1259 }
1260
1261 //
1262 // out : [B, T, Hv, Dv]
1263 // state : [num_seqs, Hv, Dv, D]
1264 // q : [B, T, H, D]
1265 // k : [B, T, H, D]
1266 // w : [B, T, Hv, D]
1267 // u : [B, T, Hv, Dv]
1268 // g : [B, NT, Hv, C]
1269 // d : [B, NT, Hv, C, C]
1270 // cu_seqlens : [num_seqs + 1]
1271 // chunk_offsets : [num_seqs + 1]
1272 template <typename scalar_t, typename state_t, int D, int CHUNK_SIZE>
1273 void chunk_gated_delta_rule_fwd_inter_kernel_impl(
1274 scalar_t* __restrict__ out,
1275 state_t* __restrict__ state,
1276 const int32_t* __restrict__ indices,
1277 const scalar_t* __restrict__ q,
1278 const scalar_t* __restrict__ k,
1279 const scalar_t* __restrict__ w,
1280 const scalar_t* __restrict__ u,
1281 const float* __restrict__ g,
1282 const float* __restrict__ d,
1283 const int32_t* __restrict__ cu_seqlens,
1284 const int32_t* __restrict__ chunk_offsets,
1285 int64_t H,
1286 int64_t Hv,
1287 int64_t num_seqs,
1288 int64_t q_strideT,
1289 int64_t q_strideH,
1290 int64_t k_strideT,
1291 int64_t k_strideH,
1292 int64_t state_strideS) {
1293 // head group, expect to be 1,2,4 for qwen3.5
1294 const int64_t HG = Hv / H;
1295
1296 // strides
1297 const int64_t w_strideT = Hv * D;
1298 const int64_t w_strideH = D;
1299 const int64_t u_strideT = Hv * D;
1300 const int64_t u_strideH = D;
1301 const int64_t o_strideT = Hv * D;
1302 const int64_t o_strideH = D;
1303
1304 // [NB]: parallel on [num_seqs, Hv]
1305 // * choose to parallel on Hv instead of H, though this means q @ kT has duplicated compute
1306 // * H might be 16 which is not enough to use 32C when num_seqs is small
1307 at::parallel_for(0, num_seqs * Hv, 0, [&](int64_t begin, int64_t end) {
1308 int64_t bs{0}, hv{0};
1309 data_index_init(begin, bs, num_seqs, hv, Hv);
1310
1311 // thread local temp buffer
1312 DECL_ZERO_BUF(scalar_t, tmp, CHUNK_SIZE * D);
1313 DECL_ZERO_BUF(scalar_t, tmp2, D * D);
1314 DECL_ZERO_BUF(float, state_tmp, D * D);
1315 DECL_ZERO_BUF(float, tmp3, CHUNK_SIZE* D);
1316 DECL_ZERO_BUF(scalar_t, tmp4, CHUNK_SIZE * D);
1317 DECL_ZERO_BUF(float, attn, CHUNK_SIZE* CHUNK_SIZE);
1318 DECL_ZERO_BUF(scalar_t, attn2, CHUNK_SIZE * CHUNK_SIZE);
1319
1320 // alias
1321 scalar_t* __restrict__ k_packed = tmp;
1322 scalar_t* __restrict__ s_packed = tmp2;
1323 float* __restrict__ v_prime = tmp3;
1324 scalar_t* __restrict__ v_prime2 = tmp;
1325 float* __restrict__ attn_inter = tmp3;
1326 scalar_t* __restrict__ qg_exp = tmp4;
1327 scalar_t* __restrict__ v_packed = tmp4;
1328 scalar_t* __restrict__ k_updated = tmp;
1329
1330 for (int64_t i = begin; i < end; ++i) {
1331 int64_t h = hv / HG;
1332 int32_t batch_offset = cu_seqlens[bs];
1333 int32_t seqlen = cu_seqlens[bs + 1] - cu_seqlens[bs];
1334 int64_t nt = chunk_offsets[bs];
1335
1336 for (int64_t mb_start = 0; mb_start < seqlen; mb_start += CHUNK_SIZE, ++nt) {
1337 int64_t mb_size = std::min(seqlen - mb_start, int64_t(CHUNK_SIZE));
1338
1339 // mb_size` is K in 4.a, pad to TILE_K;
1340 const int64_t padded_mb_size = div_up((int)mb_size, TILE_K) * TILE_K;
1341
1342 // step 1.a: attn = query @ key^T
1343 // attn_i = (q_i @ k_i.transpose(-1, -2) * decay_mask[:, :, i]).masked_fill_(mask, 0)
1344 const scalar_t* __restrict__ q_ptr = q + (batch_offset + mb_start) * q_strideT + h * q_strideH;
1345 const scalar_t* __restrict__ k_ptr = k + (batch_offset + mb_start) * k_strideT + h * k_strideH;
1346 if constexpr (brgemm_supported()) {
1347 pack_vnni<scalar_t>(
1348 /* dst */ k_packed,
1349 /* src */ k_ptr,
1350 /* N */ mb_size,
1351 /* K */ D,
1352 /* ld_src */ k_strideT,
1353 /* ld_dst */ CHUNK_SIZE);
1354
1355 at::native::cpublas::brgemm(
1356 /* M */ mb_size,
1357 /* N */ mb_size,
1358 /* K */ D,
1359 /* lda */ q_strideT,
1360 /* ldb */ CHUNK_SIZE,
1361 /* ldc */ CHUNK_SIZE,
1362 /* add_C */ false,
1363 /* A */ q_ptr,
1364 /* B */ k_packed,
1365 /* C */ attn);
1366 } else {
1367 blas_gemm(
1368 at::native::TransposeType::Transpose,
1369 at::native::TransposeType::NoTranspose,
1370 mb_size,
1371 mb_size,
1372 D,
1373 1.0f,
1374 k_ptr,
1375 k_strideT,
1376 q_ptr,
1377 q_strideT,
1378 0.0f,
1379 attn,
1380 CHUNK_SIZE);
1381 }
1382
1383 // step 1.b: attn = attn * decay_mask.masked_fill_(mask, 0)
1384 const float* __restrict__ d_ptr = d + nt * (Hv * CHUNK_SIZE * CHUNK_SIZE) + hv * (CHUNK_SIZE * CHUNK_SIZE);
1385 apply_mask_kernel<scalar_t, CHUNK_SIZE, false>::apply(attn2, attn, nullptr, d_ptr, mb_size);
1386
1387 // step 2.a: v' = w @ state (fuse state *= exp(g_last) with packing)
1388 state_t* __restrict__ state_slot =
1389 state + indices[bs] * state_strideS + hv * (D * D);
1390 float* s_ptr;
1391 if constexpr (std::is_same_v<state_t, float>) {
1392 s_ptr = state_slot;
1393 } else {
1394 using bVec = at::vec::Vectorized<state_t>;
1395 using fVec = at::vec::Vectorized<float>;
1396 constexpr int kVecSize = bVec::size();
1397 constexpr int fVecSize = fVec::size();
1398 int64_t d0 = 0;
1399 for (; d0 + kVecSize <= D * D; d0 += kVecSize) {
1400 auto [state_vec0, state_vec1] = load_float_vec2(state_slot + d0);
1401 state_vec0.store(state_tmp + d0);
1402 state_vec1.store(state_tmp + d0 + fVecSize);
1403 }
1404 for (; d0 < D * D; ++d0) {
1405 state_tmp[d0] = static_cast<float>(state_slot[d0]);
1406 }
1407 s_ptr = state_tmp;
1408 }
1409 const float* __restrict__ g_ptr = g + nt * (Hv * CHUNK_SIZE) + hv * (CHUNK_SIZE);
1410 float g_last = g_ptr[mb_size - 1];
1411 const scalar_t* __restrict__ w_ptr = w + (batch_offset + mb_start) * w_strideT + hv * w_strideH;
1412 if constexpr (brgemm_supported()) {
1413 pack_vnni2<scalar_t, D, D>(
1414 /* dst */ s_packed,
1415 /* src */ s_ptr,
1416 /* g_last */ g_last,
1417 /* ld_src */ D,
1418 /* ld_dst */ D);
1419
1420 at::native::cpublas::brgemm(
1421 /* M */ mb_size,
1422 /* N */ D,
1423 /* K */ D,
1424 /* lda */ w_strideT,
1425 /* ldb */ D,
1426 /* ldc */ D,
1427 /* add_C */ false,
1428 /* A */ w_ptr,
1429 /* B */ s_packed,
1430 /* C */ v_prime);
1431 } else {
1432 // brgemm_supported()==false path: pack_vnni2 above packs the
1433 // *unscaled* state into its dst (for this GEMM's B operand) while
1434 // separately scaling src in place by exp(g_last) (consumed later,
1435 // at step 5.3's state accumulation). Replicate both halves in the
1436 // same order: snapshot s_ptr into s_packed BEFORE scaling s_ptr,
1437 // not after, since the GEMM below needs the pre-scale state.
1438 float g_last_scale = std::exp(g_last);
1439 for (int64_t d0 = 0; d0 < D * D; ++d0) {
1440 s_packed[d0] = static_cast<scalar_t>(s_ptr[d0]);
1441 s_ptr[d0] *= g_last_scale;
1442 }
1443 blas_gemm(
1444 at::native::TransposeType::NoTranspose,
1445 at::native::TransposeType::NoTranspose,
1446 D,
1447 mb_size,
1448 D,
1449 1.0f,
1450 s_packed,
1451 D,
1452 w_ptr,
1453 w_strideT,
1454 0.0f,
1455 v_prime,
1456 D);
1457 }
1458
1459 // step 2.b: v2' = u - v'
1460 const scalar_t* __restrict__ u_ptr = u + (batch_offset + mb_start) * u_strideT + hv * u_strideH;
1461 update_value_kernel<scalar_t, D>::apply(v_prime2, u_ptr, v_prime, mb_size, padded_mb_size, u_strideT);
1462
1463 // step 3.a: qg_exp = q * exp(g)
1464 apply_beta_kernel<scalar_t, CHUNK_SIZE, D, false, true>::apply(
1465 qg_exp, q_ptr, nullptr, g_ptr, mb_size, q_strideT, D, /*b_stride*/ 0);
1466
1467 // step 3.b: attn_inter = qg_exp @ state
1468 if constexpr (brgemm_supported()) {
1469 at::native::cpublas::brgemm(
1470 /* M */ mb_size,
1471 /* N */ D,
1472 /* K */ D,
1473 /* lda */ D,
1474 /* ldb */ D,
1475 /* ldc */ D,
1476 /* add_C */ false,
1477 /* A */ qg_exp,
1478 /* B */ s_packed,
1479 /* C */ attn_inter);
1480 } else {
1481 blas_gemm(
1482 at::native::TransposeType::NoTranspose,
1483 at::native::TransposeType::NoTranspose,
1484 D,
1485 mb_size,
1486 D,
1487 1.0f,
1488 s_packed,
1489 D,
1490 qg_exp,
1491 D,
1492 0.0f,
1493 attn_inter,
1494 D);
1495 }
1496
1497 // step 4.a: attn_inter += attn2 @ v2'
1498 if constexpr (brgemm_supported()) {
1499 pack_vnni2<scalar_t>(
1500 /* dst */ v_packed,
1501 /* src */ v_prime2,
1502 /* K */ padded_mb_size,
1503 /* N */ D,
1504 /* ld_src */ D,
1505 /* ld_dst */ D);
1506
1507 at::native::cpublas::brgemm(
1508 /* M */ mb_size,
1509 /* N */ D,
1510 /* K */ padded_mb_size,
1511 /* lda */ CHUNK_SIZE,
1512 /* ldb */ D,
1513 /* ldc */ D,
1514 /* add_C */ true,
1515 /* A */ attn2,
1516 /* B */ v_packed,
1517 /* C */ attn_inter);
1518 } else {
1519 blas_gemm(
1520 at::native::TransposeType::NoTranspose,
1521 at::native::TransposeType::NoTranspose,
1522 D,
1523 mb_size,
1524 padded_mb_size,
1525 1.0f,
1526 v_prime2,
1527 D,
1528 attn2,
1529 CHUNK_SIZE,
1530 1.0f,
1531 attn_inter,
1532 D);
1533 }
1534
1535 // step 4.b: write attn_inter -> out
1536 scalar_t* __restrict__ o_ptr = out + (batch_offset + mb_start) * o_strideT + hv * o_strideH;
1537 update_kernel<scalar_t, D>::apply(o_ptr, attn_inter, mb_size, D, o_strideT);
1538
1539 // brgemm_supported()==false path: step 5.2 below overwrites k_updated,
1540 // which aliases the same buffer as v_prime2 (both are `tmp`). Snapshot
1541 // v_prime2 into v_packed (otherwise brgemm-only, and free by now since
1542 // qg_exp was consumed at step 3.b) before that happens, so step 5.3
1543 // doesn't read k_updated's data under the v_prime2 name.
1544 if constexpr (!brgemm_supported()) {
1545 std::copy(v_prime2, v_prime2 + padded_mb_size * D, v_packed);
1546 }
1547
1548 // step 5: update state
1549 // state_new = state * exp(g_last) + (k * exp(g_last - g)).T @ v2'
1550
1551 // step 5.1 state *= exp(g_last) fused with step 2.a
1552
1553 // step 5.2 k' = k * exp(g_last - g).T; TODO: fuse this with 1.a
1554 update_key_kernel<scalar_t, CHUNK_SIZE, D>::apply(k_updated, k_ptr, g_ptr, mb_size, k_strideT);
1555
1556 // step 5.3 state += k' @ v2'
1557 if constexpr (brgemm_supported()) {
1558 at::native::cpublas::brgemm(
1559 /* M */ D,
1560 /* N */ D,
1561 /* K */ padded_mb_size, // mb_size
1562 /* lda */ CHUNK_SIZE,
1563 /* ldb */ D,
1564 /* ldc */ D,
1565 /* add_C */ true,
1566 /* A */ k_updated,
1567 /* B */ v_packed,
1568 /* C */ s_ptr);
1569 } else {
1570 blas_gemm(
1571 at::native::TransposeType::NoTranspose,
1572 at::native::TransposeType::NoTranspose,
1573 D,
1574 D,
1575 padded_mb_size,
1576 1.0f,
1577 v_packed,
1578 D,
1579 k_updated,
1580 CHUNK_SIZE,
1581 1.0f,
1582 s_ptr,
1583 D);
1584 }
1585
1586 if constexpr (!std::is_same_v<state_t, float>) {
1587 using bVec = at::vec::Vectorized<state_t>;
1588 using fVec = at::vec::Vectorized<float>;
1589 constexpr int kVecSize = bVec::size();
1590 constexpr int fVecSize = fVec::size();
1591 int64_t d0 = 0;
1592 for (; d0 + kVecSize <= D * D; d0 += kVecSize) {
1593 fVec state_vec0 = fVec::loadu(s_ptr + d0);
1594 fVec state_vec1 = fVec::loadu(s_ptr + d0 + fVecSize);
1595 store_state_vec2<state_t>(state_slot + d0, state_vec0, state_vec1);
1596 }
1597 for (; d0 < D * D; ++d0) {
1598 state_slot[d0] = static_cast<state_t>(s_ptr[d0]);
1599 }
1600 }
1601 }
1602
1603 // move to the next index
1604 data_index_step(bs, num_seqs, hv, Hv);
1605 }
1606 at::native::cpublas::brgemm_release();
1607 });
1608 }
1609
1610 inline float softplus(float x, double threshold = 20.0) {
1611 if (x > threshold)
1612 return x;
1613 else if (x < -threshold)
1614 return std::exp(x);
1615 else
1616 return std::log1p(std::exp(x));
1617 }
1618
1619 inline at::vec::Vectorized<float> softplus(const at::vec::Vectorized<float>& x, double threshold = 20.0) {
1620 using Vec = at::vec::Vectorized<float>;
1621 Vec mask_hi = x > Vec(threshold);
1622 Vec mask_lo = x < Vec(-threshold);
1623
1624 Vec expx = x.exp_u20();
1625 Vec log1pex = (expx + Vec(1.0f)).log();
1626
1627 return Vec::blendv(Vec::blendv(log1pex, expx, mask_lo), x, mask_hi);
1628 }
1629
1630 template <typename scalar_t, typename param_t, typename state_t>
1631 void fused_sigmoid_gating_delta_rule_update_kernel_impl(
1632 const scalar_t* __restrict__ q_ptr,
1633 const scalar_t* __restrict__ k_ptr,
1634 const scalar_t* __restrict__ v_ptr,
1635 const param_t* __restrict__ A_log_ptr,
1636 const scalar_t* __restrict__ a_ptr,
1637 const scalar_t* __restrict__ dt_bias_ptr,
1638 const scalar_t* __restrict__ b_ptr,
1639 const int32_t* __restrict__ indices_ptr,
1640 state_t* __restrict__ state_ptr,
1641 scalar_t* __restrict__ o_ptr,
1642 float* __restrict__ qk_scale_buf,
1643 int64_t seq_len,
1644 int64_t batch_size,
1645 int64_t num_heads,
1646 int64_t head_dim,
1647 int64_t v_num_heads,
1648 int64_t v_head_dim,
1649 int64_t q_strideB,
1650 int64_t q_strideS,
1651 int64_t q_strideH,
1652 int64_t k_strideB,
1653 int64_t k_strideS,
1654 int64_t k_strideH,
1655 int64_t v_strideB,
1656 int64_t v_strideS,
1657 int64_t v_strideH,
1658 int64_t state_slot_stride,
1659 bool use_qk_l2norm_in_kernel,
1660 double softplus_threshold) {
1661 using bVec = at::vec::Vectorized<scalar_t>;
1662 using fVec = at::vec::Vectorized<float>;
1663
1664 constexpr int64_t VecSize = bVec::size();
1665 constexpr int64_t fVecSize = fVec::size();
1666 int64_t group_size = v_num_heads / num_heads;
1667 double scale = 1 / std::sqrt(head_dim);
1668 fVec scale_vec = fVec(scale);
1669 if (use_qk_l2norm_in_kernel) {
1670 float eps = 1e-5;
1671 at::parallel_for(0, batch_size * seq_len * num_heads, 0, [&](int64_t begin, int64_t end) {
1672 int64_t bi{0}, si{0}, ni{0};
1673 data_index_init(begin, bi, batch_size, si, seq_len, ni, num_heads);
1674 for (int64_t i = begin; i < end; ++i) {
1675 float sum_q = float(0);
1676 float sum_k = float(0);
1677 fVec sum_q_fvec = fVec(float(0));
1678 fVec sum_k_fvec = fVec(float(0));
1679 int64_t q_offset = bi * q_strideB + si * q_strideS + ni * q_strideH;
1680 int64_t k_offset = bi * k_strideB + si * k_strideS + ni * k_strideH;
1681 int64_t q_scale_offset = bi * seq_len * num_heads + si * num_heads + ni;
1682 int64_t k_scale_offset = q_scale_offset + batch_size * seq_len * num_heads;
1683 int64_t d;
1684 #pragma GCC unroll 4
1685 for (d = 0; d <= head_dim - VecSize; d += VecSize) {
1686 auto [q_fvec0, q_fvec1] = load_float_vec2(q_ptr + q_offset + d);
1687 sum_q_fvec += q_fvec0 * q_fvec0;
1688 sum_q_fvec += q_fvec1 * q_fvec1;
1689 auto [k_fvec0, k_fvec1] = load_float_vec2(k_ptr + k_offset + d);
1690 sum_k_fvec += k_fvec0 * k_fvec0;
1691 sum_k_fvec += k_fvec1 * k_fvec1;
1692 }
1693 #pragma GCC unroll 4
1694 for (; d < head_dim; ++d) {
1695 float q_val = static_cast<float>(q_ptr[q_offset + d]);
1696 sum_q += q_val * q_val;
1697 float k_val = static_cast<float>(k_ptr[k_offset + d]);
1698 sum_k += k_val * k_val;
1699 }
1700
1701 sum_q += vec_reduce_sum(sum_q_fvec);
1702 sum_k += vec_reduce_sum(sum_k_fvec);
1703 qk_scale_buf[q_scale_offset] = float(1) / std::sqrt(sum_q + eps);
1704 qk_scale_buf[k_scale_offset] = float(1) / std::sqrt(sum_k + eps);
1705
1706 data_index_step(bi, batch_size, si, seq_len, ni, num_heads);
1707 }
1708 });
1709 }
1710 at::parallel_for(0, batch_size * seq_len * v_num_heads, 0, [&](int64_t begin, int64_t end) {
1711 int64_t bi{0}, si{0}, ni{0};
1712 data_index_init(begin, bi, batch_size, si, seq_len, ni, v_num_heads);
1713 for (int64_t i = begin; i < end; ++i) {
1714 int64_t cache_index = indices_ptr[bi];
1715 int64_t state_offset = cache_index * state_slot_stride + ni * head_dim * v_head_dim;
1716 float g_val = -std::exp(float(A_log_ptr[ni])) *
1717 softplus(float(a_ptr[bi * v_num_heads + ni]) + float(dt_bias_ptr[ni]), softplus_threshold);
1718 float g_val_exp = std::exp(g_val);
1719 fVec g_val_exp_vec = fVec(g_val_exp);
1720 int64_t q_offset = si * q_strideS + bi * q_strideB + (ni / group_size) * q_strideH;
1721 int64_t k_offset = si * k_strideS + bi * k_strideB + (ni / group_size) * k_strideH;
1722 int64_t q_scale_offset = bi * seq_len * num_heads + si * num_heads + (ni / group_size);
1723 int64_t k_scale_offset = q_scale_offset + batch_size * seq_len * num_heads;
1724 float q_scale = use_qk_l2norm_in_kernel ? qk_scale_buf[q_scale_offset] : 1.0f;
1725 float k_scale = use_qk_l2norm_in_kernel ? qk_scale_buf[k_scale_offset] : 1.0f;
1726 int64_t v_offset = si * v_strideS + bi * v_strideB + ni * v_strideH;
1727 int64_t o_offset = ((bi * seq_len + si) * v_num_heads + ni) * v_head_dim;
1728 // See: https://github.com/sgl-project/sglang/pull/26634
1729 float beta_val = 1 / (1 + std::exp(-b_ptr[bi * v_num_heads + ni]));
1730 fVec beta_vec = fVec(beta_val);
1731 int64_t dvi = 0;
1732 for (; dvi <= v_head_dim - VecSize; dvi += VecSize) {
1733 fVec kv_mem_vec0 = fVec(float(0));
1734 fVec kv_mem_vec1 = fVec(float(0));
1735 for (int di = 0; di < head_dim; ++di) {
1736 fVec k_val_vec = fVec(k_ptr[k_offset + di] * k_scale);
1737 auto [state_vec0, state_vec1] = load_float_vec2(state_ptr + state_offset + di * v_head_dim + dvi);
1738 kv_mem_vec0 = kv_mem_vec0 + state_vec0 * g_val_exp_vec * k_val_vec;
1739 kv_mem_vec1 = kv_mem_vec1 + state_vec1 * g_val_exp_vec * k_val_vec;
1740 }
1741 auto [v_vec0, v_vec1] = load_float_vec2(v_ptr + v_offset + dvi);
1742 fVec dt_vec0 = (v_vec0 - kv_mem_vec0) * beta_vec;
1743 fVec dt_vec1 = (v_vec1 - kv_mem_vec1) * beta_vec;
1744 fVec o_vec0 = fVec(float(0));
1745 fVec o_vec1 = fVec(float(0));
1746 for (int di = 0; di < head_dim; ++di) {
1747 fVec q_vec = fVec(q_ptr[q_offset + di] * q_scale);
1748 fVec k_vec = fVec(k_ptr[k_offset + di] * k_scale);
1749 auto [state_vec0, state_vec1] = load_float_vec2(state_ptr + state_offset + di * v_head_dim + dvi);
1750 state_vec0 = state_vec0 * g_val_exp_vec + k_vec * dt_vec0;
1751 state_vec1 = state_vec1 * g_val_exp_vec + k_vec * dt_vec1;
1752 o_vec0 = o_vec0 + state_vec0 * q_vec * scale_vec;
1753 o_vec1 = o_vec1 + state_vec1 * q_vec * scale_vec;
1754 store_state_vec2<state_t>(
1755 state_ptr + state_offset + di * v_head_dim + dvi,
1756 state_vec0,
1757 state_vec1);
1758 }
1759 bVec o_vec = at::vec::convert_from_float<scalar_t>(o_vec0, o_vec1);
1760 o_vec.store(o_ptr + o_offset + dvi);
1761 }
1762 for (; dvi < v_head_dim; ++dvi) {
1763 float kv_mem_val = 0;
1764 for (int di = 0; di < head_dim; ++di) {
1765 float k_val = k_ptr[k_offset + di] * k_scale;
1766 float state_val =
1767 static_cast<float>(state_ptr[state_offset + di * v_head_dim + dvi]) *
1768 g_val_exp;
1769 kv_mem_val += state_val * k_val;
1770 }
1771 float v_val = v_ptr[v_offset + dvi];
1772 float dt_val = (v_val - kv_mem_val) * beta_val;
1773 float o_val = 0;
1774 for (int di = 0; di < head_dim; ++di) {
1775 float q_val = q_ptr[q_offset + di] * q_scale;
1776 float k_val = k_ptr[k_offset + di] * k_scale;
1777 float state_val =
1778 static_cast<float>(state_ptr[state_offset + di * v_head_dim + dvi]) *
1779 g_val_exp;
1780 state_val += k_val * dt_val;
1781 state_ptr[state_offset + di * v_head_dim + dvi] =
1782 static_cast<state_t>(state_val);
1783 o_val += state_val * q_val * scale;
1784 }
1785 o_ptr[o_offset + dvi] = o_val;
1786 }
1787 data_index_step(bi, batch_size, si, seq_len, ni, v_num_heads);
1788 }
1789 });
1790 }
1791
1792 // Speculative-decode variant: processes a varlen batch where each sequence has
1793 // ``q_len`` draft tokens, runs the recurrence sequentially over those tokens
1794 // (inside the kernel, so one dispatch handles the whole draft block), reads the
1795 // initial state from cache slot ``num_accepted-1`` and stores the state *after*
1796 // token ``t`` into cache slot ``t`` (multi-slot rollback, matching the GPU
1797 // kernel). Parallelized over (sequence, v_head); the per-sequence token loop is
1798 // sequential as required by the recurrence.
1799 template <typename scalar_t, typename param_t, typename state_t>
1800 void fused_sigmoid_gating_delta_rule_update_spec_kernel_impl(
1801 const scalar_t* __restrict__ q_ptr, // [T, HK, EK]
1802 const scalar_t* __restrict__ k_ptr, // [T, HK, EK]
1803 const scalar_t* __restrict__ v_ptr, // [T, HV, EV]
1804 const param_t* __restrict__ A_log_ptr,
1805 const scalar_t* __restrict__ a_ptr, // [T, HV]
1806 const scalar_t* __restrict__ dt_bias_ptr,
1807 const scalar_t* __restrict__ b_ptr, // [T, HV]
1808 const int32_t* __restrict__ spec_indices_ptr, // [N, S]
1809 const int32_t* __restrict__ num_accepted_ptr, // [N]
1810 const int32_t* __restrict__ cu_seqlens_ptr, // [N + 1]
1811 state_t* __restrict__ state_ptr,
1812 scalar_t* __restrict__ o_ptr, // [T, HV, EV]
1813 float* __restrict__ qk_scale_buf, // [2, T, HK]
1814 int64_t total_tokens,
1815 int64_t batch_size,
1816 int64_t spec_stride,
1817 int64_t num_heads,
1818 int64_t head_dim,
1819 int64_t v_num_heads,
1820 int64_t v_head_dim,
1821 int64_t q_strideT,
1822 int64_t q_strideH,
1823 int64_t k_strideT,
1824 int64_t k_strideH,
1825 int64_t v_strideT,
1826 int64_t v_strideH,
1827 int64_t state_slot_stride,
1828 bool use_qk_l2norm_in_kernel,
1829 double softplus_threshold) {
1830 using bVec = at::vec::Vectorized<scalar_t>;
1831 using fVec = at::vec::Vectorized<float>;
1832 constexpr int64_t VecSize = bVec::size();
1833 int64_t group_size = v_num_heads / num_heads;
1834 double scale = 1 / std::sqrt((double)head_dim);
1835 fVec scale_vec = fVec((float)scale);
1836
1837 if (use_qk_l2norm_in_kernel) {
1838 float eps = 1e-5f;
1839 at::parallel_for(0, total_tokens * num_heads, 0, [&](int64_t begin, int64_t end) {
1840 for (int64_t i = begin; i < end; ++i) {
1841 int64_t ti = i / num_heads;
1842 int64_t ni = i % num_heads;
1843 const scalar_t* qp = q_ptr + ti * q_strideT + ni * q_strideH;
1844 const scalar_t* kp = k_ptr + ti * k_strideT + ni * k_strideH;
1845 float sq = 0.f, sk = 0.f;
1846 for (int64_t d = 0; d < head_dim; ++d) {
1847 float qv = (float)qp[d];
1848 sq += qv * qv;
1849 float kv = (float)kp[d];
1850 sk += kv * kv;
1851 }
1852 qk_scale_buf[ti * num_heads + ni] = 1.f / std::sqrt(sq + eps);
1853 qk_scale_buf[total_tokens * num_heads + ti * num_heads + ni] = 1.f / std::sqrt(sk + eps);
1854 }
1855 });
1856 }
1857
1858 at::parallel_for(0, batch_size * v_num_heads, 0, [&](int64_t begin, int64_t end) {
1859 for (int64_t idx = begin; idx < end; ++idx) {
1860 int64_t bi = idx / v_num_heads;
1861 int64_t ni = idx % v_num_heads;
1862 int64_t kh = ni / group_size;
1863 int64_t q_start = cu_seqlens_ptr[bi];
1864 int64_t q_len = cu_seqlens_ptr[bi + 1] - q_start;
1865 if (q_len <= 0) {
1866 continue;
1867 }
1868 int64_t acc = (int64_t)num_accepted_ptr[bi];
1869 // Clamp acc-1 to >=0: when num_accepted is 0 the unclamped index reads
1870 // out of bounds and yields an arbitrary prev_slot used to index the SSM
1871 // state. Mirrors the GPU guard tl.maximum(num_accepted - 1, 0).
1872 int64_t prev_slot =
1873 (int64_t)spec_indices_ptr[bi * spec_stride + (acc > 0 ? acc - 1 : 0)];
1874 for (int64_t t = 0; t < q_len; ++t) {
1875 int64_t cur_slot = (int64_t)spec_indices_ptr[bi * spec_stride + t];
1876 int64_t token = q_start + t;
1877 const state_t* src =
1878 state_ptr + prev_slot * state_slot_stride + ni * head_dim * v_head_dim;
1879 state_t* dst =
1880 state_ptr + cur_slot * state_slot_stride + ni * head_dim * v_head_dim;
1881 float g_val = -std::exp((float)A_log_ptr[ni]) *
1882 softplus((float)a_ptr[token * v_num_heads + ni] + (float)dt_bias_ptr[ni], softplus_threshold);
1883 float g_val_exp = std::exp(g_val);
1884 fVec g_val_exp_vec = fVec(g_val_exp);
1885 float beta_val = 1.f / (1.f + std::exp(-(float)b_ptr[token * v_num_heads + ni]));
1886 fVec beta_vec = fVec(beta_val);
1887 int64_t q_offset = token * q_strideT + kh * q_strideH;
1888 int64_t k_offset = token * k_strideT + kh * k_strideH;
1889 float q_scale = use_qk_l2norm_in_kernel ? qk_scale_buf[token * num_heads + kh] : 1.f;
1890 float k_scale =
1891 use_qk_l2norm_in_kernel ? qk_scale_buf[total_tokens * num_heads + token * num_heads + kh] : 1.f;
1892 int64_t v_offset = token * v_strideT + ni * v_strideH;
1893 int64_t o_offset = (token * v_num_heads + ni) * v_head_dim;
1894 int64_t dvi = 0;
1895 for (; dvi <= v_head_dim - VecSize; dvi += VecSize) {
1896 fVec kv_mem_vec0 = fVec(0.f);
1897 fVec kv_mem_vec1 = fVec(0.f);
1898 for (int di = 0; di < head_dim; ++di) {
1899 fVec k_val_vec = fVec((float)k_ptr[k_offset + di] * k_scale);
1900 auto [sv0, sv1] =
1901 load_float_vec2(src + di * v_head_dim + dvi);
1902 kv_mem_vec0 = kv_mem_vec0 + sv0 * g_val_exp_vec * k_val_vec;
1903 kv_mem_vec1 = kv_mem_vec1 + sv1 * g_val_exp_vec * k_val_vec;
1904 }
1905 bVec v_bvec = bVec::loadu(v_ptr + v_offset + dvi);
1906 fVec v_vec0, v_vec1;
1907 std::tie(v_vec0, v_vec1) = at::vec::convert_to_float(v_bvec);
1908 fVec dt_vec0 = (v_vec0 - kv_mem_vec0) * beta_vec;
1909 fVec dt_vec1 = (v_vec1 - kv_mem_vec1) * beta_vec;
1910 fVec o_vec0 = fVec(0.f);
1911 fVec o_vec1 = fVec(0.f);
1912 for (int di = 0; di < head_dim; ++di) {
1913 fVec q_vec = fVec((float)q_ptr[q_offset + di] * q_scale);
1914 fVec k_vec = fVec((float)k_ptr[k_offset + di] * k_scale);
1915 auto [sv0, sv1] =
1916 load_float_vec2(src + di * v_head_dim + dvi);
1917 sv0 = sv0 * g_val_exp_vec + k_vec * dt_vec0;
1918 sv1 = sv1 * g_val_exp_vec + k_vec * dt_vec1;
1919 o_vec0 = o_vec0 + sv0 * q_vec * scale_vec;
1920 o_vec1 = o_vec1 + sv1 * q_vec * scale_vec;
1921 store_state_vec2<state_t>(
1922 dst + di * v_head_dim + dvi, sv0, sv1);
1923 }
1924 bVec o_vec = at::vec::convert_from_float<scalar_t>(o_vec0, o_vec1);
1925 o_vec.store(o_ptr + o_offset + dvi);
1926 }
1927 for (; dvi < v_head_dim; ++dvi) {
1928 float kv_mem_val = 0.f;
1929 for (int di = 0; di < head_dim; ++di) {
1930 float k_val = (float)k_ptr[k_offset + di] * k_scale;
1931 kv_mem_val += static_cast<float>(src[di * v_head_dim + dvi]) *
1932 g_val_exp * k_val;
1933 }
1934 float v_val = (float)v_ptr[v_offset + dvi];
1935 float dt_val = (v_val - kv_mem_val) * beta_val;
1936 float o_val = 0.f;
1937 for (int di = 0; di < head_dim; ++di) {
1938 float q_val = (float)q_ptr[q_offset + di] * q_scale;
1939 float k_val = (float)k_ptr[k_offset + di] * k_scale;
1940 float ns = static_cast<float>(src[di * v_head_dim + dvi]) *
1941 g_val_exp +
1942 k_val * dt_val;
1943 dst[di * v_head_dim + dvi] = static_cast<state_t>(ns);
1944 o_val += ns * q_val * scale;
1945 }
1946 o_ptr[o_offset + dvi] = (scalar_t)o_val;
1947 }
1948 prev_slot = cur_slot;
1949 }
1950 }
1951 });
1952 }
1953
1954 template <typename scalar_t>
1955 void fused_gdn_gating_kernel_impl(
1956 float* __restrict__ A_log,
1957 const scalar_t* __restrict__ a,
1958 const scalar_t* __restrict__ b,
1959 const scalar_t* __restrict__ dt_bias,
1960 float* __restrict__ out,
1961 scalar_t* __restrict__ beta,
1962 int64_t batch,
1963 int64_t num_heads) {
1964 using bVec = at::vec::Vectorized<scalar_t>;
1965 using fVec = at::vec::Vectorized<float>;
1966 constexpr int vec_size = bVec::size();
1967 constexpr int fvec_size = fVec::size();
1968 const fVec neg_one(-1.0f);
1969 at::parallel_for(0, batch, 0, [&](int64_t begin, int64_t end) {
1970 for (int64_t i = begin; i < end; ++i) {
1971 int64_t j = 0;
1972 for (; j < num_heads - (num_heads % vec_size); j += vec_size) {
1973 auto [A_log_vec0, A_log_vec1] = load_float_vec2(A_log + j);
1974 auto [dt_bias_vec0, dt_bias_vec1] = load_float_vec2(dt_bias + j);
1975 auto [a0, a1] = load_float_vec2(a + i * num_heads + j);
1976 auto [b0, b1] = load_float_vec2(b + i * num_heads + j);
1977
1978 fVec g0 = neg_one * A_log_vec0.exp_u20() * softplus(a0 + dt_bias_vec0);
1979 fVec g1 = neg_one * A_log_vec1.exp_u20() * softplus(a1 + dt_bias_vec1);
1980 fVec beta0 = fast_sigmoid(b0);
1981 fVec beta1 = fast_sigmoid(b1);
1982
1983 g0.store(out + i * num_heads + j);
1984 g1.store(out + i * num_heads + j + fvec_size);
1985 bVec beta_vec = at::vec::convert_from_float<scalar_t>(beta0, beta1);
1986 beta_vec.store(beta + i * num_heads + j);
1987 }
1988 for (; j < num_heads; ++j) {
1989 out[i * num_heads + j] = -std::exp(A_log[j]) * softplus(float(a[i * num_heads + j]) + float(dt_bias[j]));
1990 beta[i * num_heads + j] = 1 / (1 + std::exp(-b[i * num_heads + j]));
1991 }
1992 }
1993 });
1994 }
1995
1996 template <typename scalar_t>
1997 void fused_gdn_gating_kernel_impl(
1998 scalar_t* __restrict__ A_log,
1999 const scalar_t* __restrict__ a,
2000 const scalar_t* __restrict__ b,
2001 const scalar_t* __restrict__ dt_bias,
2002 float* __restrict__ out,
2003 scalar_t* __restrict__ beta,
2004 int64_t batch,
2005 int64_t num_heads) {
2006 using bVec = at::vec::Vectorized<scalar_t>;
2007 using fVec = at::vec::Vectorized<float>;
2008 constexpr int vec_size = bVec::size();
2009 constexpr int fvec_size = fVec::size();
2010 const fVec neg_one(-1.0f);
2011 at::parallel_for(0, batch, 0, [&](int64_t begin, int64_t end) {
2012 for (int64_t i = begin; i < end; ++i) {
2013 int64_t j = 0;
2014 for (; j < num_heads - (num_heads % vec_size); j += vec_size) {
2015 auto [A_log_vec0, A_log_vec1] = load_float_vec2(A_log + j);
2016 auto [dt_bias_vec0, dt_bias_vec1] = load_float_vec2(dt_bias + j);
2017 auto [a0, a1] = load_float_vec2(a + i * num_heads + j);
2018 auto [b0, b1] = load_float_vec2(b + i * num_heads + j);
2019
2020 fVec g0 = neg_one * A_log_vec0.exp_u20() * softplus(a0 + dt_bias_vec0);
2021 fVec g1 = neg_one * A_log_vec1.exp_u20() * softplus(a1 + dt_bias_vec1);
2022 fVec beta0 = fast_sigmoid(b0);
2023 fVec beta1 = fast_sigmoid(b1);
2024
2025 g0.store(out + i * num_heads + j);
2026 g1.store(out + i * num_heads + j + fvec_size);
2027 bVec beta_vec = at::vec::convert_from_float<scalar_t>(beta0, beta1);
2028 beta_vec.store(beta + i * num_heads + j);
2029 }
2030 for (; j < num_heads; ++j) {
2031 out[i * num_heads + j] = -std::exp(float(A_log[j])) * softplus(float(a[i * num_heads + j]) + float(dt_bias[j]));
2032 beta[i * num_heads + j] = 1 / (1 + std::exp(-b[i * num_heads + j]));
2033 }
2034 }
2035 });
2036 }
2037
2038 } // anonymous namespace
2039
2040 template <int CHUNK_SIZE>
2041 std::tuple<at::Tensor, at::Tensor> prepare_chunk_indices(const at::Tensor& cu_seqlens) {
2042 int64_t num_seqs = cu_seqlens.size(0) - 1;
2043 at::Tensor chunk_offsets = at::empty({num_seqs + 1}, cu_seqlens.options());
2044 // get number of chunks and chunk offsets
2045 const int32_t* offsets_data = cu_seqlens.data_ptr<int32_t>();
2046 int32_t num_chunks = 0;
2047 chunk_offsets[0] = 0;
2048 for (int64_t row = 0; row < num_seqs; ++row) {
2049 num_chunks += div_up(offsets_data[row + 1] - offsets_data[row], CHUNK_SIZE);
2050 chunk_offsets[row + 1] = num_chunks;
2051 }
2052 // get chunk indices
2053 at::Tensor chunk_indices = at::empty({num_chunks, 2}, cu_seqlens.options());
2054 int32_t* indices_data = chunk_indices.data_ptr<int32_t>();
2055
2056 int64_t idx = 0;
2057 for (int32_t row = 0; row < num_seqs; ++row) {
2058 int32_t num_chunks = div_up(offsets_data[row + 1] - offsets_data[row], CHUNK_SIZE);
2059
2060 for (int32_t col = 0; col < num_chunks; ++col) {
2061 indices_data[idx * 2 + 0] = row;
2062 indices_data[idx * 2 + 1] = col;
2063 idx++;
2064 }
2065 }
2066 return std::make_tuple(chunk_indices, chunk_offsets);
2067 }
2068
2069 #define DISPATCH_HEAD_DIM_CASE(launch_macro, hd) \
2070 case hd: { \
2071 launch_macro(hd); \
2072 break; \
2073 }
2074
2075 // [NB]: add new head_dim support here
2076 #define DISPATCH_HEAD_DIM(dim, launch_macro) \
2077 switch (dim) { \
2078 DISPATCH_HEAD_DIM_CASE(launch_macro, 64) \
2079 DISPATCH_HEAD_DIM_CASE(launch_macro, 128) \
2080 default: \
2081 TORCH_CHECK(false, "Unexpected head dim size, ", dim); \
2082 }
2083
2084 #define LAUNCH_L2NORM_KERNEL(HD) \
2085 l2norm_fwd_kernel_impl<scalar_t, HD>( \
2086 query_norm.data_ptr<scalar_t>(), \
2087 key_norm.data_ptr<scalar_t>(), \
2088 query.data_ptr<scalar_t>(), \
2089 key.data_ptr<scalar_t>(), \
2090 eps, \
2091 T, \
2092 H, \
2093 query.stride(1), \
2094 query.stride(2), \
2095 key.stride(1), \
2096 key.stride(2));
2097
2098 std::tuple<at::Tensor, at::Tensor> l2norm_fwd(const at::Tensor& query, const at::Tensor& key, double eps) {
2099 int64_t B = query.size(0);
2100 int64_t T = query.size(1);
2101 int64_t H = query.size(2);
2102 int64_t D = query.size(3);
2103
2104 at::Tensor query_norm = at::empty_like(query);
2105 at::Tensor key_norm = at::empty_like(key);
2106
2107 AT_DISPATCH_REDUCED_FLOATING_TYPES(
2108 query.scalar_type(), "l2norm_fwd", [&] { DISPATCH_HEAD_DIM(D, LAUNCH_L2NORM_KERNEL); });
2109
2110 return std::make_tuple(query_norm, key_norm);
2111 }
2112
2113 // [NB]: instantiate decay_mask to avoid heavy recomputation in the kernel with exp
2114 template <int CHUNK_SIZE>
2115 at::Tensor chunk_local_cumsum(const at::Tensor& g, const at::Tensor& cu_seqlens, const at::Tensor& chunk_indices) {
2116 int64_t B = g.size(0);
2117 // int64_t T = g.size(1);
2118 int64_t Hv = g.size(2);
2119 int64_t NT = chunk_indices.size(0);
2120
2121 at::Tensor g_ = at::empty({B, NT, Hv, CHUNK_SIZE}, g.options());
2122 AT_DISPATCH_FLOATING_TYPES(g.scalar_type(), "chunk_local_cumsum", [&] {
2123 chunk_local_cumsum_kernel_impl<scalar_t, CHUNK_SIZE>(
2124 g_.data_ptr<scalar_t>(),
2125 g.data_ptr<scalar_t>(),
2126 cu_seqlens.data_ptr<int32_t>(),
2127 chunk_indices.data_ptr<int32_t>(),
2128 Hv,
2129 NT);
2130 });
2131 return g_;
2132 }
2133
2134 #define LAUNCH_CHUNK_GATED_DELTA_RULE_FWD_INTRA_KERNEL(HD) \
2135 chunk_gated_delta_rule_fwd_intra_kernel_impl<scalar_t, HD, CHUNK_SIZE>( \
2136 w.data_ptr<scalar_t>(), \
2137 u.data_ptr<scalar_t>(), \
2138 decay_mask.data_ptr<float>(), \
2139 k.data_ptr<scalar_t>(), \
2140 v.data_ptr<scalar_t>(), \
2141 g.data_ptr<float>(), \
2142 beta.data_ptr<scalar_t>(), \
2143 cu_seqlens.data_ptr<int32_t>(), \
2144 chunk_indices.data_ptr<int32_t>(), \
2145 H, \
2146 Hv, \
2147 NT, \
2148 k.stride(1), \
2149 k.stride(2), \
2150 v.stride(1), \
2151 v.stride(2));
2152
2153 template <int CHUNK_SIZE>
2154 std::tuple<at::Tensor, at::Tensor, at::Tensor> chunk_gated_delta_rule_fwd_intra(
2155 const at::Tensor& k,
2156 const at::Tensor& v,
2157 const at::Tensor& g,
2158 const at::Tensor& beta,
2159 const at::Tensor& cu_seqlens,
2160 const at::Tensor& chunk_indices) {
2161 int64_t B = k.size(0);
2162 int64_t T = k.size(1);
2163 int64_t H = k.size(2);
2164 int64_t D = k.size(3);
2165 int64_t Hv = v.size(2);
2166 int64_t Dv = v.size(3);
2167 int64_t NT = chunk_indices.size(0);
2168
2169 at::Tensor w = at::empty({B, T, Hv, D}, k.options()); // BFloat16
2170 at::Tensor u = at::empty({B, T, Hv, Dv}, k.options()); // BFloat16
2171 at::Tensor decay_mask = at::empty({B, NT, Hv, CHUNK_SIZE, CHUNK_SIZE}, g.options()); // Float
2172 AT_DISPATCH_REDUCED_FLOATING_TYPES(k.scalar_type(), "chunk_gated_delta_rule_fwd_intra", [&] {
2173 DISPATCH_HEAD_DIM(D, LAUNCH_CHUNK_GATED_DELTA_RULE_FWD_INTRA_KERNEL);
2174 });
2175
2176 return std::make_tuple(w, u, decay_mask);
2177 }
2178
2179 #define LAUNCH_CHUNK_GATED_DELTA_RULE_FWD_INTER_KERNEL(HD) \
2180 chunk_gated_delta_rule_fwd_inter_kernel_impl<scalar_t, state_t, HD, CHUNK_SIZE>( \
2181 o.data_ptr<scalar_t>(), \
2182 initial_state.data_ptr<state_t>(), \
2183 initial_state_indices.data_ptr<int32_t>(), \
2184 q.data_ptr<scalar_t>(), \
2185 k.data_ptr<scalar_t>(), \
2186 w.data_ptr<scalar_t>(), \
2187 u.data_ptr<scalar_t>(), \
2188 g.data_ptr<float>(), \
2189 decay_mask.data_ptr<float>(), \
2190 cu_seqlens.data_ptr<int32_t>(), \
2191 chunk_offsets.data_ptr<int32_t>(), \
2192 H, \
2193 Hv, \
2194 num_seqs, \
2195 q.stride(1), \
2196 q.stride(2), \
2197 k.stride(1), \
2198 k.stride(2), \
2199 initial_state.stride(0));
2200
2201 template <typename state_t, int CHUNK_SIZE>
2202 std::tuple<at::Tensor, at::Tensor> chunk_gated_delta_rule_fwd_inter(
2203 const at::Tensor& q,
2204 const at::Tensor& k,
2205 const at::Tensor& w,
2206 const at::Tensor& u,
2207 const at::Tensor& g,
2208 const at::Tensor& decay_mask,
2209 const at::Tensor& initial_state,
2210 bool output_final_state,
2211 const at::Tensor& cu_seqlens,
2212 const at::Tensor& chunk_offsets,
2213 const at::Tensor& initial_state_indices) {
2214 const int64_t B = q.size(0);
2215 const int64_t T = q.size(1);
2216 const int64_t H = q.size(2);
2217 const int64_t D = q.size(3);
2218 const int64_t Hv = w.size(2);
2219 const int64_t Dv = u.size(3);
2220 const int64_t num_seqs = initial_state_indices.size(0);
2221
2222 at::Tensor o = at::empty({B, T, Hv, Dv}, q.options());
2223 AT_DISPATCH_REDUCED_FLOATING_TYPES(q.scalar_type(), "chunk_gated_delta_rule_fwd_inter", [&] {
2224 DISPATCH_HEAD_DIM(D, LAUNCH_CHUNK_GATED_DELTA_RULE_FWD_INTER_KERNEL);
2225 });
2226
2227 return std::make_tuple(o, initial_state);
2228 }
2229
2230 // [NB]: Support only varlen inputs
2231 // B: packed batch dim of q/k/v (== 1)
2232 // num_seqs: number of variable-length sequences
2233 //
2234 // query: [B, T, H, D]
2235 // key: [B, T, H, D]
2236 // value: [B, T, Hv, Dv]
2237 // g: [B, T, Hv] FP32
2238 // beta: [B, T, Hv]
2239 // initial_state: [num_seqs, Hv, Dv, D] FP16, BF16, or FP32 persisted state
2240 // cu_seqlens: [num_seqs + 1] INT32
2241 //
2242 std::tuple<at::Tensor, at::Tensor> chunk_gated_delta_rule_cpu(
2243 const at::Tensor& query,
2244 const at::Tensor& key,
2245 const at::Tensor& value,
2246 const at::Tensor& g,
2247 const at::Tensor& beta,
2248 const at::Tensor& initial_state,
2249 bool output_final_state,
2250 const at::Tensor& cu_seqlens,
2251 bool head_first,
2252 bool use_qk_l2norm_in_kernel,
2253 const at::Tensor& initial_state_indices,
2254 double eps = 1e-6) {
2255 TORCH_CHECK(!head_first, "chunk_gated_delta_rule_cpu: does not support head first");
2256
2257 int64_t B = query.size(0);
2258 int64_t T = query.size(1);
2259 int64_t H = query.size(2);
2260 int64_t D = query.size(3);
2261 int64_t Hv = value.size(2);
2262 int64_t Dv = value.size(3);
2263 int64_t num_seqs = initial_state_indices.size(0);
2264
2265 TORCH_CHECK(B == 1, __func__, ": expect batch size to be 1");
2266 TORCH_CHECK(Hv % H == 0, __func__, ": expect num_heads_kv multiple of num_heads.");
2267 TORCH_CHECK(D % 32 == 0, __func__, ": expect head_dim to be multiples of 32.");
2268 TORCH_CHECK(Dv % 32 == 0, __func__, ": expect head_dim_v to be multiples of 32.");
2269 TORCH_CHECK(D == Dv, __func__, ": expect head_dim to be equal to head_dim_v.");
2270 CHECK_INPUT_SHAPE_DTYPE<true>(query, {B, T, H, D}, at::kBFloat16);
2271 CHECK_INPUT_SHAPE_DTYPE<true>(key, {B, T, H, D}, at::kBFloat16);
2272 CHECK_INPUT_SHAPE_DTYPE<true>(value, {B, T, Hv, Dv}, at::kBFloat16);
2273 CHECK_INPUT_SHAPE_DTYPE<false>(g, {B, T, Hv}, at::kFloat);
2274 CHECK_INPUT_SHAPE_DTYPE<false>(beta, {B, T, Hv}, at::kBFloat16);
2275 CHECK_INPUT_SHAPE_DTYPE<false>(cu_seqlens, {num_seqs + 1}, at::kInt);
2276 TORCH_CHECK(initial_state.sizes() == at::IntArrayRef({initial_state.size(0), Hv, Dv, D}),
2277 "chunk_gated_delta_rule_cpu: initial_state shape mismatch, got ", initial_state.sizes());
2278 TORCH_CHECK(
2279 initial_state.scalar_type() == at::kFloat ||
2280 initial_state.scalar_type() == at::kBFloat16 ||
2281 initial_state.scalar_type() == at::kHalf,
2282 "chunk_gated_delta_rule_cpu: initial_state dtype must be float16, "
2283 "bfloat16, or float32");
2284 CHECK_CPU(initial_state);
2285 // initial_state may be a pooled/paged buffer with padding between slots
2286 // (e.g. mamba cache-align mode), so only the per-slot (Hv, Dv, D) layout
2287 // needs to be densely packed; dim 0's stride is read at runtime instead of
2288 // assumed, mirroring conv.cpp's conv_state_slot_stride handling.
2289 TORCH_CHECK(initial_state.stride(-1) == 1 && initial_state.stride(-2) == D &&
2290 initial_state.stride(-3) == Dv * D,
2291 "chunk_gated_delta_rule_cpu: expect initial_state to be contiguous per pool slot.");
2292 CHECK_INPUT_SHAPE_DTYPE<false>(initial_state_indices, {num_seqs}, at::kInt);
2293
2294 constexpr int CHUNK_SIZE = 64;
2295
2296 // prepare chunk indices
2297 auto [chunk_indices, chunk_offsets] = prepare_chunk_indices<CHUNK_SIZE>(cu_seqlens);
2298
2299 float scale = 1.0 / std::sqrt(D);
2300 auto [query_, key_] = use_qk_l2norm_in_kernel ? l2norm_fwd(query, key, eps) : std::make_tuple(query.mul(scale), key);
2301
2302 auto g_ = chunk_local_cumsum<CHUNK_SIZE>(g, cu_seqlens, chunk_indices);
2303
2304 // fused kkt + solve_tril + recompute_w_u
2305 auto [w, u, decay_mask] =
2306 chunk_gated_delta_rule_fwd_intra<CHUNK_SIZE>(key_, value, g_, beta, cu_seqlens, chunk_indices);
2307
2308 // fused `chunk_gated_delta_rule_fwd_h` + `chunk_fwd_o`
2309 auto launch_inter = [local_query = query_,
2310 local_key = key_,
2311 local_w = w,
2312 local_u = u,
2313 local_decay_mask = decay_mask,
2314 local_chunk_offsets = chunk_offsets,
2315 g_,
2316 initial_state,
2317 output_final_state,
2318 cu_seqlens,
2319 initial_state_indices](auto state_tag) {
2320 using state_t = decltype(state_tag);
2321 return chunk_gated_delta_rule_fwd_inter<state_t, CHUNK_SIZE>(
2322 local_query,
2323 local_key,
2324 local_w,
2325 local_u,
2326 g_,
2327 local_decay_mask,
2328 initial_state,
2329 output_final_state,
2330 cu_seqlens,
2331 local_chunk_offsets,
2332 initial_state_indices);
2333 };
2334
2335
2336
2337
2338 auto output_and_state =
2339 initial_state.scalar_type() == at::kFloat
2340 ? launch_inter(float{})
2341 : initial_state.scalar_type() == at::kBFloat16
2342 ? launch_inter(at::BFloat16{})
2343 : launch_inter(at::Half{});
2344 auto [output, final_state] = output_and_state;
2345
2346 return std::make_tuple(output, final_state);
2347 }
2348
2349 // A_log: [v_num_heads]
2350 // dt_bias: [v_num_heads]
2351 // query: [seq_len, batch_size, num_heads, head_dim]
2352 // key: [seq_len, batch_size, num_heads, head_dim]
2353 // value: [seq_len, batch_size, v_num_heads, v_head_dim]
2354 // a: [batch_size, v_num_heads]
2355 // b: [batch_size, v_num_heads]
2356 // initial_state_source:[num_tokens, v_num_heads, head_dim, v_head_dim]
2357 // FP16, BF16, or FP32 persisted state; recurrent arithmetic remains FP32.
2358 // initial_state_indices: [batch_size]
2359 // cu_seqlens: [batch_size + 1]
2360 at::Tensor fused_sigmoid_gating_delta_rule_update_cpu(
2361 const at::Tensor& A_log,
2362 const at::Tensor& dt_bias,
2363 const at::Tensor& q,
2364 const at::Tensor& k,
2365 const at::Tensor& v,
2366 const at::Tensor& a,
2367 const at::Tensor& b,
2368 at::Tensor& initial_state_source,
2369 const at::Tensor& initial_state_indices,
2370 const at::Tensor& cu_seqlens,
2371 bool use_qk_l2norm_in_kernel,
2372 double softplus_beta = 1.0,
2373 double softplus_threshold = 20.0) {
2374 CHECK_DIM(4, q);
2375 CHECK_DIM(4, v);
2376 CHECK_LAST_DIM_CONTIGUOUS_INPUT(q);
2377 int64_t seq_len = q.size(0);
2378 int64_t batch_size = q.size(1);
2379 int64_t num_heads = q.size(2);
2380 int64_t head_dim = q.size(3);
2381 int64_t v_num_heads = v.size(2);
2382 int64_t v_head_dim = v.size(3);
2383 CHECK_INPUT_SHAPE_DTYPE<true>(k, {seq_len, batch_size, num_heads, head_dim}, q.scalar_type());
2384 CHECK_INPUT_SHAPE_DTYPE<true>(v, {seq_len, batch_size, v_num_heads, v_head_dim}, q.scalar_type());
2385 CHECK_INPUT_SHAPE_DTYPE<true>(a, {batch_size, v_num_heads}, q.scalar_type());
2386 CHECK_INPUT_SHAPE_DTYPE<true>(dt_bias, {v_num_heads}, q.scalar_type());
2387 CHECK_INPUT_SHAPE_DTYPE<true>(b, {batch_size, v_num_heads}, q.scalar_type());
2388 CHECK_INPUT_SHAPE_DTYPE<true>(initial_state_indices, {batch_size}, at::kInt);
2389 CHECK_INPUT_SHAPE_DTYPE<true>(cu_seqlens, {batch_size + 1}, at::kInt);
2390 TORCH_CHECK(
2391 initial_state_source.scalar_type() == at::kFloat ||
2392 initial_state_source.scalar_type() == at::kBFloat16 ||
2393 initial_state_source.scalar_type() == at::kHalf,
2394 "fused_sigmoid_gating_delta_rule_update_cpu: initial_state dtype must "
2395 "be float16, bfloat16, or float32");
2396 TORCH_CHECK(
2397 initial_state_source.sizes() ==
2398 at::IntArrayRef(
2399 {initial_state_source.size(0), v_num_heads, head_dim, v_head_dim}),
2400 "Input tensor shape mismatch for initial_state_source");
2401 CHECK_CPU(initial_state_source);
2402 TORCH_CHECK(
2403 initial_state_source.stride(-1) == 1 &&
2404 initial_state_source.stride(-2) == v_head_dim &&
2405 initial_state_source.stride(-3) == head_dim * v_head_dim,
2406 "fused_sigmoid_gating_delta_rule_update_cpu: expect initial_state_source "
2407 "to be contiguous per pool slot.");
2408 CHECK(initial_state_source.size(0) >= batch_size);
2409 CHECK_EQ(v_num_heads % num_heads, 0);
2410 TORCH_CHECK(
2411 A_log.sizes() == at::IntArrayRef({v_num_heads}),
2412 "Input tensor shape mismatch: expected ",
2413 at::IntArrayRef({v_num_heads}),
2414 ", got ",
2415 A_log.sizes());
2416
2417 int64_t q_strideB = q.stride(1);
2418 int64_t q_strideS = q.stride(0);
2419 int64_t q_strideH = q.stride(2);
2420 int64_t k_strideB = k.stride(1);
2421 int64_t k_strideS = k.stride(0);
2422 int64_t k_strideH = k.stride(2);
2423 int64_t v_strideB = v.stride(1);
2424 int64_t v_strideS = v.stride(0);
2425 int64_t v_strideH = v.stride(2);
2426 // IMPORTANT: To make the kernal compatible with vLLM KV cache layout
2427 int64_t state_slot_stride = initial_state_source.stride(0);
2428 at::Tensor core_attn_out = at::empty({batch_size, seq_len, v_num_heads, v_head_dim}, q.options());
2429 at::Tensor qk_scale_buf = at::empty({2 * batch_size, seq_len, num_heads}, at::kFloat);
2430
2431 auto launch = [&](auto state_tag) {
2432 using state_t = decltype(state_tag);
2433 CPU_DISPATCH_REDUCED_FLOATING_TYPES_EXT(
2434 q.scalar_type(), A_log.scalar_type(),
2435 "fused_sigmoid_gating_delta_rule_update_kernel_impl", [&] {
2436 fused_sigmoid_gating_delta_rule_update_kernel_impl<scalar_t, param_t, state_t>(
2437 q.data_ptr<scalar_t>(),
2438 k.data_ptr<scalar_t>(),
2439 v.data_ptr<scalar_t>(),
2440 A_log.data_ptr<param_t>(),
2441 a.data_ptr<scalar_t>(),
2442 dt_bias.data_ptr<scalar_t>(),
2443 b.data_ptr<scalar_t>(),
2444 initial_state_indices.data_ptr<int32_t>(),
2445 initial_state_source.data_ptr<state_t>(),
2446 core_attn_out.data_ptr<scalar_t>(),
2447 qk_scale_buf.data_ptr<float>(),
2448 seq_len,
2449 batch_size,
2450 num_heads,
2451 head_dim,
2452 v_num_heads,
2453 v_head_dim,
2454 q_strideB,
2455 q_strideS,
2456 q_strideH,
2457 k_strideB,
2458 k_strideS,
2459 k_strideH,
2460 v_strideB,
2461 v_strideS,
2462 v_strideH,
2463 state_slot_stride,
2464 use_qk_l2norm_in_kernel,
2465 softplus_threshold);
2466 });
2467 };
2468 if (initial_state_source.scalar_type() == at::kFloat) {
2469 launch(float());
2470 } else if (initial_state_source.scalar_type() == at::kBFloat16) {
2471 launch(at::BFloat16());
2472 } else if (initial_state_source.scalar_type() == at::kHalf) {
2473 launch(at::Half());
2474 } else {
2475 TORCH_CHECK(
2476 false,
2477 "fused_sigmoid_gating_delta_rule_update_cpu: initial_state dtype must "
2478 "be float16, bfloat16, or float32");
2479 }
2480 return core_attn_out;
2481 }
2482
2483 // Speculative-decode update (multi-token, multi-slot rollback).
2484 // q: [T, HK, EK] k: [T, HK, EK] v: [T, HV, EV]
2485 // a: [T, HV] b: [T, HV]
2486 // initial_state_source: [N_slots, HV, EK, EV] FP16, BF16, or FP32 persisted state
2487 // (updated in place; recurrent arithmetic remains FP32)
2488 // spec_state_indices: [batch, S] INT32 (S = num_spec + 1)
2489 // num_accepted_tokens: [batch] INT32
2490 // cu_seqlens: [batch + 1] INT32
2491 // Returns output: [T, HV, EV]
2492 at::Tensor fused_sigmoid_gating_delta_rule_update_spec_cpu(
2493 const at::Tensor& A_log,
2494 const at::Tensor& dt_bias,
2495 const at::Tensor& q,
2496 const at::Tensor& k,
2497 const at::Tensor& v,
2498 const at::Tensor& a,
2499 const at::Tensor& b,
2500 at::Tensor& initial_state_source,
2501 const at::Tensor& spec_state_indices,
2502 const at::Tensor& num_accepted_tokens,
2503 const at::Tensor& cu_seqlens,
2504 bool use_qk_l2norm_in_kernel,
2505 double softplus_beta = 1.0,
2506 double softplus_threshold = 20.0) {
2507 CHECK_DIM(3, q);
2508 CHECK_DIM(3, v);
2509 CHECK_LAST_DIM_CONTIGUOUS_INPUT(q);
2510 int64_t total_tokens = q.size(0);
2511 int64_t num_heads = q.size(1);
2512 int64_t head_dim = q.size(2);
2513 int64_t v_num_heads = v.size(1);
2514 int64_t v_head_dim = v.size(2);
2515 CHECK_INPUT_SHAPE_DTYPE<true>(k, {total_tokens, num_heads, head_dim}, q.scalar_type());
2516 CHECK_INPUT_SHAPE_DTYPE<true>(v, {total_tokens, v_num_heads, v_head_dim}, q.scalar_type());
2517 CHECK_INPUT_SHAPE_DTYPE<true>(a, {total_tokens, v_num_heads}, q.scalar_type());
2518 CHECK_INPUT_SHAPE_DTYPE<true>(b, {total_tokens, v_num_heads}, q.scalar_type());
2519 CHECK_INPUT_SHAPE_DTYPE<true>(dt_bias, {v_num_heads}, q.scalar_type());
2520 CHECK_DIM(1, cu_seqlens);
2521 int64_t batch_size = cu_seqlens.size(0) - 1;
2522 CHECK_INPUT_SHAPE_DTYPE<true>(num_accepted_tokens, {batch_size}, at::kInt);
2523 CHECK_INPUT_SHAPE_DTYPE<true>(cu_seqlens, {batch_size + 1}, at::kInt);
2524 CHECK_DIM(2, spec_state_indices);
2525 CHECK_CPU(spec_state_indices);
2526 TORCH_CHECK(
2527 spec_state_indices.scalar_type() == at::kInt,
2528 "spec_state_indices must be an int32 CPU tensor");
2529 TORCH_CHECK(
2530 spec_state_indices.size(0) == batch_size,
2531 "spec_state_indices first dimension must equal batch_size");
2532 CHECK_LAST_DIM_CONTIGUOUS_INPUT(spec_state_indices);
2533 const int32_t* cu_seqlens_ptr = cu_seqlens.data_ptr<int32_t>();
2534 int64_t max_seq_len = 0;
2535 for (int64_t i = 0; i < batch_size; ++i) {
2536 int64_t span = static_cast<int64_t>(cu_seqlens_ptr[i + 1]) -
2537 static_cast<int64_t>(cu_seqlens_ptr[i]);
2538 TORCH_CHECK(
2539 span >= 0,
2540 "cu_seqlens must be non-decreasing at index ",
2541 i);
2542 if (span > max_seq_len) {
2543 max_seq_len = span;
2544 }
2545 }
2546 TORCH_CHECK(
2547 spec_state_indices.size(1) >= max_seq_len,
2548 "spec_state_indices second dimension must be at least the longest "
2549 "cu_seqlens span; got ",
2550 spec_state_indices.size(1),
2551 " but required ",
2552 max_seq_len);
2553 int64_t spec_stride = spec_state_indices.stride(0);
2554 CHECK_EQ(v_num_heads % num_heads, 0);
2555 TORCH_CHECK(A_log.sizes() == at::IntArrayRef({v_num_heads}));
2556 TORCH_CHECK(
2557 initial_state_source.scalar_type() == at::kFloat ||
2558 initial_state_source.scalar_type() == at::kBFloat16 ||
2559 initial_state_source.scalar_type() == at::kHalf,
2560 "fused_sigmoid_gating_delta_rule_update_spec_cpu: initial_state dtype "
2561 "must be float16, bfloat16, or float32");
2562 TORCH_CHECK(
2563 initial_state_source.sizes() ==
2564 at::IntArrayRef(
2565 {initial_state_source.size(0), v_num_heads, head_dim, v_head_dim}),
2566 "Input tensor shape mismatch for initial_state_source");
2567 CHECK_CPU(initial_state_source);
2568 TORCH_CHECK(
2569 initial_state_source.stride(-1) == 1 &&
2570 initial_state_source.stride(-2) == v_head_dim &&
2571 initial_state_source.stride(-3) == head_dim * v_head_dim,
2572 "fused_sigmoid_gating_delta_rule_update_spec_cpu: expect "
2573 "initial_state_source to be contiguous per pool slot.");
2574 TORCH_CHECK(initial_state_source.size(0) >= batch_size,
2575 "initial_state_source capacity too small: size(0)=",
2576 initial_state_source.size(0), ", batch_size=", batch_size);
2577
2578 int64_t q_strideT = q.stride(0);
2579 int64_t q_strideH = q.stride(1);
2580 int64_t k_strideT = k.stride(0);
2581 int64_t k_strideH = k.stride(1);
2582 int64_t v_strideT = v.stride(0);
2583 int64_t v_strideH = v.stride(1);
2584 int64_t state_slot_stride = initial_state_source.stride(0);
2585
2586 at::Tensor o = at::empty({total_tokens, v_num_heads, v_head_dim}, q.options());
2587 at::Tensor qk_scale_buf = at::empty({2, total_tokens, num_heads}, at::kFloat);
2588
2589 auto launch = [&](auto state_tag) {
2590 using state_t = decltype(state_tag);
2591 CPU_DISPATCH_REDUCED_FLOATING_TYPES_EXT(
2592 q.scalar_type(), A_log.scalar_type(),
2593 "fused_sigmoid_gating_delta_rule_update_spec_kernel_impl", [&] {
2594 fused_sigmoid_gating_delta_rule_update_spec_kernel_impl<
2595 scalar_t, param_t, state_t>(
2596 q.data_ptr<scalar_t>(),
2597 k.data_ptr<scalar_t>(),
2598 v.data_ptr<scalar_t>(),
2599 A_log.data_ptr<param_t>(),
2600 a.data_ptr<scalar_t>(),
2601 dt_bias.data_ptr<scalar_t>(),
2602 b.data_ptr<scalar_t>(),
2603 spec_state_indices.data_ptr<int32_t>(),
2604 num_accepted_tokens.data_ptr<int32_t>(),
2605 cu_seqlens.data_ptr<int32_t>(),
2606 initial_state_source.data_ptr<state_t>(),
2607 o.data_ptr<scalar_t>(),
2608 qk_scale_buf.data_ptr<float>(),
2609 total_tokens,
2610 batch_size,
2611 spec_stride,
2612 num_heads,
2613 head_dim,
2614 v_num_heads,
2615 v_head_dim,
2616 q_strideT,
2617 q_strideH,
2618 k_strideT,
2619 k_strideH,
2620 v_strideT,
2621 v_strideH,
2622 state_slot_stride,
2623 use_qk_l2norm_in_kernel,
2624 softplus_threshold);
2625 });
2626 };
2627 if (initial_state_source.scalar_type() == at::kFloat) {
2628 launch(float());
2629 } else if (initial_state_source.scalar_type() == at::kBFloat16) {
2630 launch(at::BFloat16());
2631 } else if (initial_state_source.scalar_type() == at::kHalf) {
2632 launch(at::Half());
2633 } else {
2634 TORCH_CHECK(
2635 false,
2636 "fused_sigmoid_gating_delta_rule_update_spec_cpu: initial_state "
2637 "dtype must be float16, bfloat16, or float32");
2638 }
2639 return o;
2640 }
2641
2642 // A_log: [num_v_heads]
2643 // a: [batch, num_v_heads]
2644 // b: [batch, num_v_heads]
2645 // dt_bias: [num_v_heads]
2646 // -A_log.float().exp() * F.softplus(a.float() + dt_bias)
2647 std::tuple<at::Tensor, at::Tensor>
2648 fused_gdn_gating_cpu(const at::Tensor& A_log, const at::Tensor& a, const at::Tensor& b, const at::Tensor& dt_bias) {
2649 CHECK_DIM(1, A_log);
2650 CHECK_DIM(2, a);
2651 CHECK_DIM(2, b);
2652 CHECK_DIM(1, dt_bias);
2653 CHECK_CONTIGUOUS(a);
2654 CHECK_EQ(A_log.size(0), a.size(1));
2655 CHECK_EQ(A_log.size(0), dt_bias.size(0));
2656 int batch = a.size(0);
2657 int num_heads = a.size(1);
2658 CHECK_EQ(b.size(0), batch);
2659 CHECK_EQ(b.size(1), num_heads);
2660 at::Tensor out = at::empty({1, batch, num_heads}, a.options().dtype(at::kFloat));
2661 at::Tensor beta = at::empty({1, batch, num_heads}, b.options());
2662 CPU_DISPATCH_REDUCED_FLOATING_TYPES_EXT(a.scalar_type(), A_log.scalar_type(), "fused_gdn_gating_kernel", [&] {
2663 fused_gdn_gating_kernel_impl<scalar_t>(
2664 A_log.data_ptr<param_t>(),
2665 a.data_ptr<scalar_t>(),
2666 b.data_ptr<scalar_t>(),
2667 dt_bias.data_ptr<scalar_t>(),
2668 out.data_ptr<float>(),
2669 beta.data_ptr<scalar_t>(),
2670 batch,
2671 num_heads);
2672 });
2673 return std::make_tuple(out, beta);
2674 }