[CPU] Bump up CPU kernels to latest version (#50387)
Signed-off-by: jiang1.li <jiang1.li@intel.com> Co-authored-by: Claude <noreply@anthropic.com>
Li, Jiang committed
Jul 30, 2026 at 19:18 UTC
072a4727820732fb9e1480dd3deffb9070520b41
15 files changed
+2183
-1292
csrc/cpu/sgl-kernels/common.h
+13
@@ -1,11 +1,16 @@
1
// Adapted from
2
// https://github.com/sgl-project/sglang/tree/main/sgl-kernel/csrc/cpu
3
+//
4
+// Synced from
5
+// https://github.com/sgl-project/sglang/tree/7c248dde7fe1f3b5100966f8143f97a9932c22a4/sgl-kernel/csrc/cpu
6
+// Sync date: 2026-07-29
7
8
// clang-format off
9
10
#pragma once
11
12
#include <ATen/ATen.h>
13
+#include <ATen/Dispatch.h>
14
#include <ATen/Parallel.h>
15
16
#if defined(_OPENMP)
@@ -49,6 +54,14 @@ namespace {
54
} \
55
}()
56
57
+// Half + BFloat16, plus one extra scalar type
58
+#define AT_DISPATCH_CASE_REDUCED_FLOATING_TYPES_AND(SCALARTYPE, ...) \
59
+ AT_DISPATCH_CASE_REDUCED_FLOATING_TYPES(__VA_ARGS__) \
60
+ AT_DISPATCH_CASE(SCALARTYPE, __VA_ARGS__)
61
+
62
+#define AT_DISPATCH_REDUCED_FLOATING_TYPES_AND(SCALARTYPE, TYPE, NAME, ...) \
63
+ AT_DISPATCH_SWITCH(TYPE, NAME, AT_DISPATCH_CASE_REDUCED_FLOATING_TYPES_AND(SCALARTYPE, __VA_ARGS__))
64
+
65
// dispatch: bfloat16, float16, int8_t, fp8_e4m3, uint8_t(mxfp4/int4)
66
#define CPU_DISPATCH_PACKED_TYPES(TYPE, ...) \
67
[&] { \
csrc/cpu/sgl-kernels/conv.cpp
+2
-3
@@ -182,14 +182,13 @@ struct tinygemm_kernel<at::BFloat16, K, BLOCK_N, has_bias, has_silu> {
182
183
using fVec = at::vec::Vectorized<float>;
184
using bVec = at::vec::Vectorized<at::BFloat16>;
185
- const fVec one = fVec(1.f);
185
auto storec = [&](auto i, int64_t m) {
186
constexpr int col = i;
187
fVec x0 = fVec(vc[col * 2 + 0]);
188
fVec x1 = fVec(vc[col * 2 + 1]);
189
if constexpr (has_silu) {
191
- x0 = x0 / (one + x0.neg().exp_u20());
192
- x1 = x1 / (one + x1.neg().exp_u20());
190
+ x0 = fast_silu(x0);
191
+ x1 = fast_silu(x1);
192
}
193
bVec out_vec = convert_from_float_ext<at::BFloat16>(x0, x1);
194
out_vec.store(C + m * lda + col * 32);
csrc/cpu/sgl-kernels/fla.cpp
+1701
-1005
@@ -9,929 +9,1550 @@
9
#include "vec_pack.h"
10
11
namespace {
12
-// For this cpu kernel, we have some innovations aside from the existing gpu kernels:
13
-// 1) Use less parallel loops, i.e. 4 including l2_norm.
14
-// 2) Fuse part of l2_norm with the rest of the computation.
12
16
-#define THREAD_BUFFER_ALLOC(dst, base_ptr, offset, type, size) \
17
- type* dst = reinterpret_cast<type*>((base_ptr) + (offset)); \
18
- offset += (size);
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
20
-template <typename scalar_t>
21
-inline void fill_stub(scalar_t* __restrict__ out, float val, int size) {
77
+template <typename scalar_t, int SIZE>
78
+inline void fill_stub(scalar_t* __restrict__ out, float val) {
79
using Vec = at::vec::Vectorized<scalar_t>;
80
constexpr int kVecSize = Vec::size();
81
+ static_assert(SIZE % kVecSize == 0);
82
const Vec data_vec = Vec(static_cast<scalar_t>(val));
25
- int d = 0;
26
-#pragma GCC unroll 4
27
- for (; d <= size - kVecSize; d += kVecSize) {
83
+#pragma GCC unroll 8
84
+ for (int d = 0; d < SIZE; d += kVecSize) {
85
data_vec.store(out + d);
86
}
30
- if (size - d > 0) {
31
- data_vec.store(out + d, size - d);
32
- }
87
}
88
35
-template <typename scalar_t, int64_t chunk_size = 64>
36
-void chunk_gated_delta_rule_kernel_impl(
37
- scalar_t* __restrict__ out, // [B, T, HV, EV]
38
- float* __restrict__ final_state_data, // [N, HV, EK, EV]
39
- const scalar_t* __restrict__ q_orig, // [B, T, HK, EK]
40
- const scalar_t* __restrict__ k_orig, // [B, T, HK, EK]
41
- const scalar_t* __restrict__ v_orig, // [B, T, HV, EV]
42
- const float* __restrict__ g_orig, // [B, T, HV] FP32
43
- const scalar_t* __restrict__ b_orig, // [B, T, HV]
44
- const int32_t* __restrict__ cu_seqlens_ptr, // [N + 1] INT32
45
- float* __restrict__ buff,
46
- scalar_t* __restrict__ reduced_buff,
47
- scalar_t* __restrict__ thread_buff,
48
- const int32_t* __restrict__ chunk_offsets_ptr,
49
- const int32_t* __restrict__ chunk_indices_ptr,
50
- bool use_qk_l2norm_in_kernel,
51
- const int64_t& batch_size,
52
- const int64_t& global_seq_len,
53
- const int64_t& qk_num_head,
54
- const int64_t& v_num_head,
55
- const int64_t& qk_head_size,
56
- const int64_t& v_head_size,
57
- const int64_t& qStrideH,
58
- const int64_t& qStrideT,
59
- const int64_t& kStrideH,
60
- const int64_t& kStrideT,
61
- const int64_t& vStrideH,
62
- const int64_t& vStrideT,
63
- const int64_t& oStrideH,
64
- const int64_t& oStrideT,
65
- const int64_t& global_total_seq_length,
66
- const int64_t& global_num_chunk,
67
- const int64_t& buff_size_16bit_per_thread,
68
- double eps = 1e-5) {
69
- int64_t gStrideH = 1;
70
- int64_t gStrideT = v_num_head;
71
- int64_t bStrideH = 1;
72
- int64_t bStrideT = v_num_head;
73
- int64_t final_state_StrideN = v_num_head * qk_head_size * v_head_size;
74
- int64_t final_state_StrideH = qk_head_size * v_head_size;
75
- int64_t final_state_StrideE = v_head_size;
76
- int64_t head_group = v_num_head / qk_num_head;
77
- float scale = 1.0 / std::sqrt(qk_head_size);
78
- using bVec = at::vec::Vectorized<scalar_t>;
79
- using fVec = at::vec::Vectorized<float>;
80
- constexpr int64_t VecSize = bVec::size();
81
- constexpr int64_t fVecSize = fVec::size();
82
-
83
- // Data pointers
84
- float* g_pad = buff;
85
- float* core_attn_out = g_pad + v_num_head * global_total_seq_length;
86
- float* decay_mask = core_attn_out + batch_size * v_num_head * global_total_seq_length * v_head_size;
87
- float* v_beta_attn = decay_mask + v_num_head * global_total_seq_length * chunk_size;
88
-
89
- scalar_t* q_pad = reduced_buff;
90
- scalar_t* k_pad = q_pad + qk_num_head * global_total_seq_length * qk_head_size;
91
- scalar_t* v_pad = k_pad + qk_num_head * global_total_seq_length * qk_head_size;
92
- scalar_t* k_beta = v_pad + v_num_head * global_total_seq_length * v_head_size;
93
- scalar_t* v_beta = k_beta + v_num_head * global_total_seq_length * qk_head_size;
94
- scalar_t* k_cumdecay_reduced = v_beta + v_num_head * global_total_seq_length * v_head_size;
95
- scalar_t* q_norm_sum = k_cumdecay_reduced + v_num_head * global_total_seq_length * qk_head_size;
96
- scalar_t* k_norm_sum = q_norm_sum + qk_num_head * global_seq_len;
89
+// Portable fallback for non-AVX512 builds (ARM/NEON, old x86 without
90
+// AVX512BF16), vectorized via at::vec::Vectorized<T> (portable across
91
+// AVX2/NEON/generic) mirroring the idioms used elsewhere in this file (see
92
+// l2norm_fwd_kernel_impl's predecessor and fused_gdn_gating_kernel_impl):
93
+// bVec/fVec pairs with convert_to_float/convert_from_float for bf16<->float,
94
+// plain scalar tails for remainders. Two kernels (cumsum_kernel,
95
+// update_key_kernel) write a transposed layout relative to their vectorized
96
+// read axis; those vectorize the load/compute and unpack lanes for the
97
+// (unavoidably strided) store.
98
+template <typename scalar_t, int D, bool has_scale>
99
+struct l2norm_kernel {
100
+ static inline void apply(scalar_t* __restrict__ out, const scalar_t* __restrict__ input, float eps) {
101
+ using bVec = at::vec::Vectorized<scalar_t>;
102
+ using fVec = at::vec::Vectorized<float>;
103
+ constexpr int bVecSize = bVec::size();
104
+ constexpr float scale = 1.f / std::sqrt(static_cast<float>(D));
105
+
106
+ fVec sum_fvec0(0.f), sum_fvec1(0.f);
107
+ int d = 0;
108
+ for (; d <= D - bVecSize; d += bVecSize) {
109
+ bVec in_bvec = bVec::loadu(input + d);
110
+ fVec in0, in1;
111
+ std::tie(in0, in1) = at::vec::convert_to_float(in_bvec);
112
+ sum_fvec0 = sum_fvec0 + in0 * in0;
113
+ sum_fvec1 = sum_fvec1 + in1 * in1;
114
+ }
115
+ float sqsum = vec_reduce_sum(sum_fvec0 + sum_fvec1);
116
+ for (; d < D; ++d) {
117
+ float v = static_cast<float>(input[d]);
118
+ sqsum += v * v;
119
+ }
120
98
- if (use_qk_l2norm_in_kernel) {
99
- at::parallel_for(0, qk_num_head * global_seq_len, 0, [&](int64_t begin, int64_t end) {
100
- int64_t h_qk = 0, l = 0;
101
- data_index_init(begin, h_qk, qk_num_head, l, global_seq_len);
102
- for (int64_t i = begin; i < end; ++i) {
103
- auto q_norm_sum_ptr = q_norm_sum + h_qk * global_seq_len + l;
104
- auto k_norm_sum_ptr = k_norm_sum + h_qk * global_seq_len + l;
105
- float sum_q = float(0);
106
- float sum_k = float(0);
107
- fVec sum_q_fvec = fVec(float(0));
108
- fVec sum_k_fvec = fVec(float(0));
109
- int64_t q_offset = l * qStrideT + h_qk * qStrideH;
110
- int64_t k_offset = l * qStrideT + h_qk * qStrideH;
111
- int64_t d;
112
- for (d = 0; d <= qk_head_size - VecSize; d += VecSize) {
113
- bVec q_bvec = bVec::loadu(q_orig + q_offset + d);
114
- fVec q_fvec0, q_fvec1;
115
- std::tie(q_fvec0, q_fvec1) = at::vec::convert_to_float(q_bvec);
116
- sum_q_fvec += q_fvec0 * q_fvec0;
117
- sum_q_fvec += q_fvec1 * q_fvec1;
118
- bVec k_bvec = bVec::loadu(k_orig + k_offset + d);
119
- fVec k_fvec0, k_fvec1;
120
- std::tie(k_fvec0, k_fvec1) = at::vec::convert_to_float(k_bvec);
121
- sum_k_fvec += k_fvec0 * k_fvec0;
122
- sum_k_fvec += k_fvec1 * k_fvec1;
121
+ float rscale = 1.f / std::sqrt(sqsum + eps);
122
+ fVec rscale_fvec(rscale);
123
+ fVec scale_fvec(scale);
124
+ d = 0;
125
+ for (; d <= D - bVecSize; d += bVecSize) {
126
+ bVec in_bvec = bVec::loadu(input + d);
127
+ fVec in0, in1;
128
+ std::tie(in0, in1) = at::vec::convert_to_float(in_bvec);
129
+ in0 = in0 * rscale_fvec;
130
+ in1 = in1 * rscale_fvec;
131
+ if constexpr (has_scale) {
132
+ in0 = in0 * scale_fvec;
133
+ in1 = in1 * scale_fvec;
134
+ }
135
+ bVec out_bvec = at::vec::convert_from_float<scalar_t>(in0, in1);
136
+ out_bvec.store(out + d);
137
+ }
138
+ for (; d < D; ++d) {
139
+ float v = static_cast<float>(input[d]) * rscale;
140
+ if constexpr (has_scale) {
141
+ v *= scale;
142
+ }
143
+ out[d] = static_cast<scalar_t>(v);
144
+ }
145
+ }
146
+};
147
+
148
+#if defined(CPU_CAPABILITY_AVX512)
149
+template <int D, bool has_scale>
150
+struct l2norm_kernel<at::BFloat16, D, has_scale> {
151
+ static inline void apply(at::BFloat16* __restrict__ out, const at::BFloat16* __restrict__ input, float eps) {
152
+ static_assert(D % 32 == 0);
153
+ constexpr int COLS = D / 32;
154
+
155
+ __m512bh va[COLS];
156
+ __m512 vrscale;
157
+
158
+ constexpr float scale = 1.f / std::sqrt(D);
159
+ __m512 vscale = _mm512_set1_ps(scale);
160
+
161
+ // step 1: load input and do reduce with avx512-bf16
162
+ __m512 vsum = _mm512_set1_ps(0.f);
163
+ auto reduce = [&](auto col) {
164
+ va[col] = (__m512bh)(_mm512_loadu_si512(input + col * 32));
165
+ vsum = _mm512_dpbf16_ps(vsum, va[col], va[col]);
166
+ };
167
+ Unroll<COLS>{}(reduce);
168
+
169
+ float sqsum = _mm512_reduce_add_ps(vsum);
170
+ float rscale = 1.f / std::sqrt(sqsum + eps);
171
+ vrscale = _mm512_set1_ps(rscale);
172
+
173
+ // step 2: apply scale to output
174
+ auto map = [&](auto col) {
175
+ __m512i a16 = (__m512i)va[col];
176
+ __m512 va0 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(a16, 0));
177
+ __m512 va1 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(a16, 1));
178
+ va0 = _mm512_mul_ps(va0, vrscale);
179
+ va1 = _mm512_mul_ps(va1, vrscale);
180
+ // keep the mul order same as torch code:
181
+ // query = l2norm(query) * scale
182
+ if constexpr (has_scale) {
183
+ va0 = _mm512_mul_ps(va0, vscale);
184
+ va1 = _mm512_mul_ps(va1, vscale);
185
+ }
186
+ _mm512_storeu_si512(out + col * 32, (__m512i)(_mm512_cvtne2ps_pbh(va1, va0)));
187
+ };
188
+ Unroll<COLS>{}(map);
189
+ }
190
+};
191
+#endif
192
+
193
+template <typename scalar_t, int CHUNK_SIZE, int BLOCK_H>
194
+struct cumsum_kernel {
195
+ static inline void apply(
196
+ scalar_t* __restrict__ out,
197
+ const scalar_t* __restrict__ input,
198
+ int mb_size,
199
+ int hb_size,
200
+ int ld_src,
201
+ int ld_dst) {
202
+ // out: [hb_size valid rows, CHUNK_SIZE] within a [BLOCK_H, ld_dst] buffer
203
+ // input: [mb_size valid rows, hb_size] within a [CHUNK_SIZE(padded), ld_src] buffer
204
+ // input is contiguous along j (vectorize the load/accumulate); out is
205
+ // contiguous along i instead (transposed), so the store is per-lane.
206
+ using Vec = at::vec::Vectorized<scalar_t>;
207
+ constexpr int VecSize = Vec::size();
208
+ alignas(64) scalar_t lane_buf[VecSize];
209
+ int j = 0;
210
+ for (; j <= hb_size - VecSize; j += VecSize) {
211
+ Vec running(static_cast<scalar_t>(0));
212
+ for (int i = 0; i < CHUNK_SIZE; ++i) {
213
+ if (i < mb_size) {
214
+ running = running + Vec::loadu(input + i * ld_src + j);
215
+ }
216
+ running.store(lane_buf);
217
+ for (int lane = 0; lane < VecSize; ++lane) {
218
+ out[(j + lane) * ld_dst + i] = lane_buf[lane];
219
}
124
- sum_q += vec_reduce_sum(sum_q_fvec);
125
- sum_k += vec_reduce_sum(sum_k_fvec);
126
- q_norm_sum_ptr[0] = static_cast<scalar_t>(float(1) / std::sqrt(sum_q + eps));
127
- k_norm_sum_ptr[0] = static_cast<scalar_t>(float(1) / std::sqrt(sum_k + eps));
128
- data_index_step(h_qk, qk_num_head, l, global_seq_len);
220
}
130
- });
221
+ }
222
+ for (; j < hb_size; ++j) {
223
+ float running = 0.f;
224
+ for (int i = 0; i < CHUNK_SIZE; ++i) {
225
+ if (i < mb_size) {
226
+ running += static_cast<float>(input[i * ld_src + j]);
227
+ }
228
+ out[j * ld_dst + i] = static_cast<scalar_t>(running);
229
+ }
230
+ }
231
}
132
-
133
- // query = query * scale
134
- // k_beta = key * beta.unsqueeze(-1)
135
- // v_beta = value * beta.unsqueeze(-1)
136
- // Padding for q/k/v/beta
137
- at::parallel_for(0, qk_num_head * global_num_chunk, 1, [&](int64_t begin, int64_t end) {
138
- int ompIdx = at::get_thread_num();
139
- int64_t h_qk = 0, c = 0;
140
- data_index_init(begin, h_qk, qk_num_head, c, global_num_chunk);
141
- for ([[maybe_unused]] auto z : c10::irange(begin, end)) {
142
- int64_t ib = chunk_indices_ptr[c * 2]; // idx_batch
143
- int64_t ic = chunk_indices_ptr[c * 2 + 1]; // idx_chunk
144
- int64_t l_orig = cu_seqlens_ptr[ib] + ic * chunk_size;
145
- int64_t l = c * chunk_size;
146
- bool is_tail = (c + 1 == chunk_offsets_ptr[ib + 1]);
147
- int64_t seq_len = cu_seqlens_ptr[ib + 1] - cu_seqlens_ptr[ib];
148
- int64_t real_chunk_size = is_tail ? seq_len - ic * chunk_size : chunk_size;
149
- auto q_orig_ptr = q_orig + h_qk * qStrideH + l_orig * qStrideT;
150
- auto k_orig_ptr = k_orig + h_qk * kStrideH + l_orig * kStrideT;
151
- auto v_orig_ptr = v_orig + l_orig * vStrideT;
152
- auto b_orig_ptr = b_orig + l_orig * bStrideT;
153
- auto q_pad_ptr = q_pad + h_qk * global_total_seq_length * qk_head_size + l * qk_head_size;
154
- auto k_pad_ptr = k_pad + h_qk * global_total_seq_length * qk_head_size + l * qk_head_size;
155
- auto v_pad_ptr = v_pad + l * v_head_size;
156
- auto k_beta_ptr = k_beta + l * qk_head_size;
157
- auto v_beta_ptr = v_beta + l * v_head_size;
158
-
159
- for (int64_t j = 0; j < real_chunk_size; j++) {
160
- auto curr_q_orig = q_orig_ptr + j * qStrideT;
161
- auto curr_k_orig = k_orig_ptr + j * kStrideT;
162
- auto curr_q_pad = q_pad_ptr + j * qk_head_size;
163
- auto curr_k_pad = k_pad_ptr + j * qk_head_size;
164
- auto q_scale =
165
- use_qk_l2norm_in_kernel ? *(q_norm_sum + h_qk * global_seq_len + l_orig + j) : static_cast<scalar_t>(1);
166
- auto k_scale =
167
- use_qk_l2norm_in_kernel ? *(k_norm_sum + h_qk * global_seq_len + l_orig + j) : static_cast<scalar_t>(1);
168
- auto q_scale_vec = bVec(q_scale);
169
- auto k_scale_vec = bVec(k_scale);
170
- int64_t i = 0;
171
- scalar_t scale_reduced = static_cast<scalar_t>(scale);
172
- auto vec_scale_reduced = bVec(scale_reduced);
173
- for (; i < fVecSize * (qk_head_size / fVecSize); i += fVecSize) {
174
- auto tmp0 = bVec::loadu(curr_q_orig + i, fVecSize);
175
- auto tmp1 = tmp0 * q_scale_vec * vec_scale_reduced;
176
- tmp1.store(curr_q_pad + i, fVecSize);
177
- auto tmp3 = bVec::loadu(curr_k_orig + i, fVecSize);
178
- auto tmp4 = tmp3 * k_scale_vec;
179
- tmp4.store(curr_k_pad + i, fVecSize);
232
+};
233
+
234
+#if defined(CPU_CAPABILITY_AVX512)
235
+template <int CHUNK_SIZE, int BLOCK_H>
236
+struct cumsum_kernel<float, CHUNK_SIZE, BLOCK_H> {
237
+ static inline void
238
+ apply(float* __restrict__ out, const float* __restrict__ input, int mb_size, int hb_size, int ld_src, int ld_dst) {
239
+ // vector length of fp32 for avx512
240
+ static_assert(BLOCK_H == 16);
241
+ TORCH_CHECK(hb_size > 0 && hb_size <= BLOCK_H);
242
+ const __mmask16 vmask = static_cast<__mmask16>((1u << hb_size) - 1u);
243
+
244
+ __m512i va[16];
245
+ __m512 vsum = _mm512_set1_ps(0.f);
246
+
247
+ for (int i = 0; i < CHUNK_SIZE; i += 16) {
248
+ // load input data
249
+ Unroll<16>{}([&](auto j) {
250
+ __m512 v;
251
+ if (i + j < mb_size) {
252
+ v = _mm512_maskz_loadu_ps(vmask, input + (i + j) * ld_src);
253
+ } else {
254
+ v = _mm512_setzero_ps();
255
}
181
-
182
- for (auto hi = 0; hi < head_group; hi++) {
183
- int64_t h = h_qk * head_group + hi;
184
- auto curr_v_orig = v_orig_ptr + h * vStrideH + j * vStrideT;
185
- auto curr_b_orig = b_orig_ptr + h * bStrideH + j * bStrideT;
186
- scalar_t b_orig_val_reduced = *(curr_b_orig);
187
- auto curr_v_pad = v_pad_ptr + h * global_total_seq_length * v_head_size + j * v_head_size;
188
- auto curr_k_beta = k_beta_ptr + h * global_total_seq_length * qk_head_size + j * qk_head_size;
189
- auto curr_v_beta = v_beta_ptr + h * global_total_seq_length * v_head_size + j * v_head_size;
190
-
191
- // query = query * scale
192
- // k_beta = key * beta.unsqueeze(-1)
193
- int64_t i = 0;
194
- auto vec_b_reduced = bVec(b_orig_val_reduced);
195
- for (; i < fVecSize * (qk_head_size / fVecSize); i += fVecSize) {
196
- auto tmp0 = bVec::loadu(curr_k_orig + i, fVecSize);
197
- auto tmp2 = tmp0 * k_scale_vec * vec_b_reduced;
198
- tmp2.store(curr_k_beta + i, fVecSize);
199
- }
200
- // v_beta = value * beta.unsqueeze(-1)
201
- i = 0;
202
- for (; i < VecSize * (v_head_size / VecSize); i += VecSize) {
203
- auto tmp3 = bVec::loadu(curr_v_orig + i);
204
- tmp3.store(curr_v_pad + i);
205
- auto tmp5 = tmp3 * vec_b_reduced;
206
- tmp5.store(curr_v_beta + i);
207
- }
256
+ vsum = _mm512_add_ps(vsum, v);
257
+ va[j] = _mm512_castps_si512(vsum);
258
+ });
259
+ // transpose
260
+ transpose_16x16_32bit(va);
261
+ // store output data
262
+ Unroll<16>{}([&](auto j) {
263
+ if (j < hb_size) {
264
+ _mm512_storeu_si512(out + j * ld_dst + i, va[j]);
265
}
266
+ });
267
+ }
268
+ }
269
+};
270
+#endif
271
+
272
+template <typename scalar_t, int CHUNK_SIZE>
273
+struct decay_mask_kernel {
274
+ // decay_mask = ((g.unsqueeze(-1) - g.unsqueeze(-2)).tril().exp().float()).tril()
275
+ static inline void apply(scalar_t* __restrict__ out, const scalar_t* __restrict__ input) {
276
+ using Vec = at::vec::Vectorized<scalar_t>;
277
+ constexpr int VecSize = Vec::size();
278
+ const Vec zero(static_cast<scalar_t>(0));
279
+ for (int row = 0; row < CHUNK_SIZE; ++row) {
280
+ Vec g_row(input[row]);
281
+ Vec limit_vec(static_cast<scalar_t>(row));
282
+ int col = 0;
283
+ for (; col <= CHUNK_SIZE - VecSize; col += VecSize) {
284
+ Vec g_col = Vec::loadu(input + col);
285
+ Vec vc = (g_row - g_col).exp_u20();
286
+ Vec idx = Vec::arange(static_cast<scalar_t>(col), static_cast<scalar_t>(1));
287
+ Vec result = Vec::blendv(zero, vc, idx <= limit_vec);
288
+ result.store(out + row * CHUNK_SIZE + col);
289
+ }
290
+ for (; col < CHUNK_SIZE; ++col) {
291
+ out[row * CHUNK_SIZE + col] =
292
+ col <= row
293
+ ? static_cast<scalar_t>(std::exp(static_cast<float>(input[row]) - static_cast<float>(input[col])))
294
+ : static_cast<scalar_t>(0);
295
+ }
296
+ }
297
+ }
298
+};
299
+
300
+#if defined(CPU_CAPABILITY_AVX512)
301
+template <int CHUNK_SIZE>
302
+struct decay_mask_kernel<float, CHUNK_SIZE> {
303
+ static inline void apply(float* __restrict__ out, const float* __restrict__ input) {
304
+ static_assert(CHUNK_SIZE % 16 == 0);
305
+
306
+ constexpr int ROWS = CHUNK_SIZE;
307
+ constexpr int COLS = CHUNK_SIZE / 16;
308
+
309
+ __m512 va;
310
+ __m512 vb[COLS];
311
+
312
+ // step 1: load g[j]
313
+ auto loadb = [&](auto i) { vb[i] = _mm512_loadu_ps(input + i * 16); };
314
+ Unroll<COLS>{}(loadb);
315
+
316
+ // step2: exp(g[i] - g[j])
317
+ auto compute = [&](auto i) {
318
+ constexpr int row = i / COLS;
319
+ constexpr int col = i % COLS;
320
+
321
+ if constexpr (col == 0) {
322
+ va = _mm512_set1_ps(input[row]);
323
}
324
211
- for (int64_t j = real_chunk_size; j < chunk_size; j++) {
212
- auto curr_q_pad = q_pad_ptr + j * qk_head_size;
213
- auto curr_k_pad = k_pad_ptr + j * qk_head_size;
214
- int64_t i = 0;
215
- auto vec_zero = bVec(0.0);
216
- for (; i < VecSize * (qk_head_size / VecSize); i += VecSize) {
217
- vec_zero.store(curr_q_pad + i);
218
- vec_zero.store(curr_k_pad + i);
325
+ // mask vb[col] (already loaded in step 1) for the lower-triangular region
326
+ constexpr int len = std::max(0, std::min(row + 1 - col * 16, 16));
327
+
328
+ __m512 vc;
329
+ if constexpr (len == 16) {
330
+ vc = _mm512_fexp_u20_ps(va - vb[col]);
331
+ } else if constexpr (len == 0) {
332
+ vc = _mm512_setzero_ps();
333
+ } else {
334
+ vc = _mm512_fexp_u20_ps(va - vb[col]);
335
+ // do mask for vc
336
+ constexpr __mmask16 vmask = (1 << len) - 1;
337
+ vc = _mm512_mask_blend_ps(vmask, _mm512_setzero_ps(), vc);
338
+ }
339
+ _mm512_storeu_ps(out + row * CHUNK_SIZE + col * 16, vc);
340
+ };
341
+ Unroll<ROWS * COLS>{}(compute);
342
+ }
343
+};
344
+#endif
345
+
346
+template <typename scalar_t, int CHUNK_SIZE, bool has_beta>
347
+struct apply_mask_kernel {
348
+ // has_beta: attn2 = -attn * beta * d (strict lower, col < row)
349
+ // !has_beta: attn2 = attn * d (lower incl. diagonal, col <= row)
350
+ static inline void apply(
351
+ scalar_t* __restrict__ attn2,
352
+ const float* __restrict__ attn,
353
+ const scalar_t* __restrict__ beta,
354
+ const float* __restrict__ d,
355
+ int size,
356
+ int b_stride = 0) {
357
+ using bVec = at::vec::Vectorized<scalar_t>;
358
+ using fVec = at::vec::Vectorized<float>;
359
+ constexpr int fVecSize = fVec::size();
360
+ constexpr int bVecSize = bVec::size();
361
+ const fVec zero(0.f);
362
+ for (int row = 0; row < size; ++row) {
363
+ int col_limit = has_beta ? row : row + 1;
364
+ float beta_val = 1.f;
365
+ if constexpr (has_beta) {
366
+ beta_val = -static_cast<float>(beta[row * b_stride]);
367
+ }
368
+ fVec beta_fvec(beta_val);
369
+ fVec limit_fvec(static_cast<float>(col_limit));
370
+ int col = 0;
371
+ for (; col <= CHUNK_SIZE - bVecSize; col += bVecSize) {
372
+ fVec a0 = fVec::loadu(attn + row * CHUNK_SIZE + col);
373
+ fVec a1 = fVec::loadu(attn + row * CHUNK_SIZE + col + fVecSize);
374
+ fVec d0 = fVec::loadu(d + row * CHUNK_SIZE + col);
375
+ fVec d1 = fVec::loadu(d + row * CHUNK_SIZE + col + fVecSize);
376
+ fVec v0 = a0 * beta_fvec * d0;
377
+ fVec v1 = a1 * beta_fvec * d1;
378
+ fVec idx0 = fVec::arange(static_cast<float>(col), 1.f);
379
+ fVec idx1 = fVec::arange(static_cast<float>(col + fVecSize), 1.f);
380
+ v0 = fVec::blendv(zero, v0, idx0 < limit_fvec);
381
+ v1 = fVec::blendv(zero, v1, idx1 < limit_fvec);
382
+ bVec out_bvec = at::vec::convert_from_float<scalar_t>(v0, v1);
383
+ out_bvec.store(attn2 + row * CHUNK_SIZE + col);
384
+ }
385
+ for (; col < CHUNK_SIZE; ++col) {
386
+ float v = 0.f;
387
+ if (col < col_limit) {
388
+ v = attn[row * CHUNK_SIZE + col] * beta_val * d[row * CHUNK_SIZE + col];
389
}
220
- for (auto hi = 0; hi < head_group; hi++) {
221
- int64_t h = h_qk * head_group + hi;
222
- auto curr_v_pad = v_pad_ptr + h * global_total_seq_length * v_head_size + j * v_head_size;
223
- auto curr_k_beta = k_beta_ptr + h * global_total_seq_length * qk_head_size + j * qk_head_size;
224
- auto curr_v_beta = v_beta_ptr + h * global_total_seq_length * v_head_size + j * v_head_size;
225
- int64_t i = 0;
226
- for (; i < VecSize * (qk_head_size / VecSize); i += VecSize) {
227
- vec_zero.store(curr_k_beta + i);
390
+ attn2[row * CHUNK_SIZE + col] = static_cast<scalar_t>(v);
391
+ }
392
+ }
393
+ }
394
+};
395
+
396
+#if defined(CPU_CAPABILITY_AVX512)
397
+template <int CHUNK_SIZE, bool has_beta>
398
+struct apply_mask_kernel<at::BFloat16, CHUNK_SIZE, has_beta> {
399
+ static inline void apply(
400
+ at::BFloat16* __restrict__ attn2,
401
+ const float* __restrict__ attn,
402
+ const at::BFloat16* __restrict__ beta,
403
+ const float* __restrict__ d,
404
+ int size,
405
+ int b_stride = 0) {
406
+ static_assert(CHUNK_SIZE % 16 == 0);
407
+
408
+ constexpr int ROWS = CHUNK_SIZE;
409
+ constexpr int COLS = CHUNK_SIZE / 16;
410
+
411
+ __m512 vbeta;
412
+
413
+ // has_beta: attn2 = -attn * beta * d (strict lower)
414
+ // !has_beta: attn2 = attn * d (lower incl. diagonal)
415
+ auto compute = [&](auto i) {
416
+ constexpr int row = i / COLS;
417
+ constexpr int col = i % COLS;
418
+
419
+ constexpr int len =
420
+ has_beta ? std::max(0, std::min(row - col * 16, 16)) : std::max(0, std::min(row + 1 - col * 16, 16));
421
+ if (row < size) {
422
+ if constexpr (has_beta) {
423
+ if constexpr (col == 0) {
424
+ vbeta = _mm512_set1_ps(-static_cast<float>(beta[row * b_stride]));
425
}
229
- i = 0;
230
- for (; i < VecSize * (v_head_size / VecSize); i += VecSize) {
231
- vec_zero.store(curr_v_pad + i);
232
- vec_zero.store(curr_v_beta + i);
426
+ }
427
+
428
+ __m512 vc;
429
+ if constexpr (len == 0) {
430
+ vc = _mm512_setzero_ps();
431
+ } else {
432
+ constexpr __mmask16 vmask = (1 << len) - 1;
433
+ __m512 va = _mm512_maskz_loadu_ps(vmask, attn + row * CHUNK_SIZE + col * 16);
434
+ __m512 vd = _mm512_maskz_loadu_ps(vmask, d + row * CHUNK_SIZE + col * 16);
435
+ if constexpr (has_beta) {
436
+ vc = _mm512_mul_ps(_mm512_mul_ps(va, vbeta), vd);
437
+ } else {
438
+ vc = _mm512_mul_ps(va, vd);
439
}
440
}
441
+ _mm256_storeu_si256(
442
+ reinterpret_cast<__m256i*>(attn2 + row * CHUNK_SIZE + col * 16), (__m256i)(_mm512_cvtneps_pbh(vc)));
443
+ }
444
+ };
445
+ Unroll<ROWS * COLS>{}(compute);
446
+ }
447
+};
448
+#endif
449
+
450
+template <typename scalar_t, int CHUNK_SIZE>
451
+struct solve_tril_kernel {
452
+ // (I + L)^{-1} via forward substitution, L = strict-lower part of attn2.
453
+ static inline void apply(scalar_t* __restrict__ attn2, int size) {
454
+ using bVec = at::vec::Vectorized<scalar_t>;
455
+ using fVec = at::vec::Vectorized<float>;
456
+ constexpr int fVecSize = fVec::size();
457
+ constexpr int bVecSize = bVec::size();
458
+
459
+ // for len == 0 and row < size, we don't have to write back zero again
460
+ // as in `apply_mask_kernel`, we already set zero for the upper-triangular region
461
+ for (int i = 1; i < size; ++i) {
462
+ scalar_t* __restrict__ row_ptr = attn2 + i * CHUNK_SIZE;
463
+ float vsum[CHUNK_SIZE];
464
+ int j = 0;
465
+ for (; j <= i - bVecSize; j += bVecSize) {
466
+ bVec row_bvec = bVec::loadu(row_ptr + j);
467
+ fVec f0, f1;
468
+ std::tie(f0, f1) = at::vec::convert_to_float(row_bvec);
469
+ f0.store(vsum + j);
470
+ f1.store(vsum + j + fVecSize);
471
+ }
472
+ for (; j < i; ++j) {
473
+ vsum[j] = static_cast<float>(row_ptr[j]);
474
}
236
- // Move to the next query
237
- data_index_step(h_qk, qk_num_head, c, global_num_chunk);
238
- }
239
- });
475
241
- at::parallel_for(0, v_num_head * global_num_chunk, 1, [&](int64_t begin, int64_t end) {
242
- int64_t h = 0, c = 0;
243
- data_index_init(begin, h, v_num_head, c, global_num_chunk);
244
- int ompIdx = at::get_thread_num();
245
- int64_t offset = 0;
246
- scalar_t* thread_buff_ptr = thread_buff + ompIdx * buff_size_16bit_per_thread;
247
- THREAD_BUFFER_ALLOC(k_transpose, thread_buff_ptr, offset, scalar_t, qk_head_size * chunk_size);
248
- THREAD_BUFFER_ALLOC(v_pack, thread_buff_ptr, offset, scalar_t, chunk_size * v_head_size);
249
- THREAD_BUFFER_ALLOC(k_beta_g, thread_buff_ptr, offset, scalar_t, chunk_size * qk_head_size);
250
- THREAD_BUFFER_ALLOC(k_beta_g_pack, thread_buff_ptr, offset, scalar_t, chunk_size * qk_head_size);
251
- THREAD_BUFFER_ALLOC(curr_attn, thread_buff_ptr, offset, float, chunk_size* chunk_size * 2);
252
- THREAD_BUFFER_ALLOC(curr_attn_reduced, thread_buff_ptr, offset, scalar_t, chunk_size * chunk_size);
253
- THREAD_BUFFER_ALLOC(k_cumdecay, thread_buff_ptr, offset, float, chunk_size* qk_head_size * 2);
254
- THREAD_BUFFER_ALLOC(row, thread_buff_ptr, offset, float, chunk_size * 2);
255
- THREAD_BUFFER_ALLOC(updated, thread_buff_ptr, offset, float, chunk_size * 2);
256
- for ([[maybe_unused]] auto z : c10::irange(begin, end)) {
257
- int64_t ib = chunk_indices_ptr[c * 2]; // idx_batch
258
- int64_t ic = chunk_indices_ptr[c * 2 + 1]; // idx_chunk
259
- int64_t l_orig = cu_seqlens_ptr[ib] + ic * chunk_size;
260
- int64_t seq_len = cu_seqlens_ptr[ib + 1] - cu_seqlens_ptr[ib];
261
- int64_t h_qk = h / head_group;
262
- auto curr_g_orig = g_orig + h * gStrideH + l_orig * gStrideT;
263
- auto curr_g_pad = g_pad + h * global_total_seq_length + c * chunk_size;
264
- auto curr_decay_mask = decay_mask + h * global_total_seq_length * chunk_size + c * chunk_size * chunk_size;
265
- auto curr_k_pad = k_pad + h_qk * global_total_seq_length * qk_head_size + c * chunk_size * qk_head_size;
266
- auto curr_k_beta = k_beta + h * global_total_seq_length * qk_head_size + c * chunk_size * qk_head_size;
267
- auto curr_k_cumdecay_reduced =
268
- k_cumdecay_reduced + h * global_total_seq_length * qk_head_size + c * chunk_size * qk_head_size;
269
- auto curr_v_beta = v_beta + h * global_total_seq_length * v_head_size + c * chunk_size * v_head_size;
270
- auto curr_value = v_beta_attn + h * global_total_seq_length * v_head_size + c * chunk_size * v_head_size;
271
-
272
- float acc_val = 0;
273
- for (int64_t i = 0; i < chunk_size; i++) {
274
- // Padding for g
275
- // g = g.cumsum(dim=-1)
276
- // g: [B, HV, num_chunk, chunk_size]
277
- if (ic * chunk_size + i < seq_len) {
278
- acc_val += curr_g_orig[i * gStrideT];
476
+ // row = attn[..., i, :i].clone()
477
+ // sub = attn[..., :i, :i].clone()
478
+ // vsum = row + (row.unsqueeze(-1) * sub).sum(-2)
479
+ for (int k = 0; k < i; ++k) {
480
+ // read BEFORE row_ptr is written back below (row k was finalized in
481
+ // an earlier outer iteration; row i itself is untouched until the
482
+ // final write-back after this loop)
483
+ float va = static_cast<float>(row_ptr[k]);
484
+ fVec va_vec(va);
485
+ const scalar_t* __restrict__ row_k_ptr = attn2 + k * CHUNK_SIZE;
486
+ int jj = 0;
487
+ for (; jj <= k - bVecSize; jj += bVecSize) {
488
+ bVec rk_bvec = bVec::loadu(row_k_ptr + jj);
489
+ fVec rk0, rk1;
490
+ std::tie(rk0, rk1) = at::vec::convert_to_float(rk_bvec);
491
+ fVec vsum0 = fVec::loadu(vsum + jj);
492
+ fVec vsum1 = fVec::loadu(vsum + jj + fVecSize);
493
+ vsum0 = vsum0 + va_vec * rk0;
494
+ vsum1 = vsum1 + va_vec * rk1;
495
+ vsum0.store(vsum + jj);
496
+ vsum1.store(vsum + jj + fVecSize);
497
}
280
- curr_g_pad[i] = acc_val;
281
- // decay_mask = ((g.unsqueeze(-1) - g.unsqueeze(-2)).tril().exp().float()).tril()
282
- // decay_mask: [B, HV, num_chunk, chunk_size, chunk_size]
283
- float curr_g_pad_i = static_cast<float>(curr_g_pad[i]);
284
- auto vec_curr_g_pad_i = fVec(curr_g_pad_i);
285
- int64_t j = 0;
286
- int64_t len = i + 1;
287
- for (; j < fVecSize * (len / fVecSize); j += fVecSize) {
288
- auto tmp0 = fVec::loadu(curr_g_pad + j);
289
- auto tmp1 = vec_curr_g_pad_i - tmp0;
290
- auto tmp2 = tmp1.exp_u20();
291
- tmp2.store(curr_decay_mask + i * chunk_size + j);
292
- }
293
- if (j < len) {
294
- auto tmp0 = fVec::loadu(curr_g_pad + j, len - j);
295
- auto tmp1 = vec_curr_g_pad_i - tmp0;
296
- auto tmp2 = tmp1.exp_u20();
297
- tmp2.store(curr_decay_mask + i * chunk_size + j, len - j);
498
+ for (; jj < k; ++jj) {
499
+ vsum[jj] += va * static_cast<float>(row_k_ptr[jj]);
500
}
501
}
502
301
- // attn = k_beta @ key.transpose(-1, -2)
302
- // attn: [B, HV, num_chunk, chunk_size, chunk_size]
303
- // transpose and pack for key
304
- if constexpr (brgemm_supported()) {
305
- pack_vnni<scalar_t>(
306
- /* dst */ k_transpose,
307
- /* src */ curr_k_pad,
308
- /* N */ chunk_size,
309
- /* K */ qk_head_size,
310
- /* ld_src */ qk_head_size,
311
- /* ld_dst */ chunk_size);
312
- // k_beta @ key.transpose(-1, -2)
313
- at::native::cpublas::brgemm(
314
- /* M */ chunk_size,
315
- /* N */ chunk_size,
316
- /* K */ qk_head_size,
317
- /* lda */ qk_head_size,
318
- /* ldb */ chunk_size,
319
- /* ldc */ chunk_size,
320
- /* add_C */ false,
321
- /* A */ curr_k_beta,
322
- /* B */ k_transpose,
323
- /* C */ curr_attn);
324
- } else {
325
- blas_gemm(
326
- at::native::TransposeType::Transpose,
327
- at::native::TransposeType::NoTranspose,
328
- chunk_size,
329
- chunk_size,
330
- qk_head_size,
331
- 1.0f,
332
- curr_k_pad,
333
- qk_head_size,
334
- curr_k_beta,
335
- qk_head_size,
336
- 0.0f,
337
- curr_attn,
338
- chunk_size);
503
+ j = 0;
504
+ for (; j <= i - bVecSize; j += bVecSize) {
505
+ fVec f0 = fVec::loadu(vsum + j);
506
+ fVec f1 = fVec::loadu(vsum + j + fVecSize);
507
+ bVec out_bvec = at::vec::convert_from_float<scalar_t>(f0, f1);
508
+ out_bvec.store(row_ptr + j);
509
}
340
- // attn = attn * decay_mask
341
- for (int64_t m = 0; m < chunk_size; m++) {
342
- at::vec::map2<float>(
343
- [](fVec x, fVec y) { return fVec(0) - x * y; },
344
- curr_attn + m * chunk_size,
345
- curr_attn + m * chunk_size,
346
- curr_decay_mask + m * chunk_size,
347
- chunk_size);
510
+ for (; j < i; ++j) {
511
+ row_ptr[j] = static_cast<scalar_t>(vsum[j]);
512
}
513
+ }
514
350
- // chunk decay
351
- // attn: [B, HV, num_chunk, chunk_size, chunk_size]
352
- // mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=query.device), diagonal=0)
353
- // attn = -attn.masked_fill(mask, 0)
354
- // attn[..., i, :i] = row + (row.unsqueeze(-1) * sub).sum(-2) [B, HV, num_chunk, i]
355
- // attn = attn + torch.eye(chunk_size, dtype=attn.dtype, device=attn.device)
356
- // attn = -attn.masked_fill(mask, 0)
357
- for (int i = 0; i < chunk_size; i++) {
358
- const auto vec_zero = fVec(0);
359
- int64_t len = chunk_size - i;
360
- int64_t front = len % fVecSize;
361
- int64_t j = i;
362
- // first masked vec for alignment
363
- if (front > 0) {
364
- vec_zero.store(curr_attn + i * chunk_size + j, front);
365
- j += front;
366
- }
367
- for (; j < fVecSize * (chunk_size / fVecSize); j += fVecSize) {
368
- vec_zero.store(curr_attn + i * chunk_size + j);
515
+ // attn = attn + torch.eye(chunk_size)
516
+ for (int i = 0; i < size; ++i) {
517
+ attn2[i * CHUNK_SIZE + i] = static_cast<scalar_t>(static_cast<float>(attn2[i * CHUNK_SIZE + i]) + 1.f);
518
+ }
519
+ }
520
+};
521
+
522
+#if defined(CPU_CAPABILITY_AVX512)
523
+template <int CHUNK_SIZE>
524
+struct solve_tril_kernel<at::BFloat16, CHUNK_SIZE> {
525
+ static inline void apply(at::BFloat16* __restrict__ attn2, int size) {
526
+ static_assert(CHUNK_SIZE % 16 == 0);
527
+
528
+ constexpr int COLS = CHUNK_SIZE / 16;
529
+
530
+ __m512 va;
531
+ __m512 vb[COLS];
532
+ __m512 vsum[COLS];
533
+
534
+ // for len == 0 and row < size, we don't have to write back zero again
535
+ // as in `apply_mask_kernel`, we already set zero for the upper-triangular region
536
+ for (int i = 1; i < size; ++i) {
537
+ // load row attn[..., i, :i]
538
+ at::BFloat16* __restrict__ row_ptr = attn2 + i * CHUNK_SIZE;
539
+ Unroll<COLS>{}([&](auto col) {
540
+ int len = std::min(i - col * 16, 16);
541
+ if (len > 0) {
542
+ const __mmask16 vmask = (1 << len) - 1;
543
+ vsum[col] = CVT_BF16_TO_FP32(_mm256_maskz_loadu_epi16(vmask, row_ptr + col * 16));
544
}
545
+ });
546
+
547
+ // row = attn[..., i, :i].clone()
548
+ // sub = attn[..., :i, :i].clone()
549
+ // vsum = row + (row.unsqueeze(-1) * sub).sum(-2)
550
+ for (int k = 0; k < i; ++k) {
551
+ va = _mm512_set1_ps(static_cast<float>(row_ptr[k]));
552
+
553
+ const at::BFloat16* __restrict__ row_k_ptr = attn2 + k * CHUNK_SIZE;
554
+ Unroll<COLS>{}([&](auto col) {
555
+ int len = std::min(k - col * 16, 16);
556
+ if (len > 0) {
557
+ const __mmask16 vmask = (1 << len) - 1;
558
+ vb[col] = CVT_BF16_TO_FP32(_mm256_maskz_loadu_epi16(vmask, row_k_ptr + col * 16));
559
+ vsum[col] = _mm512_fmadd_ps(va, vb[col], vsum[col]);
560
+ }
561
+ });
562
}
371
- for (int i = 1; i < chunk_size; i++) {
372
- // row = attn[..., i, :i] [B, HK, num_chunk, i]
373
- int64_t j = 0;
374
- int64_t len = i;
375
- for (; j < fVecSize * (len / fVecSize); j += fVecSize) {
376
- auto tmp0 = fVec::loadu(curr_attn + i * chunk_size + j);
377
- tmp0.store(row + j);
378
- }
379
- if (j < len) {
380
- auto tmp0 = fVec::loadu(curr_attn + i * chunk_size + j, len - j);
381
- tmp0.store(row + j, len - j);
563
+
564
+ // attn[..., i, :i] = vsum
565
+ Unroll<COLS>{}([&](auto col) {
566
+ int len = std::min(i - col * 16, 16);
567
+ if (len > 0) {
568
+ const __mmask16 vmask = (1 << len) - 1;
569
+ _mm256_mask_storeu_epi16(row_ptr + col * 16, vmask, (__m256i)(_mm512_cvtneps_pbh(vsum[col])));
570
}
383
- // (row.unsqueeze(-1) * sub).sum(-2)
384
- fill_stub(updated, 0, i);
385
- for (int k = 0; k < i; k++) {
386
- float row_k = row[k];
387
- auto vec_row_k = fVec(row_k);
388
- int64_t j = 0;
389
- int64_t len = i;
390
- for (; j < fVecSize * (len / fVecSize); j += fVecSize) {
391
- auto tmp0 = fVec::loadu(curr_attn + k * chunk_size + j);
392
- auto tmp1 = vec_row_k * tmp0;
393
- auto tmp2 = fVec::loadu(updated + j);
394
- auto tmp3 = tmp1 + tmp2;
395
- tmp3.store(updated + j);
571
+ });
572
+ }
573
+
574
+ // attn = attn + torch.eye(chunk_size)
575
+ for (int i = 0; i < size; ++i) {
576
+ attn2[i * CHUNK_SIZE + i] += 1.f;
577
+ }
578
+ }
579
+};
580
+#endif
581
+
582
+template <typename scalar_t, int CHUNK_SIZE, int D, bool has_beta, bool has_g>
583
+struct apply_beta_kernel {
584
+ static inline void apply(
585
+ scalar_t* __restrict__ out,
586
+ const scalar_t* __restrict__ input,
587
+ const scalar_t* __restrict__ beta,
588
+ const float* __restrict__ g,
589
+ int size,
590
+ int ld_src,
591
+ int ld_dst,
592
+ int b_stride) {
593
+ using bVec = at::vec::Vectorized<scalar_t>;
594
+ using fVec = at::vec::Vectorized<float>;
595
+ constexpr int fVecSize = fVec::size();
596
+ constexpr int bVecSize = bVec::size();
597
+
598
+ for (int i = 0; i < size; ++i) {
599
+ float scale = 1.f;
600
+ if constexpr (has_beta) {
601
+ scale *= static_cast<float>(beta[i * b_stride]);
602
+ }
603
+ if constexpr (has_g) {
604
+ scale *= std::exp(g[i]);
605
+ }
606
+ fVec scale_fvec(scale);
607
+ int d = 0;
608
+ for (; d <= D - bVecSize; d += bVecSize) {
609
+ bVec in_bvec = bVec::loadu(input + i * ld_src + d);
610
+ fVec in0, in1;
611
+ std::tie(in0, in1) = at::vec::convert_to_float(in_bvec);
612
+ in0 = in0 * scale_fvec;
613
+ in1 = in1 * scale_fvec;
614
+ bVec out_bvec = at::vec::convert_from_float<scalar_t>(in0, in1);
615
+ out_bvec.store(out + i * ld_dst + d);
616
+ }
617
+ for (; d < D; ++d) {
618
+ out[i * ld_dst + d] = static_cast<scalar_t>(static_cast<float>(input[i * ld_src + d]) * scale);
619
+ }
620
+ }
621
+ }
622
+};
623
+
624
+#if defined(CPU_CAPABILITY_AVX512)
625
+template <int CHUNK_SIZE, int D, bool has_beta, bool has_g>
626
+struct apply_beta_kernel<at::BFloat16, CHUNK_SIZE, D, has_beta, has_g> {
627
+ static inline void apply(
628
+ at::BFloat16* __restrict__ out,
629
+ const at::BFloat16* __restrict__ input,
630
+ const at::BFloat16* __restrict__ beta,
631
+ const float* __restrict__ g,
632
+ int size,
633
+ int ld_src,
634
+ int ld_dst,
635
+ int b_stride) {
636
+ static_assert(D % 32 == 0);
637
+ constexpr int COLS = D / 16;
638
+
639
+ // get g.exp() and g is padded to CHUNK_SIZE
640
+ alignas(64) float g_arr[CHUNK_SIZE];
641
+ if constexpr (has_g) {
642
+ Unroll<CHUNK_SIZE / 16>{}([&](auto col) {
643
+ __m512 vg = _mm512_loadu_ps(g + col * 16);
644
+ __m512 vg_exp = _mm512_fexp_u20_ps(vg);
645
+ _mm512_storeu_ps(g_arr + col * 16, vg_exp);
646
+ });
647
+ }
648
+
649
+ for (int i = 0; i < size; ++i) {
650
+ __m512 vbeta;
651
+ if constexpr (has_beta) {
652
+ vbeta = _mm512_set1_ps(static_cast<float>(beta[i * b_stride]));
653
+ }
654
+ __m512 vg;
655
+ if constexpr (has_g) {
656
+ vg = _mm512_set1_ps(g_arr[i]);
657
+ }
658
+
659
+ Unroll<COLS>{}([&](auto col) {
660
+ // load for 0, 2, 4, 6
661
+ if constexpr (col % 2 == 0) {
662
+ __m512i a16 = _mm512_loadu_si512(input + i * ld_src + col * 16);
663
+ __m512 va0 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(a16, 0));
664
+ __m512 va1 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(a16, 1));
665
+ if constexpr (has_beta) {
666
+ va0 = _mm512_mul_ps(va0, vbeta);
667
+ va1 = _mm512_mul_ps(va1, vbeta);
668
}
397
- if (j < len) {
398
- auto tmp0 = fVec::loadu(curr_attn + k * chunk_size + j, len - j);
399
- auto tmp1 = vec_row_k * tmp0;
400
- auto tmp2 = fVec::loadu(updated + j);
401
- auto tmp3 = tmp1 + tmp2;
402
- tmp3.store(updated + j, len - j);
669
+ if constexpr (has_g) {
670
+ va0 = _mm512_mul_ps(va0, vg);
671
+ va1 = _mm512_mul_ps(va1, vg);
672
}
673
+ _mm512_storeu_si512(out + i * ld_dst + col * 16, (__m512i)(_mm512_cvtne2ps_pbh(va1, va0)));
674
}
405
- // attn[..., i, :i] = row + sum(...)
406
- j = 0;
407
- len = i;
408
- for (; j < fVecSize * (len / fVecSize); j += fVecSize) {
409
- auto tmp0 = fVec::loadu(row + j);
410
- auto tmp1 = fVec::loadu(updated + j);
411
- auto tmp2 = tmp0 + tmp1;
412
- tmp2.store(curr_attn + i * chunk_size + j);
413
- }
414
- if (j < len) {
415
- auto tmp0 = fVec::loadu(row + j, len - j);
416
- auto tmp1 = fVec::loadu(updated + j, len - j);
417
- auto tmp2 = tmp0 + tmp1;
418
- tmp2.store(curr_attn + i * chunk_size + j, len - j);
675
+ });
676
+ }
677
+ }
678
+};
679
+#endif
680
+
681
+template <typename scalar_t, int D>
682
+struct update_kernel {
683
+ static inline void
684
+ apply(scalar_t* __restrict__ out, const float* __restrict__ input, int size, int ld_src, int ld_dst) {
685
+ using bVec = at::vec::Vectorized<scalar_t>;
686
+ using fVec = at::vec::Vectorized<float>;
687
+ constexpr int fVecSize = fVec::size();
688
+ constexpr int bVecSize = bVec::size();
689
+
690
+ for (int i = 0; i < size; ++i) {
691
+ int d = 0;
692
+ for (; d <= D - bVecSize; d += bVecSize) {
693
+ fVec f0 = fVec::loadu(input + i * ld_src + d);
694
+ fVec f1 = fVec::loadu(input + i * ld_src + d + fVecSize);
695
+ bVec out_bvec = at::vec::convert_from_float<scalar_t>(f0, f1);
696
+ out_bvec.store(out + i * ld_dst + d);
697
+ }
698
+ for (; d < D; ++d) {
699
+ out[i * ld_dst + d] = static_cast<scalar_t>(input[i * ld_src + d]);
700
+ }
701
+ }
702
+ }
703
+};
704
+
705
+#if defined(CPU_CAPABILITY_AVX512)
706
+template <int D>
707
+struct update_kernel<at::BFloat16, D> {
708
+ static inline void
709
+ apply(at::BFloat16* __restrict__ out, const float* __restrict__ input, int size, int ld_src, int ld_dst) {
710
+ static_assert(D % 32 == 0);
711
+ constexpr int COLS = D / 16;
712
+
713
+ for (int i = 0; i < size; ++i) {
714
+ Unroll<COLS>{}([&](auto col) {
715
+ if constexpr (col % 2 == 0) {
716
+ __m512 va0 = _mm512_loadu_ps(input + i * ld_src + (col + 0) * 16);
717
+ __m512 va1 = _mm512_loadu_ps(input + i * ld_src + (col + 1) * 16);
718
+ __m512i a16 = (__m512i)(_mm512_cvtne2ps_pbh(va1, va0));
719
+ _mm512_storeu_si512(out + i * ld_dst + col * 16, a16);
720
}
721
+ });
722
+ }
723
+ }
724
+};
725
+#endif
726
+
727
+template <typename scalar_t, int D>
728
+struct update_value_kernel {
729
+ static inline void apply(
730
+ scalar_t* __restrict__ v_prime2,
731
+ const scalar_t* __restrict__ v,
732
+ const float* __restrict__ v_prime,
733
+ int size,
734
+ int padded_size,
735
+ int v_strideT) {
736
+ using bVec = at::vec::Vectorized<scalar_t>;
737
+ using fVec = at::vec::Vectorized<float>;
738
+ constexpr int fVecSize = fVec::size();
739
+ constexpr int bVecSize = bVec::size();
740
+
741
+ // v2' = v - v'
742
+ for (int i = 0; i < size; ++i) {
743
+ int d = 0;
744
+ for (; d <= D - bVecSize; d += bVecSize) {
745
+ bVec v_bvec = bVec::loadu(v + i * v_strideT + d);
746
+ fVec v0, v1;
747
+ std::tie(v0, v1) = at::vec::convert_to_float(v_bvec);
748
+ fVec vp0 = fVec::loadu(v_prime + i * D + d);
749
+ fVec vp1 = fVec::loadu(v_prime + i * D + d + fVecSize);
750
+ v0 = v0 - vp0;
751
+ v1 = v1 - vp1;
752
+ bVec out_bvec = at::vec::convert_from_float<scalar_t>(v0, v1);
753
+ out_bvec.store(v_prime2 + i * D + d);
754
}
421
- for (int i = 0; i < chunk_size; i++) {
422
- curr_attn[i * chunk_size + i] += 1.0f;
423
- at::vec::map<scalar_t>(
424
- [](fVec x) { return x; }, curr_attn_reduced + i * chunk_size, curr_attn + i * chunk_size, chunk_size);
755
+ for (; d < D; ++d) {
756
+ float val = static_cast<float>(v[i * v_strideT + d]) - v_prime[i * D + d];
757
+ v_prime2[i * D + d] = static_cast<scalar_t>(val);
758
}
759
+ }
760
+ // pad the last chunk
761
+ const bVec zero_bvec(static_cast<scalar_t>(0));
762
+ for (int i = size; i < padded_size; ++i) {
763
+ int d = 0;
764
+ for (; d <= D - bVecSize; d += bVecSize) {
765
+ zero_bvec.store(v_prime2 + i * D + d);
766
+ }
767
+ for (; d < D; ++d) {
768
+ v_prime2[i * D + d] = static_cast<scalar_t>(0);
769
+ }
770
+ }
771
+ }
772
+};
773
+
774
+#if defined(CPU_CAPABILITY_AVX512)
775
+template <int D>
776
+struct update_value_kernel<at::BFloat16, D> {
777
+ static inline void apply(
778
+ at::BFloat16* __restrict__ v_prime2,
779
+ const at::BFloat16* __restrict__ v,
780
+ const float* __restrict__ v_prime,
781
+ int size,
782
+ int padded_size,
783
+ int v_strideT) {
784
+ static_assert(D % 32 == 0);
785
+ constexpr int COLS = D / 16;
786
+
787
+ // v2' = v - v'
788
+ for (int i = 0; i < size; ++i) {
789
+ Unroll<COLS>{}([&](auto col) {
790
+ // load for 0, 2, 4, 6
791
+ if constexpr (col % 2 == 0) {
792
+ __m512i v16 = _mm512_loadu_si512(v + i * v_strideT + col * 16);
793
+ __m512 va0 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(v16, 0));
794
+ __m512 va1 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(v16, 1));
795
+
796
+ __m512 v_prime0 = _mm512_loadu_ps(v_prime + i * D + col * 16);
797
+ __m512 v_prime1 = _mm512_loadu_ps(v_prime + i * D + col * 16 + 16);
798
+ va0 = _mm512_sub_ps(va0, v_prime0);
799
+ va1 = _mm512_sub_ps(va1, v_prime1);
800
+ __m512i o16 = (__m512i)(_mm512_cvtne2ps_pbh(va1, va0));
801
+ _mm512_storeu_si512(v_prime2 + i * D + col * 16, o16);
802
+ }
803
+ });
804
+ }
805
427
- // v_beta_attn = attn @ v_beta
428
- // k_cumdecay = attn @ (k_beta * g.exp().unsqueeze(-1))
429
- // v_beta_attn: [B, HV, num_chunk, chunk_size, EV]
430
- // k_beta_g = k_beta * g: [B, HV, num_chunk, chunk_size, EK]
431
- // k_cumdecay: [B, HV, num_chunk, chunk_size, EK]
432
- // pack for value
433
- if constexpr (brgemm_supported()) {
434
- pack_vnni2<scalar_t>(
435
- /* dst */ v_pack,
436
- /* src */ curr_v_beta,
437
- /* N */ chunk_size,
438
- /* K */ v_head_size,
439
- /* ld_src */ v_head_size,
440
- /* ld_dst */ v_head_size);
441
- // value = attn @ v_beta
442
- at::native::cpublas::brgemm(
443
- /* M */ chunk_size,
444
- /* N */ v_head_size,
445
- /* K */ chunk_size,
446
- /* lda */ chunk_size,
447
- /* ldb */ v_head_size,
448
- /* ldc */ v_head_size,
449
- /* add_C */ false,
450
- /* A */ curr_attn_reduced,
451
- /* B */ v_pack,
452
- /* C */ curr_value);
453
- } else {
454
- blas_gemm(
455
- at::native::TransposeType::NoTranspose,
456
- at::native::TransposeType::NoTranspose,
457
- v_head_size,
458
- chunk_size,
459
- chunk_size,
460
- 1.0f,
461
- curr_v_beta,
462
- v_head_size,
463
- curr_attn_reduced,
464
- chunk_size,
465
- 0.0f,
466
- curr_value,
467
- v_head_size);
806
+ // pad the last chunk
807
+ for (int i = size; i < padded_size; ++i) {
808
+ Unroll<COLS>{}([&](auto col) {
809
+ if constexpr (col % 2 == 0) {
810
+ __m512i v16 = _mm512_setzero_si512();
811
+ _mm512_storeu_si512(v_prime2 + i * D + col * 16, v16);
812
+ }
813
+ });
814
+ }
815
+ }
816
+};
817
+#endif
818
+
819
+template <typename scalar_t, int CHUNK_SIZE, int D>
820
+struct update_key_kernel {
821
+ static inline void apply(
822
+ scalar_t* __restrict__ k_updated,
823
+ const scalar_t* __restrict__ k,
824
+ const float* __restrict__ g,
825
+ int size,
826
+ int k_strideT) {
827
+ // k_updated is transposed: [D, CHUNK_SIZE], k_updated[d, t] = k[t, d] * exp(g_last - g[t]).
828
+ // k's D dim is contiguous (vectorize load/compute); k_updated's D dim is
829
+ // strided (CHUNK_SIZE apart), so the store is unpacked per lane.
830
+ using bVec = at::vec::Vectorized<scalar_t>;
831
+ using fVec = at::vec::Vectorized<float>;
832
+ constexpr int fVecSize = fVec::size();
833
+ constexpr int bVecSize = bVec::size();
834
+ alignas(64) scalar_t lane_buf[bVecSize];
835
+
836
+ const float g_last = g[size - 1];
837
+ for (int t = 0; t < size; ++t) {
838
+ float scale = std::exp(g_last - g[t]);
839
+ fVec scale_fvec(scale);
840
+ int d = 0;
841
+ for (; d <= D - bVecSize; d += bVecSize) {
842
+ bVec k_bvec = bVec::loadu(k + t * k_strideT + d);
843
+ fVec k0, k1;
844
+ std::tie(k0, k1) = at::vec::convert_to_float(k_bvec);
845
+ k0 = k0 * scale_fvec;
846
+ k1 = k1 * scale_fvec;
847
+ bVec out_bvec = at::vec::convert_from_float<scalar_t>(k0, k1);
848
+ out_bvec.store(lane_buf);
849
+ for (int lane = 0; lane < bVecSize; ++lane) {
850
+ k_updated[(d + lane) * CHUNK_SIZE + t] = lane_buf[lane];
851
+ }
852
+ }
853
+ for (; d < D; ++d) {
854
+ k_updated[d * CHUNK_SIZE + t] = static_cast<scalar_t>(static_cast<float>(k[t * k_strideT + d]) * scale);
855
}
469
- // k_beta_g = k_beta * g.exp().unsqueeze(-1)
470
- for (int64_t j = 0; j < chunk_size; j++) {
471
- int64_t i = 0;
472
- float g_exp = std::exp(curr_g_pad[j]);
473
- scalar_t g_exp_reduced = static_cast<scalar_t>(g_exp);
474
- auto vec_g_exp_reduced = bVec(g_exp_reduced);
475
- for (; i < VecSize * (qk_head_size / VecSize); i += VecSize) {
476
- auto tmp0 = bVec::loadu(curr_k_beta + j * qk_head_size + i);
477
- auto tmp1 = tmp0 * vec_g_exp_reduced;
478
- tmp1.store(k_beta_g + j * qk_head_size + i);
856
+ }
857
+ const bVec zero_bvec(static_cast<scalar_t>(0));
858
+ for (int t = size; t < CHUNK_SIZE; ++t) {
859
+ int d = 0;
860
+ for (; d <= D - bVecSize; d += bVecSize) {
861
+ zero_bvec.store(lane_buf);
862
+ for (int lane = 0; lane < bVecSize; ++lane) {
863
+ k_updated[(d + lane) * CHUNK_SIZE + t] = lane_buf[lane];
864
}
865
}
481
- // pack for k_beta_g
866
+ for (; d < D; ++d) {
867
+ k_updated[d * CHUNK_SIZE + t] = static_cast<scalar_t>(0);
868
+ }
869
+ }
870
+ }
871
+};
872
+
873
+#if defined(CPU_CAPABILITY_AVX512)
874
+template <int CHUNK_SIZE, int D>
875
+struct update_key_kernel<at::BFloat16, CHUNK_SIZE, D> {
876
+ static inline void apply(
877
+ at::BFloat16* __restrict__ k_updated,
878
+ const at::BFloat16* __restrict__ k,
879
+ const float* __restrict__ g,
880
+ int size,
881
+ int k_strideT) {
882
+ static_assert(D % 32 == 0);
883
+ const int MB = div_up(size, 16);
884
+ const int KB = D / 16;
885
+
886
+ const float g_last = g[size - 1];
887
+ const __m512 vg_last = _mm512_set1_ps(g_last);
888
+
889
+ float scale_arr[16];
890
+ __m256i va[16];
891
+
892
+ // from [C, D](MB, KB) to [D, C](KB, MB)
893
+ // pad size to 16 in this kernel so that transpose can be done in one loop
894
+ for (int mb = 0; mb < MB; ++mb) {
895
+ const int mb_size = std::min(size - mb * 16, 16);
896
+ // prepare exp(g_last - g)
897
+ __m512 vg = _mm512_loadu_ps(g + mb * 16);
898
+ _mm512_storeu_ps(scale_arr, _mm512_fexp_u20_ps(_mm512_sub_ps(vg_last, vg)));
899
+ for (int kb = 0; kb < KB; ++kb) {
900
+ const at::BFloat16* __restrict__ k_ptr = k + mb * 16 * k_strideT + kb * 16;
901
+ at::BFloat16* __restrict__ k_updated_ptr = k_updated + kb * 16 * CHUNK_SIZE + mb * 16;
902
+ // load 16 regs
903
+ Unroll<16>{}([&](auto m) {
904
+ if (m < mb_size) {
905
+ __m256i v16 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(k_ptr + m * k_strideT));
906
+ __m512 v32 = _mm512_mul_ps(CVT_BF16_TO_FP32(v16), _mm512_set1_ps(scale_arr[m]));
907
+ va[m] = (__m256i)_mm512_cvtneps_pbh(v32);
908
+ } else {
909
+ va[m] = _mm256_setzero_si256();
910
+ }
911
+ });
912
+ // transpose 16x16
913
+ transpose_16x16_16bit(va);
914
+ // store 16 regs
915
+ Unroll<16>{}(
916
+ [&](auto k) { _mm256_storeu_si256(reinterpret_cast<__m256i*>(k_updated_ptr + k * CHUNK_SIZE), va[k]); });
917
+ }
918
+ }
919
+ }
920
+};
921
+#endif
922
+
923
+
924
+// template head_dim here to reduce extra read
925
+// * normal approach: read inputs 2 times:
926
+// - reduce: 1R
927
+// - scale: 1R + 1W
928
+// * keep input data in register:
929
+// - reduce: 1R
930
+// - scale: 1W
931
+template <typename scalar_t, int D>
932
+void l2norm_fwd_kernel_impl(
933
+ scalar_t* __restrict__ query_norm,
934
+ scalar_t* __restrict__ key_norm,
935
+ const scalar_t* __restrict__ query,
936
+ const scalar_t* __restrict__ key,
937
+ float eps,
938
+ int64_t T,
939
+ int64_t H,
940
+ int64_t q_strideT,
941
+ int64_t q_strideH,
942
+ int64_t k_strideT,
943
+ int64_t k_strideH) {
944
+ // expected to be contuguous
945
+ int64_t qn_strideH = D;
946
+ int64_t kn_strideH = D;
947
+
948
+ // parallel on [B, T, H]
949
+ at::parallel_for(0, T * H, 0, [&](int64_t begin, int64_t end) {
950
+ int64_t t{0}, h{0};
951
+ data_index_init(begin, t, T, h, H);
952
+
953
+ for (int64_t i = begin; i < end; ++i) {
954
+ const scalar_t* __restrict__ q_ptr = query + t * q_strideT + h * q_strideH;
955
+ const scalar_t* __restrict__ k_ptr = key + t * k_strideT + h * k_strideH;
956
+ scalar_t* __restrict__ qn_ptr = query_norm + i * qn_strideH;
957
+ scalar_t* __restrict__ kn_ptr = key_norm + i * kn_strideH;
958
+
959
+ l2norm_kernel<scalar_t, D, true>::apply(qn_ptr, q_ptr, eps);
960
+ l2norm_kernel<scalar_t, D, false>::apply(kn_ptr, k_ptr, eps);
961
+
962
+ // move to the next index
963
+ data_index_step(t, T, h, H);
964
+ }
965
+ });
966
+}
967
+
968
+// g : [B, T, Hv]
969
+// g_ : [B, Hv, NT, C] -> [B, NT, HB, BLOCK_H, C]
970
+// cu_seqlens : [num_seqs + 1]
971
+// chunk_indices : [NT * 2]
972
+template <typename scalar_t, int CHUNK_SIZE>
973
+void chunk_local_cumsum_kernel_impl(
974
+ scalar_t* __restrict__ g_,
975
+ const scalar_t* __restrict__ g,
976
+ const int32_t* __restrict__ cu_seqlens,
977
+ const int32_t* __restrict__ chunk_indices,
978
+ int64_t Hv,
979
+ int64_t NT) {
980
+ constexpr int BLOCK_H = 16;
981
+ int64_t HB = div_up(Hv, int64_t(BLOCK_H));
982
+
983
+ // parallel on [NT * HB] to increase parallelism
984
+ at::parallel_for(0, NT * HB, 0, [&](int64_t begin, int64_t end) {
985
+ int64_t nt{0}, hb{0};
986
+ data_index_init(begin, nt, NT, hb, HB);
987
+
988
+ for (int64_t i = begin; i < end; ++i) {
989
+ int32_t bs = chunk_indices[nt * 2 + 0];
990
+ int32_t batch_offset = cu_seqlens[bs];
991
+ int32_t seqlen = cu_seqlens[bs + 1] - cu_seqlens[bs];
992
+ int64_t mb_start = chunk_indices[nt * 2 + 1] * CHUNK_SIZE;
993
+ int64_t mb_size = std::min(seqlen - mb_start, int64_t(CHUNK_SIZE));
994
+ int64_t hb_size = std::min(Hv - hb * BLOCK_H, int64_t(BLOCK_H));
995
+
996
+ const scalar_t* __restrict__ g_ptr = g + (batch_offset + mb_start) * Hv + hb * BLOCK_H;
997
+ scalar_t* __restrict__ gsum_ptr = g_ + nt * (Hv * CHUNK_SIZE) + hb * (BLOCK_H * CHUNK_SIZE);
998
+ cumsum_kernel<scalar_t, CHUNK_SIZE, BLOCK_H>::apply(gsum_ptr, g_ptr, mb_size, hb_size, Hv, CHUNK_SIZE);
999
+
1000
+ // move to the next index
1001
+ data_index_step(nt, NT, hb, HB);
1002
+ }
1003
+ });
1004
+}
1005
+
1006
+#define DECL_BUF(type, name, size_expr) alignas(64) type name[(size_expr)]
1007
+#define DECL_ZERO_BUF(type, name, size_expr) \
1008
+ DECL_BUF(type, name, size_expr); \
1009
+ fill_stub<type, (size_expr)>(name, 0.f)
1010
+
1011
+// w : [B, T, Hv, D]
1012
+// u : [B, T, Hv, Dv]
1013
+// d : [B, NT, Hv, C, C]
1014
+// k : [B, T, H, D]
1015
+// v : [B, T, Hv, Dv]
1016
+// g : [B, NT, Hv, C]
1017
+// beta : [B, T, Hv]
1018
+// cu_seqlens : [num_seqs + 1]
1019
+// chunk_indices : [NT * 2]
1020
+template <typename scalar_t, int D, int CHUNK_SIZE>
1021
+void chunk_gated_delta_rule_fwd_intra_kernel_impl(
1022
+ scalar_t* __restrict__ w,
1023
+ scalar_t* __restrict__ u,
1024
+ float* __restrict__ d,
1025
+ const scalar_t* __restrict__ k,
1026
+ const scalar_t* __restrict__ v,
1027
+ const float* __restrict__ g,
1028
+ const scalar_t* __restrict__ beta,
1029
+ const int32_t* __restrict__ cu_seqlens,
1030
+ const int32_t* __restrict__ chunk_indices,
1031
+ int64_t H,
1032
+ int64_t Hv,
1033
+ int64_t NT,
1034
+ int64_t k_strideT,
1035
+ int64_t k_strideH,
1036
+ int64_t v_strideT,
1037
+ int64_t v_strideH) {
1038
+ // head group, expect to be 1,2,4 for qwen3.5
1039
+ const int64_t HG = Hv / H;
1040
+
1041
+ // strides
1042
+ const int64_t w_strideT = Hv * D;
1043
+ const int64_t w_strideH = D;
1044
+ const int64_t u_strideT = Hv * D;
1045
+ const int64_t u_strideH = D;
1046
+
1047
+ // [NB]: parallel on [NT, H]
1048
+ // * parallel on num_heads and go sequential on num_heads_v,
1049
+ // * avoid instantialize k_beta (beta * k)
1050
+ // * compute key @ key^T * beta instead of k_beta @ key^T, same as triton impl
1051
+ // * compute key @ key^T once for each k head index and reuse for v head index
1052
+ at::parallel_for(0, NT * H, 0, [&](int64_t begin, int64_t end) {
1053
+ int64_t nt{0}, h{0};
1054
+ data_index_init(begin, nt, NT, h, H);
1055
+
1056
+ // thread local temp buffer
1057
+ DECL_ZERO_BUF(scalar_t, tmp, CHUNK_SIZE * D);
1058
+ DECL_ZERO_BUF(scalar_t, tmp2, CHUNK_SIZE * D);
1059
+ DECL_ZERO_BUF(float, attn, CHUNK_SIZE* CHUNK_SIZE);
1060
+ DECL_ZERO_BUF(scalar_t, attn2, CHUNK_SIZE * CHUNK_SIZE);
1061
+ DECL_ZERO_BUF(float, tmp3, CHUNK_SIZE* D);
1062
+
1063
+ // alias
1064
+ scalar_t* __restrict__ k_packed = tmp;
1065
+ scalar_t* __restrict__ k_beta = tmp;
1066
+ scalar_t* __restrict__ v_beta = tmp;
1067
+ scalar_t* __restrict__ k_beta_packed = tmp2;
1068
+ scalar_t* __restrict__ v_beta_packed = tmp2;
1069
+ float* __restrict__ k_updated = tmp3;
1070
+ float* __restrict__ v_updated = tmp3;
1071
+
1072
+ for (int64_t i = begin; i < end; ++i) {
1073
+ int32_t bs = chunk_indices[nt * 2 + 0];
1074
+ int32_t batch_offset = cu_seqlens[bs];
1075
+ int32_t seqlen = cu_seqlens[bs + 1] - cu_seqlens[bs];
1076
+ int64_t mb_start = chunk_indices[nt * 2 + 1] * CHUNK_SIZE;
1077
+ int64_t mb_size = std::min(seqlen - mb_start, int64_t(CHUNK_SIZE));
1078
+
1079
+ // mb_size` is K in 5.c, 5.g, pad to TILE_K;
1080
+ const int64_t padded_mb_size = div_up((int)mb_size, TILE_K) * TILE_K;
1081
+
1082
+ // step 1: decay_mask = ((g.unsqueeze(-1) - g.unsqueeze(-2)).tril().exp().float()).tril()
1083
+ for (int64_t hv = h * HG; hv < h * HG + HG; ++hv) {
1084
+ const float* __restrict__ g_ptr = g + nt * (Hv * CHUNK_SIZE) + hv * CHUNK_SIZE;
1085
+ float* __restrict__ d_ptr = d + nt * (Hv * CHUNK_SIZE * CHUNK_SIZE) + hv * (CHUNK_SIZE * CHUNK_SIZE);
1086
+ decay_mask_kernel<float, CHUNK_SIZE>::apply(d_ptr, g_ptr);
1087
+ }
1088
+
1089
+ // step 2: attn = key @ key^T
1090
+ const scalar_t* __restrict__ k_ptr = k + (batch_offset + mb_start) * k_strideT + h * k_strideH;
1091
if constexpr (brgemm_supported()) {
483
- pack_vnni2<scalar_t>(
484
- /* dst */ k_beta_g_pack,
485
- /* src */ k_beta_g,
486
- /* N */ chunk_size,
487
- /* K */ qk_head_size,
488
- /* ld_src */ qk_head_size,
489
- /* ld_dst */ qk_head_size);
490
- // k_cumdecay = attn @ k_beta_g
1092
+ pack_vnni<scalar_t>(
1093
+ /* dst */ k_packed,
1094
+ /* src */ k_ptr,
1095
+ /* N */ mb_size,
1096
+ /* K */ D,
1097
+ /* ld_src */ k_strideT,
1098
+ /* ld_dst */ CHUNK_SIZE);
1099
+
1100
at::native::cpublas::brgemm(
492
- /* M */ chunk_size,
493
- /* N */ qk_head_size,
494
- /* K */ chunk_size,
495
- /* lda */ chunk_size,
496
- /* ldb */ qk_head_size,
497
- /* ldc */ qk_head_size,
1101
+ /* M */ mb_size,
1102
+ /* N */ mb_size,
1103
+ /* K */ D,
1104
+ /* lda */ k_strideT,
1105
+ /* ldb */ CHUNK_SIZE,
1106
+ /* ldc */ CHUNK_SIZE,
1107
/* add_C */ false,
499
- /* A */ curr_attn_reduced,
500
- /* B */ k_beta_g_pack,
501
- /* C */ k_cumdecay);
1108
+ /* A */ k_ptr,
1109
+ /* B */ k_packed,
1110
+ /* C */ attn);
1111
} else {
1112
blas_gemm(
1113
+ at::native::TransposeType::Transpose,
1114
at::native::TransposeType::NoTranspose,
505
- at::native::TransposeType::NoTranspose,
506
- qk_head_size,
507
- chunk_size,
508
- chunk_size,
1115
+ mb_size,
1116
+ mb_size,
1117
+ D,
1118
1.0f,
510
- k_beta_g,
511
- qk_head_size,
512
- curr_attn_reduced,
513
- chunk_size,
1119
+ k_ptr,
1120
+ k_strideT,
1121
+ k_ptr,
1122
+ k_strideT,
1123
0.0f,
515
- k_cumdecay,
516
- qk_head_size);
1124
+ attn,
1125
+ CHUNK_SIZE);
1126
}
518
- for (int i = 0; i < chunk_size; i++) {
519
- at::vec::map<scalar_t>(
520
- [](fVec x) { return x; },
521
- curr_k_cumdecay_reduced + i * qk_head_size,
522
- k_cumdecay + i * qk_head_size,
523
- qk_head_size);
1127
+
1128
+ for (int64_t hv = h * HG; hv < h * HG + HG; ++hv) {
1129
+ // step 3: attn2 = -attn * beta * d
1130
+ const scalar_t* __restrict__ beta_ptr = beta + (batch_offset + mb_start) * Hv + hv;
1131
+ const float* __restrict__ d_ptr = d + nt * (Hv * CHUNK_SIZE * CHUNK_SIZE) + hv * (CHUNK_SIZE * CHUNK_SIZE);
1132
+ apply_mask_kernel<scalar_t, CHUNK_SIZE, true>::apply(attn2, attn, beta_ptr, d_ptr, mb_size, Hv);
1133
+
1134
+ // step 4: solve_tril(attn2) -> (I + L)^{-1}, L = strict-lower from step 3
1135
+ // for i in 1..C-1: attn2[i, :i] += (attn2[i, :i] * attn2[:i, :i]).sum(-1)
1136
+ // attn2 += eye(C)
1137
+ solve_tril_kernel<scalar_t, CHUNK_SIZE>::apply(attn2, mb_size);
1138
+
1139
+ // step 5: recompute_w_u
1140
+ // w = attn2 @ (k_beta * g.exp().unsqueeze(-1))
1141
+ // u = attn2 @ value * beta.unsqueeze(-1)
1142
+ const float* __restrict__ g_ptr = g + nt * (Hv * CHUNK_SIZE) + hv * CHUNK_SIZE;
1143
+ const scalar_t* __restrict__ v_ptr = v + (batch_offset + mb_start) * v_strideT + hv * v_strideH;
1144
+
1145
+ // 5.a key = key * beta * g.exp
1146
+ apply_beta_kernel<scalar_t, CHUNK_SIZE, D, true, true>::apply(
1147
+ k_beta, k_ptr, beta_ptr, g_ptr, mb_size, k_strideT, D, Hv);
1148
+
1149
+ // 5.b pack key
1150
+ if constexpr (brgemm_supported()) {
1151
+ pack_vnni2<scalar_t>(
1152
+ /* dst */ k_beta_packed,
1153
+ /* src */ k_beta,
1154
+ /* K */ mb_size,
1155
+ /* N */ D,
1156
+ /* ld_src */ D,
1157
+ /* ld_dst */ D);
1158
+
1159
+ // 5.c w = attn2 @ k_beta
1160
+ at::native::cpublas::brgemm(
1161
+ /* M */ mb_size,
1162
+ /* N */ D,
1163
+ /* K */ padded_mb_size, // mb_size
1164
+ /* lda */ CHUNK_SIZE,
1165
+ /* ldb */ D,
1166
+ /* ldc */ D,
1167
+ /* add_C */ false,
1168
+ /* A */ attn2,
1169
+ /* B */ k_beta_packed,
1170
+ /* C */ k_updated);
1171
+ } else {
1172
+ blas_gemm(
1173
+ at::native::TransposeType::NoTranspose,
1174
+ at::native::TransposeType::NoTranspose,
1175
+ D,
1176
+ mb_size,
1177
+ padded_mb_size,
1178
+ 1.0f,
1179
+ k_beta,
1180
+ D,
1181
+ attn2,
1182
+ CHUNK_SIZE,
1183
+ 0.0f,
1184
+ k_updated,
1185
+ D);
1186
+ }
1187
+
1188
+ // 5.d k_updated -> w
1189
+ scalar_t* __restrict__ w_ptr = w + (batch_offset + mb_start) * w_strideT + hv * w_strideH;
1190
+ update_kernel<scalar_t, D>::apply(w_ptr, k_updated, mb_size, D, w_strideT);
1191
+
1192
+ // 5.e value = value * beta
1193
+ apply_beta_kernel<scalar_t, CHUNK_SIZE, D, true, false>::apply(
1194
+ v_beta, v_ptr, beta_ptr, nullptr, mb_size, v_strideT, D, Hv);
1195
+
1196
+ // 5.f pack value
1197
+ if constexpr (brgemm_supported()) {
1198
+ pack_vnni2<scalar_t>(
1199
+ /* dst */ v_beta_packed,
1200
+ /* src */ v_beta,
1201
+ /* K */ mb_size,
1202
+ /* N */ D,
1203
+ /* ld_src */ D,
1204
+ /* ld_dst */ D);
1205
+
1206
+ // 5.g u = attn2 @ v_beta
1207
+ at::native::cpublas::brgemm(
1208
+ /* M */ mb_size,
1209
+ /* N */ D,
1210
+ /* K */ padded_mb_size, // mb_size
1211
+ /* lda */ CHUNK_SIZE,
1212
+ /* ldb */ D,
1213
+ /* ldc */ D,
1214
+ /* add_C */ false,
1215
+ /* A */ attn2,
1216
+ /* B */ v_beta_packed,
1217
+ /* C */ v_updated);
1218
+ } else {
1219
+ blas_gemm(
1220
+ at::native::TransposeType::NoTranspose,
1221
+ at::native::TransposeType::NoTranspose,
1222
+ D,
1223
+ mb_size,
1224
+ padded_mb_size,
1225
+ 1.0f,
1226
+ v_beta,
1227
+ D,
1228
+ attn2,
1229
+ CHUNK_SIZE,
1230
+ 0.0f,
1231
+ v_updated,
1232
+ D);
1233
+ }
1234
+
1235
+ // 5.h v_updated -> u
1236
+ scalar_t* __restrict__ u_ptr = u + (batch_offset + mb_start) * u_strideT + hv * u_strideH;
1237
+ update_kernel<scalar_t, D>::apply(u_ptr, v_updated, mb_size, D, u_strideT);
1238
}
1239
526
- // Move to the next query
527
- data_index_step(h, v_num_head, c, global_num_chunk);
1240
+ // move to the next index
1241
+ data_index_step(nt, NT, h, H);
1242
}
1243
+ at::native::cpublas::brgemm_release();
1244
});
1245
+}
1246
531
- // for each chunk
532
- at::parallel_for(0, batch_size * v_num_head, 1, [&](int64_t begin, int64_t end) {
533
- int64_t b = 0, h = 0;
534
- data_index_init(begin, b, batch_size, h, v_num_head);
535
- int ompIdx = at::get_thread_num();
536
- int64_t offset =
537
- /* k_transpose */ qk_head_size * chunk_size +
538
- /* v_pack */ chunk_size * v_head_size +
539
- /* k_beta_g */ chunk_size * qk_head_size +
540
- /* k_beta_g_pack */ chunk_size * qk_head_size +
541
- /* attn */ chunk_size * chunk_size * 2 +
542
- /* attn_reduced */ chunk_size * chunk_size +
543
- /* k_cumdecay */ chunk_size * qk_head_size * 2 +
544
- /* row */ chunk_size * 2 +
545
- /* updated */ chunk_size * 2;
546
- scalar_t* thread_buff_ptr = thread_buff + ompIdx * buff_size_16bit_per_thread;
547
- THREAD_BUFFER_ALLOC(
548
- curr_last_recurrent_state_reduced, thread_buff_ptr, offset, scalar_t, qk_head_size * v_head_size);
549
- THREAD_BUFFER_ALLOC(
550
- curr_last_recurrent_state_pack_reduced, thread_buff_ptr, offset, scalar_t, qk_head_size * v_head_size);
551
- THREAD_BUFFER_ALLOC(k_transpose_i, thread_buff_ptr, offset, scalar_t, qk_head_size * chunk_size);
552
- THREAD_BUFFER_ALLOC(attn_i, thread_buff_ptr, offset, float, chunk_size* chunk_size * 2);
553
- THREAD_BUFFER_ALLOC(attn_i_reduced, thread_buff_ptr, offset, scalar_t, chunk_size * chunk_size);
554
- THREAD_BUFFER_ALLOC(v_prime, thread_buff_ptr, offset, float, chunk_size* v_head_size * 2);
555
- THREAD_BUFFER_ALLOC(v_prime_reduced, thread_buff_ptr, offset, scalar_t, chunk_size * v_head_size);
556
- THREAD_BUFFER_ALLOC(v_prime_pack_reduced, thread_buff_ptr, offset, scalar_t, chunk_size * v_head_size);
557
- THREAD_BUFFER_ALLOC(qg, thread_buff_ptr, offset, scalar_t, chunk_size * qk_head_size);
558
- THREAD_BUFFER_ALLOC(attn_inter, thread_buff_ptr, offset, float, chunk_size* v_head_size * 2);
559
- THREAD_BUFFER_ALLOC(kg, thread_buff_ptr, offset, scalar_t, chunk_size * qk_head_size);
560
- THREAD_BUFFER_ALLOC(kg_transpose, thread_buff_ptr, offset, scalar_t, qk_head_size * chunk_size);
561
- THREAD_BUFFER_ALLOC(kgv, thread_buff_ptr, offset, float, qk_head_size* v_head_size * 2);
562
-
563
- for ([[maybe_unused]] auto z : c10::irange(begin, end)) {
564
- int64_t start_q = cu_seqlens_ptr[b];
565
- int64_t seq_len = cu_seqlens_ptr[b + 1] - start_q;
566
- int64_t num_chunk = chunk_offsets_ptr[b + 1] - chunk_offsets_ptr[b];
567
- int64_t chunk_offset = chunk_offsets_ptr[b];
568
- int64_t len_offset = chunk_offset * chunk_size;
569
-
570
- int64_t h_qk = h / head_group;
571
- auto out_ptr = out + start_q * oStrideT;
572
- auto curr_q = q_pad + len_offset * qk_head_size +
573
- h_qk * global_total_seq_length * qk_head_size; // [num_chunk, chunk_size, EK]
574
- auto curr_k = k_pad + len_offset * qk_head_size +
575
- h_qk * global_total_seq_length * qk_head_size; // [num_chunk, chunk_size, EK]
576
- auto curr_v = v_beta_attn + h * global_total_seq_length * v_head_size; // [num_chunk, chunk_size, EV]
577
- auto curr_decay_mask =
578
- decay_mask + h * global_total_seq_length * chunk_size; // [num_chunk, chunk_size, chunk_size]
579
- auto curr_k_cumdecay_reduced =
580
- k_cumdecay_reduced + h * global_total_seq_length * qk_head_size; // [num_chunk, chunk_size, EK]
581
- auto curr_last_recurrent_state =
582
- final_state_data + b * final_state_StrideN + h * final_state_StrideH; // [EK, EV]
583
- auto curr_g_pad = g_pad + len_offset + h * global_total_seq_length; // [num_chunk, chunk_size]
584
- auto curr_core_attn_out = core_attn_out + len_offset * v_head_size +
585
- h * global_total_seq_length * v_head_size; // [num_chunk, chunk_size, EV]
586
- for (int64_t c = 0; c < num_chunk; c++) {
587
- for (int i = 0; i < qk_head_size; i++) {
588
- at::vec::map<scalar_t>(
589
- [](fVec x) { return x; },
590
- curr_last_recurrent_state_reduced + i * v_head_size,
591
- curr_last_recurrent_state + i * v_head_size,
592
- v_head_size);
593
- }
594
- auto q_i = curr_q + c * chunk_size * qk_head_size; // [chunk_size, EK]
595
- auto k_i = curr_k + c * chunk_size * qk_head_size; // [chunk_size, EK]
596
- auto v_i = curr_v + (chunk_offset + c) * chunk_size * v_head_size; // [chunk_size, EV]
597
- auto decay_mask_i = curr_decay_mask + (chunk_offset + c) * chunk_size * chunk_size; // [chunk_size, chunk_size]
598
- auto k_cumdecay_i_reduced =
599
- curr_k_cumdecay_reduced + (chunk_offset + c) * chunk_size * qk_head_size; // [chunk_size, EK]
600
- auto g_pad_i = curr_g_pad + c * chunk_size; // [chunk_size]
601
- auto core_attn_out_i = curr_core_attn_out + c * chunk_size * v_head_size; // [chunk_size, EV]
1247
+//
1248
+// out : [B, T, Hv, Dv]
1249
+// state : [num_seqs, Hv, Dv, D]
1250
+// q : [B, T, H, D]
1251
+// k : [B, T, H, D]
1252
+// w : [B, T, Hv, D]
1253
+// u : [B, T, Hv, Dv]
1254
+// g : [B, NT, Hv, C]
1255
+// d : [B, NT, Hv, C, C]
1256
+// cu_seqlens : [num_seqs + 1]
1257
+// chunk_offsets : [num_seqs + 1]
1258
+template <typename scalar_t, int D, int CHUNK_SIZE>
1259
+void chunk_gated_delta_rule_fwd_inter_kernel_impl(
1260
+ scalar_t* __restrict__ out,
1261
+ float* __restrict__ state,
1262
+ const int32_t* __restrict__ indices,
1263
+ const scalar_t* __restrict__ q,
1264
+ const scalar_t* __restrict__ k,
1265
+ const scalar_t* __restrict__ w,
1266
+ const scalar_t* __restrict__ u,
1267
+ const float* __restrict__ g,
1268
+ const float* __restrict__ d,
1269
+ const int32_t* __restrict__ cu_seqlens,
1270
+ const int32_t* __restrict__ chunk_offsets,
1271
+ int64_t H,
1272
+ int64_t Hv,
1273
+ int64_t num_seqs,
1274
+ int64_t q_strideT,
1275
+ int64_t q_strideH,
1276
+ int64_t k_strideT,
1277
+ int64_t k_strideH,
1278
+ int64_t state_strideS) {
1279
+ // head group, expect to be 1,2,4 for qwen3.5
1280
+ const int64_t HG = Hv / H;
1281
+
1282
+ // strides
1283
+ const int64_t w_strideT = Hv * D;
1284
+ const int64_t w_strideH = D;
1285
+ const int64_t u_strideT = Hv * D;
1286
+ const int64_t u_strideH = D;
1287
+ const int64_t o_strideT = Hv * D;
1288
+ const int64_t o_strideH = D;
1289
+
1290
+ // [NB]: parallel on [num_seqs, Hv]
1291
+ // * choose to parallel on Hv instead of H, though this means q @ kT has duplicated compute
1292
+ // * H might be 16 which is not enough to use 32C when num_seqs is small
1293
+ at::parallel_for(0, num_seqs * Hv, 0, [&](int64_t begin, int64_t end) {
1294
+ int64_t bs{0}, hv{0};
1295
+ data_index_init(begin, bs, num_seqs, hv, Hv);
1296
+
1297
+ // thread local temp buffer
1298
+ DECL_ZERO_BUF(scalar_t, tmp, CHUNK_SIZE * D);
1299
+ DECL_ZERO_BUF(scalar_t, tmp2, D * D);
1300
+ DECL_ZERO_BUF(float, tmp3, CHUNK_SIZE* D);
1301
+ DECL_ZERO_BUF(scalar_t, tmp4, CHUNK_SIZE * D);
1302
+ DECL_ZERO_BUF(float, attn, CHUNK_SIZE* CHUNK_SIZE);
1303
+ DECL_ZERO_BUF(scalar_t, attn2, CHUNK_SIZE * CHUNK_SIZE);
1304
+
1305
+ // alias
1306
+ scalar_t* __restrict__ k_packed = tmp;
1307
+ scalar_t* __restrict__ s_packed = tmp2;
1308
+ float* __restrict__ v_prime = tmp3;
1309
+ scalar_t* __restrict__ v_prime2 = tmp;
1310
+ float* __restrict__ attn_inter = tmp3;
1311
+ scalar_t* __restrict__ qg_exp = tmp4;
1312
+ scalar_t* __restrict__ v_packed = tmp4;
1313
+ scalar_t* __restrict__ k_updated = tmp;
1314
+
1315
+ for (int64_t i = begin; i < end; ++i) {
1316
+ int64_t h = hv / HG;
1317
+ int32_t batch_offset = cu_seqlens[bs];
1318
+ int32_t seqlen = cu_seqlens[bs + 1] - cu_seqlens[bs];
1319
+ int64_t nt = chunk_offsets[bs];
1320
+
1321
+ for (int64_t mb_start = 0; mb_start < seqlen; mb_start += CHUNK_SIZE, ++nt) {
1322
+ int64_t mb_size = std::min(seqlen - mb_start, int64_t(CHUNK_SIZE));
1323
1324
+ // mb_size` is K in 4.a, pad to TILE_K;
1325
+ const int64_t padded_mb_size = div_up((int)mb_size, TILE_K) * TILE_K;
1326
+
1327
+ // step 1.a: attn = query @ key^T
1328
// attn_i = (q_i @ k_i.transpose(-1, -2) * decay_mask[:, :, i]).masked_fill_(mask, 0)
604
- // k_transpose_i = k_i.transpose(-1, -2)
1329
+ const scalar_t* __restrict__ q_ptr = q + (batch_offset + mb_start) * q_strideT + h * q_strideH;
1330
+ const scalar_t* __restrict__ k_ptr = k + (batch_offset + mb_start) * k_strideT + h * k_strideH;
1331
if constexpr (brgemm_supported()) {
1332
pack_vnni<scalar_t>(
607
- /* dst */ k_transpose_i,
608
- /* src */ k_i,
609
- /* N */ chunk_size,
610
- /* K */ qk_head_size,
611
- /* ld_src */ qk_head_size,
612
- /* ld_dst */ chunk_size);
613
- // attn_i = q_i @ k_transpose_i
1333
+ /* dst */ k_packed,
1334
+ /* src */ k_ptr,
1335
+ /* N */ mb_size,
1336
+ /* K */ D,
1337
+ /* ld_src */ k_strideT,
1338
+ /* ld_dst */ CHUNK_SIZE);
1339
+
1340
at::native::cpublas::brgemm(
615
- /* M */ chunk_size,
616
- /* N */ chunk_size,
617
- /* K */ qk_head_size,
618
- /* lda */ qk_head_size,
619
- /* ldb */ chunk_size,
620
- /* ldc */ chunk_size,
1341
+ /* M */ mb_size,
1342
+ /* N */ mb_size,
1343
+ /* K */ D,
1344
+ /* lda */ q_strideT,
1345
+ /* ldb */ CHUNK_SIZE,
1346
+ /* ldc */ CHUNK_SIZE,
1347
/* add_C */ false,
622
- /* A */ q_i,
623
- /* B */ k_transpose_i,
624
- /* C */ attn_i);
1348
+ /* A */ q_ptr,
1349
+ /* B */ k_packed,
1350
+ /* C */ attn);
1351
} else {
1352
blas_gemm(
1353
at::native::TransposeType::Transpose,
1354
at::native::TransposeType::NoTranspose,
629
- chunk_size,
630
- chunk_size,
631
- qk_head_size,
1355
+ mb_size,
1356
+ mb_size,
1357
+ D,
1358
1.0f,
633
- k_i,
634
- qk_head_size,
635
- q_i,
636
- qk_head_size,
1359
+ k_ptr,
1360
+ k_strideT,
1361
+ q_ptr,
1362
+ q_strideT,
1363
0.0f,
638
- attn_i,
639
- chunk_size);
640
- }
641
- // attn_i = attn_i * decay_mask_i
642
- for (int64_t m = 0; m < chunk_size; m++) {
643
- auto attn_i_m = attn_i + m * chunk_size;
644
- auto attn_i_reduced_m = attn_i_reduced + m * chunk_size;
645
- auto decay_mask_i_m = decay_mask_i + m * chunk_size;
646
- int64_t n = 0;
647
- for (; n < fVecSize * (chunk_size / fVecSize); n += fVecSize) {
648
- auto tmp0 = fVec::loadu(attn_i_m + n);
649
- auto tmp1 = fVec::loadu(decay_mask_i_m + n);
650
- auto tmp2 = tmp0 * tmp1;
651
- auto tmp3 = at::vec::convert<scalar_t>(tmp2);
652
- tmp3.store(attn_i_reduced_m + n, fVecSize);
653
- }
654
- if (n < chunk_size) {
655
- auto tmp0 = fVec::loadu(attn_i_m + n, chunk_size - n);
656
- auto tmp1 = fVec::loadu(decay_mask_i_m + n, chunk_size - n);
657
- auto tmp2 = tmp0 * tmp1;
658
- auto tmp3 = at::vec::convert<scalar_t>(tmp2);
659
- tmp3.store(attn_i_reduced_m + n, chunk_size - n);
660
- }
661
- }
662
- // mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=query.device), diagonal=1)
663
- // attn_i = attn_i.masked_fill_(mask, 0)
664
- for (int i = 0; i < chunk_size - 1; i++) {
665
- const auto vec_zero = bVec(0);
666
- int64_t len = chunk_size - i - 1;
667
- int64_t front = len % VecSize;
668
- int64_t j = i + 1;
669
- // first masked vec for alignment
670
- if (front > 0) {
671
- vec_zero.store(attn_i_reduced + i * chunk_size + j, front);
672
- j += front;
673
- }
674
- for (; j < VecSize * (chunk_size / VecSize); j += VecSize) {
675
- vec_zero.store(attn_i_reduced + i * chunk_size + j);
676
- }
1364
+ attn,
1365
+ CHUNK_SIZE);
1366
}
1367
679
- // pack for curr_last_recurrent_state
1368
+ // step 1.b: attn = attn * decay_mask.masked_fill_(mask, 0)
1369
+ const float* __restrict__ d_ptr = d + nt * (Hv * CHUNK_SIZE * CHUNK_SIZE) + hv * (CHUNK_SIZE * CHUNK_SIZE);
1370
+ apply_mask_kernel<scalar_t, CHUNK_SIZE, false>::apply(attn2, attn, nullptr, d_ptr, mb_size);
1371
+
1372
+ // step 2.a: v' = w @ state (fuse state *= exp(g_last) with packing)
1373
+ float* __restrict__ s_ptr = state + indices[bs] * state_strideS + hv * (D * D);
1374
+ const float* __restrict__ g_ptr = g + nt * (Hv * CHUNK_SIZE) + hv * (CHUNK_SIZE);
1375
+ float g_last = g_ptr[mb_size - 1];
1376
+ const scalar_t* __restrict__ w_ptr = w + (batch_offset + mb_start) * w_strideT + hv * w_strideH;
1377
if constexpr (brgemm_supported()) {
681
- pack_vnni2<scalar_t>(
682
- /* dst */ curr_last_recurrent_state_pack_reduced,
683
- /* src */ curr_last_recurrent_state_reduced,
684
- /* N */ qk_head_size,
685
- /* K */ v_head_size,
686
- /* ld_src */ v_head_size,
687
- /* ld_dst */ v_head_size);
688
-
689
- // v_prime = k_cumdecay_i @ curr_last_recurrent_state: [chunk_size, EV]
690
- // k_cumdecay_i: [chunk_size, EK]
691
- // curr_last_recurrent_state: [EK, EV]
1378
+ pack_vnni2<scalar_t, D, D>(
1379
+ /* dst */ s_packed,
1380
+ /* src */ s_ptr,
1381
+ /* g_last */ g_last,
1382
+ /* ld_src */ D,
1383
+ /* ld_dst */ D);
1384
+
1385
at::native::cpublas::brgemm(
693
- /* M */ chunk_size,
694
- /* N */ v_head_size,
695
- /* K */ qk_head_size,
696
- /* lda */ qk_head_size,
697
- /* ldb */ v_head_size,
698
- /* ldc */ v_head_size,
1386
+ /* M */ mb_size,
1387
+ /* N */ D,
1388
+ /* K */ D,
1389
+ /* lda */ w_strideT,
1390
+ /* ldb */ D,
1391
+ /* ldc */ D,
1392
/* add_C */ false,
700
- /* A */ k_cumdecay_i_reduced,
701
- /* B */ curr_last_recurrent_state_pack_reduced,
1393
+ /* A */ w_ptr,
1394
+ /* B */ s_packed,
1395
/* C */ v_prime);
1396
} else {
1397
+ // brgemm_supported()==false path: pack_vnni2 above packs the
1398
+ // *unscaled* state into its dst (for this GEMM's B operand) while
1399
+ // separately scaling src in place by exp(g_last) (consumed later,
1400
+ // at step 5.3's state accumulation). Replicate both halves in the
1401
+ // same order: snapshot s_ptr into s_packed BEFORE scaling s_ptr,
1402
+ // not after, since the GEMM below needs the pre-scale state.
1403
+ float g_last_scale = std::exp(g_last);
1404
+ for (int64_t d0 = 0; d0 < D * D; ++d0) {
1405
+ s_packed[d0] = static_cast<scalar_t>(s_ptr[d0]);
1406
+ s_ptr[d0] *= g_last_scale;
1407
+ }
1408
blas_gemm(
1409
at::native::TransposeType::NoTranspose,
1410
at::native::TransposeType::NoTranspose,
707
- v_head_size,
708
- chunk_size,
709
- qk_head_size,
1411
+ D,
1412
+ mb_size,
1413
+ D,
1414
1.0f,
711
- curr_last_recurrent_state_reduced,
712
- v_head_size,
713
- k_cumdecay_i_reduced,
714
- qk_head_size,
1415
+ s_packed,
1416
+ D,
1417
+ w_ptr,
1418
+ w_strideT,
1419
0.0f,
1420
v_prime,
717
- v_head_size);
1421
+ D);
1422
}
1423
720
- // v_new = v_prime = v_i - v_prime
721
- // v_i: [chunk_size, EV]
722
- for (int64_t m = 0; m < chunk_size; m++) {
723
- int64_t i = 0;
724
- for (; i < fVecSize * (v_head_size / fVecSize); i += fVecSize) {
725
- auto tmp0 = fVec::loadu(v_i + m * v_head_size + i);
726
- auto tmp1 = fVec::loadu(v_prime + m * v_head_size + i);
727
- auto tmp2 = tmp0 - tmp1;
728
- auto tmp3 = at::vec::convert<scalar_t>(tmp2);
729
- tmp3.store(v_prime_reduced + m * v_head_size + i, fVecSize);
730
- }
731
- }
1424
+ // step 2.b: v2' = u - v'
1425
+ const scalar_t* __restrict__ u_ptr = u + (batch_offset + mb_start) * u_strideT + hv * u_strideH;
1426
+ update_value_kernel<scalar_t, D>::apply(v_prime2, u_ptr, v_prime, mb_size, padded_mb_size, u_strideT);
1427
733
- // attn_inter = (q_i * g[:, :, i, :, None].exp()) @ last_recurrent_state
734
- // qg = q_i * g[:, :, i, :, None].exp(): [chunk_size, EK]
735
- // q_i: [chunk_size, EK]
736
- // g[:, :, i, :, None]: [chunk_size, 1]
737
- for (int64_t m = 0; m < chunk_size; m++) {
738
- auto g_pad_i_m = g_pad_i + m;
739
- auto g_exp = std::exp(*g_pad_i_m);
740
- int64_t i = 0;
741
- scalar_t g_exp_reduced = static_cast<scalar_t>(g_exp);
742
- auto vec_g_exp_reduced = bVec(g_exp_reduced);
743
- for (; i < VecSize * (qk_head_size / VecSize); i += VecSize) {
744
- auto tmp0 = bVec::loadu(q_i + m * qk_head_size + i);
745
- auto tmp2 = tmp0 * vec_g_exp_reduced;
746
- tmp2.store(qg + m * qk_head_size + i);
747
- }
748
- }
749
- // attn_inter = qg @ curr_last_recurrent_state: [chunk_size, EV]
750
- // curr_last_recurrent_state: [EK, EV]
1428
+ // step 3.a: qg_exp = q * exp(g)
1429
+ apply_beta_kernel<scalar_t, CHUNK_SIZE, D, false, true>::apply(
1430
+ qg_exp, q_ptr, nullptr, g_ptr, mb_size, q_strideT, D, /*b_stride*/ 0);
1431
+
1432
+ // step 3.b: attn_inter = qg_exp @ state
1433
if constexpr (brgemm_supported()) {
1434
at::native::cpublas::brgemm(
753
- /* M */ chunk_size,
754
- /* N */ v_head_size,
755
- /* K */ qk_head_size,
756
- /* lda */ qk_head_size,
757
- /* ldb */ v_head_size,
758
- /* ldc */ v_head_size,
1435
+ /* M */ mb_size,
1436
+ /* N */ D,
1437
+ /* K */ D,
1438
+ /* lda */ D,
1439
+ /* ldb */ D,
1440
+ /* ldc */ D,
1441
/* add_C */ false,
760
- /* A */ qg,
761
- /* B */ curr_last_recurrent_state_pack_reduced,
762
- /* C */ attn_inter);
1442
+ /* A */ qg_exp,
1443
+ /* B */ s_packed,
1444
+ /* C */ attn_inter);
1445
} else {
1446
blas_gemm(
1447
at::native::TransposeType::NoTranspose,
1448
at::native::TransposeType::NoTranspose,
767
- v_head_size,
768
- chunk_size,
769
- qk_head_size,
1449
+ D,
1450
+ mb_size,
1451
+ D,
1452
1.0f,
771
- curr_last_recurrent_state_reduced,
772
- v_head_size,
773
- qg,
774
- qk_head_size,
1453
+ s_packed,
1454
+ D,
1455
+ qg_exp,
1456
+ D,
1457
0.0f,
1458
attn_inter,
777
- v_head_size);
1459
+ D);
1460
}
1461
780
- // core_attn_out[:, :, i] = attn_inter + attn_i @ v_new
781
- // pack for v_prime
1462
+ // step 4.a: attn_inter += attn2 @ v2'
1463
if constexpr (brgemm_supported()) {
1464
pack_vnni2<scalar_t>(
784
- /* dst */ v_prime_pack_reduced,
785
- /* src */ v_prime_reduced,
786
- /* N */ chunk_size,
787
- /* K */ v_head_size,
788
- /* ld_src */ v_head_size,
789
- /* ld_dst */ v_head_size);
790
- // attn_inter = attn_inter + attn_i @ v_new: [chunk_size, EV]
791
- // attn_i: [chunk_size, chunk_size]
792
- // v_new: [chunk_size, EV]
1465
+ /* dst */ v_packed,
1466
+ /* src */ v_prime2,
1467
+ /* K */ padded_mb_size,
1468
+ /* N */ D,
1469
+ /* ld_src */ D,
1470
+ /* ld_dst */ D);
1471
+
1472
at::native::cpublas::brgemm(
794
- /* M */ chunk_size,
795
- /* N */ v_head_size,
796
- /* K */ chunk_size,
797
- /* lda */ chunk_size,
798
- /* ldb */ v_head_size,
799
- /* ldc */ v_head_size,
1473
+ /* M */ mb_size,
1474
+ /* N */ D,
1475
+ /* K */ padded_mb_size,
1476
+ /* lda */ CHUNK_SIZE,
1477
+ /* ldb */ D,
1478
+ /* ldc */ D,
1479
/* add_C */ true,
801
- /* A */ attn_i_reduced,
802
- /* B */ v_prime_pack_reduced,
803
- /* C */ attn_inter);
1480
+ /* A */ attn2,
1481
+ /* B */ v_packed,
1482
+ /* C */ attn_inter);
1483
} else {
1484
blas_gemm(
1485
at::native::TransposeType::NoTranspose,
1486
at::native::TransposeType::NoTranspose,
808
- v_head_size,
809
- chunk_size,
810
- chunk_size,
1487
+ D,
1488
+ mb_size,
1489
+ padded_mb_size,
1490
1.0f,
812
- v_prime_reduced,
813
- v_head_size,
814
- attn_i_reduced,
815
- chunk_size,
1491
+ v_prime2,
1492
+ D,
1493
+ attn2,
1494
+ CHUNK_SIZE,
1495
1.0f,
1496
attn_inter,
818
- v_head_size);
1497
+ D);
1498
}
1499
821
- // core_attn_out[:, :, i] = attn_inter
822
- for (int64_t m = 0; m < chunk_size; m++) {
823
- at::vec::map<float>(
824
- [](fVec x) { return x; }, core_attn_out_i + m * v_head_size, attn_inter + m * v_head_size, v_head_size);
1500
+ // step 4.b: write attn_inter -> out
1501
+ scalar_t* __restrict__ o_ptr = out + (batch_offset + mb_start) * o_strideT + hv * o_strideH;
1502
+ update_kernel<scalar_t, D>::apply(o_ptr, attn_inter, mb_size, D, o_strideT);
1503
+
1504
+ // brgemm_supported()==false path: step 5.2 below overwrites k_updated,
1505
+ // which aliases the same buffer as v_prime2 (both are `tmp`). Snapshot
1506
+ // v_prime2 into v_packed (otherwise brgemm-only, and free by now since
1507
+ // qg_exp was consumed at step 3.b) before that happens, so step 5.3
1508
+ // doesn't read k_updated's data under the v_prime2 name.
1509
+ if constexpr (!brgemm_supported()) {
1510
+ std::copy(v_prime2, v_prime2 + padded_mb_size * D, v_packed);
1511
}
1512
827
- // last_recurrent_state = (
828
- // last_recurrent_state * g[:, :, i, -1, None, None].exp()
829
- // + (k_i * (g[:, :, i, -1, None] - g[:, :, i]).exp()[..., None]).transpose(-1, -2) @ v_new
830
- // )
831
- // 1) last_recurrent_state * g[:, :, i, -1, None, None].exp()
832
- // curr_last_recurrent_state: [EK, EV]
833
- // g[:, :, i, -1, None, None]: [1, 1]
834
- // last_recurrent_state * g[:, :, i, -1, None, None].exp(): [EK, EV]
835
- auto g_pad_i_last = g_pad_i + chunk_size - 1;
836
- auto g_exp_last = std::exp(g_pad_i_last[0]);
837
- for (int64_t m = 0; m < qk_head_size; m++) {
838
- int64_t i = 0;
839
- auto vec_g_exp_last = fVec(g_exp_last);
840
- for (; i < fVecSize * (v_head_size / fVecSize); i += fVecSize) {
841
- auto tmp0 = bVec::loadu(curr_last_recurrent_state_reduced + m * v_head_size + i);
842
- auto tmp1 = at::vec::convert<float>(tmp0);
843
- auto tmp2 = tmp1 * vec_g_exp_last;
844
- tmp2.store(curr_last_recurrent_state + m * v_head_size + i);
845
- }
846
- if (i < v_head_size) {
847
- auto tmp0 = bVec::loadu(curr_last_recurrent_state_reduced + m * v_head_size + i, v_head_size - i);
848
- auto tmp1 = at::vec::convert<float>(tmp0);
849
- auto tmp2 = tmp1 * vec_g_exp_last;
850
- tmp2.store(curr_last_recurrent_state + m * v_head_size + i, v_head_size - i);
851
- }
852
- }
853
- // 2) (k_i * (g[:, :, i, -1, None] - g[:, :, i]).exp()[..., None]).transpose(-1, -2) @ v_new
854
- // k_i: [chunk_size, EK]
855
- // g[:, :, i, -1, None]: [1]
856
- // g[:, :, i]: [chunk_size]
857
- // (g[:, :, i, -1, None] - g[:, :, i]).exp()[..., None]: [chunk_size, 1]
858
- // kg = k_i * (g[:, :, i, -1, None] - g[:, :, i]).exp()[..., None]: [chunk_size, EK]
859
- // (k_i * (g[:, :, i, -1, None] - g[:, :, i]).exp()[..., None]).transpose(-1, -2): [EK, chunk_size]
860
- // v_new: [chunk_size, EV]
861
- // (k_i * (g[:, :, i, -1, None] - g[:, :, i]).exp()[..., None]).transpose(-1, -2) @ v_new: [EK, EV]
862
- // kg = k_i * (g[:, :, i, -1, None] - g[:, :, i]).exp()[..., None]
863
- for (int64_t m = 0; m < chunk_size; m++) {
864
- auto g_exp = std::exp((g_pad_i_last[0] - g_pad_i[m]));
865
- int64_t i = 0;
866
- scalar_t g_exp_reduced = static_cast<scalar_t>(g_exp);
867
- auto vec_g_exp_reduced = bVec(g_exp_reduced);
868
- for (; i < VecSize * (qk_head_size / VecSize); i += VecSize) {
869
- auto tmp0 = bVec::loadu(k_i + m * qk_head_size + i);
870
- auto tmp2 = tmp0 * vec_g_exp_reduced;
871
- tmp2.store(kg + m * qk_head_size + i);
872
- }
873
- }
874
- // kg.transpose(-1, -2): [EK, chunk_size]
875
- at::native::utils::transpose<scalar_t>(
876
- /* M */ chunk_size,
877
- /* N */ qk_head_size,
878
- /* src */ kg,
879
- /* ld_src */ qk_head_size,
880
- /* dst */ kg_transpose,
881
- /* ld_dst */ chunk_size);
882
- // kgv = kg.transpose(-1, -2) @ v_new
883
- // v_new: [chunk_size, EV]
1513
+ // step 5: update state
1514
+ // state_new = state * exp(g_last) + (k * exp(g_last - g)).T @ v2'
1515
+
1516
+ // step 5.1 state *= exp(g_last) fused with step 2.a
1517
+
1518
+ // step 5.2 k' = k * exp(g_last - g).T; TODO: fuse this with 1.a
1519
+ update_key_kernel<scalar_t, CHUNK_SIZE, D>::apply(k_updated, k_ptr, g_ptr, mb_size, k_strideT);
1520
+
1521
+ // step 5.3 state += k' @ v2'
1522
if constexpr (brgemm_supported()) {
1523
at::native::cpublas::brgemm(
886
- /* M */ qk_head_size,
887
- /* N */ v_head_size,
888
- /* K */ chunk_size,
889
- /* lda */ chunk_size,
890
- /* ldb */ v_head_size,
891
- /* ldc */ v_head_size,
892
- /* add_C */ false,
893
- /* A */ kg_transpose,
894
- /* B */ v_prime_pack_reduced,
895
- /* C */ kgv);
1524
+ /* M */ D,
1525
+ /* N */ D,
1526
+ /* K */ padded_mb_size, // mb_size
1527
+ /* lda */ CHUNK_SIZE,
1528
+ /* ldb */ D,
1529
+ /* ldc */ D,
1530
+ /* add_C */ true,
1531
+ /* A */ k_updated,
1532
+ /* B */ v_packed,
1533
+ /* C */ s_ptr);
1534
} else {
1535
blas_gemm(
1536
at::native::TransposeType::NoTranspose,
1537
at::native::TransposeType::NoTranspose,
900
- v_head_size,
901
- qk_head_size,
902
- chunk_size,
1538
+ D,
1539
+ D,
1540
+ padded_mb_size,
1541
1.0f,
904
- v_prime_reduced,
905
- v_head_size,
906
- kg_transpose,
907
- chunk_size,
908
- 0.0f,
909
- kgv,
910
- v_head_size);
911
- }
912
- // last_recurrent_state = 1) + 2)
913
- for (int64_t m = 0; m < qk_head_size; m++) {
914
- at::vec::map2<float>(
915
- [](fVec x, fVec y) { return x + y; },
916
- curr_last_recurrent_state + m * v_head_size,
917
- curr_last_recurrent_state + m * v_head_size,
918
- kgv + m * v_head_size,
919
- v_head_size);
1542
+ v_packed,
1543
+ D,
1544
+ k_updated,
1545
+ CHUNK_SIZE,
1546
+ 1.0f,
1547
+ s_ptr,
1548
+ D);
1549
}
1550
}
1551
923
- // core_attn_out -> output
924
- // output: [B, T, HV, EV]
925
- // core_attn_out: [B, HV, padded_T, EV]
926
- auto curr_out = out_ptr + h * oStrideH;
927
- for (int64_t m = 0; m < seq_len; m++) {
928
- at::vec::map<scalar_t>(
929
- [](fVec x) { return x; }, curr_out + m * oStrideT, curr_core_attn_out + m * v_head_size, v_head_size);
930
- }
931
-
932
- // Move to the next query
933
- data_index_step(b, batch_size, h, v_num_head);
1552
+ // move to the next index
1553
+ data_index_step(bs, num_seqs, hv, Hv);
1554
}
1555
+ at::native::cpublas::brgemm_release();
1556
});
1557
}
1558
@@ -1011,14 +1632,10 @@ void fused_sigmoid_gating_delta_rule_update_kernel_impl(
1632
int64_t d;
1633
#pragma GCC unroll 4
1634
for (d = 0; d <= head_dim - VecSize; d += VecSize) {
1014
- bVec q_bvec = bVec::loadu(q_ptr + q_offset + d);
1015
- fVec q_fvec0, q_fvec1;
1016
- std::tie(q_fvec0, q_fvec1) = at::vec::convert_to_float(q_bvec);
1635
+ auto [q_fvec0, q_fvec1] = load_float_vec2(q_ptr + q_offset + d);
1636
sum_q_fvec += q_fvec0 * q_fvec0;
1637
sum_q_fvec += q_fvec1 * q_fvec1;
1019
- bVec k_bvec = bVec::loadu(k_ptr + k_offset + d);
1020
- fVec k_fvec0, k_fvec1;
1021
- std::tie(k_fvec0, k_fvec1) = at::vec::convert_to_float(k_bvec);
1638
+ auto [k_fvec0, k_fvec1] = load_float_vec2(k_ptr + k_offset + d);
1639
sum_k_fvec += k_fvec0 * k_fvec0;
1640
sum_k_fvec += k_fvec1 * k_fvec1;
1641
}
@@ -1066,14 +1683,11 @@ void fused_sigmoid_gating_delta_rule_update_kernel_impl(
1683
fVec kv_mem_vec1 = fVec(float(0));
1684
for (int di = 0; di < head_dim; ++di) {
1685
fVec k_val_vec = fVec(k_ptr[k_offset + di] * k_scale);
1069
- fVec state_vec0 = fVec::loadu(state_ptr + state_offset + di * v_head_dim + dvi);
1070
- fVec state_vec1 = fVec::loadu(state_ptr + state_offset + di * v_head_dim + dvi + fVecSize);
1686
+ auto [state_vec0, state_vec1] = load_float_vec2(state_ptr + state_offset + di * v_head_dim + dvi);
1687
kv_mem_vec0 = kv_mem_vec0 + state_vec0 * g_val_exp_vec * k_val_vec;
1688
kv_mem_vec1 = kv_mem_vec1 + state_vec1 * g_val_exp_vec * k_val_vec;
1689
}
1074
- bVec v_bvec = bVec::loadu(v_ptr + v_offset + dvi);
1075
- fVec v_vec0, v_vec1;
1076
- std::tie(v_vec0, v_vec1) = at::vec::convert_to_float(v_bvec);
1690
+ auto [v_vec0, v_vec1] = load_float_vec2(v_ptr + v_offset + dvi);
1691
fVec dt_vec0 = (v_vec0 - kv_mem_vec0) * beta_vec;
1692
fVec dt_vec1 = (v_vec1 - kv_mem_vec1) * beta_vec;
1693
fVec o_vec0 = fVec(float(0));
@@ -1081,8 +1695,7 @@ void fused_sigmoid_gating_delta_rule_update_kernel_impl(
1695
for (int di = 0; di < head_dim; ++di) {
1696
fVec q_vec = fVec(q_ptr[q_offset + di] * q_scale);
1697
fVec k_vec = fVec(k_ptr[k_offset + di] * k_scale);
1084
- fVec state_vec0 = fVec::loadu(state_ptr + state_offset + di * v_head_dim + dvi);
1085
- fVec state_vec1 = fVec::loadu(state_ptr + state_offset + di * v_head_dim + dvi + fVecSize);
1698
+ auto [state_vec0, state_vec1] = load_float_vec2(state_ptr + state_offset + di * v_head_dim + dvi);
1699
state_vec0 = state_vec0 * g_val_exp_vec + k_vec * dt_vec0;
1700
state_vec1 = state_vec1 * g_val_exp_vec + k_vec * dt_vec1;
1701
o_vec0 = o_vec0 + state_vec0 * q_vec * scale_vec;
@@ -1289,25 +1902,19 @@ void fused_gdn_gating_kernel_impl(
1902
constexpr int vec_size = bVec::size();
1903
constexpr int fvec_size = fVec::size();
1904
const fVec neg_one(-1.0f);
1292
- const fVec one(1.0f);
1905
at::parallel_for(0, batch, 0, [&](int64_t begin, int64_t end) {
1906
for (int64_t i = begin; i < end; ++i) {
1907
int64_t j = 0;
1908
for (; j < num_heads - (num_heads % vec_size); j += vec_size) {
1297
- fVec A_log_vec0 = fVec::loadu(A_log + j);
1298
- fVec A_log_vec1 = fVec::loadu(A_log + j + fvec_size);
1299
- bVec dt_bias_vec = bVec::loadu(dt_bias + j);
1300
- bVec a_bvec = bVec::loadu(a + i * num_heads + j);
1301
- bVec b_bvec = bVec::loadu(b + i * num_heads + j);
1302
- fVec a0, a1, dt_bias_vec0, dt_bias_vec1, b0, b1;
1303
- std::tie(a0, a1) = at::vec::convert_to_float(a_bvec);
1304
- std::tie(b0, b1) = at::vec::convert_to_float(b_bvec);
1305
- std::tie(dt_bias_vec0, dt_bias_vec1) = at::vec::convert_to_float(dt_bias_vec);
1909
+ auto [A_log_vec0, A_log_vec1] = load_float_vec2(A_log + j);
1910
+ auto [dt_bias_vec0, dt_bias_vec1] = load_float_vec2(dt_bias + j);
1911
+ auto [a0, a1] = load_float_vec2(a + i * num_heads + j);
1912
+ auto [b0, b1] = load_float_vec2(b + i * num_heads + j);
1913
1914
fVec g0 = neg_one * A_log_vec0.exp_u20() * softplus(a0 + dt_bias_vec0);
1915
fVec g1 = neg_one * A_log_vec1.exp_u20() * softplus(a1 + dt_bias_vec1);
1309
- fVec beta0 = one / (one + (neg_one * b0).exp_u20());
1310
- fVec beta1 = one / (one + (neg_one * b1).exp_u20());
1916
+ fVec beta0 = fast_sigmoid(b0);
1917
+ fVec beta1 = fast_sigmoid(b1);
1918
1919
g0.store(out + i * num_heads + j);
1920
g1.store(out + i * num_heads + j + fvec_size);
@@ -1337,26 +1944,19 @@ void fused_gdn_gating_kernel_impl(
1944
constexpr int vec_size = bVec::size();
1945
constexpr int fvec_size = fVec::size();
1946
const fVec neg_one(-1.0f);
1340
- const fVec one(1.0f);
1947
at::parallel_for(0, batch, 0, [&](int64_t begin, int64_t end) {
1948
for (int64_t i = begin; i < end; ++i) {
1949
int64_t j = 0;
1950
for (; j < num_heads - (num_heads % vec_size); j += vec_size) {
1345
- bVec A_log_bvec = bVec::loadu(A_log + j);
1346
- fVec A_log_vec0, A_log_vec1;
1347
- std::tie(A_log_vec0, A_log_vec1) = at::vec::convert_to_float(A_log_bvec);
1348
- bVec dt_bias_vec = bVec::loadu(dt_bias + j);
1349
- bVec a_bvec = bVec::loadu(a + i * num_heads + j);
1350
- bVec b_bvec = bVec::loadu(b + i * num_heads + j);
1351
- fVec a0, a1, dt_bias_vec0, dt_bias_vec1, b0, b1;
1352
- std::tie(a0, a1) = at::vec::convert_to_float(a_bvec);
1353
- std::tie(b0, b1) = at::vec::convert_to_float(b_bvec);
1354
- std::tie(dt_bias_vec0, dt_bias_vec1) = at::vec::convert_to_float(dt_bias_vec);
1951
+ auto [A_log_vec0, A_log_vec1] = load_float_vec2(A_log + j);
1952
+ auto [dt_bias_vec0, dt_bias_vec1] = load_float_vec2(dt_bias + j);
1953
+ auto [a0, a1] = load_float_vec2(a + i * num_heads + j);
1954
+ auto [b0, b1] = load_float_vec2(b + i * num_heads + j);
1955
1956
fVec g0 = neg_one * A_log_vec0.exp_u20() * softplus(a0 + dt_bias_vec0);
1957
fVec g1 = neg_one * A_log_vec1.exp_u20() * softplus(a1 + dt_bias_vec1);
1358
- fVec beta0 = one / (one + (neg_one * b0).exp_u20());
1359
- fVec beta1 = one / (one + (neg_one * b1).exp_u20());
1958
+ fVec beta0 = fast_sigmoid(b0);
1959
+ fVec beta1 = fast_sigmoid(b1);
1960
1961
g0.store(out + i * num_heads + j);
1962
g1.store(out + i * num_heads + j + fvec_size);
@@ -1373,29 +1973,208 @@ void fused_gdn_gating_kernel_impl(
1973
1974
} // anonymous namespace
1975
1376
-template <bool is_last_dim_contiguous>
1377
-inline void
1378
-CHECK_INPUT_SHAPE_DTYPE(const at::Tensor& tensor, const int64_t& dim, const at::IntArrayRef& sizes, at::ScalarType st) {
1379
- TORCH_CHECK(tensor.sizes() == sizes, "Input tensor shape mismatch: expected ", sizes, ", got ", tensor.sizes());
1380
- TORCH_CHECK(tensor.dtype() == st, "Input tensor dtype mismatch");
1381
- CHECK_DIM(dim, tensor);
1382
- if (is_last_dim_contiguous) {
1383
- CHECK_LAST_DIM_CONTIGUOUS_INPUT(tensor);
1384
- } else {
1385
- CHECK_CONTIGUOUS(tensor);
1976
+template <int CHUNK_SIZE>
1977
+std::tuple<at::Tensor, at::Tensor> prepare_chunk_indices(const at::Tensor& cu_seqlens) {
1978
+ int64_t num_seqs = cu_seqlens.size(0) - 1;
1979
+ at::Tensor chunk_offsets = at::empty({num_seqs + 1}, cu_seqlens.options());
1980
+ // get number of chunks and chunk offsets
1981
+ const int32_t* offsets_data = cu_seqlens.data_ptr<int32_t>();
1982
+ int32_t num_chunks = 0;
1983
+ chunk_offsets[0] = 0;
1984
+ for (int64_t row = 0; row < num_seqs; ++row) {
1985
+ num_chunks += div_up(offsets_data[row + 1] - offsets_data[row], CHUNK_SIZE);
1986
+ chunk_offsets[row + 1] = num_chunks;
1987
}
1988
+ // get chunk indices
1989
+ at::Tensor chunk_indices = at::empty({num_chunks, 2}, cu_seqlens.options());
1990
+ int32_t* indices_data = chunk_indices.data_ptr<int32_t>();
1991
+
1992
+ int64_t idx = 0;
1993
+ for (int32_t row = 0; row < num_seqs; ++row) {
1994
+ int32_t num_chunks = div_up(offsets_data[row + 1] - offsets_data[row], CHUNK_SIZE);
1995
+
1996
+ for (int32_t col = 0; col < num_chunks; ++col) {
1997
+ indices_data[idx * 2 + 0] = row;
1998
+ indices_data[idx * 2 + 1] = col;
1999
+ idx++;
2000
+ }
2001
+ }
2002
+ return std::make_tuple(chunk_indices, chunk_offsets);
2003
}
2004
1389
-// query: [B, T, HK, EK]
1390
-// key: [B, T, HK, EK]
1391
-// value: [B, T, HV, EV]
1392
-// g: [B, T, HV] FP32
1393
-// beta: [B, T, HV]
1394
-// initial_state: [N, HV, EK, EV] FP32
1395
-// output_final_state: bool
1396
-// cu_seqlens: [N + 1] INT32
1397
-// head_first: bool
1398
-// use_qk_l2norm_in_kernel: bool
2005
+#define DISPATCH_HEAD_DIM_CASE(launch_macro, hd) \
2006
+ case hd: { \
2007
+ launch_macro(hd); \
2008
+ break; \
2009
+ }
2010
+
2011
+// [NB]: add new head_dim support here
2012
+#define DISPATCH_HEAD_DIM(dim, launch_macro) \
2013
+ switch (dim) { \
2014
+ DISPATCH_HEAD_DIM_CASE(launch_macro, 64) \
2015
+ DISPATCH_HEAD_DIM_CASE(launch_macro, 128) \
2016
+ default: \
2017
+ TORCH_CHECK(false, "Unexpected head dim size, ", dim); \
2018
+ }
2019
+
2020
+#define LAUNCH_L2NORM_KERNEL(HD) \
2021
+ l2norm_fwd_kernel_impl<scalar_t, HD>( \
2022
+ query_norm.data_ptr<scalar_t>(), \
2023
+ key_norm.data_ptr<scalar_t>(), \
2024
+ query.data_ptr<scalar_t>(), \
2025
+ key.data_ptr<scalar_t>(), \
2026
+ eps, \
2027
+ T, \
2028
+ H, \
2029
+ query.stride(1), \
2030
+ query.stride(2), \
2031
+ key.stride(1), \
2032
+ key.stride(2));
2033
+
2034
+std::tuple<at::Tensor, at::Tensor> l2norm_fwd(const at::Tensor& query, const at::Tensor& key, double eps) {
2035
+ int64_t B = query.size(0);
2036
+ int64_t T = query.size(1);
2037
+ int64_t H = query.size(2);
2038
+ int64_t D = query.size(3);
2039
+
2040
+ at::Tensor query_norm = at::empty_like(query);
2041
+ at::Tensor key_norm = at::empty_like(key);
2042
+
2043
+ AT_DISPATCH_REDUCED_FLOATING_TYPES(
2044
+ query.scalar_type(), "l2norm_fwd", [&] { DISPATCH_HEAD_DIM(D, LAUNCH_L2NORM_KERNEL); });
2045
+
2046
+ return std::make_tuple(query_norm, key_norm);
2047
+}
2048
+
2049
+// [NB]: instantiate decay_mask to avoid heavy recomputation in the kernel with exp
2050
+template <int CHUNK_SIZE>
2051
+at::Tensor chunk_local_cumsum(const at::Tensor& g, const at::Tensor& cu_seqlens, const at::Tensor& chunk_indices) {
2052
+ int64_t B = g.size(0);
2053
+ // int64_t T = g.size(1);
2054
+ int64_t Hv = g.size(2);
2055
+ int64_t NT = chunk_indices.size(0);
2056
+
2057
+ at::Tensor g_ = at::empty({B, NT, Hv, CHUNK_SIZE}, g.options());
2058
+ AT_DISPATCH_FLOATING_TYPES(g.scalar_type(), "chunk_local_cumsum", [&] {
2059
+ chunk_local_cumsum_kernel_impl<scalar_t, CHUNK_SIZE>(
2060
+ g_.data_ptr<scalar_t>(),
2061
+ g.data_ptr<scalar_t>(),
2062
+ cu_seqlens.data_ptr<int32_t>(),
2063
+ chunk_indices.data_ptr<int32_t>(),
2064
+ Hv,
2065
+ NT);
2066
+ });
2067
+ return g_;
2068
+}
2069
+
2070
+#define LAUNCH_CHUNK_GATED_DELTA_RULE_FWD_INTRA_KERNEL(HD) \
2071
+ chunk_gated_delta_rule_fwd_intra_kernel_impl<scalar_t, HD, CHUNK_SIZE>( \
2072
+ w.data_ptr<scalar_t>(), \
2073
+ u.data_ptr<scalar_t>(), \
2074
+ decay_mask.data_ptr<float>(), \
2075
+ k.data_ptr<scalar_t>(), \
2076
+ v.data_ptr<scalar_t>(), \
2077
+ g.data_ptr<float>(), \
2078
+ beta.data_ptr<scalar_t>(), \
2079
+ cu_seqlens.data_ptr<int32_t>(), \
2080
+ chunk_indices.data_ptr<int32_t>(), \
2081
+ H, \
2082
+ Hv, \
2083
+ NT, \
2084
+ k.stride(1), \
2085
+ k.stride(2), \
2086
+ v.stride(1), \
2087
+ v.stride(2));
2088
+
2089
+template <int CHUNK_SIZE>
2090
+std::tuple<at::Tensor, at::Tensor, at::Tensor> chunk_gated_delta_rule_fwd_intra(
2091
+ const at::Tensor& k,
2092
+ const at::Tensor& v,
2093
+ const at::Tensor& g,
2094
+ const at::Tensor& beta,
2095
+ const at::Tensor& cu_seqlens,
2096
+ const at::Tensor& chunk_indices) {
2097
+ int64_t B = k.size(0);
2098
+ int64_t T = k.size(1);
2099
+ int64_t H = k.size(2);
2100
+ int64_t D = k.size(3);
2101
+ int64_t Hv = v.size(2);
2102
+ int64_t Dv = v.size(3);
2103
+ int64_t NT = chunk_indices.size(0);
2104
+
2105
+ at::Tensor w = at::empty({B, T, Hv, D}, k.options()); // BFloat16
2106
+ at::Tensor u = at::empty({B, T, Hv, Dv}, k.options()); // BFloat16
2107
+ at::Tensor decay_mask = at::empty({B, NT, Hv, CHUNK_SIZE, CHUNK_SIZE}, g.options()); // Float
2108
+ AT_DISPATCH_REDUCED_FLOATING_TYPES(k.scalar_type(), "chunk_gated_delta_rule_fwd_intra", [&] {
2109
+ DISPATCH_HEAD_DIM(D, LAUNCH_CHUNK_GATED_DELTA_RULE_FWD_INTRA_KERNEL);
2110
+ });
2111
+
2112
+ return std::make_tuple(w, u, decay_mask);
2113
+}
2114
+
2115
+#define LAUNCH_CHUNK_GATED_DELTA_RULE_FWD_INTER_KERNEL(HD) \
2116
+ chunk_gated_delta_rule_fwd_inter_kernel_impl<scalar_t, HD, CHUNK_SIZE>( \
2117
+ o.data_ptr<scalar_t>(), \
2118
+ initial_state.data_ptr<float>(), \
2119
+ initial_state_indices.data_ptr<int32_t>(), \
2120
+ q.data_ptr<scalar_t>(), \
2121
+ k.data_ptr<scalar_t>(), \
2122
+ w.data_ptr<scalar_t>(), \
2123
+ u.data_ptr<scalar_t>(), \
2124
+ g.data_ptr<float>(), \
2125
+ decay_mask.data_ptr<float>(), \
2126
+ cu_seqlens.data_ptr<int32_t>(), \
2127
+ chunk_offsets.data_ptr<int32_t>(), \
2128
+ H, \
2129
+ Hv, \
2130
+ num_seqs, \
2131
+ q.stride(1), \
2132
+ q.stride(2), \
2133
+ k.stride(1), \
2134
+ k.stride(2), \
2135
+ initial_state.stride(0));
2136
+
2137
+template <int CHUNK_SIZE>
2138
+std::tuple<at::Tensor, at::Tensor> chunk_gated_delta_rule_fwd_inter(
2139
+ const at::Tensor& q,
2140
+ const at::Tensor& k,
2141
+ const at::Tensor& w,
2142
+ const at::Tensor& u,
2143
+ const at::Tensor& g,
2144
+ const at::Tensor& decay_mask,
2145
+ const at::Tensor& initial_state,
2146
+ bool output_final_state,
2147
+ const at::Tensor& cu_seqlens,
2148
+ const at::Tensor& chunk_offsets,
2149
+ const at::Tensor& initial_state_indices) {
2150
+ const int64_t B = q.size(0);
2151
+ const int64_t T = q.size(1);
2152
+ const int64_t H = q.size(2);
2153
+ const int64_t D = q.size(3);
2154
+ const int64_t Hv = w.size(2);
2155
+ const int64_t Dv = u.size(3);
2156
+ const int64_t num_seqs = initial_state_indices.size(0);
2157
+
2158
+ at::Tensor o = at::empty({B, T, Hv, Dv}, q.options());
2159
+ AT_DISPATCH_REDUCED_FLOATING_TYPES(q.scalar_type(), "chunk_gated_delta_rule_fwd_inter", [&] {
2160
+ DISPATCH_HEAD_DIM(D, LAUNCH_CHUNK_GATED_DELTA_RULE_FWD_INTER_KERNEL);
2161
+ });
2162
+
2163
+ return std::make_tuple(o, initial_state);
2164
+}
2165
+
2166
+// [NB]: Support only varlen inputs
2167
+// B: packed batch dim of q/k/v (== 1)
2168
+// num_seqs: number of variable-length sequences
2169
+//
2170
+// query: [B, T, H, D]
2171
+// key: [B, T, H, D]
2172
+// value: [B, T, Hv, Dv]
2173
+// g: [B, T, Hv] FP32
2174
+// beta: [B, T, Hv]
2175
+// initial_state: [num_seqs, Hv, Dv, D] FP32
2176
+// cu_seqlens: [num_seqs + 1] INT32
2177
+//
2178
std::tuple<at::Tensor, at::Tensor> chunk_gated_delta_rule_cpu(
2179
const at::Tensor& query,
2180
const at::Tensor& key,
@@ -1407,154 +2186,71 @@ std::tuple<at::Tensor, at::Tensor> chunk_gated_delta_rule_cpu(
2186
const at::Tensor& cu_seqlens,
2187
bool head_first,
2188
bool use_qk_l2norm_in_kernel,
1410
- double eps = 1e-5) {
1411
- TORCH_CHECK(head_first == false, "chunk_gated_delta_rule_cpu does not support head first");
1412
- int64_t B = query.size(0);
1413
- int64_t global_seq_len = query.size(1);
1414
- int64_t qk_num_head = query.size(2);
1415
- int64_t qk_head_size = query.size(3);
1416
- int64_t v_num_head = value.size(2);
1417
- int64_t v_head_size = value.size(3);
1418
- int64_t batch_size = initial_state.size(0);
1419
- CHECK_EQ(B, 1);
1420
- TORCH_CHECK(v_num_head % qk_num_head == 0, "expect v_num_head multiple of qk_num_head.");
1421
- TORCH_CHECK(qk_head_size % 32 == 0, "expect qk_head_size to be multiples of 32.");
1422
- TORCH_CHECK(v_head_size % 32 == 0, "expect v_head_size to be multiples of 32.");
1423
- CHECK_INPUT_SHAPE_DTYPE<true>(query, 4, {B, global_seq_len, qk_num_head, qk_head_size}, at::kBFloat16);
1424
- CHECK_INPUT_SHAPE_DTYPE<true>(key, 4, {B, global_seq_len, qk_num_head, qk_head_size}, at::kBFloat16);
1425
- CHECK_INPUT_SHAPE_DTYPE<true>(value, 4, {B, global_seq_len, v_num_head, v_head_size}, at::kBFloat16);
1426
- CHECK_INPUT_SHAPE_DTYPE<false>(g, 3, {B, global_seq_len, v_num_head}, at::kFloat);
1427
- CHECK_INPUT_SHAPE_DTYPE<false>(beta, 3, {B, global_seq_len, v_num_head}, at::kBFloat16);
1428
- CHECK_INPUT_SHAPE_DTYPE<false>(cu_seqlens, 1, {batch_size + 1}, at::kInt);
1429
- CHECK_INPUT_SHAPE_DTYPE<false>(initial_state, 4, {batch_size, v_num_head, qk_head_size, v_head_size}, at::kFloat);
1430
-
1431
- at::Tensor output = at::empty_like(value, value.options()); // [B, T, HV, EV]
1432
- at::Tensor final_state = initial_state.to(at::kFloat); // [N, HV, EK, EV]
1433
-
1434
- // Strides
1435
- int64_t qStrideH = query.stride(2);
1436
- int64_t qStrideT = query.stride(1);
1437
- int64_t kStrideH = key.stride(2);
1438
- int64_t kStrideT = key.stride(1);
1439
- int64_t vStrideH = value.stride(2);
1440
- int64_t vStrideT = value.stride(1);
1441
- int64_t oStrideH = output.stride(2);
1442
- int64_t oStrideT = output.stride(1);
1443
-
1444
- constexpr int64_t chunk_size = 64;
1445
- // Deduce the global chunks
1446
- // e.g. cu_seqlens: [0, 5, 13, 16], chunk_size = 4
1447
- // chunk_offsets: [0, 2, 4, 5]
1448
- // chunk_indices (batch_id, local_chunk_id): [[0, 0], [0, 1], [1, 0], [1, 1], [2, 0]]
1449
- at::Tensor chunk_offsets = at::empty(batch_size + 1, cu_seqlens.options());
1450
- auto chunk_offsets_ptr = chunk_offsets.data_ptr<int32_t>();
1451
- chunk_offsets_ptr[0] = 0;
1452
- int32_t* cu_seqlens_ptr = cu_seqlens.data_ptr<int32_t>();
1453
- int64_t s = 0;
1454
- int64_t e = 0;
1455
- int64_t s_pad = 0;
1456
- int64_t e_pad = 0;
1457
- for (int64_t b = 0; b < batch_size; b++) {
1458
- e = cu_seqlens_ptr[b + 1];
1459
- int64_t seq_len = e - s;
1460
- int64_t pad_size = (chunk_size - seq_len % chunk_size) % chunk_size;
1461
- int64_t total_seq_length = seq_len + pad_size;
1462
- e_pad = s_pad + total_seq_length;
1463
- chunk_offsets[b + 1] = e_pad / chunk_size;
1464
- s = e;
1465
- s_pad = e_pad;
1466
- }
1467
- int64_t global_total_seq_length = e_pad;
1468
- int64_t global_num_chunk = chunk_offsets_ptr[batch_size];
1469
- at::Tensor chunk_indices = at::empty(global_num_chunk * 2, cu_seqlens.options());
1470
- auto chunk_indices_ptr = chunk_indices.data_ptr<int32_t>();
1471
- int64_t curr_c = 0;
1472
- for (int64_t b = 0; b < batch_size; b++) {
1473
- int64_t batch_chunk_num = chunk_offsets_ptr[b + 1] - chunk_offsets_ptr[b];
1474
- for (int64_t c = 0; c < batch_chunk_num; c++) {
1475
- chunk_indices_ptr[curr_c * 2] = b;
1476
- chunk_indices_ptr[curr_c * 2 + 1] = c;
1477
- curr_c += 1;
1478
- }
1479
- }
2189
+ const at::Tensor& initial_state_indices,
2190
+ double eps = 1e-6) {
2191
+ TORCH_CHECK(!head_first, "chunk_gated_delta_rule_cpu: does not support head first");
2192
1481
- // Allocate buffer
1482
- int64_t buff_size = v_num_head * global_total_seq_length // g_pad_data
1483
- + batch_size * v_num_head * global_total_seq_length * v_head_size // core_attn
1484
- + v_num_head * global_total_seq_length * chunk_size // decay_mask
1485
- + v_num_head * global_total_seq_length * v_head_size; // v_beta_attn
1486
- at::Tensor buff_data = at::empty({buff_size}, query.options().dtype(at::kFloat));
1487
- int64_t reduced_buff_size = qk_num_head * global_total_seq_length * qk_head_size // q_pad_data
1488
- + qk_num_head * global_total_seq_length * qk_head_size // k_pad_data
1489
- + v_num_head * global_total_seq_length * v_head_size // v_pad_data
1490
- + v_num_head * global_total_seq_length * qk_head_size // k_beta_data
1491
- + v_num_head * global_total_seq_length * v_head_size // v_beta_data
1492
- + v_num_head * global_total_seq_length * qk_head_size // k_cumdecay_reduced
1493
- + qk_num_head * global_seq_len // q_norm_sum
1494
- + qk_num_head * global_seq_len; // k_norm_sum
1495
- at::Tensor reduced_buff_data = at::empty({reduced_buff_size}, query.options());
1496
- int64_t num_thread = at::get_num_threads();
1497
- int64_t buff_size_16bit_per_thread =
1498
- /* k_transpose */ qk_head_size * chunk_size +
1499
- /* v_pack */ chunk_size * v_head_size +
1500
- /* k_beta_g */ chunk_size * qk_head_size +
1501
- /* k_beta_g_pack */ chunk_size * qk_head_size +
1502
- /* attn */ chunk_size * chunk_size * 2 +
1503
- /* attn_reduced */ chunk_size * chunk_size +
1504
- /* k_cumdecay */ chunk_size * qk_head_size * 2 +
1505
- /* row */ chunk_size * 2 +
1506
- /* updated */ chunk_size * 2 +
1507
- /* curr_last_recurrent_state_reduced */ qk_head_size * v_head_size +
1508
- /* curr_last_recurrent_state_pack_reduced */ qk_head_size * v_head_size +
1509
- /* k_transpose_i */ qk_head_size * chunk_size +
1510
- /* attn_i */ chunk_size * chunk_size * 2 +
1511
- /* attn_i_reduced */ chunk_size * chunk_size +
1512
- /* v_prime */ chunk_size * v_head_size * 2 +
1513
- /* v_prime_reduced */ chunk_size * v_head_size +
1514
- /* v_prime_pack_reduced */ chunk_size * v_head_size +
1515
- /* qg */ chunk_size * qk_head_size +
1516
- /* attn_inter */ chunk_size * v_head_size * 2 +
1517
- /* kg */ chunk_size * qk_head_size +
1518
- /* kg_transpose */ qk_head_size * chunk_size +
1519
- /* kgv */ qk_head_size * v_head_size * 2;
1520
- at::Tensor thread_buff_data = at::empty({num_thread, buff_size_16bit_per_thread}, query.options());
1521
-
1522
- AT_DISPATCH_REDUCED_FLOATING_TYPES(query.scalar_type(), "chunk_gated_delta_rule_kernel", [&] {
1523
- chunk_gated_delta_rule_kernel_impl<scalar_t, chunk_size>(
1524
- output.data_ptr<scalar_t>(),
1525
- final_state.data_ptr<float>(),
1526
- query.data_ptr<scalar_t>(),
1527
- key.data_ptr<scalar_t>(),
1528
- value.data_ptr<scalar_t>(),
1529
- g.data_ptr<float>(),
1530
- beta.data_ptr<scalar_t>(),
1531
- cu_seqlens_ptr,
1532
- buff_data.data_ptr<float>(),
1533
- reduced_buff_data.data_ptr<scalar_t>(),
1534
- thread_buff_data.data_ptr<scalar_t>(),
1535
- chunk_offsets_ptr,
1536
- chunk_indices_ptr,
1537
- use_qk_l2norm_in_kernel,
1538
- batch_size,
1539
- global_seq_len,
1540
- qk_num_head,
1541
- v_num_head,
1542
- qk_head_size,
1543
- v_head_size,
1544
- qStrideH,
1545
- qStrideT,
1546
- kStrideH,
1547
- kStrideT,
1548
- vStrideH,
1549
- vStrideT,
1550
- oStrideH,
1551
- oStrideT,
1552
- global_total_seq_length,
1553
- global_num_chunk,
1554
- buff_size_16bit_per_thread,
1555
- eps);
1556
- });
1557
- return std::make_tuple(std::move(output), std::move(final_state));
2193
+ int64_t B = query.size(0);
2194
+ int64_t T = query.size(1);
2195
+ int64_t H = query.size(2);
2196
+ int64_t D = query.size(3);
2197
+ int64_t Hv = value.size(2);
2198
+ int64_t Dv = value.size(3);
2199
+ int64_t num_seqs = initial_state_indices.size(0);
2200
+
2201
+ TORCH_CHECK(B == 1, __func__, ": expect batch size to be 1");
2202
+ TORCH_CHECK(Hv % H == 0, __func__, ": expect num_heads_kv multiple of num_heads.");
2203
+ TORCH_CHECK(D % 32 == 0, __func__, ": expect head_dim to be multiples of 32.");
2204
+ TORCH_CHECK(Dv % 32 == 0, __func__, ": expect head_dim_v to be multiples of 32.");
2205
+ TORCH_CHECK(D == Dv, __func__, ": expect head_dim to be equal to head_dim_v.");
2206
+ CHECK_INPUT_SHAPE_DTYPE<true>(query, {B, T, H, D}, at::kBFloat16);
2207
+ CHECK_INPUT_SHAPE_DTYPE<true>(key, {B, T, H, D}, at::kBFloat16);
2208
+ CHECK_INPUT_SHAPE_DTYPE<true>(value, {B, T, Hv, Dv}, at::kBFloat16);
2209
+ CHECK_INPUT_SHAPE_DTYPE<false>(g, {B, T, Hv}, at::kFloat);
2210
+ CHECK_INPUT_SHAPE_DTYPE<false>(beta, {B, T, Hv}, at::kBFloat16);
2211
+ CHECK_INPUT_SHAPE_DTYPE<false>(cu_seqlens, {num_seqs + 1}, at::kInt);
2212
+ TORCH_CHECK(initial_state.sizes() == at::IntArrayRef({initial_state.size(0), Hv, Dv, D}),
2213
+ "chunk_gated_delta_rule_cpu: initial_state shape mismatch, got ", initial_state.sizes());
2214
+ TORCH_CHECK(initial_state.scalar_type() == at::kFloat, "chunk_gated_delta_rule_cpu: initial_state dtype mismatch");
2215
+ CHECK_CPU(initial_state);
2216
+ // initial_state may be a pooled/paged buffer with padding between slots
2217
+ // (e.g. mamba cache-align mode), so only the per-slot (Hv, Dv, D) layout
2218
+ // needs to be densely packed; dim 0's stride is read at runtime instead of
2219
+ // assumed, mirroring conv.cpp's conv_state_slot_stride handling.
2220
+ TORCH_CHECK(initial_state.stride(-1) == 1 && initial_state.stride(-2) == D &&
2221
+ initial_state.stride(-3) == Dv * D,
2222
+ "chunk_gated_delta_rule_cpu: expect initial_state to be contiguous per pool slot.");
2223
+ CHECK_INPUT_SHAPE_DTYPE<false>(initial_state_indices, {num_seqs}, at::kInt);
2224
+
2225
+ constexpr int CHUNK_SIZE = 64;
2226
+
2227
+ // prepare chunk indices
2228
+ auto [chunk_indices, chunk_offsets] = prepare_chunk_indices<CHUNK_SIZE>(cu_seqlens);
2229
+
2230
+ float scale = 1.0 / std::sqrt(D);
2231
+ auto [query_, key_] = use_qk_l2norm_in_kernel ? l2norm_fwd(query, key, eps) : std::make_tuple(query.mul(scale), key);
2232
+
2233
+ auto g_ = chunk_local_cumsum<CHUNK_SIZE>(g, cu_seqlens, chunk_indices);
2234
+
2235
+ // fused kkt + solve_tril + recompute_w_u
2236
+ auto [w, u, decay_mask] =
2237
+ chunk_gated_delta_rule_fwd_intra<CHUNK_SIZE>(key_, value, g_, beta, cu_seqlens, chunk_indices);
2238
+
2239
+ // fused `chunk_gated_delta_rule_fwd_h` + `chunk_fwd_o`
2240
+ auto [output, final_state] = chunk_gated_delta_rule_fwd_inter<CHUNK_SIZE>(
2241
+ query_,
2242
+ key_,
2243
+ w,
2244
+ u,
2245
+ g_,
2246
+ decay_mask,
2247
+ initial_state,
2248
+ output_final_state,
2249
+ cu_seqlens,
2250
+ chunk_offsets,
2251
+ initial_state_indices);
2252
+
2253
+ return std::make_tuple(output, final_state);
2254
}
2255
2256
// A_log: [v_num_heads]
csrc/cpu/sgl-kernels/gemm.cpp
+22
-28
@@ -116,8 +116,7 @@ inline void copy_stub(scalar_t* __restrict__ out, const float* __restrict__ inpu
116
int64_t d;
117
#pragma GCC unroll 4
118
for (d = 0; d <= size - kVecSize; d += kVecSize) {
119
- fVec data0 = fVec::loadu(input + d);
120
- fVec data1 = fVec::loadu(input + d + fVec::size());
119
+ auto [data0, data1] = load_float_vec2(input + d);
120
bVec out_vec = convert_from_float_ext<scalar_t>(data0, data1);
121
out_vec.store(out + d);
122
}
@@ -135,9 +134,7 @@ inline void copy_stub(float* __restrict__ out, const scalar_t* __restrict__ inpu
134
int64_t d;
135
#pragma GCC unroll 4
136
for (d = 0; d <= size - kVecSize; d += kVecSize) {
138
- fVec data0, data1;
139
- bVec b_vec = bVec::loadu(input + d);
140
- std::tie(data0, data1) = at::vec::convert_to_float(b_vec);
137
+ auto [data0, data1] = load_float_vec2(input + d);
138
data0.store(out + d);
139
data1.store(out + d + fVec::size());
140
}
@@ -156,9 +153,9 @@ inline void copy_add_stub(
153
int64_t d;
154
#pragma GCC unroll 4
155
for (d = 0; d <= size - kVecSize; d += kVecSize) {
159
- fVec data0 = fVec::loadu(input + d) + fVec::loadu(bias + d);
160
- fVec data1 = fVec::loadu(input + d + fVec::size()) + fVec::loadu(bias + d + fVec::size());
161
- bVec out_vec = convert_from_float_ext<scalar_t>(data0, data1);
156
+ auto [data0, data1] = load_float_vec2(input + d);
157
+ auto [bias0, bias1] = load_float_vec2(bias + d);
158
+ bVec out_vec = convert_from_float_ext<scalar_t>(data0 + bias0, data1 + bias1);
159
out_vec.store(out + d);
160
}
161
for (; d < size; ++d) {
@@ -176,7 +173,6 @@ inline void scalar_sigmoid_and_mul(
173
using bVec = at::vec::Vectorized<scalar_t>;
174
using fVec = at::vec::Vectorized<float>;
175
// scalar sigmoid
179
- const fVec one = fVec(1.f);
176
fVec X;
177
if constexpr (has_bias) {
178
assert(bias != nullptr);
@@ -184,18 +180,13 @@ inline void scalar_sigmoid_and_mul(
180
} else {
181
X = fVec(input[0]);
182
}
187
- X = one / (one + X.neg().exp_u20());
183
+ X = fast_sigmoid(X);
184
185
// vec mul
186
constexpr int kVecSize = bVec::size();
187
for (int d = 0; d < SIZE; d += kVecSize) {
192
- bVec m_bvec = bVec::loadu(mul + d);
193
- fVec m_fvec0, m_fvec1;
194
- std::tie(m_fvec0, m_fvec1) = at::vec::convert_to_float(m_bvec);
195
- m_fvec0 = m_fvec0 * X;
196
- m_fvec1 = m_fvec1 * X;
197
-
198
- bVec out_vec = convert_from_float_ext<scalar_t>(m_fvec0, m_fvec1);
188
+ auto [m_fvec0, m_fvec1] = load_float_vec2(mul + d);
189
+ bVec out_vec = convert_from_float_ext<scalar_t>(m_fvec0 * X, m_fvec1 * X);
190
out_vec.store(out + d);
191
}
192
}
@@ -727,10 +718,10 @@ at::Tensor convert_scale_packed(at::Tensor& scale) {
718
return packed_scale;
719
}
720
730
-// mat1 : [M, K]
721
+// mat1 : [*, K]
722
// mat2 : [N, K] ([K, N] if use_fma_gemm)
723
// bias : [N]
733
-// out : [M, N]
724
+// out : [*, N]
725
//
726
at::Tensor
727
weight_packed_linear(at::Tensor& mat1, at::Tensor& mat2, const std::optional<at::Tensor>& bias, bool is_vnni) {
@@ -740,23 +731,25 @@ weight_packed_linear(at::Tensor& mat1, at::Tensor& mat2, const std::optional<at:
731
use_fma_gemm = true;
732
}
733
743
- int64_t M = mat1.size(0);
744
- int64_t K = mat1.size(1);
745
- int64_t N = use_fma_gemm ? mat2.size(1) : mat2.size(0);
746
-
734
CHECK_LAST_DIM_CONTIGUOUS_INPUT(mat1);
735
CHECK_INPUT(mat2);
749
- CHECK_DIM(2, mat1);
736
+ const int64_t ndim = mat1.ndimension();
737
+ auto input_sizes = mat1.sizes().vec();
738
+ int64_t N = use_fma_gemm ? mat2.size(1) : mat2.size(0);
739
+ int64_t K = use_fma_gemm ? mat1.size(1) : mat2.size(1);
740
+ int64_t M = use_fma_gemm ? mat1.size(0) : mat1.numel() / K;
741
CHECK_DIM(2, mat2);
751
- if (!use_fma_gemm) {
752
- CHECK_EQ(mat1.size(1), K);
742
+ if (use_fma_gemm) {
743
+ CHECK_DIM(2, mat1);
744
+ } else {
745
+ CHECK_EQ(mat1.size(ndim - 1), K);
746
}
747
748
auto dispatch_type = mat1.scalar_type();
749
auto out = at::empty({M, N}, mat1.options());
750
// strides
751
int64_t out_strideM = out.stride(0);
759
- int64_t mat1_strideM = mat1.stride(0);
752
+ int64_t mat1_strideM = mat1.stride(-2);
753
754
const bool has_bias = bias.has_value();
755
const float* bias_data = nullptr;
@@ -792,7 +785,8 @@ weight_packed_linear(at::Tensor& mat1, at::Tensor& mat2, const std::optional<at:
785
}
786
});
787
795
- return out;
788
+ input_sizes[ndim - 1] = N;
789
+ return out.view(input_sizes);
790
}
791
792
// mat1 : [M, K]
csrc/cpu/sgl-kernels/gemm.h
+17
-10
@@ -83,15 +83,11 @@ inline int64_t get_row_size(int64_t K, bool use_int8_w8a8) {
83
return use_int8_w8a8 ? K + sizeof(int32_t) : K;
84
}
85
86
-enum class CPUAcTMethod : int { silu_and_mul = 0, swiglu = 1 };
87
-
88
-constexpr bool operator==(CPUAcTMethod a, int b) {
89
- return static_cast<int>(a) == b;
90
-}
91
-
92
-constexpr bool operator==(int a, CPUAcTMethod b) {
93
- return a == static_cast<int>(b);
94
-}
86
+enum class CPUActMethod : int {
87
+ silu_and_mul = 0,
88
+ swiglu = 1,
89
+ gelu_and_mul = 2,
90
+};
91
92
enum class CPUQuantMethod : int64_t { BF16 = 0, INT8_W8A8 = 1, FP8_W8A16 = 2, INT4_W4A8 = 3, MXFP4 = 4 };
93
@@ -113,6 +109,17 @@ constexpr bool operator==(int64_t a, CPUQuantAlgo b) {
109
return a == static_cast<int64_t>(b);
110
}
111
112
+inline int64_t get_row_size(CPUQuantMethod quant, int64_t K) {
113
+ switch (quant) {
114
+ case CPUQuantMethod::INT8_W8A8:
115
+ return K + sizeof(int32_t);
116
+ case CPUQuantMethod::MXFP4:
117
+ return K >> 1;
118
+ default:
119
+ return K;
120
+ }
121
+}
122
+
123
inline int64_t get_4bit_block_k_size(int64_t group_size) {
124
return group_size > 128 ? 128 : group_size;
125
}
@@ -184,7 +191,7 @@ void fused_experts_fp_kernel_impl(
191
int64_t num_tokens_post_pad,
192
float alpha,
193
float limit,
187
- CPUAcTMethod act_func,
194
+ CPUActMethod act_func,
195
bool with_bias);
196
197
// shared expert implementation for int8 w8a8
csrc/cpu/sgl-kernels/gemm_fp8.cpp
+6
-8
@@ -18,8 +18,7 @@ inline void copy_stub(scalar_t* __restrict__ out, const float* __restrict__ inpu
18
int64_t d;
19
#pragma GCC unroll 4
20
for (d = 0; d <= size - kVecSize; d += kVecSize) {
21
- fVec data0 = fVec::loadu(input + d);
22
- fVec data1 = fVec::loadu(input + d + fVec::size());
21
+ auto [data0, data1] = load_float_vec2(input + d);
22
bVec out_vec = convert_from_float_ext<scalar_t>(data0, data1);
23
out_vec.store(out + d);
24
}
@@ -38,9 +37,9 @@ inline void copy_add_stub(
37
int64_t d;
38
#pragma GCC unroll 4
39
for (d = 0; d <= size - kVecSize; d += kVecSize) {
41
- fVec data0 = fVec::loadu(input + d) + fVec::loadu(bias + d);
42
- fVec data1 = fVec::loadu(input + d + fVec::size()) + fVec::loadu(bias + d + fVec::size());
43
- bVec out_vec = convert_from_float_ext<scalar_t>(data0, data1);
40
+ auto [data0, data1] = load_float_vec2(input + d);
41
+ auto [bias0, bias1] = load_float_vec2(bias + d);
42
+ bVec out_vec = convert_from_float_ext<scalar_t>(data0 + bias0, data1 + bias1);
43
out_vec.store(out + d);
44
}
45
for (; d < size; ++d) {
@@ -57,9 +56,8 @@ inline void copy_mul_stub(scalar_t* __restrict__ out, const float* __restrict__
56
int d;
57
#pragma GCC unroll 4
58
for (d = 0; d <= size - kVecSize; d += kVecSize) {
60
- fVec data0 = fVec::loadu(input + d) * vscale;
61
- fVec data1 = fVec::loadu(input + d + fVec::size()) * vscale;
62
- bVec out_vec = convert_from_float_ext<scalar_t>(data0, data1);
59
+ auto [data0, data1] = load_float_vec2(input + d);
60
+ bVec out_vec = convert_from_float_ext<scalar_t>(data0 * vscale, data1 * vscale);
61
out_vec.store(out + d);
62
}
63
for (; d < size; ++d) {
csrc/cpu/sgl-kernels/moe.cpp
+124
-100
@@ -123,46 +123,6 @@ int moe_align_block_size(
123
return num_tokens_post_pad;
124
}
125
126
-// silu : shape leading dimension
127
-// input0 [m_size, BLOCK_N] BLOCK_N
128
-// input1 [m_size, BLOCK_N] BLOCK_N
129
-// output [M * topk, N] N
130
-template <typename scalar_t, int BLOCK_N>
131
-inline void silu_and_mul(
132
- scalar_t* __restrict__ output,
133
- const float* __restrict__ input0, // x: x0, x1
134
- const float* __restrict__ input1, // y: y0, y1
135
- int64_t m_size,
136
- int64_t N) {
137
- using bVec = at::vec::Vectorized<scalar_t>;
138
- using fVec = at::vec::Vectorized<float>;
139
-
140
- const fVec one = fVec(1.f);
141
-
142
- // no remainder
143
- for (int64_t m = 0; m < m_size; ++m) {
144
- scalar_t* __restrict__ out = output + m * N;
145
- const float* __restrict__ x = input0 + m * BLOCK_N;
146
- const float* __restrict__ y = input1 + m * BLOCK_N;
147
-
148
- for (int64_t d = 0; d < BLOCK_N; d += bVec::size()) {
149
- fVec x0 = fVec::loadu(x + d);
150
- fVec x1 = fVec::loadu(x + d + fVec::size());
151
- fVec y0 = fVec::loadu(y + d);
152
- fVec y1 = fVec::loadu(y + d + fVec::size());
153
- // silu
154
- x0 = x0 / (one + x0.neg().exp_u20());
155
- x1 = x1 / (one + x1.neg().exp_u20());
156
- // mul
157
- x0 = x0 * y0;
158
- x1 = x1 * y1;
159
- // convert
160
- bVec out_vec = convert_from_float_ext<scalar_t>(x0, x1);
161
- out_vec.store(out + d);
162
- }
163
- }
164
-}
165
-
126
template <typename scalar_t, int BLOCK_M, int BLOCK_N>
127
struct tinygemm_kernel_nn2 {
128
static inline void apply(
@@ -239,23 +199,17 @@ struct tinygemm_kernel_nn2<at::BFloat16, BLOCK_M, BLOCK_N> {
199
Unroll<ROWS * COLS>{}(compute, k);
200
}
201
242
- using Vec = at::vec::Vectorized<float>;
243
- const Vec one = Vec(1.f);
202
auto storec = [&](auto i) {
203
constexpr int row = i / COLS;
204
constexpr int col = i % COLS;
205
// for COLS = 2, 4 use 512bit store
206
if constexpr (col % 2 == 0) {
249
- Vec x0 = vc0[row * COLS + col + 0];
250
- Vec x1 = vc0[row * COLS + col + 1];
251
- Vec y0 = vc1[row * COLS + col + 0];
252
- Vec y1 = vc1[row * COLS + col + 1];
253
- // silu
254
- x0 = x0 / (one + x0.neg().exp_u20());
255
- x1 = x1 / (one + x1.neg().exp_u20());
256
- // mul
257
- x0 = x0 * y0;
258
- x1 = x1 * y1;
207
+ __m512 x0 = vc0[row * COLS + col + 0];
208
+ __m512 x1 = vc0[row * COLS + col + 1];
209
+ __m512 y0 = vc1[row * COLS + col + 0];
210
+ __m512 y1 = vc1[row * COLS + col + 1];
211
+ x0 = _mm512_mul_ps(_mm512_rcp14_silu_ps(x0), y0);
212
+ x1 = _mm512_mul_ps(_mm512_rcp14_silu_ps(x1), y1);
213
214
_mm512_storeu_si512(
215
reinterpret_cast<__m512i*>((C + row * ldc + col * 16)),
@@ -455,6 +409,8 @@ void fused_experts_kernel_impl(
409
const scalar_t* __restrict__ input,
410
const scalar_t* __restrict__ packed_w1,
411
const scalar_t* __restrict__ packed_w2,
412
+ const float* __restrict__ w1_bias,
413
+ const float* __restrict__ w2_bias,
414
const float* __restrict__ topk_weights,
415
const int32_t* __restrict__ sorted_ids,
416
const int32_t* __restrict__ expert_ids,
@@ -464,7 +420,11 @@ void fused_experts_kernel_impl(
420
int64_t K,
421
int64_t E,
422
int64_t topk,
467
- int64_t num_tokens_post_pad) {
423
+ int64_t num_tokens_post_pad,
424
+ float alpha,
425
+ float limit,
426
+ CPUActMethod act_func,
427
+ bool with_bias) {
428
// handle 2 tiles per block
429
constexpr int64_t BLOCK_M = block_size_m();
430
constexpr int64_t BLOCK_N = block_size_n();
@@ -499,6 +459,8 @@ void fused_experts_kernel_impl(
459
int32_t expert_id = expert_ids[mb];
460
const scalar_t* __restrict__ B0 = packed_w1 + expert_id * stride_e + nb_upper * BLOCK_N * stride_n;
461
const scalar_t* __restrict__ B1 = packed_w1 + expert_id * stride_e + nb_lower * BLOCK_N * stride_n;
462
+ const float* __restrict__ B0_bias = w1_bias + expert_id * 2 * N + nb_upper * BLOCK_N;
463
+ const float* __restrict__ B1_bias = w1_bias + expert_id * 2 * N + nb_lower * BLOCK_N;
464
465
int64_t m_size = offsets[mb + 1] - offsets[mb];
466
@@ -538,23 +500,62 @@ void fused_experts_kernel_impl(
500
/* B */ B1,
501
/* C */ C1);
502
541
- // 1.d silu and mul
542
- const int64_t offset = offsets[mb];
543
- silu_and_mul<scalar_t, BLOCK_N>(ic1 + offset * N + nb * BLOCK_N, C0, C1, m_size, N);
503
} else {
545
- // fused 1.bcd: silu_and_mul(A @ B0, A @ B1)
504
const int64_t offset = offsets[mb];
547
- tinygemm_kernel(
548
- /* A */ A,
549
- /* B0 */ B0,
550
- /* B1 */ B1,
551
- /* C */ ic1 + offset * N + nb * BLOCK_N,
552
- /* M */ m_size,
553
- /* N */ n_size,
554
- /* K */ K,
555
- /* lda */ K,
556
- /* ldb */ n_size,
557
- /* ldc */ N);
505
+ if (act_func == CPUActMethod::swiglu) {
506
+ tinygemm_kernel(
507
+ /* A */ A,
508
+ /* B */ B0,
509
+ /* C */ C0,
510
+ /* M */ m_size,
511
+ /* N */ n_size,
512
+ /* K */ K,
513
+ /* lda */ K,
514
+ /* ldb */ n_size,
515
+ /* ldc */ BLOCK_N);
516
+ tinygemm_kernel(
517
+ /* A */ A,
518
+ /* B */ B1,
519
+ /* C */ C1,
520
+ /* M */ m_size,
521
+ /* N */ n_size,
522
+ /* K */ K,
523
+ /* lda */ K,
524
+ /* ldb */ n_size,
525
+ /* ldc */ BLOCK_N);
526
+ } else {
527
+ // fused 1.bcd: silu_and_mul(A @ B0, A @ B1)
528
+ tinygemm_kernel(
529
+ /* A */ A,
530
+ /* B0 */ B0,
531
+ /* B1 */ B1,
532
+ /* C */ ic1 + offset * N + nb * BLOCK_N,
533
+ /* M */ m_size,
534
+ /* N */ n_size,
535
+ /* K */ K,
536
+ /* lda */ K,
537
+ /* ldb */ n_size,
538
+ /* ldc */ N);
539
+ }
540
+ }
541
+ if (with_bias) {
542
+ for (int64_t m = 0; m < m_size; ++m) {
543
+ add_bias_stub(C0 + m * BLOCK_N, B0_bias, n_size);
544
+ add_bias_stub(C1 + m * BLOCK_N, B1_bias, n_size);
545
+ }
546
+ }
547
+ // 1.d silu and mul
548
+ const int64_t offset = offsets[mb];
549
+ if (act_func == CPUActMethod::silu_and_mul && use_brgemm) {
550
+ for (int64_t m = 0; m < m_size; ++m) {
551
+ silu_and_mul_stub(ic1 + (offset + m) * N + nb * BLOCK_N, C0 + m * BLOCK_N, C1 + m * BLOCK_N, BLOCK_N);
552
+ }
553
+ } else if (act_func == CPUActMethod::swiglu) {
554
+ for (int64_t m = 0; m < m_size; ++m) {
555
+ scalar_t* __restrict__ ic1_row = ic1 + (offset + m) * N;
556
+ clamp_sigmoid_and_mul_stub(ic1_row + nb * BLOCK_N / 2, C0 + m * BLOCK_N, BLOCK_N / 2, alpha, limit);
557
+ clamp_sigmoid_and_mul_stub(ic1_row + N / 2 + nb * BLOCK_N / 2, C1 + m * BLOCK_N, BLOCK_N / 2, alpha, limit);
558
+ }
559
}
560
});
561
@@ -591,6 +592,7 @@ void fused_experts_kernel_impl(
592
// B shape [IC, n_size] in vnni format
593
int32_t expert_id = expert_ids[mb];
594
const scalar_t* __restrict__ B = packed_w2 + expert_id * stride_e2 + nb * BLOCK_N * stride_oc;
595
+ const float* __restrict__ B_bias = w2_bias + expert_id * OC + nb * BLOCK_N;
596
597
// 2.a gemm: C = A @ B
598
if (use_brgemm) {
@@ -618,6 +620,11 @@ void fused_experts_kernel_impl(
620
/* ldc */ BLOCK_N);
621
}
622
623
+ if (with_bias) {
624
+ for (int64_t m = 0; m < m_size; ++m) {
625
+ add_bias_stub(C + m * BLOCK_N, B_bias, n_size);
626
+ }
627
+ }
628
// 2.b copy from C to ic2 in original order
629
// and also mul topk_weights in float32
630
for (int64_t m = 0; m < m_size; ++m) {
@@ -717,7 +724,9 @@ void shared_expert_kernel_impl(
724
/* C */ C1);
725
726
// 1.d silu and mul
720
- silu_and_mul<scalar_t, BLOCK_N>(ic1 + mb * BLOCK_M * N + nb * BLOCK_N, C0, C1, m_size, N);
727
+ for (int64_t m = 0; m < m_size; ++m) {
728
+ silu_and_mul_stub(ic1 + (mb * BLOCK_M + m) * N + nb * BLOCK_N, C0 + m * BLOCK_N, C1 + m * BLOCK_N, BLOCK_N);
729
+ }
730
} else {
731
// fused 1.bcd: silu_and_mul(A @ B0, A @ B1)
732
tinygemm_kernel(
@@ -809,24 +818,20 @@ void shared_expert_kernel_impl(
818
} // anonymous namespace
819
820
// common checks
821
+template <CPUQuantMethod quant>
822
static inline void check_moe_scales(
813
- bool use_int8_w8a8,
814
- bool use_fp8_w8a16,
815
- bool use_mxfp4,
823
const std::optional<at::Tensor>& w1_scale,
824
const std::optional<at::Tensor>& w2_scale,
825
const std::optional<std::vector<int64_t>> block_size) {
819
- if (use_int8_w8a8) {
826
+ if constexpr (quant == CPUQuantMethod::INT8_W8A8) {
827
TORCH_CHECK(w1_scale.has_value(), "missing w1_scale for int8 w8a8.");
828
TORCH_CHECK(w2_scale.has_value(), "missing w2_scale for int8 w8a8.");
822
- }
823
- if (use_fp8_w8a16) {
829
+ } else if constexpr (quant == CPUQuantMethod::FP8_W8A16) {
830
TORCH_CHECK(w1_scale.has_value(), "missing w1_scale for fp8 w8a16.");
831
TORCH_CHECK(w2_scale.has_value(), "missing w2_scale for fp8 w8a16.");
832
TORCH_CHECK(block_size.has_value(), "missing block_size for fp8 w8a16.");
833
TORCH_CHECK(block_size.value().size() == 2, "expect block_size.size() to be 2.");
828
- }
829
- if (use_mxfp4) {
834
+ } else if constexpr (quant == CPUQuantMethod::MXFP4) {
835
TORCH_CHECK(w1_scale.has_value(), "missing w1_scale for mxfp4.");
836
TORCH_CHECK(w2_scale.has_value(), "missing w2_scale for mxfp4.");
837
TORCH_CHECK(w1_scale.value().scalar_type() == at::kByte, "expect w1_scale to be uint8.");
@@ -834,6 +839,20 @@ static inline void check_moe_scales(
839
}
840
}
841
842
+static inline void check_moe_scales(
843
+ int64_t moe_comp_method,
844
+ const std::optional<at::Tensor>& w1_scale,
845
+ const std::optional<at::Tensor>& w2_scale,
846
+ const std::optional<std::vector<int64_t>> block_size) {
847
+ if (moe_comp_method == CPUQuantMethod::INT8_W8A8) {
848
+ check_moe_scales<CPUQuantMethod::INT8_W8A8>(w1_scale, w2_scale, block_size);
849
+ } else if (moe_comp_method == CPUQuantMethod::FP8_W8A16) {
850
+ check_moe_scales<CPUQuantMethod::FP8_W8A16>(w1_scale, w2_scale, block_size);
851
+ } else if (moe_comp_method == CPUQuantMethod::MXFP4) {
852
+ check_moe_scales<CPUQuantMethod::MXFP4>(w1_scale, w2_scale, block_size);
853
+ }
854
+}
855
+
856
#define CHECK_MOE_SCALES_FP8(DIM0, DIM1) \
857
auto w1s = w1_scale.value(); \
858
auto w2s = w2_scale.value(); \
@@ -877,10 +896,14 @@ at::Tensor fused_experts_cpu(
896
constexpr int64_t BLOCK_N = block_size_n();
897
898
const auto st = hidden_states.scalar_type();
899
+ // TODO: fused_topk_torch_native (CPU fallback for models like MiniMax)
900
+ // returns int64 topk_ids; fused_experts_cpu requires int32. Remove the typecast after topk kernel is provided
901
+ auto topk_ids_ = topk_ids.scalar_type() == at::kInt ? topk_ids : topk_ids.to(at::kInt);
902
+
903
CHECK_INPUT(hidden_states);
904
CHECK_INPUT(w1);
905
CHECK_INPUT(w2);
883
- CHECK_EQ(topk_weights.sizes(), topk_ids.sizes());
906
+ CHECK_EQ(topk_weights.sizes(), topk_ids_.sizes());
907
CHECK_DIM(2, hidden_states);
908
if (moe_comp_method == CPUQuantMethod::INT4_W4A8 && is_vnni) {
909
CHECK_DIM(4, w1);
@@ -890,9 +913,8 @@ at::Tensor fused_experts_cpu(
913
CHECK_DIM(3, w2);
914
}
915
CHECK_DIM(2, topk_weights);
893
- CHECK_DIM(2, topk_ids);
894
-
895
- CHECK_EQ(topk_ids.scalar_type(), at::kInt);
916
+ CHECK_DIM(2, topk_ids_);
917
+ CHECK_EQ(topk_ids_.scalar_type(), at::kInt);
918
919
// TODO: support topk_weights to be bf16 or fp16 in the kernel.
920
// The topk_weights of llama4 is computed via Llama4MoE:custom_routing_function and is bf16/fp16
@@ -908,12 +930,8 @@ at::Tensor fused_experts_cpu(
930
int64_t topk = topk_weights_.size(1);
931
932
// we use int32_t compensation for int8 w8a8
911
- int64_t packed_K = moe_comp_method == CPUQuantMethod::MXFP4
912
- ? get_row_size<uint8_t>(K)
913
- : get_row_size(K, moe_comp_method == CPUQuantMethod::INT8_W8A8);
914
- int64_t packed_N = moe_comp_method == CPUQuantMethod::MXFP4
915
- ? get_row_size<uint8_t>(N)
916
- : get_row_size(N, moe_comp_method == CPUQuantMethod::INT8_W8A8);
933
+ int64_t packed_K = get_row_size(static_cast<CPUQuantMethod>(moe_comp_method), K);
934
+ int64_t packed_N = get_row_size(static_cast<CPUQuantMethod>(moe_comp_method), N);
935
936
// check weight shapes
937
CHECK_EQ(w2.size(0), E);
@@ -923,13 +941,7 @@ at::Tensor fused_experts_cpu(
941
CHECK_EQ(packed_w2.size(2), packed_N / (moe_comp_method == CPUQuantMethod::INT4_W4A8 ? 2 : 1));
942
}
943
// check scales
926
- check_moe_scales(
927
- moe_comp_method == CPUQuantMethod::INT8_W8A8,
928
- moe_comp_method == CPUQuantMethod::FP8_W8A16,
929
- moe_comp_method == CPUQuantMethod::MXFP4,
930
- w1_scale,
931
- w2_scale,
932
- block_size);
944
+ check_moe_scales(moe_comp_method, w1_scale, w2_scale, block_size);
945
946
at::Tensor out_hidden_states = inplace ? hidden_states : at::empty_like(hidden_states);
947
@@ -945,7 +957,7 @@ at::Tensor fused_experts_cpu(
957
int64_t max_num_blocks = div_up(max_num_tokens_padded, BLOCK_M);
958
auto buffer = at::empty(
959
{max_num_tokens_padded + max_num_blocks + (num_threads + 1) * E + (E + 1) + (max_num_blocks + 1)},
948
- topk_ids.options());
960
+ topk_ids_.options());
961
962
int32_t* __restrict__ sorted_ids = buffer.data_ptr<int32_t>();
963
int32_t* __restrict__ expert_ids = sorted_ids + max_num_tokens_padded;
@@ -969,7 +981,7 @@ at::Tensor fused_experts_cpu(
981
982
// align experts index
983
int64_t num_tokens_post_pad = moe_align_block_size<BLOCK_M>(
972
- sorted_ids, expert_ids, topk_ids.data_ptr<int32_t>(), total_cnts, cumsums, offsets, E, numel, num_threads);
984
+ sorted_ids, expert_ids, topk_ids_.data_ptr<int32_t>(), total_cnts, cumsums, offsets, E, numel, num_threads);
985
986
// unlike triton kernel, we fuse silu with gemm1 so only need 2 intermediate_caches:
987
// 1. intermediate_cache1 : [M * topk, N]
@@ -1048,7 +1060,7 @@ at::Tensor fused_experts_cpu(
1060
scalar_t* __restrict__ intermediate_cache0 = (scalar_t*)((void*)(C_tmp + num_threads * 2 * BLOCK_M * BLOCK_N));
1061
scalar_t* __restrict__ B_tmp = (scalar_t*)((void*)(intermediate_cache0 + M * topk * 2 * N));
1062
bool with_bias = w1_bias.has_value();
1051
- auto act_func = alpha.has_value() && limit.has_value() ? CPUAcTMethod::swiglu : CPUAcTMethod::silu_and_mul;
1063
+ auto act_func = alpha.has_value() && limit.has_value() ? CPUActMethod::swiglu : CPUActMethod::silu_and_mul;
1064
1065
CHECK_MOE_SCALES_FP8(1, 2);
1066
fused_experts_fp_kernel_impl<scalar_t, at::Float8_e4m3fn, float, false>(
@@ -1088,7 +1100,7 @@ at::Tensor fused_experts_cpu(
1100
scalar_t* __restrict__ intermediate_cache0 = (scalar_t*)((void*)(C_tmp + num_threads * 2 * BLOCK_M * BLOCK_N));
1101
scalar_t* __restrict__ B_tmp = (scalar_t*)((void*)(intermediate_cache0 + M * topk * 2 * N));
1102
bool with_bias = w1_bias.has_value();
1091
- auto act_func = alpha.has_value() && limit.has_value() ? CPUAcTMethod::swiglu : CPUAcTMethod::silu_and_mul;
1103
+ auto act_func = alpha.has_value() && limit.has_value() ? CPUActMethod::swiglu : CPUActMethod::silu_and_mul;
1104
1105
// mxfp4 supports only group size of 32 (2^5)
1106
constexpr int64_t group_size = 32;
@@ -1172,6 +1184,8 @@ at::Tensor fused_experts_cpu(
1184
} else {
1185
scalar_t* __restrict__ A_tmp = intermediate_cache2 + M * topk * K;
1186
float* __restrict__ C_tmp = (float*)((void*)(A_tmp + num_threads * BLOCK_M * K));
1187
+ bool with_bias = w1_bias.has_value();
1188
+ auto act_func = alpha.has_value() && limit.has_value() ? CPUActMethod::swiglu : CPUActMethod::silu_and_mul;
1189
1190
fused_experts_kernel_impl<scalar_t>(
1191
out_hidden_states.data_ptr<scalar_t>(),
@@ -1182,6 +1196,8 @@ at::Tensor fused_experts_cpu(
1196
hidden_states.data_ptr<scalar_t>(),
1197
packed_w1.data_ptr<scalar_t>(),
1198
packed_w2.data_ptr<scalar_t>(),
1199
+ with_bias ? w1_bias.value().data_ptr<float>() : nullptr,
1200
+ with_bias ? w2_bias.value().data_ptr<float>() : nullptr,
1201
topk_weights_.data_ptr<float>(),
1202
sorted_ids,
1203
expert_ids,
@@ -1191,7 +1207,11 @@ at::Tensor fused_experts_cpu(
1207
K,
1208
E,
1209
topk,
1194
- num_tokens_post_pad);
1210
+ num_tokens_post_pad,
1211
+ alpha.has_value() ? float(alpha.value()) : 0,
1212
+ limit.has_value() ? float(limit.value()) : 0,
1213
+ act_func,
1214
+ with_bias);
1215
}
1216
});
1217
return out_hidden_states;
@@ -1254,7 +1274,11 @@ at::Tensor shared_expert_cpu(
1274
CHECK_EQ(packed_w2.size(1), packed_N);
1275
1276
// check scales
1257
- check_moe_scales(use_int8_w8a8, use_fp8_w8a16, false, w1_scale, w2_scale, block_size);
1277
+ if (use_int8_w8a8) {
1278
+ check_moe_scales<CPUQuantMethod::INT8_W8A8>(w1_scale, w2_scale, block_size);
1279
+ } else if (use_fp8_w8a16) {
1280
+ check_moe_scales<CPUQuantMethod::FP8_W8A16>(w1_scale, w2_scale, block_size);
1281
+ }
1282
1283
at::Tensor out_hidden_states = inplace ? hidden_states : at::empty_like(hidden_states);
1284
csrc/cpu/sgl-kernels/moe.h
+41
-82
@@ -64,9 +64,7 @@ inline void copy_mul_stub(scalar_t* __restrict__ out, const input_t* __restrict_
64
#pragma GCC unroll 4
65
for (d = 0; d <= size - kVecSize; d += kVecSize) {
66
auto [x0, x1] = load_float_vec2(input + d);
67
- x0 = x0 * weight_vec;
68
- x1 = x1 * weight_vec;
69
- bVec out_vec = convert_from_float_ext<scalar_t>(x0, x1);
67
+ bVec out_vec = convert_from_float_ext<scalar_t>(x0 * weight_vec, x1 * weight_vec);
68
out_vec.store(out + d);
69
}
70
for (; d < size; ++d) {
@@ -91,10 +89,7 @@ inline void sum_stub(scalar_t* __restrict__ out, const scalar_t* __restrict__ in
89
fVec sum_fvec0 = fVec(0.f);
90
fVec sum_fvec1 = fVec(0.f);
91
for (int t = 0; t < topk; ++t) {
94
- bVec x_bvec = bVec::loadu(input + t * K + d);
95
- fVec x_fvec0, x_fvec1;
96
- std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
97
-
92
+ auto [x_fvec0, x_fvec1] = load_float_vec2(input + t * K + d);
93
sum_fvec0 += x_fvec0;
94
sum_fvec1 += x_fvec1;
95
}
@@ -137,11 +132,7 @@ inline void add_mul_stub(
132
#pragma GCC unroll 4
133
for (d = 0; d <= size - kVecSize; d += kVecSize) {
134
auto [x0, x1] = load_float_vec2(input + d);
140
-
141
- bVec y_bvec = bVec::loadu(input2 + d);
142
- fVec y0, y1;
143
- std::tie(y0, y1) = at::vec::convert_to_float(y_bvec);
144
-
135
+ auto [y0, y1] = load_float_vec2(input2 + d);
136
x0 = x0 + y0 * s_vec;
137
x1 = x1 + y1 * s_vec;
138
bVec out_vec = convert_from_float_ext<scalar_t>(x0, x1);
@@ -152,31 +143,52 @@ inline void add_mul_stub(
143
}
144
}
145
155
-template <typename scalar_t>
146
+template <typename scalar_t, typename input_t>
147
inline void silu_and_mul_stub(
157
- scalar_t* __restrict__ out, const scalar_t* __restrict__ input, const scalar_t* __restrict__ input2, int64_t size) {
148
+ scalar_t* __restrict__ out, const input_t* __restrict__ input, const input_t* __restrict__ input2, int64_t size) {
149
+ static_assert(
150
+ std::is_same_v<input_t, float> || std::is_same_v<input_t, scalar_t>,
151
+ "silu_and_mul_stub only supports input_t == float or input_t == scalar_t");
152
using bVec = at::vec::Vectorized<scalar_t>;
153
using fVec = at::vec::Vectorized<float>;
160
- const fVec one = fVec(1.f);
154
155
// no remainder
156
#pragma GCC unroll 4
157
for (int64_t d = 0; d < size; d += bVec::size()) {
165
- bVec x = bVec::loadu(input + d);
166
- fVec x0, x1;
167
- std::tie(x0, x1) = at::vec::convert_to_float(x);
168
- bVec y = bVec::loadu(input2 + d);
169
- fVec y0, y1;
170
- std::tie(y0, y1) = at::vec::convert_to_float(y);
171
- x0 = x0 / (one + x0.neg().exp_u20());
172
- x1 = x1 / (one + x1.neg().exp_u20());
173
- x0 = x0 * y0;
174
- x1 = x1 * y1;
158
+ auto [x0, x1] = load_float_vec2(input + d);
159
+ auto [y0, y1] = load_float_vec2(input2 + d);
160
+ x0 = fast_silu(x0) * y0;
161
+ x1 = fast_silu(x1) * y1;
162
bVec out_vec = convert_from_float_ext<scalar_t>(x0, x1);
163
out_vec.store(out + d);
164
}
165
}
166
167
+template <typename scalar_t, typename input_t>
168
+inline void clamp_sigmoid_and_mul_stub(
169
+ scalar_t* __restrict__ out, const input_t* __restrict__ input, int64_t size, const float alpha, const float limit) {
170
+ static_assert(
171
+ std::is_same_v<input_t, float> || std::is_same_v<input_t, scalar_t>,
172
+ "clamp_sigmoid_and_mul_stub only supports input_t == float or input_t == scalar_t");
173
+ using bVec = at::vec::Vectorized<scalar_t>;
174
+ using fVec = at::vec::Vectorized<float>;
175
+ const fVec one = fVec(1.f);
176
+ const fVec limit_v = fVec(limit);
177
+ const fVec nlimit_v = fVec(-limit);
178
+ const fVec alpha_v = fVec(alpha);
179
+
180
+#pragma GCC unroll 4
181
+ for (int64_t d = 0; d < 2 * size; d += bVec::size()) {
182
+ auto [x0_, y0_] = load_float_vec2(input + d);
183
+ auto [x0, y0] = at::vec::deinterleave2<float>(x0_, y0_);
184
+
185
+ x0 = at::vec::minimum(x0, limit_v);
186
+ y0 = at::vec::minimum(limit_v, at::vec::maximum(nlimit_v, y0));
187
+ x0 = fast_sigmoid_glu(x0, alpha_v) * (y0 + one);
188
+ store_from_float_ext(out + d / 2, x0);
189
+ }
190
+}
191
+
192
template <typename scalar_t>
193
inline void copy_mul_stub(scalar_t* __restrict__ out, const float* __restrict__ input, float weight, int64_t size) {
194
using bVec = at::vec::Vectorized<scalar_t>;
@@ -186,9 +198,8 @@ inline void copy_mul_stub(scalar_t* __restrict__ out, const float* __restrict__
198
int64_t d;
199
#pragma GCC unroll 4
200
for (d = 0; d <= size - kVecSize; d += kVecSize) {
189
- fVec data0 = fVec::loadu(input + d) * weight_vec;
190
- fVec data1 = fVec::loadu(input + d + fVec::size()) * weight_vec;
191
- bVec out_vec = convert_from_float_ext<scalar_t>(data0, data1);
201
+ auto [x0, x1] = load_float_vec2(input + d);
202
+ bVec out_vec = convert_from_float_ext<scalar_t>(x0 * weight_vec, x1 * weight_vec);
203
out_vec.store(out + d);
204
}
205
for (; d < size; ++d) {
@@ -222,63 +233,11 @@ inline void copy_mul_stub(scalar_t* __restrict__ out, const scalar_t* __restrict
233
int64_t d;
234
#pragma GCC unroll 4
235
for (d = 0; d <= size - kVecSize; d += kVecSize) {
225
- bVec x = bVec::loadu(input + d);
226
- fVec x0, x1;
227
- std::tie(x0, x1) = at::vec::convert_to_float(x);
228
- x0 = x0 * weight_vec;
229
- x1 = x1 * weight_vec;
230
- bVec out_vec = convert_from_float_ext<scalar_t>(x0, x1);
236
+ auto [x0, x1] = load_float_vec2(input + d);
237
+ bVec out_vec = convert_from_float_ext<scalar_t>(x0 * weight_vec, x1 * weight_vec);
238
out_vec.store(out + d);
239
}
240
for (; d < size; ++d) {
241
out[d] = static_cast<scalar_t>(input[d] * weight);
242
}
243
}
237
-
238
-template <typename scalar_t>
239
-inline void clamp_sigmoid_and_mul_stub(
240
- scalar_t* __restrict__ out,
241
- const scalar_t* __restrict__ input,
242
- int64_t size,
243
- const float alpha,
244
- const float limit) {
245
- using bVec = at::vec::Vectorized<scalar_t>;
246
- using fVec = at::vec::Vectorized<float>;
247
- const fVec one = fVec(1.f);
248
- const fVec zero = fVec(0.f);
249
- const fVec limit_v = fVec(limit);
250
- const fVec nlimit_v = fVec(-limit);
251
- const fVec alpha_v = fVec(alpha);
252
-
253
- // no remainder
254
-#pragma GCC unroll 4
255
- for (int64_t d = 0; d < size; d += bVec::size()) {
256
- bVec x = bVec::loadu(input + d);
257
- fVec x0_, y0_;
258
- std::tie(x0_, y0_) = at::vec::convert_to_float(x);
259
- float tmp_buffer[fVec::size() * 2]; // 32
260
- float tmp_glu[fVec::size()]; // 16
261
- float tmp_linear[fVec::size()]; // 16
262
- x0_.store(tmp_buffer);
263
- y0_.store(tmp_buffer + fVec::size());
264
- // interleaved: x[2i] = glu, x[2i+1] = linear
265
- for (int j = 0; j < fVec::size(); ++j) {
266
- // x0 [0,2,..30]
267
- tmp_glu[j] = tmp_buffer[j * 2];
268
- // y0 [1,3,...31]
269
- tmp_linear[j] = tmp_buffer[j * 2 + 1];
270
- }
271
- fVec x0 = fVec::loadu(tmp_glu);
272
- fVec y0 = fVec::loadu(tmp_linear);
273
-
274
- // clamp
275
- x0 = at::vec::minimum(x0, limit_v);
276
- y0 = at::vec::minimum(limit_v, at::vec::maximum(nlimit_v, y0));
277
- // x * sigmoid(x * alpha)
278
- x0 = x0 / (one + (x0 * alpha_v).neg().exp_u20());
279
- // (y + 1) * x
280
- y0 = y0 + one;
281
- x0 = x0 * y0;
282
- convert_from_float_and_store<scalar_t>(out + d / 2, x0);
283
- }
284
-}
csrc/cpu/sgl-kernels/moe_fp8.cpp
+6
-6
@@ -37,7 +37,7 @@ void fused_experts_fp_kernel_impl(
37
int64_t num_tokens_post_pad,
38
float alpha,
39
float limit,
40
- CPUAcTMethod act_func,
40
+ CPUActMethod act_func,
41
bool with_bias) {
42
constexpr int64_t BLOCK_M = block_size_m();
43
constexpr int64_t BLOCK_N = block_size_n();
@@ -122,17 +122,17 @@ void fused_experts_fp_kernel_impl(
122
});
123
124
// stage 1.5: intermediate_cache1 = silu(intermediate_cache0)
125
- if (act_func == CPUAcTMethod::silu_and_mul) {
125
+ if (act_func == CPUActMethod::silu_and_mul) {
126
at::parallel_for(0, M * topk, 0, [&](int64_t begin, int64_t end) {
127
for (int64_t m = begin; m < end; ++m) {
128
silu_and_mul_stub(ic1 + m * N, ic0 + m * 2 * N, ic0 + m * 2 * N + N, N);
129
}
130
});
131
- } else if (act_func == CPUAcTMethod::swiglu) {
131
+ } else if (act_func == CPUActMethod::swiglu) {
132
at::parallel_for(0, M * topk, 0, [&](int64_t begin, int64_t end) {
133
for (int64_t m = begin; m < end; ++m) {
134
- clamp_sigmoid_and_mul_stub(ic1 + m * N, ic0 + m * 2 * N, N, alpha, limit);
135
- clamp_sigmoid_and_mul_stub(ic1 + m * N + N / 2, ic0 + m * 2 * N + N, N, alpha, limit);
134
+ clamp_sigmoid_and_mul_stub(ic1 + m * N, ic0 + m * 2 * N, N / 2, alpha, limit);
135
+ clamp_sigmoid_and_mul_stub(ic1 + m * N + N / 2, ic0 + m * 2 * N + N, N / 2, alpha, limit);
136
}
137
});
138
}
@@ -243,7 +243,7 @@ void fused_experts_fp_kernel_impl(
243
int64_t num_tokens_post_pad, \
244
float alpha, \
245
float limit, \
246
- CPUAcTMethod act_func, \
246
+ CPUActMethod act_func, \
247
bool with_bias)
248
249
INSTANTIATE_MOE_FP_TEMPLATE(at::BFloat16, at::Float8_e4m3fn, float, false);
csrc/cpu/sgl-kernels/moe_int8.cpp
+11
-19
@@ -53,16 +53,14 @@ inline void silu_and_mul(
53
vc1[col] = _mm512_mul_ps(_mm512_mul_ps(vc1[col], vas), vbs1[col]);
54
};
55
56
- using bVec = at::vec::Vectorized<scalar_t>;
57
- using fVec = at::vec::Vectorized<float>;
58
- const fVec one = fVec(1.f);
56
auto silu_and_mul = [&](auto col) {
60
- fVec x = fVec(vc0[col]);
61
- fVec y = fVec(vc1[col]);
62
- x = x / (one + x.neg().exp_u20());
63
- vc0[col] = x * y;
57
+ __m512 x = vc0[col];
58
+ __m512 y = vc1[col];
59
+ vc0[col] = _mm512_mul_ps(_mm512_rcp14_silu_ps(x), y);
60
};
61
62
+ using bVec = at::vec::Vectorized<scalar_t>;
63
+ using fVec = at::vec::Vectorized<float>;
64
auto storec = [&](auto col, int64_t m) {
65
if constexpr (col % 2 == 0) {
66
fVec x0 = fVec(vc0[col + 0]);
@@ -229,23 +227,17 @@ struct tinygemm_kernel_vnni<at::BFloat16, BLOCK_M, BLOCK_N> {
227
};
228
Unroll<ROWS * COLS>{}(scalec);
229
232
- using Vec = at::vec::Vectorized<float>;
233
- const Vec one = Vec(1.f);
230
auto storec = [&](auto i) {
231
constexpr int row = i / COLS;
232
constexpr int col = i % COLS;
233
// for COLS = 2, 4 use 512bit store
234
if constexpr (col % 2 == 0) {
239
- Vec x0 = _mm512_castsi512_ps(vc0[row * COLS + col + 0]);
240
- Vec x1 = _mm512_castsi512_ps(vc0[row * COLS + col + 1]);
241
- Vec y0 = _mm512_castsi512_ps(vc1[row * COLS + col + 0]);
242
- Vec y1 = _mm512_castsi512_ps(vc1[row * COLS + col + 1]);
243
- // silu
244
- x0 = x0 / (one + x0.neg().exp_u20());
245
- x1 = x1 / (one + x1.neg().exp_u20());
246
- // mul
247
- x0 = x0 * y0;
248
- x1 = x1 * y1;
235
+ __m512 x0 = _mm512_castsi512_ps(vc0[row * COLS + col + 0]);
236
+ __m512 x1 = _mm512_castsi512_ps(vc0[row * COLS + col + 1]);
237
+ __m512 y0 = _mm512_castsi512_ps(vc1[row * COLS + col + 0]);
238
+ __m512 y1 = _mm512_castsi512_ps(vc1[row * COLS + col + 1]);
239
+ x0 = _mm512_mul_ps(_mm512_rcp14_silu_ps(x0), y0);
240
+ x1 = _mm512_mul_ps(_mm512_rcp14_silu_ps(x1), y1);
241
242
_mm512_storeu_si512(
243
reinterpret_cast<__m512i*>((C + row * ldc + col * 16)),
csrc/cpu/sgl-kernels/vec.h
+195
-6
@@ -32,11 +32,11 @@ inline Vectorized<scalar_t> convert_from_float_ext(const Vectorized<float>& a, c
32
}
33
34
template <typename scalar_t>
35
-inline void convert_from_float_and_store(scalar_t* out, const Vectorized<float>& a) {
36
- float out_buffer[at::vec::Vectorized<float>::size()];
35
+inline void store_from_float_ext(scalar_t* out, const Vectorized<float>& a) {
36
+ float out_buffer[Vectorized<float>::size()];
37
a.store(out_buffer);
38
- for (int i = 0; i < 16; i++) {
39
- out[i] = (scalar_t)out_buffer[i];
38
+ for (int i = 0; i < Vectorized<float>::size(); ++i) {
39
+ out[i] = static_cast<scalar_t>(out_buffer[i]);
40
}
41
}
42
@@ -59,6 +59,17 @@ inline std::tuple<Vectorized<float>, Vectorized<float>> load_float_vec2(const fl
59
return std::make_tuple(x0, x1);
60
}
61
62
+template <typename scalar_t, typename std::enable_if_t<is_reduced_floating_point_v<scalar_t>, int> = 1>
63
+inline at::vec::Vectorized<float> load_float_vec(const scalar_t* __restrict__ data) {
64
+ at::vec::Vectorized<float> out;
65
+ if constexpr (std::is_same_v<scalar_t, at::BFloat16>) {
66
+ at::vec::load_fp32_from_bf16(data, out);
67
+ } else {
68
+ at::vec::load_fp32_from_fp16(data, out);
69
+ }
70
+ return out;
71
+}
72
+
73
#if defined(CPU_CAPABILITY_AVX512)
74
75
// `at::vec::convert_from_float<>` from PyTorch doesn't have avx512-bf16 intrinsics
@@ -70,8 +81,14 @@ convert_from_float_ext<at::BFloat16>(const Vectorized<float>& a, const Vectorize
81
}
82
83
template <>
73
-inline void convert_from_float_and_store<at::BFloat16>(at::BFloat16* out, const Vectorized<float>& a) {
74
- _mm256_storeu_si256((__m256i*)out, (__m256i)(_mm512_cvtneps_pbh(__m512(a))));
84
+inline void store_from_float_ext<at::BFloat16>(at::BFloat16* out, const Vectorized<float>& a) {
85
+ _mm256_storeu_si256(reinterpret_cast<__m256i*>(out), (__m256i)(_mm512_cvtneps_pbh(__m512(a))));
86
+}
87
+
88
+template <>
89
+inline void store_from_float_ext<at::Half>(at::Half* out, const Vectorized<float>& a) {
90
+ _mm256_storeu_si256(
91
+ reinterpret_cast<__m256i*>(out), _mm512_cvtps_ph(__m512(a), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC));
92
}
93
94
#define CVT_BF16_TO_FP32(a) _mm512_castsi512_ps(_mm512_slli_epi32(_mm512_cvtepu16_epi32(a), 16))
@@ -301,6 +318,77 @@ inline void quantize_row_int8<at::BFloat16>(
318
// transpose utils
319
// taken from my PR in ggml: https://github.com/ggml-org/llama.cpp/pull/8998
320
#if defined(CPU_CAPABILITY_AVX512)
321
+inline void transpose_16x16_16bit(__m256i* v) {
322
+ __m256i v1[16];
323
+ v1[0] = _mm256_unpacklo_epi16(v[0], v[1]);
324
+ v1[1] = _mm256_unpackhi_epi16(v[0], v[1]);
325
+ v1[2] = _mm256_unpacklo_epi16(v[2], v[3]);
326
+ v1[3] = _mm256_unpackhi_epi16(v[2], v[3]);
327
+ v1[4] = _mm256_unpacklo_epi16(v[4], v[5]);
328
+ v1[5] = _mm256_unpackhi_epi16(v[4], v[5]);
329
+ v1[6] = _mm256_unpacklo_epi16(v[6], v[7]);
330
+ v1[7] = _mm256_unpackhi_epi16(v[6], v[7]);
331
+ v1[8] = _mm256_unpacklo_epi16(v[8], v[9]);
332
+ v1[9] = _mm256_unpackhi_epi16(v[8], v[9]);
333
+ v1[10] = _mm256_unpacklo_epi16(v[10], v[11]);
334
+ v1[11] = _mm256_unpackhi_epi16(v[10], v[11]);
335
+ v1[12] = _mm256_unpacklo_epi16(v[12], v[13]);
336
+ v1[13] = _mm256_unpackhi_epi16(v[12], v[13]);
337
+ v1[14] = _mm256_unpacklo_epi16(v[14], v[15]);
338
+ v1[15] = _mm256_unpackhi_epi16(v[14], v[15]);
339
+
340
+ v[0] = _mm256_unpacklo_epi32(v1[0], v1[2]);
341
+ v[1] = _mm256_unpackhi_epi32(v1[0], v1[2]);
342
+ v[2] = _mm256_unpacklo_epi32(v1[1], v1[3]);
343
+ v[3] = _mm256_unpackhi_epi32(v1[1], v1[3]);
344
+ v[4] = _mm256_unpacklo_epi32(v1[4], v1[6]);
345
+ v[5] = _mm256_unpackhi_epi32(v1[4], v1[6]);
346
+ v[6] = _mm256_unpacklo_epi32(v1[5], v1[7]);
347
+ v[7] = _mm256_unpackhi_epi32(v1[5], v1[7]);
348
+ v[8] = _mm256_unpacklo_epi32(v1[8], v1[10]);
349
+ v[9] = _mm256_unpackhi_epi32(v1[8], v1[10]);
350
+ v[10] = _mm256_unpacklo_epi32(v1[9], v1[11]);
351
+ v[11] = _mm256_unpackhi_epi32(v1[9], v1[11]);
352
+ v[12] = _mm256_unpacklo_epi32(v1[12], v1[14]);
353
+ v[13] = _mm256_unpackhi_epi32(v1[12], v1[14]);
354
+ v[14] = _mm256_unpacklo_epi32(v1[13], v1[15]);
355
+ v[15] = _mm256_unpackhi_epi32(v1[13], v1[15]);
356
+
357
+ v1[0] = _mm256_unpacklo_epi64(v[0], v[4]);
358
+ v1[1] = _mm256_unpackhi_epi64(v[0], v[4]);
359
+ v1[2] = _mm256_unpacklo_epi64(v[1], v[5]);
360
+ v1[3] = _mm256_unpackhi_epi64(v[1], v[5]);
361
+ v1[4] = _mm256_unpacklo_epi64(v[2], v[6]);
362
+ v1[5] = _mm256_unpackhi_epi64(v[2], v[6]);
363
+ v1[6] = _mm256_unpacklo_epi64(v[3], v[7]);
364
+ v1[7] = _mm256_unpackhi_epi64(v[3], v[7]);
365
+ v1[8] = _mm256_unpacklo_epi64(v[8], v[12]);
366
+ v1[9] = _mm256_unpackhi_epi64(v[8], v[12]);
367
+ v1[10] = _mm256_unpacklo_epi64(v[9], v[13]);
368
+ v1[11] = _mm256_unpackhi_epi64(v[9], v[13]);
369
+ v1[12] = _mm256_unpacklo_epi64(v[10], v[14]);
370
+ v1[13] = _mm256_unpackhi_epi64(v[10], v[14]);
371
+ v1[14] = _mm256_unpacklo_epi64(v[11], v[15]);
372
+ v1[15] = _mm256_unpackhi_epi64(v[11], v[15]);
373
+
374
+ v[0] = _mm256_permute2x128_si256(v1[0], v1[8], 0x20);
375
+ v[1] = _mm256_permute2x128_si256(v1[1], v1[9], 0x20);
376
+ v[2] = _mm256_permute2x128_si256(v1[2], v1[10], 0x20);
377
+ v[3] = _mm256_permute2x128_si256(v1[3], v1[11], 0x20);
378
+ v[4] = _mm256_permute2x128_si256(v1[4], v1[12], 0x20);
379
+ v[5] = _mm256_permute2x128_si256(v1[5], v1[13], 0x20);
380
+ v[6] = _mm256_permute2x128_si256(v1[6], v1[14], 0x20);
381
+ v[7] = _mm256_permute2x128_si256(v1[7], v1[15], 0x20);
382
+ v[8] = _mm256_permute2x128_si256(v1[0], v1[8], 0x31);
383
+ v[9] = _mm256_permute2x128_si256(v1[1], v1[9], 0x31);
384
+ v[10] = _mm256_permute2x128_si256(v1[2], v1[10], 0x31);
385
+ v[11] = _mm256_permute2x128_si256(v1[3], v1[11], 0x31);
386
+ v[12] = _mm256_permute2x128_si256(v1[4], v1[12], 0x31);
387
+ v[13] = _mm256_permute2x128_si256(v1[5], v1[13], 0x31);
388
+ v[14] = _mm256_permute2x128_si256(v1[6], v1[14], 0x31);
389
+ v[15] = _mm256_permute2x128_si256(v1[7], v1[15], 0x31);
390
+}
391
+
392
inline void transpose_16x16_32bit(__m512i* v) {
393
__m512i v1[16];
394
v1[0] = _mm512_unpacklo_epi32(v[0], v[1]);
@@ -394,6 +482,62 @@ inline std::tuple<__m512i, __m512i> transpose_2x32_16bit(__m512i r0, __m512i r1)
482
}
483
#pragma GCC diagnostic pop
484
485
+// Note: mapped from aten exp_u20
486
+inline __attribute__((always_inline)) __m512 _mm512_exp_u20_ps(const __m512 values) {
487
+ const __m512 vec_factorial_1 = _mm512_set1_ps(0.999999701f);
488
+ const __m512 vec_factorial_2 = _mm512_set1_ps(0.499991506f);
489
+ const __m512 vec_factorial_3 = _mm512_set1_ps(0.166676521f);
490
+ const __m512 vec_factorial_4 = _mm512_set1_ps(0.0418978221f);
491
+ const __m512 vec_factorial_5 = _mm512_set1_ps(0.00828929059f);
492
+ const __m512 vec_exp_log2ef = _mm512_castsi512_ps(_mm512_set1_epi32(0x3fb8aa3b)); // log2(e)
493
+ const __m512 vec_half = _mm512_set1_ps(0.5f);
494
+ const __m512 vec_one = _mm512_set1_ps(1.f);
495
+ const __m512 vec_zero = _mm512_set1_ps(0.f);
496
+ const __m512 vec_two = _mm512_set1_ps(2.f);
497
+ const __m512 vec_ln2f = _mm512_castsi512_ps(_mm512_set1_epi32(0x3f317218));
498
+ const __m512 vec_ln_flt_min = _mm512_castsi512_ps(_mm512_set1_epi32(0xc2aeac50));
499
+ const __m512 vec_ln_flt_max = _mm512_castsi512_ps(_mm512_set1_epi32(0x42b17218));
500
+ const __m512i vec_127 = _mm512_set1_epi32(0x0000007f);
501
+ const int n_mantissa_bits = 23;
502
+
503
+ // exp(x) =
504
+ // = exp(n * ln(2) + r) // divide x by ln(2) and get quot and rem
505
+ // = 2^n * exp(r) // simplify the exp(n*ln(2)) expression
506
+
507
+ auto less_ln_flt_min_mask = _mm512_cmp_ps_mask(values, vec_ln_flt_min, 1 /*_CMP_LT_OS*/);
508
+ auto vec_src = _mm512_min_ps(values, vec_ln_flt_max);
509
+ vec_src = _mm512_max_ps(vec_src, vec_ln_flt_min);
510
+
511
+ // fx = floorf(x * log2ef + 0.5)
512
+ auto vec_fx = _mm512_fmadd_ps(vec_src, vec_exp_log2ef, vec_half);
513
+ auto vec_fx_i = _mm512_cvt_roundps_epi32(vec_fx, _MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC);
514
+ vec_fx = _mm512_cvtepi32_ps(vec_fx_i);
515
+
516
+ // x = x - fx * ln2
517
+ auto vec_exp_poly = _mm512_fnmadd_ps(vec_fx, vec_ln2f, vec_src);
518
+
519
+ // compute polynomial
520
+ auto vec_res = _mm512_fmadd_ps(vec_exp_poly, vec_factorial_5, vec_factorial_4);
521
+ vec_res = _mm512_fmadd_ps(vec_exp_poly, vec_res, vec_factorial_3);
522
+ vec_res = _mm512_fmadd_ps(vec_exp_poly, vec_res, vec_factorial_2);
523
+ vec_res = _mm512_fmadd_ps(vec_exp_poly, vec_res, vec_factorial_1);
524
+ vec_res = _mm512_fmadd_ps(vec_exp_poly, vec_res, vec_one);
525
+
526
+ // compute 2^(n-1)
527
+ auto vec_exp_number = _mm512_sub_ps(vec_fx, vec_one);
528
+ auto vec_exp_number_i = _mm512_cvtps_epi32(vec_exp_number);
529
+ auto vec_two_pow_n_i = _mm512_add_epi32(vec_exp_number_i, vec_127);
530
+ vec_two_pow_n_i = _mm512_slli_epi32(vec_two_pow_n_i, n_mantissa_bits);
531
+ auto vec_two_pow_n = _mm512_castsi512_ps(vec_two_pow_n_i);
532
+ vec_two_pow_n = _mm512_mask_blend_ps(less_ln_flt_min_mask, vec_two_pow_n, vec_zero);
533
+
534
+ // y = y * 2^n
535
+ vec_res = _mm512_mul_ps(vec_res, vec_two_pow_n);
536
+ vec_res = _mm512_mul_ps(vec_res, vec_two);
537
+ return vec_res;
538
+}
539
+
540
+// Note: mapped from aten fexp_u20
541
inline __attribute__((always_inline)) __m512 _mm512_fexp_u20_ps(const __m512 values) {
542
const __m512 vec_c0 = _mm512_set1_ps(0.00010703434948458272f);
543
const __m512 vec_c1 = _mm512_set1_ps(0.30354260500649682f);
@@ -444,6 +588,51 @@ inline __attribute__((always_inline)) __m512 _mm512_fexp_u20_ps(const __m512 val
588
// final interpretation to float
589
return _mm512_castsi512_ps(casted_integer);
590
}
591
+
592
+// sigmoid(x) = 1 / (1 + exp(-x)); avoid vdivps via rcp14
593
+inline __attribute__((always_inline)) __m512 _mm512_rcp14_sigmoid_ps(__m512 x) {
594
+ __m512 minus_x = _mm512_xor_ps(_mm512_set1_ps(-0.f), x);
595
+ __m512 denom = _mm512_add_ps(_mm512_exp_u20_ps(minus_x), _mm512_set1_ps(1.f));
596
+ return _mm512_rcp14_ps(denom);
597
+}
598
+
599
+// SiLU(x) = x * sigmoid(x)
600
+inline __attribute__((always_inline)) __m512 _mm512_rcp14_silu_ps(__m512 x) {
601
+ return _mm512_mul_ps(x, _mm512_rcp14_sigmoid_ps(x));
602
+}
603
+
604
+// x * sigmoid(x * alpha) for clamped SwiGLU
605
+inline __attribute__((always_inline)) __m512 _mm512_rcp14_sigmoid_glu_ps(__m512 x, __m512 alpha) {
606
+ __m512 xa = _mm512_mul_ps(x, alpha);
607
+ return _mm512_mul_ps(x, _mm512_rcp14_sigmoid_ps(xa));
608
+}
609
+
610
+#endif
611
+
612
+inline at::vec::Vectorized<float> fast_sigmoid(const at::vec::Vectorized<float>& x) {
613
+#if defined(CPU_CAPABILITY_AVX512)
614
+ return at::vec::Vectorized<float>(_mm512_rcp14_sigmoid_ps(x));
615
+#else
616
+ const auto one = at::vec::Vectorized<float>(1.f);
617
+ return one / (one + x.neg().exp_u20());
618
+#endif
619
+}
620
+
621
+inline at::vec::Vectorized<float> fast_silu(const at::vec::Vectorized<float>& x) {
622
+#if defined(CPU_CAPABILITY_AVX512)
623
+ return at::vec::Vectorized<float>(_mm512_rcp14_silu_ps(x));
624
+#else
625
+ return x * fast_sigmoid(x);
626
#endif
627
+}
628
+
629
+inline at::vec::Vectorized<float>
630
+fast_sigmoid_glu(const at::vec::Vectorized<float>& x, const at::vec::Vectorized<float>& alpha) {
631
+#if defined(CPU_CAPABILITY_AVX512)
632
+ return at::vec::Vectorized<float>(_mm512_rcp14_sigmoid_glu_ps(x, alpha));
633
+#else
634
+ return x * fast_sigmoid(x * alpha);
635
+#endif
636
+}
637
638
} // anonymous namespace
csrc/cpu/torch_bindings.cpp
+3
-2
@@ -110,7 +110,7 @@ std::tuple<at::Tensor, at::Tensor> chunk_gated_delta_rule_cpu(
110
const at::Tensor& g, const at::Tensor& beta,
111
const at::Tensor& initial_state, bool output_final_state,
112
const at::Tensor& cu_seqlens, bool head_first, bool use_qk_l2norm_in_kernel,
113
- double eps = 1e-5);
113
+ const at::Tensor& initial_state_indices, double eps = 1e-5);
114
115
at::Tensor fused_sigmoid_gating_delta_rule_update_cpu(
116
const at::Tensor& A_log, const at::Tensor& dt_bias, const at::Tensor& q,
@@ -546,7 +546,8 @@ TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
546
"Tensor g, Tensor beta, "
547
"Tensor initial_state, bool output_final_state, Tensor cu_seqlens, bool "
548
"head_first, "
549
- "bool use_qk_l2norm_in_kernel, float eps=1e-5) -> (Tensor, Tensor)");
549
+ "bool use_qk_l2norm_in_kernel, Tensor initial_state_indices, float "
550
+ "eps=1e-5) -> (Tensor, Tensor)");
551
ops.impl("chunk_gated_delta_rule_cpu", torch::kCPU,
552
&chunk_gated_delta_rule_cpu);
553
ops.def(
tests/kernels/mamba/cpu/test_cpu_gdn_ops.py
+13
-2
@@ -26,6 +26,13 @@ HEAD_DIMS = [
26
(32, 32),
27
(64, 32),
28
]
29
+# chunk_gated_delta_rule_cpu (the chunked-prefill kernel) only supports
30
+# head_dim == head_dim_v in {64, 128}; the decode-path update kernel above
31
+# has no such restriction and keeps using the wider HEAD_DIMS list.
32
+CHUNK_HEAD_DIMS = [
33
+ (64, 64),
34
+ (128, 128),
35
+]
36
CHUNK_SIZE = 64
37
CONV_DIM = 128
38
CONV_KERNEL = 4
@@ -258,7 +265,7 @@ def test_fused_sigmoid_gating_delta_rule_update_cpu(
265
# prefill path
266
@pytest.mark.parametrize("seq_lens", PREFILL_SEQ_LENS)
267
@pytest.mark.parametrize("num_heads", NUM_HEADS)
261
-@pytest.mark.parametrize("head_dims", HEAD_DIMS)
268
+@pytest.mark.parametrize("head_dims", CHUNK_HEAD_DIMS)
269
@torch.inference_mode()
270
def test_chunk_gated_delta_rule_cpu(
271
seq_lens: list[int],
@@ -307,6 +314,7 @@ def test_chunk_gated_delta_rule_cpu(
314
cu_seqlens=cu_seqlens,
315
head_first=False,
316
use_qk_l2norm_in_kernel=True,
317
+ initial_state_indices=torch.arange(len(seq_lens), dtype=torch.int32),
318
)
319
320
torch.testing.assert_close(out, out_ref, atol=1e-2, rtol=1e-2)
@@ -331,7 +339,7 @@ TWO_CALL_SPLITS = [
339
340
@pytest.mark.parametrize("total_tokens, split", TWO_CALL_SPLITS)
341
@pytest.mark.parametrize("num_heads", NUM_HEADS)
334
-@pytest.mark.parametrize("head_dims", HEAD_DIMS)
342
+@pytest.mark.parametrize("head_dims", CHUNK_HEAD_DIMS)
343
@torch.inference_mode()
344
def test_chunk_gated_delta_rule_cpu_two_call_split(
345
total_tokens: int,
@@ -370,6 +378,7 @@ def test_chunk_gated_delta_rule_cpu_two_call_split(
378
cu_seqlens=torch.tensor([0, total_tokens], dtype=torch.int32),
379
head_first=False,
380
use_qk_l2norm_in_kernel=True,
381
+ initial_state_indices=torch.zeros(1, dtype=torch.int32),
382
)
383
384
# Call 1: tokens [0:split], no initial state, capture final state.
@@ -384,6 +393,7 @@ def test_chunk_gated_delta_rule_cpu_two_call_split(
393
cu_seqlens=torch.tensor([0, split], dtype=torch.int32),
394
head_first=False,
395
use_qk_l2norm_in_kernel=True,
396
+ initial_state_indices=torch.zeros(1, dtype=torch.int32),
397
)
398
# Call 2: tokens [split:T] seeded with call 1's final state and a cu_seqlens
399
# rebased to start at 0, as cpu_gdn_attention_core continues a prefill chunk.
@@ -399,6 +409,7 @@ def test_chunk_gated_delta_rule_cpu_two_call_split(
409
cu_seqlens=torch.tensor([0, tail], dtype=torch.int32),
410
head_first=False,
411
use_qk_l2norm_in_kernel=True,
412
+ initial_state_indices=torch.zeros(1, dtype=torch.int32),
413
)
414
415
out_split = torch.cat([out1, out2], dim=1)
vllm/_custom_ops.py
+2
@@ -3491,6 +3491,7 @@ def chunk_gated_delta_rule_cpu(
3491
cu_seqlens: torch.Tensor,
3492
head_first: bool,
3493
use_qk_l2norm_in_kernel: bool,
3494
+ initial_state_indices: torch.Tensor,
3495
eps: float = 1e-5,
3496
) -> tuple[torch.Tensor, torch.Tensor]:
3497
return torch.ops._C.chunk_gated_delta_rule_cpu(
@@ -3504,6 +3505,7 @@ def chunk_gated_delta_rule_cpu(
3505
cu_seqlens,
3506
head_first,
3507
use_qk_l2norm_in_kernel,
3508
+ initial_state_indices,
3509
eps,
3510
)
3511
vllm/model_executor/layers/mamba/ops/cpu/gdn_attention.py
+27
-21
@@ -236,22 +236,24 @@ def _cpu_gdn_attention_nonspec(
236
A_log=layer.A_log, a=prefill_a, b=prefill_b, dt_bias=layer.dt_bias
237
)
238
239
- initial_state = ssm_state[prefill_state_indices]
240
- initial_state[~prefill_has_initial_state, ...] = 0
241
- attn_out, last_recurrent_state = ops.chunk_gated_delta_rule_cpu(
239
+ # zero pool slots for sequences without a prior state; the kernel
240
+ # gathers/mutates ssm_state in place via prefill_state_indices, so no
241
+ # separate scatter-back is needed after the call.
242
+ no_initial_state = prefill_state_indices[~prefill_has_initial_state]
243
+ if no_initial_state.numel() > 0:
244
+ ssm_state[no_initial_state] = 0
245
+ attn_out, _ = ops.chunk_gated_delta_rule_cpu(
246
query=query,
247
key=key,
248
value=value,
249
g=g,
250
beta=beta,
247
- initial_state=initial_state,
251
+ initial_state=ssm_state,
252
output_final_state=True,
253
cu_seqlens=prefill_query_start_loc,
254
head_first=False,
255
use_qk_l2norm_in_kernel=True,
252
- )
253
- ssm_state[prefill_state_indices] = last_recurrent_state.to(
254
- ssm_state.dtype, copy=False
256
+ initial_state_indices=prefill_state_indices,
257
)
258
core_attn_out[prefill_token_start:prefill_token_end] = attn_out.squeeze(0)
259
@@ -616,22 +618,24 @@ def _spec_aware_nonspec(
618
g, beta = ops.fused_gdn_gating_cpu(
619
A_log=layer.A_log, a=prefill_a, b=prefill_b, dt_bias=layer.dt_bias
620
)
619
- initial_state = ssm_state[prefill_state_indices]
620
- initial_state[~prefill_has_initial_state, ...] = 0
621
- attn_out, last_recurrent_state = ops.chunk_gated_delta_rule_cpu(
621
+ # zero pool slots for sequences without a prior state; the kernel
622
+ # gathers/mutates ssm_state in place via prefill_state_indices, so no
623
+ # separate scatter-back is needed after the call.
624
+ no_initial_state = prefill_state_indices[~prefill_has_initial_state]
625
+ if no_initial_state.numel() > 0:
626
+ ssm_state[no_initial_state] = 0
627
+ attn_out, _ = ops.chunk_gated_delta_rule_cpu(
628
query=query,
629
key=key,
630
value=value,
631
g=g,
632
beta=beta,
627
- initial_state=initial_state,
633
+ initial_state=ssm_state,
634
output_final_state=True,
635
cu_seqlens=prefill_query_start_loc,
636
head_first=False,
637
use_qk_l2norm_in_kernel=True,
632
- )
633
- ssm_state[prefill_state_indices] = last_recurrent_state.to(
634
- ssm_state.dtype, copy=False
638
+ initial_state_indices=prefill_state_indices,
639
)
640
core_attn_out[prefill_token_start:prefill_token_end] = attn_out.squeeze(0)
641
@@ -690,22 +694,24 @@ def _spec_aware_nonspec_subset(
694
g, beta = ops.fused_gdn_gating_cpu(
695
A_log=layer.A_log, a=a, b=b, dt_bias=layer.dt_bias
696
)
693
- initial_state = ssm_state[prefill_state_indices]
694
- initial_state[~has_initial_state, ...] = 0
695
- attn_out, last_recurrent_state = ops.chunk_gated_delta_rule_cpu(
697
+ # zero pool slots for sequences without a prior state; the kernel
698
+ # gathers/mutates ssm_state in place via prefill_state_indices, so no
699
+ # separate scatter-back is needed after the call.
700
+ no_initial_state = prefill_state_indices[~has_initial_state]
701
+ if no_initial_state.numel() > 0:
702
+ ssm_state[no_initial_state] = 0
703
+ attn_out, _ = ops.chunk_gated_delta_rule_cpu(
704
query=query,
705
key=key,
706
value=value,
707
g=g,
708
beta=beta,
701
- initial_state=initial_state,
709
+ initial_state=ssm_state,
710
output_final_state=True,
711
cu_seqlens=prefill_qsl,
712
head_first=False,
713
use_qk_l2norm_in_kernel=True,
706
- )
707
- ssm_state[prefill_state_indices] = last_recurrent_state.to(
708
- ssm_state.dtype, copy=False
714
+ initial_state_indices=prefill_state_indices,
715
)
716
return attn_out.squeeze(0)
717