← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
attention_decode_fused.c
Go to the documentation of this file.
1/**
2 * @file attention_decode_fused.c
3 * @brief Fused attention decode kernel (legacy v6/v6.5)
4 *
5 * CK-ENGINE KERNEL RULES:
6 * =======================
7 * 1. NO malloc/free - memory via bump allocator, pointers passed in
8 * 2. NO OpenMP - parallelization at orchestrator/codegen layer
9 * 3. API must define: inputs, outputs, workspace, and memory layouts
10 * 4. Pure computation - deterministic, no side effects
11 *
12 * After changes: make test && make llamacpp-parity-full
13 *
14 * LEGACY: This file is from v6/v6.5 and kept for backward compatibility.
15 */
16
18
19#include "ckernel_engine.h"
20
21#include <stddef.h>
22
23#if defined(__AVX__) || defined(__AVX2__) || defined(__AVX512F__)
24#include <immintrin.h>
25#endif
26
27#if defined(__AVX__) && !defined(__AVX512F__)
28static inline float ck_hsum256_ps(__m256 v)
29{
30 __m128 lo = _mm256_castps256_ps128(v);
31 __m128 hi = _mm256_extractf128_ps(v, 1);
32 __m128 sum128 = _mm_add_ps(lo, hi);
33 __m128 shuf = _mm_movehdup_ps(sum128);
34 __m128 sums = _mm_add_ps(sum128, shuf);
35 shuf = _mm_movehl_ps(shuf, sums);
36 sums = _mm_add_ss(sums, shuf);
37 return _mm_cvtss_f32(sums);
38}
39#endif
40
41static inline float ck_dot_f32(const float *a, const float *b, int len)
42{
43#if defined(__AVX512F__)
44 __m512 acc = _mm512_setzero_ps();
45 int i = 0;
46 for (; i <= len - 16; i += 16) {
47 __m512 va = _mm512_loadu_ps(a + i);
48 __m512 vb = _mm512_loadu_ps(b + i);
49 acc = _mm512_fmadd_ps(va, vb, acc);
50 }
51 float sum = _mm512_reduce_add_ps(acc);
52 for (; i < len; ++i) {
53 sum += a[i] * b[i];
54 }
55 return sum;
56#elif defined(__AVX__)
57 __m256 acc = _mm256_setzero_ps();
58 int i = 0;
59 for (; i <= len - 8; i += 8) {
60 __m256 va = _mm256_loadu_ps(a + i);
61 __m256 vb = _mm256_loadu_ps(b + i);
62 acc = _mm256_add_ps(acc, _mm256_mul_ps(va, vb));
63 }
64 float sum = ck_hsum256_ps(acc);
65 for (; i < len; ++i) {
66 sum += a[i] * b[i];
67 }
68 return sum;
69#else
70 float sum = 0.0f;
71 for (int i = 0; i < len; ++i) {
72 sum += a[i] * b[i];
73 }
74 return sum;
75#endif
76}
77
78void ck_qkv_project_head_major_token(const float *input_row,
79 const float *wq, const float *bq,
80 const float *wk, const float *bk,
81 const float *wv, const float *bv,
82 float *q_token,
83 float *k_token,
84 float *v_token,
85 int aligned_embed_dim,
86 int num_heads,
87 int num_kv_heads,
88 int aligned_head_dim)
89{
90 if (!input_row || !wq || !wk || !wv || !q_token || !k_token || !v_token) {
91 return;
92 }
93
94 const int q_out = num_heads * aligned_head_dim;
95 gemm_blocked_serial(input_row, wq, bq, q_token,
96 /*tokens=*/1, q_out, aligned_embed_dim);
97
98 size_t head_weight_stride = (size_t)aligned_head_dim * (size_t)aligned_embed_dim;
99#pragma omp parallel for schedule(static) if(num_kv_heads > 1)
100 for (int h = 0; h < num_kv_heads; ++h) {
101 const float *wk_h = wk + (size_t)h * head_weight_stride;
102 const float *wv_h = wv + (size_t)h * head_weight_stride;
103 const float *bk_h = bk ? (bk + (size_t)h * (size_t)aligned_head_dim) : NULL;
104 const float *bv_h = bv ? (bv + (size_t)h * (size_t)aligned_head_dim) : NULL;
105 float *k_h = k_token + (size_t)h * (size_t)aligned_head_dim;
106 float *v_h = v_token + (size_t)h * (size_t)aligned_head_dim;
107
108 gemm_blocked_serial(input_row, wk_h, bk_h, k_h,
109 /*tokens=*/1, aligned_head_dim, aligned_embed_dim);
110 gemm_blocked_serial(input_row, wv_h, bv_h, v_h,
111 /*tokens=*/1, aligned_head_dim, aligned_embed_dim);
112 }
113}
114
116 const float *wo,
117 const float *bo,
118 float *out_token,
119 int embed_dim,
120 int aligned_embed_dim,
121 int num_heads,
122 int aligned_head_dim)
123{
124 const size_t head_in_stride = (size_t)aligned_head_dim;
125 const size_t head_weight_stride = (size_t)aligned_embed_dim * (size_t)aligned_head_dim;
126
127#pragma omp parallel for schedule(static)
128 for (int j = 0; j < embed_dim; ++j) {
129 float sum = bo ? bo[j] : 0.0f;
130 for (int h = 0; h < num_heads; ++h) {
131 const float *head_in = attn_token + (size_t)h * head_in_stride;
132 const float *wo_row = wo + (size_t)h * head_weight_stride + (size_t)j * (size_t)aligned_head_dim;
133 sum += ck_dot_f32(head_in, wo_row, aligned_head_dim);
134 }
135 out_token[j] = sum;
136 }
137
138 for (int j = embed_dim; j < aligned_embed_dim; ++j) {
139 out_token[j] = 0.0f;
140 }
141}
142
143// Per-output dot products keep the attn token hot, but can be slower than a matvec
144// microkernel on some CPUs.
146 const float *wo,
147 const float *bo,
148 const float *residual_in,
149 float *proj_out,
150 float *residual_out,
151 int embed_dim,
152 int aligned_embed_dim,
153 int num_heads,
154 int aligned_head_dim)
155{
156 const size_t head_in_stride = (size_t)aligned_head_dim;
157 const size_t head_weight_stride = (size_t)aligned_embed_dim * (size_t)aligned_head_dim;
158
159#pragma omp parallel for schedule(static)
160 for (int j = 0; j < embed_dim; ++j) {
161 float sum = bo ? bo[j] : 0.0f;
162 for (int h = 0; h < num_heads; ++h) {
163 const float *head_in = attn_token + (size_t)h * head_in_stride;
164 const float *wo_row = wo + (size_t)h * head_weight_stride + (size_t)j * (size_t)aligned_head_dim;
165 sum += ck_dot_f32(head_in, wo_row, aligned_head_dim);
166 }
167 if (proj_out) {
168 proj_out[j] = sum;
169 }
170 residual_out[j] = sum + residual_in[j];
171 }
172
173 for (int j = embed_dim; j < aligned_embed_dim; ++j) {
174 if (proj_out) {
175 proj_out[j] = 0.0f;
176 }
177 residual_out[j] = 0.0f;
178 }
179}
180
182 int token_index,
183 int cache_capacity,
184 int fuse_mlp)
185{
186 if (!p) {
187 return;
188 }
189 if (!p->input || !p->ln1_gamma || !p->ln2_gamma || !p->wq || !p->wk || !p->wv || !p->wo ||
190 !p->w1 || !p->w2 || !p->k || !p->v || !p->residual1 || !p->ln2_out || !p->swiglu_out ||
191 !p->mlp_out || !p->output) {
192 return;
193 }
194 if (!fuse_mlp && !p->fc1_out) {
195 return;
196 }
197 if (token_index < 0 || cache_capacity <= 0 || token_index >= cache_capacity) {
198 return;
199 }
200 if (p->num_heads <= 0 || p->num_kv_heads <= 0 || p->aligned_head_dim <= 0) {
201 return;
202 }
203
204 const int D = p->embed_dim;
205 const int aligned_D = p->aligned_embed_dim;
206 const int H = p->num_heads;
207 const int H_kv = p->num_kv_heads;
208 const int hd = p->head_dim;
209 const int ad = p->aligned_head_dim;
210 const int aligned_intermediate = p->aligned_intermediate_dim;
211
212 /* Decode buffers are single-token; token_index only applies to KV cache. */
213 const size_t token_slot = 0;
214 const float *input_row = p->input + token_slot * (size_t)aligned_D;
215 float *proj_row = NULL;
216 float *residual_row = p->residual1 + token_slot * (size_t)aligned_D;
217 float *ln2_row = p->ln2_out + token_slot * (size_t)aligned_D;
218 float *swiglu_row = p->swiglu_out + token_slot * (size_t)aligned_intermediate;
219 float *mlp_row = p->mlp_out + token_slot * (size_t)aligned_D;
220 float *out_row = p->output + token_slot * (size_t)aligned_D;
221
222 float ln1_rstd_tmp = 0.0f;
223 float ln2_rstd_tmp = 0.0f;
224 float *ln2_rstd = p->ln2_rstd ? (p->ln2_rstd + token_slot) : &ln2_rstd_tmp;
225
226 float ln1_row[aligned_D];
227
228 rmsnorm_forward(input_row,
229 p->ln1_gamma,
230 ln1_row,
231 &ln1_rstd_tmp,
232 /*tokens=*/1,
233 D,
234 aligned_D,
235 p->eps);
236
237 size_t q_elems = (size_t)H * (size_t)ad;
238 size_t kv_elems = (size_t)H_kv * (size_t)ad;
239 float q_token[q_elems];
240 float k_token[kv_elems];
241 float v_token[kv_elems];
242 float attn_token[q_elems];
243
245 p->wq, p->bq,
246 p->wk, p->bk,
247 p->wv, p->bv,
248 q_token, k_token, v_token,
249 aligned_D,
250 H,
251 H_kv,
252 ad);
253
254 if (p->rope_cos && p->rope_sin) {
255 rope_forward_qk(q_token,
256 k_token,
257 p->rope_cos,
258 p->rope_sin,
259 H,
260 H_kv,
261 /*num_tokens=*/1,
262 hd,
263 ad,
264 p->rope_pos_offset);
265 }
266
268 v_token,
269 p->k,
270 p->v,
271 H_kv,
272 token_index,
273 cache_capacity,
274 hd,
275 ad);
276
278 p->k,
279 p->v,
280 attn_token,
281 H,
282 H_kv,
283 /*kv_tokens=*/token_index + 1,
284 cache_capacity,
285 hd,
286 ad);
287
289 p->wo,
290 p->bo,
291 input_row,
292 proj_row,
293 residual_row,
294 D,
295 aligned_D,
296 H,
297 ad);
298
299 rmsnorm_forward(residual_row,
300 p->ln2_gamma,
301 ln2_row,
302 ln2_rstd,
303 /*tokens=*/1,
304 D,
305 aligned_D,
306 p->eps);
307
308 if (fuse_mlp) {
309 // Fully fused MLP avoids writing SwiGLU activations to DRAM (aligned dims
310 // match padded weight layout).
312 p->w1,
313 p->b1,
314 p->w2,
315 p->b2,
316 mlp_row,
317 aligned_D,
318 aligned_intermediate);
319 } else {
320 int up_dim = 2 * aligned_intermediate;
321 float *fc1_row = p->fc1_out + token_slot * (size_t)up_dim;
322
323 ck_mlp_swiglu_forward(ln2_row,
324 p->w1,
325 p->b1,
326 p->w2,
327 p->b2,
328 fc1_row,
329 swiglu_row,
330 mlp_row,
331 /*tokens=*/1,
332 aligned_D,
333 aligned_intermediate);
334 }
335
336 ck_residual_add_token_major(residual_row,
337 mlp_row,
338 out_row,
339 /*tokens=*/1,
340 aligned_D);
341}
342
344 int token_index,
345 int cache_capacity)
346{
348 token_index,
349 cache_capacity,
350 /*fuse_mlp=*/0);
351}
352
354 int token_index,
355 int cache_capacity)
356{
358 token_index,
359 cache_capacity,
360 /*fuse_mlp=*/1);
361}
static void ck_layer_forward_rmsnorm_swiglu_decode_fused_attn_impl(const CKLayerForwardParams *p, int token_index, int cache_capacity, int fuse_mlp)
static float ck_dot_f32(const float *a, const float *b, int len)
void ck_layer_forward_rmsnorm_swiglu_decode_fused_attn_mlp(const CKLayerForwardParams *p, int token_index, int cache_capacity)
static void ck_attention_project_head_major_decode_token_residual(const float *attn_token, const float *wo, const float *bo, const float *residual_in, float *proj_out, float *residual_out, int embed_dim, int aligned_embed_dim, int num_heads, int aligned_head_dim)
void ck_qkv_project_head_major_token(const float *input_row, const float *wq, const float *bq, const float *wk, const float *bk, const float *wv, const float *bv, float *q_token, float *k_token, float *v_token, int aligned_embed_dim, int num_heads, int num_kv_heads, int aligned_head_dim)
void ck_layer_forward_rmsnorm_swiglu_decode_fused_attn(const CKLayerForwardParams *p, int token_index, int cache_capacity)
void ck_attention_project_head_major_decode_token(const float *attn_token, const float *wo, const float *bo, float *out_token, int embed_dim, int aligned_embed_dim, int num_heads, int aligned_head_dim)
void kv_cache_write_head_major(const float *__restrict k_token, const float *__restrict v_token, float *__restrict k_cache, float *__restrict v_cache, int num_kv_heads, int token_index, int cache_capacity, int head_dim, int aligned_head_dim)
void attention_forward_decode_head_major_gqa_regular(const float *q_token, const float *k_cache, const float *v_cache, float *out_token, int num_heads, int num_kv_heads, int kv_tokens, int cache_capacity, int head_dim, int aligned_head_dim)
WARNING: This is NOT true flash attention!
void rmsnorm_forward(const float *input, const float *gamma, float *output, float *rstd_cache, int tokens, int d_model, int aligned_embed_dim, float eps)
void ck_residual_add_token_major(const float *a, const float *b, float *out, int tokens, int aligned_embed_dim)
void rope_forward_qk(float *q, float *k, const float *cos_cache, const float *sin_cache, int num_heads, int num_kv_heads, int num_tokens, int head_dim, int aligned_head_dim, int pos_offset)
void gemm_blocked_serial(const float *A, const float *B, const float *bias, float *C, int M, int N, int K)
void ck_mlp_swiglu_forward(const float *input, const float *w1, const float *b1, const float *w2, const float *b2, float *fc1_out, float *swiglu_out, float *output, int tokens, int aligned_embed_dim, int aligned_intermediate_dim)
void ck_mlp_swiglu_forward_fully_fused_token(const float *input_row, const float *w1, const float *b1, const float *w2, const float *b2, float *output_row, int aligned_embed_dim, int aligned_intermediate_dim)