← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
attention_kernels_sliding.c
Go to the documentation of this file.
1/**
2 * @file attention_kernels_sliding.c
3 * @brief Sliding-window flash attention kernels split from attention_kernels.c
4 */
5
6#include "ckernel_engine.h"
7#include "ck_threadpool.h"
8#include <math.h>
9#include <stdlib.h>
10
11#if defined(__AVX__) || defined(__AVX2__) || defined(__AVX512F__)
12#include <immintrin.h>
13#endif
14
15/* Local head-major index helper: [head][token][dim] with aligned dim stride. */
16static inline size_t qkv_index(int h,
17 int t,
18 int d,
19 int num_tokens,
20 int aligned_head_dim)
21{
22 return ((size_t)h * (size_t)num_tokens + (size_t)t) * (size_t)aligned_head_dim
23 + (size_t)d;
24}
25
26static inline size_t attention_output_index(int h,
27 int t,
28 int num_heads,
29 int num_tokens,
30 int aligned_head_dim,
31 int output_token_major)
32{
33 if (output_token_major) {
34 return ((size_t)t * (size_t)num_heads + (size_t)h)
35 * (size_t)aligned_head_dim;
36 }
37 return qkv_index(h, t, 0, num_tokens, aligned_head_dim);
38}
39
40#if defined(__AVX2__)
41static inline float hsum256_ps_flash(__m256 v) {
42 __m128 hi = _mm256_extractf128_ps(v, 1);
43 __m128 lo = _mm256_castps256_ps128(v);
44 __m128 sum128 = _mm_add_ps(lo, hi);
45 sum128 = _mm_hadd_ps(sum128, sum128);
46 sum128 = _mm_hadd_ps(sum128, sum128);
47 return _mm_cvtss_f32(sum128);
48}
49#endif
50
51#if defined(__AVX__) && !defined(__AVX2__)
52static inline float hsum256_ps_flash_avx(__m256 v) {
53 __m128 hi = _mm256_extractf128_ps(v, 1);
54 __m128 lo = _mm256_castps256_ps128(v);
55 __m128 sum128 = _mm_add_ps(lo, hi);
56 sum128 = _mm_hadd_ps(sum128, sum128);
57 sum128 = _mm_hadd_ps(sum128, sum128);
58 return _mm_cvtss_f32(sum128);
59}
60#endif
61
62// ============================================================================
63// SLIDING-WINDOW ATTENTION - Flash-style with sliding window mask
64// ============================================================================
65//
66// Sliding-window attention: each token attends only to the last W tokens.
67// For token at position i, the valid key range is [max(0, i - W + 1) .. i].
68// This is equivalent to causal attention with a window size limit.
69//
70// Key difference from regular causal attention:
71// - Regular causal: token i attends to [0 .. i] (all previous tokens)
72// - Sliding window: token i attends to [max(0, i - W + 1) .. i] (last W tokens only)
73
74// ============================================================================
75// AVX-512 Sliding-Window Flash Attention
76// ============================================================================
77#if defined(__AVX512F__)
78static void attention_flash_query_sliding_avx512(const float *q_vec,
79 const float *k_head,
80 const float *v_head,
81 int query_pos, // Position of query token (0-indexed)
82 int kv_tokens, // Total KV tokens available
83 int head_dim,
84 int aligned_head_dim,
85 float scale,
86 float *out_vec,
87 int sliding_window) // Window size (0 = no limit)
88{
89 float m = -INFINITY;
90 float s = 0.0f;
91
92 // Compute sliding window bounds
93 int window_start = 0;
94 if (sliding_window > 0) {
95 window_start = query_pos - sliding_window + 1;
96 if (window_start < 0) window_start = 0;
97 }
98
99 // Zero output using SIMD
100 int d = 0;
101 for (; d + 16 <= aligned_head_dim; d += 16) {
102 _mm512_storeu_ps(&out_vec[d], _mm512_setzero_ps());
103 }
104 for (; d < aligned_head_dim; ++d) {
105 out_vec[d] = 0.0f;
106 }
107
108 // Process only tokens in the sliding window [window_start .. min(query_pos, kv_tokens-1)]
109 int effective_kv_end = query_pos < kv_tokens ? query_pos : kv_tokens - 1;
110 for (int j = window_start; j <= effective_kv_end; ++j) {
111 const float *k_vec = k_head + (size_t)j * (size_t)aligned_head_dim;
112 const float *v_vec = v_head + (size_t)j * (size_t)aligned_head_dim;
113
114 // Vectorized dot product Q·K
115 __m512 dot_acc = _mm512_setzero_ps();
116 d = 0;
117 for (; d + 16 <= head_dim; d += 16) {
118 __m512 q_v = _mm512_loadu_ps(&q_vec[d]);
119 __m512 k_v = _mm512_loadu_ps(&k_vec[d]);
120 dot_acc = _mm512_fmadd_ps(q_v, k_v, dot_acc);
121 }
122 float dot = _mm512_reduce_add_ps(dot_acc);
123 for (; d < head_dim; ++d) {
124 dot += q_vec[d] * k_vec[d];
125 }
126 float score = dot * scale;
127
128 if (score > m) {
129 float exp_m = (m == -INFINITY) ? 0.0f : expf(m - score);
130 s *= exp_m;
131
132 __m512 exp_m_vec = _mm512_set1_ps(exp_m);
133 d = 0;
134 for (; d + 16 <= head_dim; d += 16) {
135 __m512 out_v = _mm512_loadu_ps(&out_vec[d]);
136 __m512 v_v = _mm512_loadu_ps(&v_vec[d]);
137 out_v = _mm512_fmadd_ps(out_v, exp_m_vec, v_v);
138 _mm512_storeu_ps(&out_vec[d], out_v);
139 }
140 for (; d < head_dim; ++d) {
141 out_vec[d] = out_vec[d] * exp_m + v_vec[d];
142 }
143
144 s += 1.0f;
145 m = score;
146 } else {
147 float e = expf(score - m);
148 s += e;
149
150 __m512 e_vec = _mm512_set1_ps(e);
151 d = 0;
152 for (; d + 16 <= head_dim; d += 16) {
153 __m512 out_v = _mm512_loadu_ps(&out_vec[d]);
154 __m512 v_v = _mm512_loadu_ps(&v_vec[d]);
155 out_v = _mm512_fmadd_ps(e_vec, v_v, out_v);
156 _mm512_storeu_ps(&out_vec[d], out_v);
157 }
158 for (; d < head_dim; ++d) {
159 out_vec[d] += e * v_vec[d];
160 }
161 }
162 }
163
164 // Normalize: out /= s
165 float inv_s = 1.0f / s;
166 __m512 inv_s_vec = _mm512_set1_ps(inv_s);
167 d = 0;
168 for (; d + 16 <= head_dim; d += 16) {
169 __m512 out_v = _mm512_loadu_ps(&out_vec[d]);
170 _mm512_storeu_ps(&out_vec[d], _mm512_mul_ps(out_v, inv_s_vec));
171 }
172 for (; d < head_dim; ++d) {
173 out_vec[d] *= inv_s;
174 }
175
176 // Zero padding
177 for (d = head_dim; d < aligned_head_dim; ++d) {
178 out_vec[d] = 0.0f;
179 }
180}
181#endif // __AVX512F__
182
183// ============================================================================
184// AVX2 Sliding-Window Flash Attention
185// ============================================================================
186#if defined(__AVX2__)
187static void attention_flash_query_sliding_avx2(const float *q_vec,
188 const float *k_head,
189 const float *v_head,
190 int query_pos,
191 int kv_tokens,
192 int head_dim,
193 int aligned_head_dim,
194 float scale,
195 float *out_vec,
196 int sliding_window)
197{
198 float m = -INFINITY;
199 float s = 0.0f;
200
201 int window_start = 0;
202 if (sliding_window > 0) {
203 window_start = query_pos - sliding_window + 1;
204 if (window_start < 0) window_start = 0;
205 }
206
207 int d = 0;
208 for (; d + 8 <= aligned_head_dim; d += 8) {
209 _mm256_storeu_ps(&out_vec[d], _mm256_setzero_ps());
210 }
211 for (; d < aligned_head_dim; ++d) {
212 out_vec[d] = 0.0f;
213 }
214
215 int effective_kv_end = query_pos < kv_tokens ? query_pos : kv_tokens - 1;
216 for (int j = window_start; j <= effective_kv_end; ++j) {
217 const float *k_vec = k_head + (size_t)j * (size_t)aligned_head_dim;
218 const float *v_vec = v_head + (size_t)j * (size_t)aligned_head_dim;
219
220 __m256 dot_acc = _mm256_setzero_ps();
221 d = 0;
222 for (; d + 8 <= head_dim; d += 8) {
223 __m256 q_v = _mm256_loadu_ps(&q_vec[d]);
224 __m256 k_v = _mm256_loadu_ps(&k_vec[d]);
225 dot_acc = _mm256_fmadd_ps(q_v, k_v, dot_acc);
226 }
227 float dot = hsum256_ps_flash(dot_acc);
228 for (; d < head_dim; ++d) {
229 dot += q_vec[d] * k_vec[d];
230 }
231 float score = dot * scale;
232
233 if (score > m) {
234 float exp_m = (m == -INFINITY) ? 0.0f : expf(m - score);
235 s *= exp_m;
236
237 __m256 exp_m_vec = _mm256_set1_ps(exp_m);
238 d = 0;
239 for (; d + 8 <= head_dim; d += 8) {
240 __m256 out_v = _mm256_loadu_ps(&out_vec[d]);
241 __m256 v_v = _mm256_loadu_ps(&v_vec[d]);
242 out_v = _mm256_fmadd_ps(out_v, exp_m_vec, v_v);
243 _mm256_storeu_ps(&out_vec[d], out_v);
244 }
245 for (; d < head_dim; ++d) {
246 out_vec[d] = out_vec[d] * exp_m + v_vec[d];
247 }
248
249 s += 1.0f;
250 m = score;
251 } else {
252 float e = expf(score - m);
253 s += e;
254
255 __m256 e_vec = _mm256_set1_ps(e);
256 d = 0;
257 for (; d + 8 <= head_dim; d += 8) {
258 __m256 out_v = _mm256_loadu_ps(&out_vec[d]);
259 __m256 v_v = _mm256_loadu_ps(&v_vec[d]);
260 out_v = _mm256_fmadd_ps(e_vec, v_v, out_v);
261 _mm256_storeu_ps(&out_vec[d], out_v);
262 }
263 for (; d < head_dim; ++d) {
264 out_vec[d] += e * v_vec[d];
265 }
266 }
267 }
268
269 float inv_s = 1.0f / s;
270 __m256 inv_s_vec = _mm256_set1_ps(inv_s);
271 d = 0;
272 for (; d + 8 <= head_dim; d += 8) {
273 __m256 out_v = _mm256_loadu_ps(&out_vec[d]);
274 _mm256_storeu_ps(&out_vec[d], _mm256_mul_ps(out_v, inv_s_vec));
275 }
276 for (; d < head_dim; ++d) {
277 out_vec[d] *= inv_s;
278 }
279
280 for (d = head_dim; d < aligned_head_dim; ++d) {
281 out_vec[d] = 0.0f;
282 }
283}
284#endif // __AVX2__
285
286// ============================================================================
287// AVX Sliding-Window Flash Attention (no FMA)
288// ============================================================================
289#if defined(__AVX__) && !defined(__AVX2__)
290static void attention_flash_query_sliding_avx(const float *q_vec,
291 const float *k_head,
292 const float *v_head,
293 int query_pos,
294 int kv_tokens,
295 int head_dim,
296 int aligned_head_dim,
297 float scale,
298 float *out_vec,
299 int sliding_window)
300{
301 float m = -INFINITY;
302 float s = 0.0f;
303
304 int window_start = 0;
305 if (sliding_window > 0) {
306 window_start = query_pos - sliding_window + 1;
307 if (window_start < 0) window_start = 0;
308 }
309
310 int d = 0;
311 for (; d + 8 <= aligned_head_dim; d += 8) {
312 _mm256_storeu_ps(&out_vec[d], _mm256_setzero_ps());
313 }
314 for (; d < aligned_head_dim; ++d) {
315 out_vec[d] = 0.0f;
316 }
317
318 int effective_kv_end = query_pos < kv_tokens ? query_pos : kv_tokens - 1;
319 for (int j = window_start; j <= effective_kv_end; ++j) {
320 const float *k_vec = k_head + (size_t)j * (size_t)aligned_head_dim;
321 const float *v_vec = v_head + (size_t)j * (size_t)aligned_head_dim;
322
323 __m256 dot_acc = _mm256_setzero_ps();
324 d = 0;
325 for (; d + 8 <= head_dim; d += 8) {
326 __m256 q_v = _mm256_loadu_ps(&q_vec[d]);
327 __m256 k_v = _mm256_loadu_ps(&k_vec[d]);
328 dot_acc = _mm256_add_ps(dot_acc, _mm256_mul_ps(q_v, k_v));
329 }
330 float dot = hsum256_ps_flash_avx(dot_acc);
331 for (; d < head_dim; ++d) {
332 dot += q_vec[d] * k_vec[d];
333 }
334 float score = dot * scale;
335
336 if (score > m) {
337 float exp_m = (m == -INFINITY) ? 0.0f : expf(m - score);
338 s *= exp_m;
339
340 __m256 exp_m_vec = _mm256_set1_ps(exp_m);
341 d = 0;
342 for (; d + 8 <= head_dim; d += 8) {
343 __m256 out_v = _mm256_loadu_ps(&out_vec[d]);
344 __m256 v_v = _mm256_loadu_ps(&v_vec[d]);
345 out_v = _mm256_add_ps(_mm256_mul_ps(out_v, exp_m_vec), v_v);
346 _mm256_storeu_ps(&out_vec[d], out_v);
347 }
348 for (; d < head_dim; ++d) {
349 out_vec[d] = out_vec[d] * exp_m + v_vec[d];
350 }
351
352 s += 1.0f;
353 m = score;
354 } else {
355 float e = expf(score - m);
356 s += e;
357
358 __m256 e_vec = _mm256_set1_ps(e);
359 d = 0;
360 for (; d + 8 <= head_dim; d += 8) {
361 __m256 out_v = _mm256_loadu_ps(&out_vec[d]);
362 __m256 v_v = _mm256_loadu_ps(&v_vec[d]);
363 out_v = _mm256_add_ps(out_v, _mm256_mul_ps(e_vec, v_v));
364 _mm256_storeu_ps(&out_vec[d], out_v);
365 }
366 for (; d < head_dim; ++d) {
367 out_vec[d] += e * v_vec[d];
368 }
369 }
370 }
371
372 float inv_s = 1.0f / s;
373 __m256 inv_s_vec = _mm256_set1_ps(inv_s);
374 d = 0;
375 for (; d + 8 <= head_dim; d += 8) {
376 __m256 out_v = _mm256_loadu_ps(&out_vec[d]);
377 _mm256_storeu_ps(&out_vec[d], _mm256_mul_ps(out_v, inv_s_vec));
378 }
379 for (; d < head_dim; ++d) {
380 out_vec[d] *= inv_s;
381 }
382
383 for (d = head_dim; d < aligned_head_dim; ++d) {
384 out_vec[d] = 0.0f;
385 }
386}
387#endif // __AVX__ && !__AVX2__
388
389// ============================================================================
390// Scalar Sliding-Window Flash Attention Fallback
391// ============================================================================
392static void attention_flash_query_sliding(const float *q_vec,
393 const float *k_head,
394 const float *v_head,
395 int query_pos,
396 int kv_tokens,
397 int head_dim,
398 int aligned_head_dim,
399 float scale,
400 float *out_vec,
401 int sliding_window)
402{
403 float m = -INFINITY;
404 float s = 0.0f;
405
406 int window_start = 0;
407 if (sliding_window > 0) {
408 window_start = query_pos - sliding_window + 1;
409 if (window_start < 0) window_start = 0;
410 }
411
412 for (int d = 0; d < head_dim; ++d) {
413 out_vec[d] = 0.0f;
414 }
415
416 int effective_kv_end = query_pos < kv_tokens ? query_pos : kv_tokens - 1;
417 for (int j = window_start; j <= effective_kv_end; ++j) {
418 const float *k_vec = k_head + (size_t)j * (size_t)aligned_head_dim;
419 const float *v_vec = v_head + (size_t)j * (size_t)aligned_head_dim;
420
421 float dot = 0.0f;
422 for (int d = 0; d < head_dim; ++d) {
423 dot += q_vec[d] * k_vec[d];
424 }
425 float score = dot * scale;
426
427 if (score > m) {
428 float exp_m = (m == -INFINITY) ? 0.0f : expf(m - score);
429 s *= exp_m;
430 for (int d = 0; d < head_dim; ++d) {
431 out_vec[d] *= exp_m;
432 }
433 s += 1.0f;
434 for (int d = 0; d < head_dim; ++d) {
435 out_vec[d] += v_vec[d];
436 }
437 m = score;
438 } else {
439 float e = expf(score - m);
440 s += e;
441 for (int d = 0; d < head_dim; ++d) {
442 out_vec[d] += e * v_vec[d];
443 }
444 }
445 }
446
447 float inv_s = 1.0f / s;
448 for (int d = 0; d < head_dim; ++d) {
449 out_vec[d] *= inv_s;
450 }
451 for (int d = head_dim; d < aligned_head_dim; ++d) {
452 out_vec[d] = 0.0f;
453 }
454}
455
456typedef struct {
457 const float *q;
458 const float *k;
459 const float *v;
460 float *output;
461 int num_heads;
462 int num_kv_heads;
463 int num_tokens;
464 int head_dim;
465 int aligned_head_dim;
466 int kv_stride_tokens;
467 int sliding_window;
468 int output_token_major;
469 float scale;
470} ck_sliding_attention_args_t;
471
472static int ck_env_int_default(const char *name, int fallback)
473{
474 const char *v = getenv(name);
475 if (!v || !v[0]) return fallback;
476 char *end = NULL;
477 long parsed = strtol(v, &end, 10);
478 if (end == v || (end && *end != '\0')) return fallback;
479 if (parsed < 0) parsed = 0;
480 if (parsed > 1 << 20) parsed = 1 << 20;
481 return (int)parsed;
482}
483
485{
486 const char *v = getenv("CK_DISABLE_SLIDING_ATTN_PARALLEL");
487 return v && v[0] && v[0] != '0';
488}
489
490static int ck_sliding_attention_pick_threads(ck_threadpool_t *pool,
491 int total_jobs,
492 int num_tokens,
493 int head_dim)
494{
495 if (!pool || total_jobs <= 0) return 1;
497 if (ck_threadpool_thread_id(pool) > 0) return 1;
498
499 const int min_tokens = ck_env_int_default("CK_SLIDING_ATTN_PARALLEL_MIN_TOKENS", 128);
500 if (num_tokens < min_tokens || head_dim < 8) return 1;
501
502 int active = ck_threadpool_n_threads(pool);
503 const int cap = ck_env_int_default("CK_SLIDING_ATTN_THREAD_CAP", active);
504 if (cap > 0 && active > cap) active = cap;
505 if (active > total_jobs) active = total_jobs;
506 return active > 1 ? active : 1;
507}
508
509static void ck_sliding_attention_compute_one(const ck_sliding_attention_args_t *a,
510 int job)
511{
512 if (!a || job < 0) return;
513
514 const int T = a->num_tokens;
515 const size_t kv_head_stride = (size_t)a->kv_stride_tokens * (size_t)a->aligned_head_dim;
516
517#if defined(__AVX512F__)
518 #define CK_SLIDING_FLASH_IMPL attention_flash_query_sliding_avx512
519#elif defined(__AVX2__)
520 #define CK_SLIDING_FLASH_IMPL attention_flash_query_sliding_avx2
521#elif defined(__AVX__)
522 #define CK_SLIDING_FLASH_IMPL attention_flash_query_sliding_avx
523#else
524 #define CK_SLIDING_FLASH_IMPL attention_flash_query_sliding
525#endif
526
527 const int h = job / T;
528 const int i = job - h * T;
529 const int kv_head = (int)((long long)h * (long long)a->num_kv_heads /
530 (long long)a->num_heads);
531 const float *k_head = a->k + (size_t)kv_head * kv_head_stride;
532 const float *v_head = a->v + (size_t)kv_head * kv_head_stride;
533 const float *q_vec = a->q + qkv_index(h, i, 0, T, a->aligned_head_dim);
534 float *out_vec = a->output + attention_output_index(
535 h, i, a->num_heads, T, a->aligned_head_dim, a->output_token_major);
536
537 CK_SLIDING_FLASH_IMPL(q_vec, k_head, v_head,
538 /*query_pos=*/i,
539 /*kv_tokens=*/T,
540 a->head_dim, a->aligned_head_dim,
541 a->scale, out_vec,
542 a->sliding_window);
543
544#undef CK_SLIDING_FLASH_IMPL
545}
546
547static void ck_sliding_attention_work_fn(int ith, int nth, void *args)
548{
549 const ck_sliding_attention_args_t *a = (const ck_sliding_attention_args_t *)args;
550 if (!a || ith < 0 || nth <= 0 || ith >= nth) return;
551 const int total_jobs = a->num_heads * a->num_tokens;
552 for (int job = ith; job < total_jobs; job += nth) {
554 }
555}
556
557/**
558 * Flash attention forward with sliding window (prefill)
559 * @test test_attention.py::TestAttentionForward::test_sliding_window_prefill
560 *
561 * Sliding-window attention for prefill: each token attends to the last W tokens.
562 * When sliding_window <= 0, behaves like regular causal attention.
563 *
564 * After changes: make test
565 */
567 const float *q,
568 const float *k,
569 const float *v,
570 float *output,
571 int num_heads,
572 int num_kv_heads,
573 int num_tokens,
574 int head_dim,
575 int aligned_head_dim,
576 int kv_stride_tokens,
577 int sliding_window)
578{
579 if (!q || !k || !v || !output) {
580 return;
581 }
582 if (num_heads <= 0 || num_kv_heads <= 0 || num_tokens <= 0) {
583 return;
584 }
585 if (kv_stride_tokens < num_tokens) {
586 return;
587 }
588
589 /* Debug escape hatch:
590 * For Gemma bring-up, force the proven non-sliding flash kernel to isolate
591 * whether divergence is caused by sliding-window implementation details.
592 */
593 if (getenv("CK_FORCE_NONSLIDING_ATTN")) {
595 q, k, v, output, num_heads, num_kv_heads, num_tokens,
596 head_dim, aligned_head_dim, kv_stride_tokens
597 );
598 return;
599 }
600
601 const float scale = 1.0f / sqrtf((float)head_dim);
602 const int T = num_tokens;
603 const size_t kv_head_stride = (size_t)kv_stride_tokens * (size_t)aligned_head_dim;
604
605 const int total_jobs = num_heads * T;
606 ck_threadpool_t *pool = ck_threadpool_global();
607 const int active = ck_sliding_attention_pick_threads(pool, total_jobs, T, head_dim);
608 if (pool && active > 1) {
609 ck_sliding_attention_args_t args = {
610 .q = q,
611 .k = k,
612 .v = v,
613 .output = output,
614 .num_heads = num_heads,
615 .num_kv_heads = num_kv_heads,
616 .num_tokens = T,
617 .head_dim = head_dim,
618 .aligned_head_dim = aligned_head_dim,
619 .kv_stride_tokens = kv_stride_tokens,
620 .sliding_window = sliding_window,
621 .output_token_major = 0,
622 .scale = scale,
623 };
625 return;
626 }
627
628#if defined(__AVX512F__)
629 #define SLIDING_FLASH_IMPL attention_flash_query_sliding_avx512
630#elif defined(__AVX2__)
631 #define SLIDING_FLASH_IMPL attention_flash_query_sliding_avx2
632#elif defined(__AVX__)
633 #define SLIDING_FLASH_IMPL attention_flash_query_sliding_avx
634#else
635 #define SLIDING_FLASH_IMPL attention_flash_query_sliding
636#endif
637
638 for (int h = 0; h < num_heads; ++h) {
639 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
640 const float *k_head = k + (size_t)kv_head * kv_head_stride;
641 const float *v_head = v + (size_t)kv_head * kv_head_stride;
642
643 for (int i = 0; i < T; ++i) {
644 const float *q_vec = q + qkv_index(h, i, 0, T, aligned_head_dim);
645 float *out_vec = output + attention_output_index(
646 h, i, num_heads, T, aligned_head_dim, 0);
647 SLIDING_FLASH_IMPL(q_vec, k_head, v_head,
648 /*query_pos=*/i,
649 /*kv_tokens=*/T,
650 head_dim, aligned_head_dim,
651 scale, out_vec,
652 sliding_window);
653 }
654 }
655
656#undef SLIDING_FLASH_IMPL
657}
658
659/**
660 * Flash attention decode with sliding window
661 * @test test_attention.py::TestAttentionForward::test_sliding_window_decode
662 *
663 * Single query token attends to the last W tokens in the KV cache.
664 * For decode: effective_kv_tokens = min(kv_tokens, sliding_window)
665 *
666 * After changes: make test
667 */
669 const float *q,
670 const float *k,
671 const float *v,
672 float *output,
673 int num_heads,
674 int num_kv_heads,
675 int num_tokens,
676 int head_dim,
677 int aligned_head_dim,
678 int kv_stride_tokens,
679 int sliding_window,
680 int output_token_major)
681{
682 if (!q || !k || !v || !output) {
683 return;
684 }
685 if (num_heads <= 0 || num_kv_heads <= 0 || num_tokens <= 0) {
686 return;
687 }
688 if (kv_stride_tokens < num_tokens) {
689 return;
690 }
691
692 if (getenv("CK_FORCE_NONSLIDING_ATTN")) {
693 if (output_token_major) {
695 q, k, v, output, num_heads, num_kv_heads, num_tokens,
696 head_dim, aligned_head_dim, kv_stride_tokens);
697 } else {
699 q, k, v, output, num_heads, num_kv_heads, num_tokens,
700 head_dim, aligned_head_dim, kv_stride_tokens);
701 }
702 return;
703 }
704
705 const float scale = 1.0f;
706 const int T = num_tokens;
707 const size_t kv_head_stride = (size_t)kv_stride_tokens * (size_t)aligned_head_dim;
708
709 const int total_jobs = num_heads * T;
710 ck_threadpool_t *pool = ck_threadpool_global();
711 const int active = ck_sliding_attention_pick_threads(pool, total_jobs, T, head_dim);
712 if (pool && active > 1) {
713 ck_sliding_attention_args_t args = {
714 .q = q,
715 .k = k,
716 .v = v,
717 .output = output,
718 .num_heads = num_heads,
719 .num_kv_heads = num_kv_heads,
720 .num_tokens = T,
721 .head_dim = head_dim,
722 .aligned_head_dim = aligned_head_dim,
723 .kv_stride_tokens = kv_stride_tokens,
724 .sliding_window = sliding_window,
725 .output_token_major = output_token_major,
726 .scale = scale,
727 };
729 return;
730 }
731
732#if defined(__AVX512F__)
733 #define SLIDING_FLASH_IMPL_GEMMA4 attention_flash_query_sliding_avx512
734#elif defined(__AVX2__)
735 #define SLIDING_FLASH_IMPL_GEMMA4 attention_flash_query_sliding_avx2
736#elif defined(__AVX__)
737 #define SLIDING_FLASH_IMPL_GEMMA4 attention_flash_query_sliding_avx
738#else
739 #define SLIDING_FLASH_IMPL_GEMMA4 attention_flash_query_sliding
740#endif
741
742 for (int h = 0; h < num_heads; ++h) {
743 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
744 const float *k_head = k + (size_t)kv_head * kv_head_stride;
745 const float *v_head = v + (size_t)kv_head * kv_head_stride;
746
747 for (int i = 0; i < T; ++i) {
748 const float *q_vec = q + qkv_index(h, i, 0, T, aligned_head_dim);
749 float *out_vec = output + attention_output_index(
750 h, i, num_heads, T, aligned_head_dim, output_token_major);
751 SLIDING_FLASH_IMPL_GEMMA4(q_vec, k_head, v_head,
752 /*query_pos=*/i,
753 /*kv_tokens=*/T,
754 head_dim, aligned_head_dim,
755 scale, out_vec,
756 sliding_window);
757 }
758 }
759
760#undef SLIDING_FLASH_IMPL_GEMMA4
761}
762
764 const float *q,
765 const float *k,
766 const float *v,
767 float *output,
768 int num_heads,
769 int num_kv_heads,
770 int num_tokens,
771 int head_dim,
772 int aligned_head_dim,
773 int kv_stride_tokens,
774 int sliding_window)
775{
777 q, k, v, output, num_heads, num_kv_heads, num_tokens, head_dim,
778 aligned_head_dim, kv_stride_tokens, sliding_window,
779 /*output_token_major=*/0);
780}
781
783 const float *q,
784 const float *k,
785 const float *v,
786 float *output,
787 int num_heads,
788 int num_kv_heads,
789 int num_tokens,
790 int head_dim,
791 int aligned_head_dim,
792 int kv_stride_tokens,
793 int sliding_window)
794{
796 q, k, v, output, num_heads, num_kv_heads, num_tokens, head_dim,
797 aligned_head_dim, kv_stride_tokens, sliding_window,
798 /*output_token_major=*/1);
799}
800
802 const float *q,
803 float *output,
804 int num_heads,
805 int num_tokens,
806 int head_dim,
807 int aligned_head_dim,
808 int kv_stride_tokens,
809 int sliding_window)
810{
812 q, q, q, output, num_heads, num_heads, num_tokens,
813 head_dim, aligned_head_dim, kv_stride_tokens, sliding_window
814 );
815}
816
818 const float *q_token,
819 const float *k_cache,
820 const float *v_cache,
821 float *out_token,
822 int num_heads,
823 int num_kv_heads,
824 int kv_tokens,
825 int cache_capacity,
826 int head_dim,
827 int aligned_head_dim,
828 int sliding_window)
829{
830 if (!q_token || !k_cache || !v_cache || !out_token) {
831 return;
832 }
833 if (num_heads <= 0 || num_kv_heads <= 0 || cache_capacity <= 0) {
834 return;
835 }
836 if (kv_tokens <= 0 || kv_tokens > cache_capacity || head_dim <= 0 || aligned_head_dim <= 0) {
837 return;
838 }
839
840 /* Debug escape hatch:
841 * Route decode through non-sliding flash attention when requested to
842 * quickly A/B check sliding-window kernel correctness.
843 */
844 if (getenv("CK_FORCE_NONSLIDING_ATTN")) {
846 q_token, k_cache, v_cache, out_token,
847 num_heads, num_kv_heads, kv_tokens, cache_capacity,
848 head_dim, aligned_head_dim
849 );
850 return;
851 }
852
853 const float scale = 1.0f / sqrtf((float)head_dim);
854 const size_t head_stride = (size_t)cache_capacity * (size_t)aligned_head_dim;
855
856 // Compute effective KV tokens based on sliding window
857 int effective_kv_tokens = kv_tokens;
858 if (sliding_window > 0 && sliding_window < kv_tokens) {
859 effective_kv_tokens = sliding_window;
860 }
861
862 // Guard against empty window (shouldn't happen with kv_tokens >= 1)
863 if (effective_kv_tokens <= 0) {
864 return;
865 }
866
867 // Offset to start reading from the last effective_kv_tokens entries
868 int kv_start_offset = kv_tokens - effective_kv_tokens;
869
870#if defined(__AVX512F__)
871 #define SLIDING_DECODE_IMPL attention_flash_query_sliding_avx512
872#elif defined(__AVX2__)
873 #define SLIDING_DECODE_IMPL attention_flash_query_sliding_avx2
874#elif defined(__AVX__)
875 #define SLIDING_DECODE_IMPL attention_flash_query_sliding_avx
876#else
877 #define SLIDING_DECODE_IMPL attention_flash_query_sliding
878#endif
879
880 for (int h = 0; h < num_heads; ++h) {
881 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
882 const float *q_head = q_token + (size_t)h * (size_t)aligned_head_dim;
883 // Offset K/V pointer to start from the first token in the sliding window
884 const float *k_head = k_cache + (size_t)kv_head * head_stride
885 + (size_t)kv_start_offset * (size_t)aligned_head_dim;
886 const float *v_head = v_cache + (size_t)kv_head * head_stride
887 + (size_t)kv_start_offset * (size_t)aligned_head_dim;
888 float *out_head = out_token + (size_t)h * (size_t)aligned_head_dim;
889
890 // Use query_pos relative to the windowed KV (last token = effective_kv_tokens - 1)
891 // sliding_window = 0 since we've already windowed via K/V pointer offset
892 SLIDING_DECODE_IMPL(q_head, k_head, v_head,
893 /*query_pos=*/effective_kv_tokens - 1,
894 /*kv_tokens=*/effective_kv_tokens,
895 head_dim, aligned_head_dim,
896 scale, out_head,
897 /*sliding_window=*/0);
898 }
899
900#undef SLIDING_DECODE_IMPL
901}
902
904 const float *q_token,
905 const float *k_cache,
906 const float *v_cache,
907 float *out_token,
908 int num_heads,
909 int num_kv_heads,
910 int kv_tokens,
911 int cache_capacity,
912 int head_dim,
913 int aligned_head_dim,
914 int sliding_window)
915{
916 if (!q_token || !k_cache || !v_cache || !out_token) {
917 return;
918 }
919 if (num_heads <= 0 || num_kv_heads <= 0 || cache_capacity <= 0) {
920 return;
921 }
922 if (kv_tokens <= 0 || kv_tokens > cache_capacity || head_dim <= 0 || aligned_head_dim <= 0) {
923 return;
924 }
925
926 if (getenv("CK_FORCE_NONSLIDING_ATTN")) {
928 q_token, k_cache, v_cache, out_token,
929 num_heads, num_kv_heads, kv_tokens, cache_capacity,
930 head_dim, aligned_head_dim
931 );
932 return;
933 }
934
935 const float scale = 1.0f;
936 const size_t head_stride = (size_t)cache_capacity * (size_t)aligned_head_dim;
937 int effective_kv_tokens = kv_tokens;
938 if (sliding_window > 0 && sliding_window < kv_tokens) {
939 effective_kv_tokens = sliding_window;
940 }
941 if (effective_kv_tokens <= 0) {
942 return;
943 }
944 int kv_start_offset = kv_tokens - effective_kv_tokens;
945
946#if defined(__AVX512F__)
947 #define SLIDING_DECODE_IMPL_GEMMA4 attention_flash_query_sliding_avx512
948#elif defined(__AVX2__)
949 #define SLIDING_DECODE_IMPL_GEMMA4 attention_flash_query_sliding_avx2
950#elif defined(__AVX__)
951 #define SLIDING_DECODE_IMPL_GEMMA4 attention_flash_query_sliding_avx
952#else
953 #define SLIDING_DECODE_IMPL_GEMMA4 attention_flash_query_sliding
954#endif
955
956 for (int h = 0; h < num_heads; ++h) {
957 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
958 const float *q_head = q_token + (size_t)h * (size_t)aligned_head_dim;
959 const float *k_head = k_cache + (size_t)kv_head * head_stride
960 + (size_t)kv_start_offset * (size_t)aligned_head_dim;
961 const float *v_head = v_cache + (size_t)kv_head * head_stride
962 + (size_t)kv_start_offset * (size_t)aligned_head_dim;
963 float *out_head = out_token + (size_t)h * (size_t)aligned_head_dim;
964
965 SLIDING_DECODE_IMPL_GEMMA4(q_head, k_head, v_head,
966 /*query_pos=*/effective_kv_tokens - 1,
967 /*kv_tokens=*/effective_kv_tokens,
968 head_dim, aligned_head_dim,
969 scale, out_head,
970 /*sliding_window=*/0);
971 }
972
973#undef SLIDING_DECODE_IMPL_GEMMA4
974}
975
977 const float *q_token,
978 const float *k_cache,
979 const float *v_cache,
980 float *out_token,
981 int num_heads,
982 int kv_tokens,
983 int cache_capacity,
984 int head_dim,
985 int aligned_head_dim,
986 int sliding_window)
987{
989 q_token, k_cache, v_cache, out_token, num_heads, num_heads,
990 kv_tokens, cache_capacity, head_dim, aligned_head_dim, sliding_window
991 );
992}
static size_t qkv_index(int h, int t, int d, int num_tokens, int aligned_head_dim)
void attention_forward_causal_head_major_gqa_flash_strided_sliding_gemma4(const float *q, const float *k, const float *v, float *output, int num_heads, int num_kv_heads, int num_tokens, int head_dim, int aligned_head_dim, int kv_stride_tokens, int sliding_window)
static int ck_env_int_default(const char *name, int fallback)
void attention_forward_decode_head_major_gqa_flash_sliding_gemma4(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, int sliding_window)
static int ck_sliding_attention_pick_threads(ck_threadpool_t *pool, int total_jobs, int num_tokens, int head_dim)
void attention_forward_causal_head_major_gqa_flash_strided_sliding(const float *q, const float *k, const float *v, float *output, int num_heads, int num_kv_heads, int num_tokens, int head_dim, int aligned_head_dim, int kv_stride_tokens, int sliding_window)
static int ck_sliding_attention_parallel_disabled(void)
void attention_forward_decode_head_major_gqa_flash_sliding(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, int sliding_window)
void attention_forward_causal_head_major_gqa_flash_strided_sliding_gemma4_token_output(const float *q, const float *k, const float *v, float *output, int num_heads, int num_kv_heads, int num_tokens, int head_dim, int aligned_head_dim, int kv_stride_tokens, int sliding_window)
#define CK_SLIDING_FLASH_IMPL
#define SLIDING_DECODE_IMPL_GEMMA4
#define SLIDING_DECODE_IMPL
static void attention_flash_query_sliding(const float *q_vec, const float *k_head, const float *v_head, int query_pos, int kv_tokens, int head_dim, int aligned_head_dim, float scale, float *out_vec, int sliding_window)
static void ck_sliding_attention_compute_one(const ck_sliding_attention_args_t *a, int job)
void attention_forward_decode_head_major_shared_kv_sliding_gemma4(const float *q_token, const float *k_cache, const float *v_cache, float *out_token, int num_heads, int kv_tokens, int cache_capacity, int head_dim, int aligned_head_dim, int sliding_window)
#define SLIDING_FLASH_IMPL_GEMMA4
#define SLIDING_FLASH_IMPL
void attention_forward_causal_head_major_shared_kv_sliding_gemma4(const float *q, float *output, int num_heads, int num_tokens, int head_dim, int aligned_head_dim, int kv_stride_tokens, int sliding_window)
static size_t attention_output_index(int h, int t, int num_heads, int num_tokens, int aligned_head_dim, int output_token_major)
static void attention_forward_causal_head_major_gqa_flash_strided_sliding_gemma4_impl(const float *q, const float *k, const float *v, float *output, int num_heads, int num_kv_heads, int num_tokens, int head_dim, int aligned_head_dim, int kv_stride_tokens, int sliding_window, int output_token_major)
static void ck_sliding_attention_work_fn(int ith, int nth, void *args)
Persistent pthread thread pool for CK-Engine inference.
void ck_threadpool_dispatch_n(ck_threadpool_t *pool, int active_threads, ck_work_fn_t fn, void *args)
ck_threadpool_t * ck_threadpool_global(void)
int ck_threadpool_thread_id(const ck_threadpool_t *pool)
int ck_threadpool_n_threads(const ck_threadpool_t *pool)
void attention_forward_causal_head_major_gqa_flash_strided(const float *q, const float *k, const float *v, float *output, int num_heads, int num_kv_heads, int num_tokens, int head_dim, int aligned_head_dim, int kv_stride_tokens)
void attention_forward_decode_head_major_gqa_flash_gemma4(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)
void attention_forward_causal_head_major_gqa_flash_strided_gemma4(const float *q, const float *k, const float *v, float *output, int num_heads, int num_kv_heads, int num_tokens, int head_dim, int aligned_head_dim, int kv_stride_tokens)
void attention_forward_decode_head_major_gqa_flash(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)
void attention_forward_causal_head_major_gqa_flash_strided_gemma4_token_output(const float *q, const float *k, const float *v, float *output, int num_heads, int num_kv_heads, int num_tokens, int head_dim, int aligned_head_dim, int kv_stride_tokens)
int32_t float * score
Definition tokenizer.h:328
uint32_t end
Definition utf8.c:215