← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
attention_kernels.c
Go to the documentation of this file.
1/**
2 * @file attention_kernels.c
3 * @brief Attention score/softmax/output kernels with SIMD (SSE/AVX/AVX512)
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 * Attention: softmax(Q @ K^T / sqrt(d)) @ V
15 * Supports GQA (grouped-query attention) with head broadcasting.
16 */
17
18#ifndef CK_ENABLE_LLAMA_CPP_PARITY
19#define CK_ENABLE_LLAMA_CPP_PARITY 0
20#endif
21
22#include "bf16_utils.h"
24#include "ckernel_engine.h"
25#include "ck_threadpool.h"
26#if CK_ENABLE_LLAMA_CPP_PARITY
27#include <ggml.h>
28#endif
29#include <dlfcn.h>
30#include <limits.h>
31#ifndef RTLD_DEFAULT
32#define RTLD_DEFAULT ((void *)0)
33#endif
34#include <math.h>
35#include <float.h>
36#include <pthread.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include "ck_speed_profiles.h"
40#include <string.h>
41#if defined(__AVX__) || defined(__AVX2__) || defined(__AVX512F__) || defined(__SSE2__)
42#include <immintrin.h>
43#endif
44
45typedef struct {
46 const float *query;
47 const float *key;
48 const float *value;
49 float *output;
50 float *score_scratch;
51 float *key_transpose_scratch;
52 int num_heads;
53 int query_tokens;
54 int key_tokens;
55 int head_dim;
56 float scale;
57} ck_attention_query_key_f32_args_t;
58
59static void ck_attention_query_key_f32_transpose_work(int ith, int nth, void *opaque)
60{
61 ck_attention_query_key_f32_args_t *args =
62 (ck_attention_query_key_f32_args_t *)opaque;
63 const int rows = args->num_heads * args->head_dim;
64 for (int row = ith; row < rows; row += nth) {
65 const int head = row / args->head_dim;
66 const int dim = row % args->head_dim;
67 const float *key_head = args->key +
68 (size_t)head * args->key_tokens * args->head_dim;
69 float *packed = args->key_transpose_scratch +
70 (size_t)row * args->key_tokens;
71 for (int key_token = 0; key_token < args->key_tokens; ++key_token) {
72 packed[key_token] =
73 key_head[(size_t)key_token * args->head_dim + dim];
74 }
75 }
76}
77
78static void ck_attention_query_key_f32_work(int ith, int nth, void *opaque)
79{
80 ck_attention_query_key_f32_args_t *args =
81 (ck_attention_query_key_f32_args_t *)opaque;
82 for (int q_token = ith; q_token < args->query_tokens; q_token += nth) {
83 float *scores = args->score_scratch +
84 (size_t)q_token * args->key_tokens;
85 for (int head = 0; head < args->num_heads; ++head) {
86 const float *q_head = args->query +
87 (size_t)head * args->query_tokens * args->head_dim;
88 const float *k_head = args->key +
89 (size_t)head * args->key_tokens * args->head_dim;
90 const float *v_head = args->value +
91 (size_t)head * args->key_tokens * args->head_dim;
92 float *out_head = args->output +
93 (size_t)head * args->query_tokens * args->head_dim;
94 const float *q_row = q_head + (size_t)q_token * args->head_dim;
95 float maximum = -FLT_MAX;
96 int k_token = 0;
97#if defined(__AVX2__) && defined(__FMA__)
98 for (; args->key_transpose_scratch != NULL &&
99 k_token + 7 < args->key_tokens; k_token += 8) {
100 __m256 dots = _mm256_setzero_ps();
101 for (int dim = 0; dim < args->head_dim; ++dim) {
102 const float *packed = args->key_transpose_scratch +
103 ((size_t)head * args->head_dim + dim) *
104 args->key_tokens + k_token;
105 dots = _mm256_fmadd_ps(
106 _mm256_set1_ps(q_row[dim]),
107 _mm256_loadu_ps(packed), dots);
108 }
109 float dot_values[8];
110 _mm256_storeu_ps(dot_values, dots);
111 for (int lane = 0; lane < 8; ++lane) {
112 const float score = dot_values[lane] * args->scale;
113 scores[k_token + lane] = score;
114 maximum = fmaxf(maximum, score);
115 }
116 }
117#endif
118 for (; k_token < args->key_tokens; ++k_token) {
119 const float *k_row = k_head +
120 (size_t)k_token * args->head_dim;
121 float dot = 0.0f;
122 for (int dim = 0; dim < args->head_dim; ++dim) {
123 dot = fmaf(q_row[dim], k_row[dim], dot);
124 }
125 const float score = dot * args->scale;
126 scores[k_token] = score;
127 maximum = fmaxf(maximum, score);
128 }
129 double denominator = 0.0;
130 for (int k_token = 0; k_token < args->key_tokens; ++k_token) {
131 const float probability = expf(scores[k_token] - maximum);
132 scores[k_token] = probability;
133 denominator += (double)probability;
134 }
135 const float inverse = denominator > 0.0 ? (float)(1.0 / denominator) : 0.0f;
136 float *out_row = out_head +
137 (size_t)q_token * args->head_dim;
138 for (int dim = 0; dim < args->head_dim; ++dim) {
139 out_row[dim] = 0.0f;
140 }
141 for (int k_token = 0; k_token < args->key_tokens; ++k_token) {
142 const float probability = scores[k_token] * inverse;
143 const float *v_row = v_head +
144 (size_t)k_token * args->head_dim;
145 for (int dim = 0; dim < args->head_dim; ++dim) {
146 out_row[dim] = fmaf(probability, v_row[dim], out_row[dim]);
147 }
148 }
149 }
150 }
151}
152
154 const float *query,
155 const float *key,
156 const float *value,
157 float *output,
158 float *score_scratch,
159 float *key_transpose_scratch,
160 int num_heads,
161 int query_tokens,
162 int key_tokens,
163 int head_dim,
164 float scale)
165{
166 if (query == NULL || key == NULL || value == NULL || output == NULL ||
167 score_scratch == NULL) {
168 return -1;
169 }
170 if (num_heads <= 0 || query_tokens <= 0 || key_tokens <= 0 ||
171 head_dim <= 0 || !isfinite(scale)) {
172 return -2;
173 }
174 ck_attention_query_key_f32_args_t args = {
175 .query = query,
176 .key = key,
177 .value = value,
178 .output = output,
179 .score_scratch = score_scratch,
180 .key_transpose_scratch = key_transpose_scratch,
181 .num_heads = num_heads,
182 .query_tokens = query_tokens,
183 .key_tokens = key_tokens,
184 .head_dim = head_dim,
185 .scale = scale,
186 };
187 ck_threadpool_t *pool = ck_threadpool_global();
188 int active = pool ? ck_threadpool_n_threads(pool) : 1;
189 if (key_transpose_scratch != NULL) {
190 int transpose_active = active;
191 const int transpose_rows = num_heads * head_dim;
192 if (transpose_active > transpose_rows) transpose_active = transpose_rows;
193 if (pool && transpose_active > 1) {
194 ck_threadpool_dispatch_n(pool, transpose_active,
196 } else {
198 }
199 }
200 if (active > query_tokens) active = query_tokens;
201 if (pool && active > 1) {
203 pool, active, ck_attention_query_key_f32_work, &args);
204 } else {
206 }
207 return 0;
208}
209
211 const float *query,
212 const float *key,
213 const float *value,
214 float *output,
215 float *score_scratch,
216 int num_heads,
217 int query_tokens,
218 int key_tokens,
219 int head_dim,
220 float scale)
221{
223 query, key, value, output, score_scratch, NULL, num_heads,
224 query_tokens, key_tokens, head_dim, scale);
225}
226
227typedef struct {
228 const float *query;
229 const float *key;
230 const float *value;
231 float *output;
232 float *score_scratch;
233 int num_heads;
234 int query_tokens;
235 int key_tokens;
236 int head_dim;
237 float scale;
238} ck_attention_query_key_f32_head_args_t;
239
240static void ck_attention_query_key_f32_head_work(int ith, int nth, void *opaque)
241{
242 ck_attention_query_key_f32_head_args_t *args =
243 (ck_attention_query_key_f32_head_args_t *)opaque;
244 const size_t query_stride =
245 (size_t)args->query_tokens * (size_t)args->head_dim;
246 const size_t kv_stride =
247 (size_t)args->key_tokens * (size_t)args->head_dim;
248 for (int head = ith; head < args->num_heads; head += nth) {
250 args->query + (size_t)head * query_stride,
251 args->key + (size_t)head * kv_stride,
252 args->value + (size_t)head * kv_stride,
253 args->output + (size_t)head * query_stride,
254 args->score_scratch + (size_t)head * args->key_tokens,
255 NULL, 1, args->query_tokens, args->key_tokens,
256 args->head_dim, args->scale);
257 }
258}
259
261 const float *query,
262 const float *key,
263 const float *value,
264 float *output,
265 float *score_scratch,
266 int num_heads,
267 int query_tokens,
268 int key_tokens,
269 int head_dim,
270 float scale)
271{
272 if (query == NULL || key == NULL || value == NULL || output == NULL ||
273 score_scratch == NULL) return -1;
274 if (num_heads <= 0 || query_tokens <= 0 || key_tokens <= 0 ||
275 head_dim <= 0 || !isfinite(scale)) return -2;
276
277 ck_attention_query_key_f32_head_args_t args = {
278 .query = query,
279 .key = key,
280 .value = value,
281 .output = output,
282 .score_scratch = score_scratch,
283 .num_heads = num_heads,
284 .query_tokens = query_tokens,
285 .key_tokens = key_tokens,
286 .head_dim = head_dim,
287 .scale = scale,
288 };
289 ck_threadpool_t *pool = ck_threadpool_global();
290 int active = pool ? ck_threadpool_n_threads(pool) : 1;
291 if (active <= 1) {
293 return 0;
294 }
295 if (active > num_heads) active = num_heads;
296 ck_threadpool_dispatch_n(pool, active,
298 return 0;
299}
300
302 const float *query,
303 const float *key,
304 const float *value,
305 float *output,
306 float *score_scratch,
307 float *key_transpose_scratch,
308 int num_heads,
309 int query_tokens,
310 int key_tokens,
311 int head_dim,
312 float scale)
313{
314 if (key_transpose_scratch == NULL) return -1;
316 query, key, value, output, score_scratch, key_transpose_scratch,
317 num_heads, query_tokens, key_tokens, head_dim, scale);
318}
319
320/* Convert BF16 tensor to FP32 using caller-provided buffer (no malloc!) */
321static void convert_bf16_tensor_to_buf(const uint16_t *src, float *dst, size_t count)
322{
323 if (!dst || !src) return;
324 bf16_tensor_to_float(src, dst, count);
325}
326
327// Helpers for head-major layouts used in attention.
328// Q/K/V layout: [head][token][head_dim] with stride aligned_head_dim.
329static inline size_t qkv_index(int h,
330 int t,
331 int d,
332 int num_tokens,
333 int aligned_head_dim)
334{
335 return ((size_t)h * (size_t)num_tokens + (size_t)t) * (size_t)aligned_head_dim
336 + (size_t)d;
337}
338
339static inline size_t attention_output_index(int h, int token,
340 int num_heads, int num_tokens,
341 int aligned_head_dim,
342 int token_major)
343{
344 if (token_major) {
345 return ((size_t)token * (size_t)num_heads + (size_t)h) *
346 (size_t)aligned_head_dim;
347 }
348 return qkv_index(h, token, 0, num_tokens, aligned_head_dim);
349}
350
351// Match llama.cpp flash-attention input handling where F32 K/V are rounded through F16.
352static inline float ck_round_fp16_scalar(float x) {
354}
355
356static void ck_round_fp16_buffer(const float *src, float *dst, size_t count)
357{
358 size_t i = 0;
359#if defined(__AVX2__) && defined(__F16C__)
360 for (; i + 8 <= count; i += 8) {
361 const __m256 value = _mm256_loadu_ps(src + i);
362 const __m128i half = _mm256_cvtps_ph(
363 value, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC);
364 _mm256_storeu_ps(dst + i, _mm256_cvtph_ps(half));
365 }
366#endif
367 for (; i < count; ++i) {
368 dst[i] = ck_round_fp16_scalar(src[i]);
369 }
370}
371
372static inline void ck_local_fp16_to_fp32_row(const uint16_t *src, float *dst, int n)
373{
374 if (!src || !dst || n <= 0) {
375 return;
376 }
377 for (int i = 0; i < n; ++i) {
378 dst[i] = CK_FP16_TO_FP32(src[i]);
379 }
380}
381
382static inline void ck_local_fp16_to_fp32_2d(const uint16_t *src,
383 float *dst,
384 int rows,
385 int cols,
386 int src_stride,
387 int dst_stride)
388{
389 if (!src || !dst || rows <= 0 || cols <= 0) {
390 return;
391 }
392 for (int r = 0; r < rows; ++r) {
393 ck_local_fp16_to_fp32_row(src + (size_t)r * (size_t)src_stride,
394 dst + (size_t)r * (size_t)dst_stride,
395 cols);
396 }
397}
398
399#if defined(__GNUC__) || defined(__clang__)
400#define CK_NOINLINE __attribute__((noinline))
401#else
402#define CK_NOINLINE
403#endif
404
405#if defined(__clang__)
406#define CK_OPTNONE __attribute__((optnone))
407#elif defined(__GNUC__)
408#define CK_OPTNONE __attribute__((optimize("O0")))
409#else
410#define CK_OPTNONE
411#endif
412
413static CK_NOINLINE CK_OPTNONE float ck_vec_dot_f32_strict(const float *x,
414 const float *y,
415 int n)
416{
417 float sumf = 0.0f;
418 for (int i = 0; i < n; ++i) {
419 volatile float prod = x[i] * y[i];
420 volatile float next = sumf + prod;
421 sumf = next;
422 }
423 return sumf;
424}
425
427 const float *y,
428 int n)
429{
430 double sum = 0.0;
431 for (int i = 0; i < n; ++i) {
432 volatile double prod = (double) x[i] * (double) y[i];
433 volatile double next = sum + prod;
434 sum = next;
435 }
436 return (float) sum;
437}
438
440 const float *y,
441 int n)
442{
443 float sumf = 0.0f;
444 for (int i = n - 1; i >= 0; --i) {
445 volatile float prod = x[i] * y[i];
446 volatile float next = sumf + prod;
447 sumf = next;
448 }
449 return sumf;
450}
451
452typedef struct {
453 char magic[8];
454 uint32_t version;
455 int32_t layer_id;
456 char op_name[32];
457 uint32_t dtype;
458 uint32_t rank;
459 int64_t shape[4];
460 uint32_t elem_count;
461 int32_t token_id;
462 uint8_t reserved[32];
463} __attribute__((packed)) ck_attention_vec_dump_header_t;
464
465static const char ck_attention_vec_dump_magic[8] = {'C', 'K', 'D', 'M', 'P', '\0', '\0', '\0'};
466static const uint32_t ck_attention_vec_dump_version = 1u;
468
469static void ck_attention_trace_query(const char *tag,
470 int layer_id,
471 int head_id,
472 int query_id,
473 int value);
474
476{
477 const char *v = getenv("CK_STRICT_ATTN_VEC_DUMP");
478 return v && v[0] && strcmp(v, "0") != 0;
479}
480
482{
483 const char *v = getenv("CK_STRICT_ATTN_DUMP_VCOLS");
484 return v && v[0] && strcmp(v, "0") != 0;
485}
486
488{
489 const char *v = getenv("CK_STRICT_ATTN_REVERSE_OUT_DOT");
490 return v && v[0] && strcmp(v, "0") != 0;
491}
492
493#if CK_ENABLE_LLAMA_CPP_PARITY
495{
496 const char *v = getenv("CK_STRICT_ATTN_GGML_OUT_GRAPH");
497 return v && v[0] && strcmp(v, "0") != 0;
498}
499#else
501{
502 return 0;
503}
504#endif
505
506static int ck_attention_vec_dump_parse_env_int(const char *name, int *out)
507{
508 const char *v = getenv(name);
509 if (!v || !v[0]) {
510 return 0;
511 }
512 char *end = NULL;
513 long parsed = strtol(v, &end, 10);
514 if (end == v || (end && *end != '\0') || parsed < 0 || parsed > INT32_MAX) {
515 return 0;
516 }
517 if (out) {
518 *out = (int) parsed;
519 }
520 return 1;
521}
522
523static int ck_attention_vec_dump_should_emit(int layer_id, int head_id, int query_id)
524{
526 return 0;
527 }
528 int want_layer = -1;
529 int want_head = -1;
530 int want_query = -1;
531 if (!ck_attention_vec_dump_parse_env_int("CK_STRICT_ATTN_DUMP_LAYER", &want_layer)) {
532 return 0;
533 }
534 const int have_head = ck_attention_vec_dump_parse_env_int("CK_STRICT_ATTN_DUMP_HEAD", &want_head);
535 const int have_query = ck_attention_vec_dump_parse_env_int("CK_STRICT_ATTN_DUMP_QUERY", &want_query);
536 const int trace_target = layer_id == want_layer &&
537 (!have_head || head_id == want_head) &&
538 (!have_query || query_id == want_query);
539 if (layer_id != want_layer) {
540 return 0;
541 }
542 if (have_head && head_id != want_head) {
543 return 0;
544 }
545 if (have_query && query_id != want_query) {
546 return 0;
547 }
548 if (trace_target) {
549 ck_attention_trace_query("vec_dump_should_emit", layer_id, head_id, query_id, 1);
550 }
551 return 1;
552}
553
555{
556 const int layer_id = ck_attention_vec_dump_layer_seq;
558 return layer_id;
559}
560
561static void ck_attention_vec_dump_tensor(const char *name,
562 int layer_id,
563 int query_id,
564 const float *data,
565 size_t elem_count)
566{
567 const char *dir = getenv("CK_PARITY_DIR");
568 if (!dir || !dir[0] || !name || !name[0] || !data || elem_count == 0) {
569 return;
570 }
571
572 char path[4096];
573 snprintf(path, sizeof(path), "%s/%s", dir, "strict_internal.bin");
574 FILE *f = fopen(path, "ab");
575 if (!f) {
576 return;
577 }
578
579 ck_attention_vec_dump_header_t h;
580 memset(&h, 0, sizeof(h));
583 h.layer_id = layer_id;
584 strncpy(h.op_name, name, sizeof(h.op_name) - 1);
585 h.dtype = 0u;
586 h.rank = 1u;
587 h.shape[0] = (int64_t) elem_count;
588 h.elem_count = (uint32_t) elem_count;
589 h.token_id = query_id;
590
591 fwrite(&h, sizeof(h), 1, f);
592 fwrite(data, sizeof(float), elem_count, f);
593 fclose(f);
594}
595
596static void ck_attention_trace(const char *branch, int layer_id, int head_id)
597{
598 const char *enabled = getenv("CK_STRICT_ATTN_TRACE");
599 const char *dir = getenv("CK_PARITY_DIR");
600 if (!enabled || !enabled[0] || strcmp(enabled, "0") == 0 || !dir || !dir[0] || !branch || !branch[0]) {
601 return;
602 }
603 char path[4096];
604 snprintf(path, sizeof(path), "%s/%s", dir, "strict_trace.txt");
605 FILE *f = fopen(path, "a");
606 if (!f) {
607 return;
608 }
609 fprintf(f, "layer=%d head=%d branch=%s\n", layer_id, head_id, branch);
610 fclose(f);
611}
612
613static void ck_attention_trace_query(const char *tag,
614 int layer_id,
615 int head_id,
616 int query_id,
617 int value)
618{
619 const char *enabled = getenv("CK_STRICT_ATTN_TRACE");
620 const char *dir = getenv("CK_PARITY_DIR");
621 if (!enabled || !enabled[0] || strcmp(enabled, "0") == 0 || !dir || !dir[0] || !tag || !tag[0]) {
622 return;
623 }
624 char path[4096];
625 snprintf(path, sizeof(path), "%s/%s", dir, "strict_trace.txt");
626 FILE *f = fopen(path, "a");
627 if (!f) {
628 return;
629 }
630 fprintf(f, "layer=%d head=%d query=%d tag=%s value=%d\n", layer_id, head_id, query_id, tag, value);
631 fclose(f);
632}
633
634static void ck_attention_trace_float(const char *tag,
635 int layer_id,
636 int head_id,
637 float value)
638{
639 const char *enabled = getenv("CK_STRICT_ATTN_TRACE");
640 const char *dir = getenv("CK_PARITY_DIR");
641 if (!enabled || !enabled[0] || strcmp(enabled, "0") == 0 || !dir || !dir[0] || !tag || !tag[0]) {
642 return;
643 }
644 char path[4096];
645 snprintf(path, sizeof(path), "%s/%s", dir, "strict_trace.txt");
646 FILE *f = fopen(path, "a");
647 if (!f) {
648 return;
649 }
650 fprintf(f, "layer=%d head=%d tag=%s float=%.17g\n", layer_id, head_id, tag, (double) value);
651 fclose(f);
652}
653
654static void ck_attention_vec_dump_selected_query(const float *raw_scores,
655 const float *probs,
656 const float *out_vec,
657 const float *v_cols,
658 int kv_tokens,
659 int head_dim,
660 int layer_id,
661 int head_id,
662 int query_id)
663{
664 if (!ck_attention_vec_dump_should_emit(layer_id, head_id, query_id)) {
665 return;
666 }
667 ck_attention_trace_query("vec_dump_selected_query", layer_id, head_id, query_id, 1);
668 char name[32];
669 snprintf(name, sizeof(name), "kq_scores_h%d_q%d", head_id, query_id);
670 ck_attention_vec_dump_tensor(name, layer_id, query_id, raw_scores, (size_t) kv_tokens);
671 snprintf(name, sizeof(name), "kq_soft_h%d_q%d", head_id, query_id);
672 ck_attention_vec_dump_tensor(name, layer_id, query_id, probs, (size_t) kv_tokens);
673 snprintf(name, sizeof(name), "kqv_out_h%d_q%d", head_id, query_id);
674 ck_attention_vec_dump_tensor(name, layer_id, query_id, out_vec, (size_t) head_dim);
675 if (v_cols && ck_attention_vec_dump_vcols_enabled()) {
676 snprintf(name, sizeof(name), "vcols_h%d_q%d", head_id, query_id);
677 ck_attention_vec_dump_tensor(name, layer_id, query_id, v_cols, (size_t) kv_tokens * (size_t) head_dim);
678 }
679}
680
681static inline void ck_vec_scale_f32_inplace(float *x, int n, float scale);
682static inline float ck_vec_max_f32_contig(const float *x, int n);
683
684#if CK_ENABLE_LLAMA_CPP_PARITY
685struct ggml_compute_params;
686typedef void (*ck_ggml_vec_dot_f32_fn)(int, float *, size_t, const float *, size_t, const float *, size_t, int);
687typedef double (*ck_ggml_vec_soft_max_f32_fn)(int, float *, const float *, float);
688typedef void (*ck_ggml_compute_forward_mul_mat_fn)(const struct ggml_compute_params *, struct ggml_tensor *);
689typedef void (*ck_ggml_compute_forward_soft_max_fn)(const struct ggml_compute_params *, struct ggml_tensor *);
690typedef void (*ck_ggml_cpu_init_fn)(void);
691typedef struct ggml_context *(*ck_ggml_init_fn)(struct ggml_init_params);
692typedef void (*ck_ggml_free_fn)(struct ggml_context *);
693typedef struct ggml_tensor *(*ck_ggml_new_tensor_2d_fn)(struct ggml_context *, enum ggml_type, int64_t, int64_t);
694typedef struct ggml_tensor *(*ck_ggml_mul_mat_graph_fn)(struct ggml_context *, struct ggml_tensor *, struct ggml_tensor *);
695typedef struct ggml_cgraph *(*ck_ggml_new_graph_fn)(struct ggml_context *);
696typedef void (*ck_ggml_build_forward_expand_fn)(struct ggml_cgraph *, struct ggml_tensor *);
697typedef enum ggml_status (*ck_ggml_graph_compute_with_ctx_fn)(struct ggml_context *, struct ggml_cgraph *, int);
698typedef void (*ck_ggml_set_input_fn)(struct ggml_tensor *);
699
700struct ggml_threadpool;
701struct ggml_compute_params {
702 int ith, nth;
703 size_t wsize;
704 void * wdata;
705 struct ggml_threadpool * threadpool;
706 bool use_ref;
707};
708
709static void *ck_resolve_ggml_cpu_so_handle(void)
710{
711 static int tried = 0;
712 static void *handle = NULL;
713 if (!tried) {
714 tried = 1;
715 const char *env_path = getenv("CK_GGML_CPU_SO");
716 const char *env_dir = getenv("CK_GGML_LIB_DIR");
717 const char *dirs[] = {
718 "/opt/app-root/src/Software/llama.cpp/build/bin",
719 "./llama.cpp/build/bin",
720 "llama.cpp/build/bin",
721 NULL,
722 };
723 char path_buf[512];
724 if (env_dir && env_dir[0]) {
725 snprintf(path_buf, sizeof(path_buf), "%s/libggml-base.so", env_dir);
726 dlopen(path_buf, RTLD_NOW | RTLD_GLOBAL);
727 snprintf(path_buf, sizeof(path_buf), "%s/libggml.so", env_dir);
728 dlopen(path_buf, RTLD_NOW | RTLD_GLOBAL);
729 snprintf(path_buf, sizeof(path_buf), "%s/libggml-cpu.so", env_dir);
730 handle = dlopen(path_buf, RTLD_NOW | RTLD_GLOBAL);
731 }
732 for (int i = 0; !handle && dirs[i] != NULL; ++i) {
733 snprintf(path_buf, sizeof(path_buf), "%s/libggml-base.so", dirs[i]);
734 dlopen(path_buf, RTLD_NOW | RTLD_GLOBAL);
735 snprintf(path_buf, sizeof(path_buf), "%s/libggml.so", dirs[i]);
736 dlopen(path_buf, RTLD_NOW | RTLD_GLOBAL);
737 snprintf(path_buf, sizeof(path_buf), "%s/libggml-cpu.so", dirs[i]);
738 handle = dlopen(path_buf, RTLD_NOW | RTLD_GLOBAL);
739 if (handle) {
740 break;
741 }
742 }
743 if (!handle && env_path && env_path[0]) {
744 handle = dlopen(env_path, RTLD_NOW | RTLD_GLOBAL);
745 }
746 const char *candidates[] = {
747 "libggml-cpu.so",
748 "libggml-cpu.so.0",
749 NULL,
750 };
751 for (int i = 0; !handle && candidates[i] != NULL; ++i) {
752 handle = dlopen(candidates[i], RTLD_NOW | RTLD_GLOBAL);
753 }
754 }
755 return handle;
756}
757
758static void *ck_resolve_ggml_symbol(const char *name)
759{
760 void *sym = dlsym(RTLD_DEFAULT, name);
761 if (sym) {
762 return sym;
763 }
764 void *handle = ck_resolve_ggml_cpu_so_handle();
765 if (!handle) {
766 return NULL;
767 }
768 return dlsym(handle, name);
769}
770
771static ck_ggml_vec_dot_f32_fn ck_resolve_ggml_vec_dot_f32(void)
772{
773 static int tried = 0;
774 static ck_ggml_vec_dot_f32_fn fn = NULL;
775 if (!tried) {
776 tried = 1;
777 fn = (ck_ggml_vec_dot_f32_fn) ck_resolve_ggml_symbol("ggml_vec_dot_f32");
778 }
779 return fn;
780}
781
782static ck_ggml_vec_soft_max_f32_fn ck_resolve_ggml_vec_soft_max_f32(void)
783{
784 static int tried = 0;
785 static ck_ggml_vec_soft_max_f32_fn fn = NULL;
786 if (!tried) {
787 tried = 1;
788 fn = (ck_ggml_vec_soft_max_f32_fn) ck_resolve_ggml_symbol("ggml_vec_soft_max_f32");
789 }
790 return fn;
791}
792
793static ck_ggml_compute_forward_mul_mat_fn ck_resolve_ggml_compute_forward_mul_mat(void)
794{
795 static int tried = 0;
796 static ck_ggml_compute_forward_mul_mat_fn fn = NULL;
797 if (!tried) {
798 tried = 1;
799 fn = (ck_ggml_compute_forward_mul_mat_fn) ck_resolve_ggml_symbol("ggml_compute_forward_mul_mat");
800 }
801 return fn;
802}
803
804static ck_ggml_compute_forward_soft_max_fn ck_resolve_ggml_compute_forward_soft_max(void)
805{
806 static int tried = 0;
807 static ck_ggml_compute_forward_soft_max_fn fn = NULL;
808 if (!tried) {
809 tried = 1;
810 fn = (ck_ggml_compute_forward_soft_max_fn) ck_resolve_ggml_symbol("ggml_compute_forward_soft_max");
811 }
812 return fn;
813}
814
816{
817 static int tried = 0;
818 static ck_ggml_cpu_init_fn fn = NULL;
819 if (!tried) {
820 tried = 1;
821 fn = (ck_ggml_cpu_init_fn) ck_resolve_ggml_symbol("ggml_cpu_init");
822 }
823 return fn;
824}
825
827{
828 static int tried = 0;
829 static ck_ggml_init_fn fn = NULL;
830 if (!tried) {
831 tried = 1;
832 fn = (ck_ggml_init_fn) ck_resolve_ggml_symbol("ggml_init");
833 }
834 return fn;
835}
836
838{
839 static int tried = 0;
840 static ck_ggml_free_fn fn = NULL;
841 if (!tried) {
842 tried = 1;
843 fn = (ck_ggml_free_fn) ck_resolve_ggml_symbol("ggml_free");
844 }
845 return fn;
846}
847
849{
850 static int tried = 0;
851 static ck_ggml_new_tensor_2d_fn fn = NULL;
852 if (!tried) {
853 tried = 1;
854 fn = (ck_ggml_new_tensor_2d_fn) ck_resolve_ggml_symbol("ggml_new_tensor_2d");
855 }
856 return fn;
857}
858
860{
861 static int tried = 0;
862 static ck_ggml_mul_mat_graph_fn fn = NULL;
863 if (!tried) {
864 tried = 1;
865 fn = (ck_ggml_mul_mat_graph_fn) ck_resolve_ggml_symbol("ggml_mul_mat");
866 }
867 return fn;
868}
869
871{
872 static int tried = 0;
873 static ck_ggml_new_graph_fn fn = NULL;
874 if (!tried) {
875 tried = 1;
876 fn = (ck_ggml_new_graph_fn) ck_resolve_ggml_symbol("ggml_new_graph");
877 }
878 return fn;
879}
880
882{
883 static int tried = 0;
884 static ck_ggml_build_forward_expand_fn fn = NULL;
885 if (!tried) {
886 tried = 1;
887 fn = (ck_ggml_build_forward_expand_fn) ck_resolve_ggml_symbol("ggml_build_forward_expand");
888 }
889 return fn;
890}
891
893{
894 static int tried = 0;
895 static ck_ggml_graph_compute_with_ctx_fn fn = NULL;
896 if (!tried) {
897 tried = 1;
898 fn = (ck_ggml_graph_compute_with_ctx_fn) ck_resolve_ggml_symbol("ggml_graph_compute_with_ctx");
899 }
900 return fn;
901}
902
904{
905 static int tried = 0;
906 static ck_ggml_set_input_fn fn = NULL;
907 if (!tried) {
908 tried = 1;
909 fn = (ck_ggml_set_input_fn) ck_resolve_ggml_symbol("ggml_set_input");
910 }
911 return fn;
912}
913
914static inline void ck_ggml_init_tensor_f32(struct ggml_tensor *t,
915 int64_t ne0,
916 int64_t ne1,
917 int64_t ne2,
918 int64_t ne3,
919 size_t nb0,
920 size_t nb1,
921 size_t nb2,
922 size_t nb3,
923 void *data)
924{
925 memset(t, 0, sizeof(*t));
926 t->type = GGML_TYPE_F32;
927 t->buffer = NULL;
928 t->ne[0] = ne0;
929 t->ne[1] = ne1;
930 t->ne[2] = ne2;
931 t->ne[3] = ne3;
932 t->nb[0] = nb0;
933 t->nb[1] = nb1;
934 t->nb[2] = nb2;
935 t->nb[3] = nb3;
936 t->op = GGML_OP_NONE;
937 t->data = data;
938}
939#endif
940
941#if defined(__SSE2__)
942#if defined(__FMA__)
943#define CK_MADD128(x, y, z) _mm_fmadd_ps(x, y, z)
944#define CK_NMADD128(x, y, z) _mm_fnmadd_ps(x, y, z)
945#else
946#define CK_MADD128(x, y, z) _mm_add_ps(_mm_mul_ps(x, y), z)
947#define CK_NMADD128(x, y, z) _mm_sub_ps(z, _mm_mul_ps(x, y))
948#endif
949
950static inline float ck_hsum128_ps(__m128 v) {
951 v = _mm_add_ps(v, _mm_movehl_ps(v, v));
952 v = _mm_add_ss(v, _mm_movehdup_ps(v));
953 return _mm_cvtss_f32(v);
954}
955#endif
956
957#if defined(__AVX__) || defined(__AVX2__) || defined(__AVX512F__)
958static inline float ck_hsum256_ps(__m256 v) {
959 const __m128 lo = _mm256_castps256_ps128(v);
960 const __m128 hi = _mm256_extractf128_ps(v, 1);
961 return ck_hsum128_ps(_mm_add_ps(lo, hi));
962}
963#endif
964
965#if defined(__AVX512F__) && defined(__AVX512DQ__)
966static inline __m512 ck_ggml_v_expf512(__m512 x) {
967 const __m512 r = _mm512_set1_ps(0x1.8p23f);
968 const __m512 z = _mm512_fmadd_ps(x, _mm512_set1_ps(0x1.715476p+0f), r);
969 const __m512 n = _mm512_sub_ps(z, r);
970 const __m512 b = _mm512_fnmadd_ps(
971 n,
972 _mm512_set1_ps(0x1.7f7d1cp-20f),
973 _mm512_fnmadd_ps(n, _mm512_set1_ps(0x1.62e4p-1f), x));
974 const __mmask16 d = _mm512_cmp_ps_mask(
975 _mm512_abs_ps(n), _mm512_set1_ps(192), _CMP_GT_OQ);
976 const __m512 u = _mm512_mul_ps(b, b);
977 const __m512 j = _mm512_fmadd_ps(
978 _mm512_fmadd_ps(
979 _mm512_fmadd_ps(
980 _mm512_set1_ps(0x1.0e4020p-7f),
981 b,
982 _mm512_set1_ps(0x1.573e2ep-5f)),
983 u,
984 _mm512_fmadd_ps(
985 _mm512_set1_ps(0x1.555e66p-3f),
986 b,
987 _mm512_set1_ps(0x1.fffdb6p-2f))),
988 u,
989 _mm512_fmadd_ps(
990 _mm512_set1_ps(0x1.ffffecp-1f),
991 b,
992 _mm512_set1_ps(1.0f)));
993 const __m512 res = _mm512_scalef_ps(j, n);
994 if (_mm512_kortestz(d, d)) {
995 return res;
996 }
997 const __m512 zero = _mm512_setzero_ps();
998 const __m512 alt = _mm512_mask_blend_ps(
999 _mm512_cmp_ps_mask(n, zero, _CMP_LE_OQ),
1000 _mm512_set1_ps(INFINITY),
1001 zero);
1002 return _mm512_mask_blend_ps(d, res, alt);
1003}
1004#endif
1005
1006#if defined(__AVX2__) && defined(__FMA__)
1007static inline __m256 ck_ggml_v_expf256(__m256 x) {
1008 const __m256 r = _mm256_set1_ps(0x1.8p23f);
1009 const __m256 z = _mm256_fmadd_ps(x, _mm256_set1_ps(0x1.715476p+0f), r);
1010 const __m256 n = _mm256_sub_ps(z, r);
1011 const __m256 b = _mm256_fnmadd_ps(n, _mm256_set1_ps(0x1.7f7d1cp-20f),
1012 _mm256_fnmadd_ps(n, _mm256_set1_ps(0x1.62e4p-1f), x));
1013 const __m256i e = _mm256_slli_epi32(_mm256_castps_si256(z), 23);
1014 const __m256 k = _mm256_castsi256_ps(
1015 _mm256_add_epi32(e, _mm256_castps_si256(_mm256_set1_ps(1))));
1016 const __m256i c = _mm256_castps_si256(
1017 _mm256_cmp_ps(_mm256_andnot_ps(_mm256_set1_ps(-0.f), n),
1018 _mm256_set1_ps(126), _CMP_GT_OQ));
1019 const __m256 u = _mm256_mul_ps(b, b);
1020 const __m256 j = _mm256_fmadd_ps(
1021 _mm256_fmadd_ps(
1022 _mm256_fmadd_ps(_mm256_set1_ps(0x1.0e4020p-7f), b, _mm256_set1_ps(0x1.573e2ep-5f)),
1023 u,
1024 _mm256_fmadd_ps(_mm256_set1_ps(0x1.555e66p-3f), b, _mm256_set1_ps(0x1.fffdb6p-2f))),
1025 u,
1026 _mm256_mul_ps(_mm256_set1_ps(0x1.ffffecp-1f), b));
1027 if (!_mm256_movemask_ps(_mm256_castsi256_ps(c))) {
1028 return _mm256_fmadd_ps(j, k, k);
1029 }
1030 const __m256i g = _mm256_and_si256(
1031 _mm256_castps_si256(_mm256_cmp_ps(n, _mm256_setzero_ps(), _CMP_LE_OQ)),
1032 _mm256_set1_epi32(0x82000000u));
1033 const __m256 s1 =
1034 _mm256_castsi256_ps(_mm256_add_epi32(g, _mm256_set1_epi32(0x7f000000u)));
1035 const __m256 s2 = _mm256_castsi256_ps(_mm256_sub_epi32(e, g));
1036 const __m256i d = _mm256_castps_si256(
1037 _mm256_cmp_ps(_mm256_andnot_ps(_mm256_set1_ps(-0.f), n),
1038 _mm256_set1_ps(192), _CMP_GT_OQ));
1039 return _mm256_or_ps(
1040 _mm256_and_ps(_mm256_castsi256_ps(d), _mm256_mul_ps(s1, s1)),
1041 _mm256_andnot_ps(
1042 _mm256_castsi256_ps(d),
1043 _mm256_or_ps(
1044 _mm256_and_ps(_mm256_castsi256_ps(c),
1045 _mm256_mul_ps(_mm256_fmadd_ps(s2, j, s2), s1)),
1046 _mm256_andnot_ps(_mm256_castsi256_ps(c), _mm256_fmadd_ps(k, j, k)))));
1047}
1048#endif
1049
1050#if defined(__SSE2__)
1051static inline __m128 ck_ggml_v_expf128(__m128 x) {
1052 const __m128 r = _mm_set1_ps(0x1.8p23f);
1053 const __m128 z = CK_MADD128(x, _mm_set1_ps(0x1.715476p+0f), r);
1054 const __m128 n = _mm_sub_ps(z, r);
1055 const __m128 b = CK_NMADD128(n, _mm_set1_ps(0x1.7f7d1cp-20f),
1056 CK_NMADD128(n, _mm_set1_ps(0x1.62e4p-1f), x));
1057 const __m128i e = _mm_slli_epi32(_mm_castps_si128(z), 23);
1058 const __m128 k = _mm_castsi128_ps(
1059 _mm_add_epi32(e, _mm_castps_si128(_mm_set1_ps(1))));
1060 const __m128i c = _mm_castps_si128(
1061 _mm_cmpgt_ps(_mm_andnot_ps(_mm_set1_ps(-0.f), n), _mm_set1_ps(126)));
1062 const __m128 u = _mm_mul_ps(b, b);
1063 const __m128 j = CK_MADD128(
1064 CK_MADD128(
1065 CK_MADD128(_mm_set1_ps(0x1.0e4020p-7f), b, _mm_set1_ps(0x1.573e2ep-5f)),
1066 u,
1067 CK_MADD128(_mm_set1_ps(0x1.555e66p-3f), b, _mm_set1_ps(0x1.fffdb6p-2f))),
1068 u,
1069 _mm_mul_ps(_mm_set1_ps(0x1.ffffecp-1f), b));
1070 if (!_mm_movemask_ps(_mm_castsi128_ps(c))) {
1071 return CK_MADD128(j, k, k);
1072 }
1073 const __m128i g = _mm_and_si128(
1074 _mm_castps_si128(_mm_cmple_ps(n, _mm_setzero_ps())),
1075 _mm_set1_epi32(0x82000000u));
1076 const __m128 s1 = _mm_castsi128_ps(_mm_add_epi32(g, _mm_set1_epi32(0x7f000000u)));
1077 const __m128 s2 = _mm_castsi128_ps(_mm_sub_epi32(e, g));
1078 const __m128i d = _mm_castps_si128(
1079 _mm_cmpgt_ps(_mm_andnot_ps(_mm_set1_ps(-0.f), n), _mm_set1_ps(192)));
1080 return _mm_or_ps(
1081 _mm_and_ps(_mm_castsi128_ps(d), _mm_mul_ps(s1, s1)),
1082 _mm_andnot_ps(
1083 _mm_castsi128_ps(d),
1084 _mm_or_ps(
1085 _mm_and_ps(_mm_castsi128_ps(c), _mm_mul_ps(CK_MADD128(s2, j, s2), s1)),
1086 _mm_andnot_ps(_mm_castsi128_ps(c), CK_MADD128(k, j, k)))));
1087}
1088#endif
1089
1091 const float *y,
1092 int n)
1093{
1094 // Keep this a literal port of ggml_vec_dot_f32 so strict parity can match
1095 // llama.cpp's CPU attention path instead of merely approximating it.
1096#if defined(__AVX__)
1097 float sumf = 0.0f;
1098 const int np = (n & ~31);
1099 __m256 sum[4] = {
1100 _mm256_setzero_ps(),
1101 _mm256_setzero_ps(),
1102 _mm256_setzero_ps(),
1103 _mm256_setzero_ps(),
1104 };
1105
1106 for (int i = 0; i < np; i += 32) {
1107 for (int j = 0; j < 4; ++j) {
1108 const __m256 ax = _mm256_loadu_ps(x + i + j * 8);
1109 const __m256 ay = _mm256_loadu_ps(y + i + j * 8);
1110#if defined(__FMA__)
1111 sum[j] = _mm256_fmadd_ps(ax, ay, sum[j]);
1112#else
1113 sum[j] = _mm256_add_ps(_mm256_mul_ps(ax, ay), sum[j]);
1114#endif
1115 }
1116 }
1117
1118 sum[0] = _mm256_add_ps(sum[0], sum[2]);
1119 sum[1] = _mm256_add_ps(sum[1], sum[3]);
1120 sum[0] = _mm256_add_ps(sum[0], sum[1]);
1121 const __m128 t0 = _mm_add_ps(_mm256_castps256_ps128(sum[0]),
1122 _mm256_extractf128_ps(sum[0], 1));
1123 const __m128 t1 = _mm_hadd_ps(t0, t0);
1124 sumf = _mm_cvtss_f32(_mm_hadd_ps(t1, t1));
1125
1126 for (int i = np; i < n; ++i) {
1127 sumf += x[i] * y[i];
1128 }
1129 return sumf;
1130#elif defined(__SSE2__)
1131 float sumf = 0.0f;
1132 const int np = (n & ~15);
1133 __m128 sum[4] = {
1134 _mm_setzero_ps(),
1135 _mm_setzero_ps(),
1136 _mm_setzero_ps(),
1137 _mm_setzero_ps(),
1138 };
1139
1140 for (int i = 0; i < np; i += 16) {
1141 for (int j = 0; j < 4; ++j) {
1142 const __m128 ax = _mm_loadu_ps(x + i + j * 4);
1143 const __m128 ay = _mm_loadu_ps(y + i + j * 4);
1144#if defined(__FMA__)
1145 sum[j] = _mm_fmadd_ps(ax, ay, sum[j]);
1146#else
1147 sum[j] = _mm_add_ps(_mm_mul_ps(ax, ay), sum[j]);
1148#endif
1149 }
1150 }
1151
1152 sum[0] = _mm_add_ps(sum[0], sum[2]);
1153 sum[1] = _mm_add_ps(sum[1], sum[3]);
1154 sum[0] = _mm_add_ps(sum[0], sum[1]);
1155#if defined(__AVX__) || defined(__AVX2__) || defined(__AVX512F__)
1156 sum[0] = _mm_add_ps(sum[0], _mm_movehl_ps(sum[0], sum[0]));
1157 sum[0] = _mm_add_ss(sum[0], _mm_movehdup_ps(sum[0]));
1158#else
1159 __m128 tmp = _mm_shuffle_ps(sum[0], sum[0], _MM_SHUFFLE(2, 3, 0, 1));
1160 sum[0] = _mm_add_ps(sum[0], tmp);
1161 tmp = _mm_movehl_ps(tmp, sum[0]);
1162 sum[0] = _mm_add_ss(sum[0], tmp);
1163#endif
1164 sumf = _mm_cvtss_f32(sum[0]);
1165
1166 for (int i = np; i < n; ++i) {
1167 sumf += x[i] * y[i];
1168 }
1169 return sumf;
1170#else
1171 double sumf = 0.0;
1172 for (int i = 0; i < n; ++i) {
1173 sumf += (double) (x[i] * y[i]);
1174 }
1175 return (float) sumf;
1176#endif
1177}
1178
1179static inline float ck_attention_dot_f16_unfused_llama(const uint16_t *x,
1180 const uint16_t *y,
1181 int n)
1182{
1183 int i = 0;
1184#if defined(__AVX2__) && defined(__F16C__)
1185 __m256 sum0 = _mm256_setzero_ps();
1186 __m256 sum1 = _mm256_setzero_ps();
1187 __m256 sum2 = _mm256_setzero_ps();
1188 __m256 sum3 = _mm256_setzero_ps();
1189 const int n32 = n & ~31;
1190 for (; i < n32; i += 32) {
1191 const __m256 x0 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *) (x + i)));
1192 const __m256 y0 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *) (y + i)));
1193 const __m256 x1 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *) (x + i + 8)));
1194 const __m256 y1 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *) (y + i + 8)));
1195 const __m256 x2 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *) (x + i + 16)));
1196 const __m256 y2 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *) (y + i + 16)));
1197 const __m256 x3 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *) (x + i + 24)));
1198 const __m256 y3 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *) (y + i + 24)));
1199#if defined(__FMA__)
1200 sum0 = _mm256_fmadd_ps(x0, y0, sum0);
1201 sum1 = _mm256_fmadd_ps(x1, y1, sum1);
1202 sum2 = _mm256_fmadd_ps(x2, y2, sum2);
1203 sum3 = _mm256_fmadd_ps(x3, y3, sum3);
1204#else
1205 sum0 = _mm256_add_ps(sum0, _mm256_mul_ps(x0, y0));
1206 sum1 = _mm256_add_ps(sum1, _mm256_mul_ps(x1, y1));
1207 sum2 = _mm256_add_ps(sum2, _mm256_mul_ps(x2, y2));
1208 sum3 = _mm256_add_ps(sum3, _mm256_mul_ps(x3, y3));
1209#endif
1210 }
1211 sum0 = _mm256_add_ps(sum0, sum2);
1212 sum1 = _mm256_add_ps(sum1, sum3);
1213 sum0 = _mm256_add_ps(sum0, sum1);
1214 const __m128 pair = _mm_add_ps(
1215 _mm256_castps256_ps128(sum0),
1216 _mm256_extractf128_ps(sum0, 1));
1217 const __m128 half = _mm_hadd_ps(pair, pair);
1218 float result = _mm_cvtss_f32(_mm_hadd_ps(half, half));
1219#else
1220 float result = 0.0f;
1221#endif
1222 for (; i < n; ++i) {
1223 result += CK_FP16_TO_FP32(x[i]) * CK_FP16_TO_FP32(y[i]);
1224 }
1225 return result;
1226}
1227
1229{
1230 // Keep strict parity on the precise libm sqrtf path. icx -O3 on AVX2 was
1231 // lowering 1/sqrtf(d) to a slightly smaller effective scale, which is
1232 // enough to move layer-0 vision softmax by ~2e-7 and snowball later.
1233 volatile float hd = (float) head_dim;
1234 float (*sqrtf_fn)(float) = sqrtf;
1235 volatile float root = sqrtf_fn(hd);
1236 volatile float one = 1.0f;
1237 volatile float scale = one / root;
1238 return scale;
1239}
1240
1241typedef float (*ck_attention_math_f32_fn)(float);
1242
1243static float ck_attention_reference_expf(float value)
1244{
1245#if defined(__linux__)
1246 static void *libm_handle = NULL;
1247 static ck_attention_math_f32_fn fn = NULL;
1248 static int resolved = 0;
1249 if (!resolved) {
1250 libm_handle = dlopen("libm.so.6", RTLD_NOW | RTLD_LOCAL);
1251 if (libm_handle) {
1252 fn = (ck_attention_math_f32_fn) dlsym(libm_handle, "expf");
1253 }
1254 resolved = 1;
1255 }
1256 if (fn) {
1257 return fn(value);
1258 }
1259#endif
1260 return expf(value);
1261}
1262
1263#if defined(__INTEL_LLVM_COMPILER)
1264static CK_NOINLINE CK_OPTNONE float
1265ck_attention_mul_add_rounded_f32(float lhs, float rhs, float addend)
1266{
1267 /*
1268 * The ICX llama.cpp graph observes the rounded product before the
1269 * addition. Keep this boundary explicit because ICX otherwise contracts
1270 * the expression at -O3. GCC's AVX2 graph uses FMA at this boundary.
1271 */
1272 volatile float product = lhs * rhs;
1273 volatile float result = product + addend;
1274 return result;
1275}
1276#endif
1277
1279 float *y,
1280 const float *x,
1281 float max)
1282{
1283 int i = 0;
1284 double sum = 0.0;
1285
1286#if defined(__AVX512F__) && defined(__AVX512DQ__)
1287 for (; i + 15 < n; i += 16) {
1288 const __m512 val = ck_ggml_v_expf512(
1289 _mm512_sub_ps(_mm512_loadu_ps(x + i), _mm512_set1_ps(max)));
1290 _mm512_storeu_ps(y + i, val);
1291 sum += (double) _mm512_reduce_add_ps(val);
1292 }
1293#elif defined(__AVX2__) && defined(__FMA__)
1294 for (; i + 7 < n; i += 8) {
1295 const __m256 val = ck_ggml_v_expf256(
1296 _mm256_sub_ps(_mm256_loadu_ps(x + i), _mm256_set1_ps(max)));
1297 _mm256_storeu_ps(y + i, val);
1298 __m128 val2 = _mm_add_ps(_mm256_extractf128_ps(val, 1),
1299 _mm256_castps256_ps128(val));
1300 val2 = _mm_add_ps(val2, _mm_movehl_ps(val2, val2));
1301 val2 = _mm_add_ss(val2, _mm_movehdup_ps(val2));
1302 sum += (double) _mm_cvtss_f32(val2);
1303 }
1304#elif defined(__SSE2__)
1305 for (; i + 3 < n; i += 4) {
1306 const __m128 val = ck_ggml_v_expf128(
1307 _mm_sub_ps(_mm_loadu_ps(x + i), _mm_set1_ps(max)));
1308 _mm_storeu_ps(y + i, val);
1309#if defined(__AVX__) || defined(__AVX2__) || defined(__AVX512F__)
1310 __m128 acc = _mm_add_ps(val, _mm_movehl_ps(val, val));
1311 acc = _mm_add_ss(acc, _mm_movehdup_ps(acc));
1312#else
1313 __m128 tmp = _mm_shuffle_ps(val, val, _MM_SHUFFLE(2, 3, 0, 1));
1314 __m128 acc = _mm_add_ps(val, tmp);
1315 tmp = _mm_movehl_ps(tmp, acc);
1316 acc = _mm_add_ss(acc, tmp);
1317#endif
1318 sum += (double) _mm_cvtss_f32(acc);
1319 }
1320#endif
1321
1322 for (; i < n; ++i) {
1323 const float val = expf(x[i] - max);
1324 y[i] = val;
1325 sum += (double) val;
1326 }
1327
1328 return sum;
1329}
1330
1331// Scores layout matches causal_softmax_head_major:
1332// [head][query_token][key_token] with stride aligned_context_window.
1333static inline size_t score_index(int h,
1334 int i,
1335 int j,
1336 int aligned_context_window)
1337{
1338 return ((size_t)h * (size_t)aligned_context_window * (size_t)aligned_context_window)
1339 + (size_t)i * (size_t)aligned_context_window
1340 + (size_t)j;
1341}
1342
1343/**
1344 * Causal attention forward (score-matrix version)
1345 * @test test_attention.py::TestAttentionForward::test_causal_forward
1346 * @test test_attention.py::TestAttentionForward::test_gqa_broadcast
1347 * @test test_attention.py::TestAttentionForward::test_exact_vs_fast
1348 * @test test_parity.py::test_attention_parity
1349 *
1350 * Computes softmax(Q @ K^T / sqrt(d)) @ V with causal masking.
1351 * Uses O(N^2) memory for scores matrix.
1352 *
1353 * After changes: make test && make llamacpp-parity-full
1354 */
1356 const float *k,
1357 const float *v,
1358 float *scores,
1359 float *output,
1360 int num_heads,
1361 int num_tokens,
1362 int head_dim,
1363 int aligned_head_dim,
1364 int aligned_context_window)
1365{
1366 const float scale = 1.0f / sqrtf((float)head_dim);
1367
1368 // Phase 1: compute scaled dot-product scores Q·K^T / sqrt(d_k),
1369 // lower triangle only (j <= i).
1370 for (int h = 0; h < num_heads; ++h) {
1371 for (int i = 0; i < num_tokens; ++i) {
1372 for (int j = 0; j <= i; ++j) {
1373 float dot = 0.0f;
1374 size_t base_q = qkv_index(h, i, 0, num_tokens, aligned_head_dim);
1375 size_t base_k = qkv_index(h, j, 0, num_tokens, aligned_head_dim);
1376
1377 for (int d = 0; d < head_dim; ++d) {
1378 dot += q[base_q + d] * k[base_k + d];
1379 }
1380
1381 scores[score_index(h, i, j, aligned_context_window)] = dot * scale;
1382 }
1383
1384 // Ensure upper triangle is zeroed so there are no stale values
1385 // before the softmax kernel runs.
1386 for (int j = i + 1; j < num_tokens; ++j) {
1387 scores[score_index(h, i, j, aligned_context_window)] = 0.0f;
1388 }
1389 }
1390 }
1391
1392 // Phase 2: apply causal row-wise softmax in-place over j <= i.
1394 num_heads,
1395 num_tokens,
1396 aligned_context_window);
1397
1398 // Phase 3: attention weights · V.
1399 for (int h = 0; h < num_heads; ++h) {
1400 for (int i = 0; i < num_tokens; ++i) {
1401 size_t out_base = qkv_index(h, i, 0, num_tokens, aligned_head_dim);
1402
1403 // Zero the full aligned head slice so padded dims stay clean.
1404 for (int d = 0; d < aligned_head_dim; ++d) {
1405 output[out_base + d] = 0.0f;
1406 }
1407
1408 // Weighted sum over causal positions.
1409 for (int j = 0; j <= i; ++j) {
1410 float w = scores[score_index(h, i, j, aligned_context_window)];
1411 size_t v_base = qkv_index(h, j, 0, num_tokens, aligned_head_dim);
1412
1413 for (int d = 0; d < head_dim; ++d) {
1414 output[out_base + d] += w * v[v_base + d];
1415 }
1416 }
1417 }
1418 }
1419}
1420
1421/**
1422 * Causal attention forward (exact version using stdlib expf)
1423 * @test test_attention.py::TestAttentionForward::test_exact_single
1424 * @test test_attention.py::TestAttentionForward::test_exact_vs_fast
1425 *
1426 * Uses standard library expf for numerical accuracy reference.
1427 * Slower but provides maximum accuracy.
1428 *
1429 * After changes: make test
1430 */
1432 const float *k,
1433 const float *v,
1434 float *scores,
1435 float *output,
1436 int num_heads,
1437 int num_tokens,
1438 int head_dim,
1439 int aligned_head_dim,
1440 int aligned_context_window)
1441{
1442 const float scale = 1.0f / sqrtf((float)head_dim);
1443
1444 // Phase 1: compute scaled dot-product scores Q·K^T / sqrt(d_k),
1445 // lower triangle only (j <= i).
1446 for (int h = 0; h < num_heads; ++h) {
1447 for (int i = 0; i < num_tokens; ++i) {
1448 for (int j = 0; j <= i; ++j) {
1449 float dot = 0.0f;
1450 size_t base_q = qkv_index(h, i, 0, num_tokens, aligned_head_dim);
1451 size_t base_k = qkv_index(h, j, 0, num_tokens, aligned_head_dim);
1452
1453 for (int d = 0; d < head_dim; ++d) {
1454 dot += q[base_q + d] * k[base_k + d];
1455 }
1456
1457 scores[score_index(h, i, j, aligned_context_window)] = dot * scale;
1458 }
1459
1460 // Ensure upper triangle is zeroed so there are no stale values
1461 // before the softmax kernel runs.
1462 for (int j = i + 1; j < num_tokens; ++j) {
1463 scores[score_index(h, i, j, aligned_context_window)] = 0.0f;
1464 }
1465 }
1466 }
1467
1468 // Phase 2: apply causal row-wise softmax using exact expf.
1470 num_heads,
1471 num_tokens,
1472 aligned_context_window);
1473
1474 // Phase 3: attention weights · V.
1475 for (int h = 0; h < num_heads; ++h) {
1476 for (int i = 0; i < num_tokens; ++i) {
1477 size_t out_base = qkv_index(h, i, 0, num_tokens, aligned_head_dim);
1478
1479 // Zero the full aligned head slice so padded dims stay clean.
1480 for (int d = 0; d < aligned_head_dim; ++d) {
1481 output[out_base + d] = 0.0f;
1482 }
1483
1484 // Weighted sum over causal positions.
1485 for (int j = 0; j <= i; ++j) {
1486 float w = scores[score_index(h, i, j, aligned_context_window)];
1487 size_t v_base = qkv_index(h, j, 0, num_tokens, aligned_head_dim);
1488
1489 for (int d = 0; d < head_dim; ++d) {
1490 output[out_base + d] += w * v[v_base + d];
1491 }
1492 }
1493 }
1494 }
1495}
1496
1497/**
1498 * GQA causal attention forward (score-matrix version)
1499 * @test test_attention.py::TestAttentionForward::test_gqa_forward
1500 * @test test_attention.py::TestAttentionForward::test_gqa_broadcast
1501 * @test test_attention_backward.py::TestAttentionBackwardGQA::test_gqa_backward
1502 * @test test_parity.py::test_attention_gqa_parity
1503 *
1504 * Grouped-query attention: Q has num_heads, K/V have num_kv_heads.
1505 * Each query head maps to a KV head via ratio.
1506 *
1507 * After changes: make test && make llamacpp-parity-full
1508 */
1510 const float *k,
1511 const float *v,
1512 float *scores,
1513 float *output,
1514 int num_heads,
1515 int num_kv_heads,
1516 int num_tokens,
1517 int head_dim,
1518 int aligned_head_dim,
1519 int aligned_context_window)
1520{
1521 const float scale = 1.0f / sqrtf((float)head_dim);
1522
1523 for (int h = 0; h < num_heads; ++h) {
1524 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
1525 for (int i = 0; i < num_tokens; ++i) {
1526 for (int j = 0; j <= i; ++j) {
1527 float dot = 0.0f;
1528 size_t base_q = qkv_index(h, i, 0, num_tokens, aligned_head_dim);
1529 size_t base_k = qkv_index(kv_head, j, 0, num_tokens, aligned_head_dim);
1530
1531 for (int d = 0; d < head_dim; ++d) {
1532 dot += q[base_q + d] * k[base_k + d];
1533 }
1534
1535 scores[score_index(h, i, j, aligned_context_window)] = dot * scale;
1536 }
1537
1538 for (int j = i + 1; j < num_tokens; ++j) {
1539 scores[score_index(h, i, j, aligned_context_window)] = 0.0f;
1540 }
1541 }
1542 }
1543
1545 num_heads,
1546 num_tokens,
1547 aligned_context_window);
1548
1549 for (int h = 0; h < num_heads; ++h) {
1550 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
1551 for (int i = 0; i < num_tokens; ++i) {
1552 size_t out_base = qkv_index(h, i, 0, num_tokens, aligned_head_dim);
1553 for (int d = 0; d < aligned_head_dim; ++d) {
1554 output[out_base + d] = 0.0f;
1555 }
1556
1557 for (int j = 0; j <= i; ++j) {
1558 float w = scores[score_index(h, i, j, aligned_context_window)];
1559 size_t v_base = qkv_index(kv_head, j, 0, num_tokens, aligned_head_dim);
1560
1561 for (int d = 0; d < head_dim; ++d) {
1562 output[out_base + d] += w * v[v_base + d];
1563 }
1564 }
1565 }
1566 }
1567}
1568
1569/**
1570 * GQA causal attention forward (exact version using stdlib expf)
1571 * @test test_attention.py::TestAttentionForward::test_gqa_exact
1572 * @test bf16/test_attention_bf16.py::TestAttentionBF16::test_bf16_gqa
1573 *
1574 * Uses standard library expf for numerical accuracy reference.
1575 * Used by BF16 wrapper to avoid approximation error accumulation.
1576 *
1577 * After changes: make test
1578 */
1580 const float *k,
1581 const float *v,
1582 float *scores,
1583 float *output,
1584 int num_heads,
1585 int num_kv_heads,
1586 int num_tokens,
1587 int head_dim,
1588 int aligned_head_dim,
1589 int aligned_context_window)
1590{
1591 const float scale = 1.0f / sqrtf((float)head_dim);
1592
1593 for (int h = 0; h < num_heads; ++h) {
1594 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
1595 for (int i = 0; i < num_tokens; ++i) {
1596 for (int j = 0; j <= i; ++j) {
1597 float dot = 0.0f;
1598 size_t base_q = qkv_index(h, i, 0, num_tokens, aligned_head_dim);
1599 size_t base_k = qkv_index(kv_head, j, 0, num_tokens, aligned_head_dim);
1600
1601 for (int d = 0; d < head_dim; ++d) {
1602 dot += q[base_q + d] * k[base_k + d];
1603 }
1604
1605 scores[score_index(h, i, j, aligned_context_window)] = dot * scale;
1606 }
1607
1608 for (int j = i + 1; j < num_tokens; ++j) {
1609 scores[score_index(h, i, j, aligned_context_window)] = 0.0f;
1610 }
1611 }
1612 }
1613
1614 // Use exact softmax with standard library expf
1616 num_heads,
1617 num_tokens,
1618 aligned_context_window);
1619
1620 for (int h = 0; h < num_heads; ++h) {
1621 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
1622 for (int i = 0; i < num_tokens; ++i) {
1623 size_t out_base = qkv_index(h, i, 0, num_tokens, aligned_head_dim);
1624 for (int d = 0; d < aligned_head_dim; ++d) {
1625 output[out_base + d] = 0.0f;
1626 }
1627
1628 for (int j = 0; j <= i; ++j) {
1629 float w = scores[score_index(h, i, j, aligned_context_window)];
1630 size_t v_base = qkv_index(kv_head, j, 0, num_tokens, aligned_head_dim);
1631
1632 for (int d = 0; d < head_dim; ++d) {
1633 output[out_base + d] += w * v[v_base + d];
1634 }
1635 }
1636 }
1637 }
1638}
1639
1640/**
1641 * BF16 GQA causal attention forward
1642 * @test bf16/test_attention_bf16.py::TestAttentionBF16::test_bf16_forward
1643 * @test bf16/test_attention_bf16.py::TestAttentionBF16::test_bf16_gqa
1644 * @test bf16/test_attention_bf16.py::TestAttentionBF16::test_bf16_flash
1645 *
1646 * Accepts BF16 inputs, converts to FP32, uses exact softmax.
1647 * Caller provides scratch buffers (no per-call malloc).
1648 *
1649 * After changes: make test
1650 */
1652 const uint16_t *k,
1653 const uint16_t *v,
1654 float *scores,
1655 float *output,
1656 int num_heads,
1657 int num_kv_heads,
1658 int num_tokens,
1659 int head_dim,
1660 int aligned_head_dim,
1661 int aligned_context_window,
1662 float *scratch_q,
1663 float *scratch_k,
1664 float *scratch_v)
1665{
1666 const size_t q_elems = (size_t)num_heads * (size_t)num_tokens * (size_t)aligned_head_dim;
1667 const size_t kv_elems = (size_t)num_kv_heads * (size_t)num_tokens * (size_t)aligned_head_dim;
1668
1669 if (!scratch_q || !scratch_k || !scratch_v) return;
1670
1671 convert_bf16_tensor_to_buf(q, scratch_q, q_elems);
1672 convert_bf16_tensor_to_buf(k, scratch_k, kv_elems);
1673 convert_bf16_tensor_to_buf(v, scratch_v, kv_elems);
1674
1675 // Use exact version to avoid fast exp approximation error accumulating
1676 // with BF16 precision loss.
1677 attention_forward_causal_head_major_gqa_exact(scratch_q, scratch_k, scratch_v,
1678 scores, output,
1679 num_heads, num_kv_heads,
1680 num_tokens, head_dim,
1681 aligned_head_dim, aligned_context_window);
1682 /* No free - caller owns scratch buffers */
1683}
1684
1685// ============================================================================
1686// ATTENTION FORWARD - Flash-style (no scores materialization)
1687// ============================================================================
1688//
1689// Computes the same causal attention output as `attention_forward_causal_head_major_gqa`,
1690// but does not materialize the [H, T, T] score/weight matrices. This is useful for:
1691// - Prefill: avoids large scratch buffers and improves cache locality
1692// - Decode: supports KV-cache attention for a single token
1693//
1694// SIMD-optimized implementations for AVX-512, AVX2, and AVX follow.
1695
1696// ============================================================================
1697// AVX-512 SIMD Flash Attention (16 floats per vector)
1698// ============================================================================
1699#if defined(__AVX512F__)
1700static void attention_flash_query_causal_avx512(const float *q_vec,
1701 const float *k_head,
1702 const float *v_head,
1703 int kv_tokens,
1704 int head_dim,
1705 int aligned_head_dim,
1706 float scale,
1707 float *out_vec)
1708{
1709 // Online softmax: m = running max, s = running sum(exp(score - m))
1710 float m = -INFINITY;
1711 float s = 0.0f;
1712
1713 // Zero output using SIMD
1714 int d = 0;
1715 for (; d + 16 <= aligned_head_dim; d += 16) {
1716 _mm512_storeu_ps(&out_vec[d], _mm512_setzero_ps());
1717 }
1718 for (; d < aligned_head_dim; ++d) {
1719 out_vec[d] = 0.0f;
1720 }
1721
1722 for (int j = 0; j < kv_tokens; ++j) {
1723 const float *k_vec = k_head + (size_t)j * (size_t)aligned_head_dim;
1724 const float *v_vec = v_head + (size_t)j * (size_t)aligned_head_dim;
1725
1726 // Vectorized dot product Q·K
1727 __m512 dot_acc = _mm512_setzero_ps();
1728 d = 0;
1729 for (; d + 16 <= head_dim; d += 16) {
1730 __m512 q_v = _mm512_loadu_ps(&q_vec[d]);
1731 __m512 k_v = _mm512_loadu_ps(&k_vec[d]);
1732 dot_acc = _mm512_fmadd_ps(q_v, k_v, dot_acc);
1733 }
1734 float dot = _mm512_reduce_add_ps(dot_acc);
1735 // Scalar tail
1736 for (; d < head_dim; ++d) {
1737 dot += q_vec[d] * k_vec[d];
1738 }
1739 float score = dot * scale;
1740
1741 if (score > m) {
1742 float exp_m = (m == -INFINITY) ? 0.0f : expf(m - score);
1743 s *= exp_m;
1744
1745 // Vectorized: out *= exp_m, then out += v
1746 __m512 exp_m_vec = _mm512_set1_ps(exp_m);
1747 d = 0;
1748 for (; d + 16 <= head_dim; d += 16) {
1749 __m512 out_v = _mm512_loadu_ps(&out_vec[d]);
1750 __m512 v_v = _mm512_loadu_ps(&v_vec[d]);
1751 out_v = _mm512_fmadd_ps(out_v, exp_m_vec, v_v);
1752 _mm512_storeu_ps(&out_vec[d], out_v);
1753 }
1754 for (; d < head_dim; ++d) {
1755 out_vec[d] = out_vec[d] * exp_m + v_vec[d];
1756 }
1757
1758 s += 1.0f;
1759 m = score;
1760 } else {
1761 float e = expf(score - m);
1762 s += e;
1763
1764 // Vectorized: out += e * v
1765 __m512 e_vec = _mm512_set1_ps(e);
1766 d = 0;
1767 for (; d + 16 <= head_dim; d += 16) {
1768 __m512 out_v = _mm512_loadu_ps(&out_vec[d]);
1769 __m512 v_v = _mm512_loadu_ps(&v_vec[d]);
1770 out_v = _mm512_fmadd_ps(e_vec, v_v, out_v);
1771 _mm512_storeu_ps(&out_vec[d], out_v);
1772 }
1773 for (; d < head_dim; ++d) {
1774 out_vec[d] += e * v_vec[d];
1775 }
1776 }
1777 }
1778
1779 // Normalize: out /= s
1780 float inv_s = 1.0f / s;
1781 __m512 inv_s_vec = _mm512_set1_ps(inv_s);
1782 d = 0;
1783 for (; d + 16 <= head_dim; d += 16) {
1784 __m512 out_v = _mm512_loadu_ps(&out_vec[d]);
1785 _mm512_storeu_ps(&out_vec[d], _mm512_mul_ps(out_v, inv_s_vec));
1786 }
1787 for (; d < head_dim; ++d) {
1788 out_vec[d] *= inv_s;
1789 }
1790
1791 // Zero padding
1792 for (d = head_dim; d < aligned_head_dim; ++d) {
1793 out_vec[d] = 0.0f;
1794 }
1795}
1796#endif // __AVX512F__
1797
1798// ============================================================================
1799// AVX2 SIMD Flash Attention (8 floats per vector)
1800// ============================================================================
1801#if defined(__AVX2__)
1802static inline float hsum256_ps_flash(__m256 v) {
1803 __m128 hi = _mm256_extractf128_ps(v, 1);
1804 __m128 lo = _mm256_castps256_ps128(v);
1805 __m128 sum128 = _mm_add_ps(lo, hi);
1806 sum128 = _mm_hadd_ps(sum128, sum128);
1807 sum128 = _mm_hadd_ps(sum128, sum128);
1808 return _mm_cvtss_f32(sum128);
1809}
1810
1811static void attention_flash_query_causal_avx2(const float *q_vec,
1812 const float *k_head,
1813 const float *v_head,
1814 int kv_tokens,
1815 int head_dim,
1816 int aligned_head_dim,
1817 float scale,
1818 float *out_vec)
1819{
1820 float m = -INFINITY;
1821 float s = 0.0f;
1822
1823 // Zero output using SIMD
1824 int d = 0;
1825 for (; d + 8 <= aligned_head_dim; d += 8) {
1826 _mm256_storeu_ps(&out_vec[d], _mm256_setzero_ps());
1827 }
1828 for (; d < aligned_head_dim; ++d) {
1829 out_vec[d] = 0.0f;
1830 }
1831
1832 for (int j = 0; j < kv_tokens; ++j) {
1833 const float *k_vec = k_head + (size_t)j * (size_t)aligned_head_dim;
1834 const float *v_vec = v_head + (size_t)j * (size_t)aligned_head_dim;
1835
1836 // Vectorized dot product Q·K
1837 __m256 dot_acc = _mm256_setzero_ps();
1838 d = 0;
1839 for (; d + 8 <= head_dim; d += 8) {
1840 __m256 q_v = _mm256_loadu_ps(&q_vec[d]);
1841 __m256 k_v = _mm256_loadu_ps(&k_vec[d]);
1842 dot_acc = _mm256_fmadd_ps(q_v, k_v, dot_acc);
1843 }
1844 float dot = hsum256_ps_flash(dot_acc);
1845 for (; d < head_dim; ++d) {
1846 dot += q_vec[d] * k_vec[d];
1847 }
1848 float score = dot * scale;
1849
1850 if (score > m) {
1851 float exp_m = (m == -INFINITY) ? 0.0f : expf(m - score);
1852 s *= exp_m;
1853
1854 __m256 exp_m_vec = _mm256_set1_ps(exp_m);
1855 d = 0;
1856 for (; d + 8 <= head_dim; d += 8) {
1857 __m256 out_v = _mm256_loadu_ps(&out_vec[d]);
1858 __m256 v_v = _mm256_loadu_ps(&v_vec[d]);
1859 out_v = _mm256_fmadd_ps(out_v, exp_m_vec, v_v);
1860 _mm256_storeu_ps(&out_vec[d], out_v);
1861 }
1862 for (; d < head_dim; ++d) {
1863 out_vec[d] = out_vec[d] * exp_m + v_vec[d];
1864 }
1865
1866 s += 1.0f;
1867 m = score;
1868 } else {
1869 float e = expf(score - m);
1870 s += e;
1871
1872 __m256 e_vec = _mm256_set1_ps(e);
1873 d = 0;
1874 for (; d + 8 <= head_dim; d += 8) {
1875 __m256 out_v = _mm256_loadu_ps(&out_vec[d]);
1876 __m256 v_v = _mm256_loadu_ps(&v_vec[d]);
1877 out_v = _mm256_fmadd_ps(e_vec, v_v, out_v);
1878 _mm256_storeu_ps(&out_vec[d], out_v);
1879 }
1880 for (; d < head_dim; ++d) {
1881 out_vec[d] += e * v_vec[d];
1882 }
1883 }
1884 }
1885
1886 // Normalize
1887 float inv_s = 1.0f / s;
1888 __m256 inv_s_vec = _mm256_set1_ps(inv_s);
1889 d = 0;
1890 for (; d + 8 <= head_dim; d += 8) {
1891 __m256 out_v = _mm256_loadu_ps(&out_vec[d]);
1892 _mm256_storeu_ps(&out_vec[d], _mm256_mul_ps(out_v, inv_s_vec));
1893 }
1894 for (; d < head_dim; ++d) {
1895 out_vec[d] *= inv_s;
1896 }
1897
1898 for (d = head_dim; d < aligned_head_dim; ++d) {
1899 out_vec[d] = 0.0f;
1900 }
1901}
1902#endif // __AVX2__
1903
1904// ============================================================================
1905// AVX SIMD Flash Attention (8 floats per vector, no FMA)
1906// ============================================================================
1907#if defined(__AVX__) && !defined(__AVX2__)
1908static inline float hsum256_ps_flash_avx(__m256 v) {
1909 __m128 hi = _mm256_extractf128_ps(v, 1);
1910 __m128 lo = _mm256_castps256_ps128(v);
1911 __m128 sum128 = _mm_add_ps(lo, hi);
1912 sum128 = _mm_hadd_ps(sum128, sum128);
1913 sum128 = _mm_hadd_ps(sum128, sum128);
1914 return _mm_cvtss_f32(sum128);
1915}
1916
1917static void attention_flash_query_causal_avx(const float *q_vec,
1918 const float *k_head,
1919 const float *v_head,
1920 int kv_tokens,
1921 int head_dim,
1922 int aligned_head_dim,
1923 float scale,
1924 float *out_vec)
1925{
1926 float m = -INFINITY;
1927 float s = 0.0f;
1928
1929 // Zero output using SIMD
1930 int d = 0;
1931 for (; d + 8 <= aligned_head_dim; d += 8) {
1932 _mm256_storeu_ps(&out_vec[d], _mm256_setzero_ps());
1933 }
1934 for (; d < aligned_head_dim; ++d) {
1935 out_vec[d] = 0.0f;
1936 }
1937
1938 for (int j = 0; j < kv_tokens; ++j) {
1939 const float *k_vec = k_head + (size_t)j * (size_t)aligned_head_dim;
1940 const float *v_vec = v_head + (size_t)j * (size_t)aligned_head_dim;
1941
1942 // Vectorized dot product Q·K (no FMA, use mul + add)
1943 __m256 dot_acc = _mm256_setzero_ps();
1944 d = 0;
1945 for (; d + 8 <= head_dim; d += 8) {
1946 __m256 q_v = _mm256_loadu_ps(&q_vec[d]);
1947 __m256 k_v = _mm256_loadu_ps(&k_vec[d]);
1948 dot_acc = _mm256_add_ps(dot_acc, _mm256_mul_ps(q_v, k_v));
1949 }
1950 float dot = hsum256_ps_flash_avx(dot_acc);
1951 for (; d < head_dim; ++d) {
1952 dot += q_vec[d] * k_vec[d];
1953 }
1954 float score = dot * scale;
1955
1956 if (score > m) {
1957 float exp_m = (m == -INFINITY) ? 0.0f : expf(m - score);
1958 s *= exp_m;
1959
1960 __m256 exp_m_vec = _mm256_set1_ps(exp_m);
1961 d = 0;
1962 for (; d + 8 <= head_dim; d += 8) {
1963 __m256 out_v = _mm256_loadu_ps(&out_vec[d]);
1964 __m256 v_v = _mm256_loadu_ps(&v_vec[d]);
1965 // out = out * exp_m + v (no FMA)
1966 out_v = _mm256_add_ps(_mm256_mul_ps(out_v, exp_m_vec), v_v);
1967 _mm256_storeu_ps(&out_vec[d], out_v);
1968 }
1969 for (; d < head_dim; ++d) {
1970 out_vec[d] = out_vec[d] * exp_m + v_vec[d];
1971 }
1972
1973 s += 1.0f;
1974 m = score;
1975 } else {
1976 float e = expf(score - m);
1977 s += e;
1978
1979 __m256 e_vec = _mm256_set1_ps(e);
1980 d = 0;
1981 for (; d + 8 <= head_dim; d += 8) {
1982 __m256 out_v = _mm256_loadu_ps(&out_vec[d]);
1983 __m256 v_v = _mm256_loadu_ps(&v_vec[d]);
1984 // out = out + e * v (no FMA)
1985 out_v = _mm256_add_ps(out_v, _mm256_mul_ps(e_vec, v_v));
1986 _mm256_storeu_ps(&out_vec[d], out_v);
1987 }
1988 for (; d < head_dim; ++d) {
1989 out_vec[d] += e * v_vec[d];
1990 }
1991 }
1992 }
1993
1994 // Normalize
1995 float inv_s = 1.0f / s;
1996 __m256 inv_s_vec = _mm256_set1_ps(inv_s);
1997 d = 0;
1998 for (; d + 8 <= head_dim; d += 8) {
1999 __m256 out_v = _mm256_loadu_ps(&out_vec[d]);
2000 _mm256_storeu_ps(&out_vec[d], _mm256_mul_ps(out_v, inv_s_vec));
2001 }
2002 for (; d < head_dim; ++d) {
2003 out_vec[d] *= inv_s;
2004 }
2005
2006 for (d = head_dim; d < aligned_head_dim; ++d) {
2007 out_vec[d] = 0.0f;
2008 }
2009}
2010#endif // __AVX__ && !__AVX2__
2011
2012// ============================================================================
2013// Scalar fallback (original implementation)
2014// ============================================================================
2015static void attention_flash_query_causal(const float *q_vec,
2016 const float *k_head,
2017 const float *v_head,
2018 int kv_tokens,
2019 int head_dim,
2020 int aligned_head_dim,
2021 float scale,
2022 float *out_vec)
2023{
2024 // Online softmax:
2025 // m = running max, s = running sum(exp(score - m))
2026 // out = sum(exp(score - m) * v)
2027 float m = -INFINITY;
2028 float s = 0.0f;
2029
2030 for (int d = 0; d < head_dim; ++d) {
2031 out_vec[d] = 0.0f;
2032 }
2033
2034 for (int j = 0; j < kv_tokens; ++j) {
2035 const float *k_vec = k_head + (size_t)j * (size_t)aligned_head_dim;
2036 const float *v_vec = v_head + (size_t)j * (size_t)aligned_head_dim;
2037
2038 float dot = 0.0f;
2039 for (int d = 0; d < head_dim; ++d) {
2040 dot += q_vec[d] * k_vec[d];
2041 }
2042 float score = dot * scale;
2043
2044 if (score > m) {
2045 float exp_m = (m == -INFINITY) ? 0.0f : expf(m - score);
2046 s *= exp_m;
2047 for (int d = 0; d < head_dim; ++d) {
2048 out_vec[d] *= exp_m;
2049 }
2050 s += 1.0f;
2051 for (int d = 0; d < head_dim; ++d) {
2052 out_vec[d] += v_vec[d];
2053 }
2054 m = score;
2055 } else {
2056 float e = expf(score - m);
2057 s += e;
2058 for (int d = 0; d < head_dim; ++d) {
2059 out_vec[d] += e * v_vec[d];
2060 }
2061 }
2062 }
2063
2064 float inv_s = 1.0f / s;
2065 for (int d = 0; d < head_dim; ++d) {
2066 out_vec[d] *= inv_s;
2067 }
2068 for (int d = head_dim; d < aligned_head_dim; ++d) {
2069 out_vec[d] = 0.0f;
2070 }
2071}
2072
2073// Strict parity reference for flash-style query path.
2074// Uses a two-pass exact softmax formulation per query:
2075// 1) max(score), 2) exp(score-max) accumulation for sum and weighted V.
2076// This avoids online-softmax re-normalization drift in long reductions.
2077static void attention_flash_query_causal_exact(const float *q_vec,
2078 const float *k_head,
2079 const float *v_head,
2080 int kv_tokens,
2081 int head_dim,
2082 int aligned_head_dim,
2083 float scale,
2084 float *out_vec)
2085{
2086 if (kv_tokens <= 0) {
2087 for (int d = 0; d < aligned_head_dim; ++d) {
2088 out_vec[d] = 0.0f;
2089 }
2090 return;
2091 }
2092
2093 for (int d = 0; d < aligned_head_dim; ++d) {
2094 out_vec[d] = 0.0f;
2095 }
2096
2097 float max_score = -INFINITY;
2098 for (int j = 0; j < kv_tokens; ++j) {
2099 const float *k_vec = k_head + (size_t)j * (size_t)aligned_head_dim;
2100 float dot = 0.0f;
2101 for (int d = 0; d < head_dim; ++d) {
2102 dot += q_vec[d] * k_vec[d];
2103 }
2104 float score = dot * scale;
2105 if (score > max_score) {
2106 max_score = score;
2107 }
2108 }
2109
2110 float sum = 0.0f;
2111 for (int j = 0; j < kv_tokens; ++j) {
2112 const float *k_vec = k_head + (size_t)j * (size_t)aligned_head_dim;
2113 const float *v_vec = v_head + (size_t)j * (size_t)aligned_head_dim;
2114 float dot = 0.0f;
2115 for (int d = 0; d < head_dim; ++d) {
2116 dot += q_vec[d] * k_vec[d];
2117 }
2118 float score = dot * scale;
2119 float w = expf(score - max_score);
2120 sum += w;
2121 for (int d = 0; d < head_dim; ++d) {
2122 out_vec[d] += w * v_vec[d];
2123 }
2124 }
2125
2126 if (sum > 0.0f) {
2127 float inv_sum = 1.0f / sum;
2128 for (int d = 0; d < head_dim; ++d) {
2129 out_vec[d] *= inv_sum;
2130 }
2131 } else {
2132 for (int d = 0; d < head_dim; ++d) {
2133 out_vec[d] = 0.0f;
2134 }
2135 }
2136 for (int d = head_dim; d < aligned_head_dim; ++d) {
2137 out_vec[d] = 0.0f;
2138 }
2139}
2140
2141static void ck_attention_vec_dump_exact_query(const float *q_vec,
2142 const float *k_head,
2143 const float *out_vec,
2144 int kv_tokens,
2145 int head_dim,
2146 int aligned_head_dim,
2147 float scale,
2148 int layer_id,
2149 int head_id,
2150 int query_id)
2151{
2152 if (!ck_attention_vec_dump_should_emit(layer_id, head_id, query_id)) {
2153 return;
2154 }
2155
2156 float *raw_scores = (float *) alloca((size_t) kv_tokens * sizeof(float));
2157 float *probs = (float *) alloca((size_t) kv_tokens * sizeof(float));
2158 float max_score = -INFINITY;
2159 for (int j = 0; j < kv_tokens; ++j) {
2160 const float *k_vec = k_head + (size_t) j * (size_t) aligned_head_dim;
2161 float dot = 0.0f;
2162 for (int d = 0; d < head_dim; ++d) {
2163 dot += q_vec[d] * k_vec[d];
2164 }
2165 raw_scores[j] = dot;
2166 const float scaled = dot * scale;
2167 probs[j] = scaled;
2168 if (scaled > max_score) {
2169 max_score = scaled;
2170 }
2171 }
2172
2173 float sum = 0.0f;
2174 for (int j = 0; j < kv_tokens; ++j) {
2175 probs[j] = expf(probs[j] - max_score);
2176 sum += probs[j];
2177 }
2178 if (sum > 0.0f) {
2179 const float inv_sum = 1.0f / sum;
2180 for (int j = 0; j < kv_tokens; ++j) {
2181 probs[j] *= inv_sum;
2182 }
2183 } else {
2184 memset(probs, 0, (size_t) kv_tokens * sizeof(float));
2185 }
2186
2187 ck_attention_vec_dump_selected_query(raw_scores, probs, out_vec, NULL,
2188 kv_tokens, head_dim,
2189 layer_id, head_id, query_id);
2190}
2191
2192// Llama-parity attention reference: K/V are rounded through F16 before use.
2193static void attention_flash_query_causal_exact_f16kv(const float *q_vec,
2194 const float *k_head,
2195 const float *v_head,
2196 int kv_tokens,
2197 int head_dim,
2198 int aligned_head_dim,
2199 float scale,
2200 float *out_vec)
2201{
2202 if (kv_tokens <= 0) {
2203 for (int d = 0; d < aligned_head_dim; ++d) {
2204 out_vec[d] = 0.0f;
2205 }
2206 return;
2207 }
2208
2209 for (int d = 0; d < aligned_head_dim; ++d) {
2210 out_vec[d] = 0.0f;
2211 }
2212
2213 // Mirror llama.cpp GGML flash-attention more closely:
2214 // - Q is converted through FP16 before the KQ dot
2215 // - V accumulation is rounded through FP16 at each update
2216 // - the softmax accumulator uses the online max/sum form
2217 float sum = 0.0f;
2218 float max_score = -INFINITY;
2219 for (int j = 0; j < kv_tokens; ++j) {
2220 const float *k_vec = k_head + (size_t)j * (size_t)aligned_head_dim;
2221 const float *v_vec = v_head + (size_t)j * (size_t)aligned_head_dim;
2222 float dot = 0.0f;
2223 for (int d = 0; d < head_dim; ++d) {
2224 dot += ck_round_fp16_scalar(q_vec[d]) * ck_round_fp16_scalar(k_vec[d]);
2225 }
2226 float score = dot * scale;
2227
2228 const float prev_max = max_score;
2229 float max_scale = 1.0f;
2230 float value_scale = 1.0f;
2231
2232 if (score > max_score) {
2233 max_score = score;
2234 max_scale = isfinite(prev_max) ? expf(prev_max - max_score) : 0.0f;
2235 for (int d = 0; d < head_dim; ++d) {
2236 out_vec[d] = ck_round_fp16_scalar(out_vec[d] * max_scale);
2237 }
2238 } else {
2239 value_scale = expf(score - max_score);
2240 }
2241
2242 for (int d = 0; d < head_dim; ++d) {
2243 const float v_rounded = ck_round_fp16_scalar(v_vec[d]);
2244 const float updated = out_vec[d] + value_scale * v_rounded;
2245 out_vec[d] = ck_round_fp16_scalar(updated);
2246 }
2247
2248 sum = sum * max_scale + value_scale;
2249 }
2250
2251 if (sum > 0.0f) {
2252 float inv_sum = 1.0f / sum;
2253 for (int d = 0; d < head_dim; ++d) {
2254 out_vec[d] *= inv_sum;
2255 }
2256 } else {
2257 for (int d = 0; d < head_dim; ++d) {
2258 out_vec[d] = 0.0f;
2259 }
2260 }
2261 for (int d = head_dim; d < aligned_head_dim; ++d) {
2262 out_vec[d] = 0.0f;
2263 }
2264}
2265
2266// K/V are already rounded through FP16 once for the complete attention call.
2267// Keep the online softmax and FP16 accumulator order identical to the oracle.
2269 const float *q_vec, const float *k_head, const float *v_head,
2270 int kv_tokens, int head_dim, int aligned_head_dim, float scale,
2271 float *out_vec)
2272{
2273 if (kv_tokens <= 0) {
2274 memset(out_vec, 0, (size_t)aligned_head_dim * sizeof(float));
2275 return;
2276 }
2277
2278 float *q_rounded = (float *)alloca((size_t)head_dim * sizeof(float));
2279 ck_round_fp16_buffer(q_vec, q_rounded, (size_t)head_dim);
2280 memset(out_vec, 0, (size_t)aligned_head_dim * sizeof(float));
2281
2282 float sum = 0.0f;
2283 float max_score = -INFINITY;
2284 for (int j = 0; j < kv_tokens; ++j) {
2285 const float *k_vec = k_head + (size_t)j * (size_t)aligned_head_dim;
2286 const float *v_vec = v_head + (size_t)j * (size_t)aligned_head_dim;
2287 float dot = 0.0f;
2288 for (int d = 0; d < head_dim; ++d) {
2289 dot += q_rounded[d] * k_vec[d];
2290 }
2291 const float score = dot * scale;
2292 const float prev_max = max_score;
2293 float max_scale = 1.0f;
2294 float value_scale = 1.0f;
2295 if (score > max_score) {
2296 max_score = score;
2297 max_scale = isfinite(prev_max) ? expf(prev_max - max_score) : 0.0f;
2298 for (int d = 0; d < head_dim; ++d) {
2299 out_vec[d] = ck_round_fp16_scalar(out_vec[d] * max_scale);
2300 }
2301 } else {
2302 value_scale = expf(score - max_score);
2303 }
2304 for (int d = 0; d < head_dim; ++d) {
2305 const float updated = out_vec[d] + value_scale * v_vec[d];
2306 out_vec[d] = ck_round_fp16_scalar(updated);
2307 }
2308 sum = sum * max_scale + value_scale;
2309 }
2310
2311 if (sum > 0.0f) {
2312 const float inv_sum = 1.0f / sum;
2313 for (int d = 0; d < head_dim; ++d) {
2314 out_vec[d] *= inv_sum;
2315 }
2316 } else {
2317 memset(out_vec, 0, (size_t)head_dim * sizeof(float));
2318 }
2319}
2320
2322 const float *k_head,
2323 const float *v_cols,
2324 int kv_tokens,
2325 int head_dim,
2326 int aligned_head_dim,
2327 float scale,
2328 float *score_row,
2329 float *out_vec,
2330 int layer_id,
2331 int head_id,
2332 int query_id)
2333{
2334 if (kv_tokens <= 0) {
2335 for (int d = 0; d < aligned_head_dim; ++d) {
2336 out_vec[d] = 0.0f;
2337 }
2338 return;
2339 }
2340
2341 for (int j = 0; j < kv_tokens; ++j) {
2342 const float *k_vec = k_head + (size_t) j * (size_t) aligned_head_dim;
2343 score_row[j] = ck_vec_dot_f32_strict(q_vec, k_vec, head_dim);
2344 }
2345 float *raw_dump = NULL;
2346 if (ck_attention_vec_dump_should_emit(layer_id, head_id, query_id)) {
2347 raw_dump = (float *) alloca((size_t) kv_tokens * sizeof(float));
2348 memcpy(raw_dump, score_row, (size_t) kv_tokens * sizeof(float));
2349 }
2350
2351 float max_score = -INFINITY;
2352 for (int j = 0; j < kv_tokens; ++j) {
2353 const float score = score_row[j] * scale;
2354 score_row[j] = score;
2355 if (score > max_score) {
2356 max_score = score;
2357 }
2358 }
2359
2360 double sum = 0.0;
2361 for (int j = 0; j < kv_tokens; ++j) {
2362 const float w = expf(score_row[j] - max_score);
2363 score_row[j] = w;
2364 volatile double next = sum + (double) w;
2365 sum = next;
2366 }
2367
2368 if (sum > 0.0) {
2369 const float inv_sum = (float) (1.0 / sum);
2370 for (int j = 0; j < kv_tokens; ++j) {
2371 score_row[j] *= inv_sum;
2372 }
2373 for (int d = 0; d < head_dim; ++d) {
2374 const float *v_col = v_cols + (size_t) d * (size_t) kv_tokens;
2375 const float dot = ck_vec_dot_f32x_f32_to_f32_via_f64(score_row, v_col, kv_tokens);
2376 out_vec[d] = dot;
2377 }
2378 } else {
2379 for (int d = 0; d < head_dim; ++d) {
2380 out_vec[d] = 0.0f;
2381 }
2382 }
2383
2384 for (int d = head_dim; d < aligned_head_dim; ++d) {
2385 out_vec[d] = 0.0f;
2386 }
2387 if (raw_dump) {
2388 ck_attention_vec_dump_selected_query(raw_dump, score_row, out_vec, v_cols, kv_tokens, head_dim,
2389 layer_id, head_id, query_id);
2390 }
2391}
2392
2394 const float *k_head,
2395 const float *v_cols,
2396 int kv_tokens,
2397 int head_dim,
2398 int aligned_head_dim,
2399 float scale,
2400 float *score_row,
2401 float *out_vec,
2402 int layer_id,
2403 int head_id,
2404 int query_id)
2405{
2406 if (kv_tokens <= 0) {
2407 for (int d = 0; d < aligned_head_dim; ++d) {
2408 out_vec[d] = 0.0f;
2409 }
2410 return;
2411 }
2412
2413 for (int j = 0; j < kv_tokens; ++j) {
2414 const float *k_vec = k_head + (size_t) j * (size_t) aligned_head_dim;
2415 score_row[j] = ck_ggml_vec_dot_f32_contig(q_vec, k_vec, head_dim);
2416 }
2417 float *raw_dump = NULL;
2418 if (ck_attention_vec_dump_should_emit(layer_id, head_id, query_id)) {
2419 raw_dump = (float *) alloca((size_t) kv_tokens * sizeof(float));
2420 memcpy(raw_dump, score_row, (size_t) kv_tokens * sizeof(float));
2421 }
2422
2423 float *logit_row = (float *) alloca((size_t) kv_tokens * sizeof(float));
2424 memcpy(logit_row, score_row, (size_t) kv_tokens * sizeof(float));
2425 ck_vec_scale_f32_inplace(logit_row, kv_tokens, scale);
2426 const float max_score = ck_vec_max_f32_contig(logit_row, kv_tokens);
2427 const double sum = ck_ggml_vec_soft_max_row(kv_tokens, score_row, logit_row, max_score);
2428 if (sum > 0.0) {
2429 const float inv_sum = (float) (1.0 / sum);
2430 ck_vec_scale_f32_inplace(score_row, kv_tokens, inv_sum);
2431 for (int d = 0; d < head_dim; ++d) {
2432 const float *v_col = v_cols + (size_t) d * (size_t) kv_tokens;
2433 out_vec[d] = ck_ggml_vec_dot_f32_contig(score_row, v_col, kv_tokens);
2434 }
2435 } else {
2436 for (int d = 0; d < head_dim; ++d) {
2437 out_vec[d] = 0.0f;
2438 }
2439 }
2440
2441 for (int d = head_dim; d < aligned_head_dim; ++d) {
2442 out_vec[d] = 0.0f;
2443 }
2444 if (raw_dump) {
2445 ck_attention_vec_dump_selected_query(raw_dump, score_row, out_vec, v_cols, kv_tokens, head_dim,
2446 layer_id, head_id, query_id);
2447 }
2448}
2449
2450#if CK_ENABLE_LLAMA_CPP_PARITY
2451static CK_NOINLINE CK_OPTNONE void attention_query_full_dyn_ggml_regular(const float *q_vec,
2452 const float *k_head,
2453 const float *v_cols,
2454 int kv_tokens,
2455 int head_dim,
2456 int aligned_head_dim,
2457 float scale,
2458 float *score_row,
2459 float *prob_row,
2460 float *out_vec,
2461 ck_ggml_vec_dot_f32_fn dot_fn,
2462 ck_ggml_vec_soft_max_f32_fn softmax_fn,
2463 int layer_id,
2464 int head_id,
2465 int query_id)
2466{
2467 if (kv_tokens <= 0 || !dot_fn || !softmax_fn || !score_row || !prob_row) {
2468 for (int d = 0; d < aligned_head_dim; ++d) {
2469 out_vec[d] = 0.0f;
2470 }
2471 return;
2472 }
2473
2474 for (int j = 0; j < kv_tokens; ++j) {
2475 const float *k_vec = k_head + (size_t) j * (size_t) aligned_head_dim;
2476 float dot = 0.0f;
2477 dot_fn(head_dim, &dot, 0, q_vec, 0, k_vec, 0, 1);
2478 score_row[j] = dot;
2479 }
2480 float *raw_dump = NULL;
2481 if (ck_attention_vec_dump_should_emit(layer_id, head_id, query_id)) {
2482 raw_dump = (float *) alloca((size_t) kv_tokens * sizeof(float));
2483 memcpy(raw_dump, score_row, (size_t) kv_tokens * sizeof(float));
2484 }
2485
2486 // Mirror ggml_compute_forward_soft_max_f32:
2487 // copy raw scores to scratch, scale the scratch buffer, compute the max from
2488 // that scaled buffer, then emit exp/logits into a separate output buffer.
2489 memcpy(prob_row, score_row, (size_t) kv_tokens * sizeof(float));
2490 ck_vec_scale_f32_inplace(prob_row, kv_tokens, scale);
2491 const float max_score = ck_vec_max_f32_contig(prob_row, kv_tokens);
2492 const double sum = softmax_fn(kv_tokens, score_row, prob_row, max_score);
2493 if (sum > 0.0) {
2494 const float inv_sum = (float) (1.0 / sum);
2495 ck_vec_scale_f32_inplace(score_row, kv_tokens, inv_sum);
2496 const int reverse_out_dot = ck_attention_reverse_out_dot_enabled();
2497 for (int d = 0; d < head_dim; ++d) {
2498 const float *v_col = v_cols + (size_t) d * (size_t) kv_tokens;
2499 if (reverse_out_dot) {
2500 out_vec[d] = ck_vec_dot_f32_reverse_strict(score_row, v_col, kv_tokens);
2501 } else {
2502 float dot = 0.0f;
2503 dot_fn(kv_tokens, &dot, 0, score_row, 0, v_col, 0, 1);
2504 out_vec[d] = dot;
2505 }
2506 }
2507 } else {
2508 for (int d = 0; d < head_dim; ++d) {
2509 out_vec[d] = 0.0f;
2510 }
2511 }
2512
2513 for (int d = head_dim; d < aligned_head_dim; ++d) {
2514 out_vec[d] = 0.0f;
2515 }
2516 if (raw_dump) {
2517 ck_attention_vec_dump_selected_query(raw_dump, score_row, out_vec, v_cols, kv_tokens, head_dim,
2518 layer_id, head_id, query_id);
2519 }
2520}
2521
2522static int attention_out_mul_mat_graph_block(const float *v_cols,
2523 const float *prob_block,
2524 int kv_tokens,
2525 int head_dim,
2526 int query_block,
2527 float *out_block)
2528{
2529 ck_ggml_cpu_init_fn ggml_cpu_init_fn = ck_resolve_ggml_cpu_init();
2530 ck_ggml_init_fn ggml_init_fn = ck_resolve_ggml_init();
2531 ck_ggml_free_fn ggml_free_fn = ck_resolve_ggml_free();
2534 ck_ggml_new_graph_fn ggml_new_graph_fn = ck_resolve_ggml_new_graph();
2537 ck_ggml_set_input_fn ggml_set_input_fn = ck_resolve_ggml_set_input();
2538
2539 if (!ggml_cpu_init_fn || !ggml_init_fn || !ggml_free_fn ||
2540 !ggml_new_tensor_2d_fn || !ggml_mul_mat_fn || !ggml_new_graph_fn ||
2541 !ggml_build_forward_expand_fn || !ggml_graph_compute_with_ctx_fn ||
2542 !ggml_set_input_fn || !v_cols || !prob_block || !out_block ||
2543 kv_tokens <= 0 || head_dim <= 0 || query_block <= 0) {
2544 return 0;
2545 }
2546
2547 ggml_cpu_init_fn();
2548
2549 const size_t out_bytes = (size_t) head_dim * (size_t) query_block * sizeof(float);
2550 const size_t mem_size = (size_t) 8 * 1024 * 1024 + out_bytes + (size_t) 512 * 1024;
2551 struct ggml_init_params params = {
2552 .mem_size = mem_size,
2553 .mem_buffer = NULL,
2554 .no_alloc = false,
2555 };
2556 struct ggml_context *ctx = ggml_init_fn(params);
2557 if (!ctx) {
2558 return 0;
2559 }
2560
2561 int ok = 0;
2562 struct ggml_tensor *v_tensor = ggml_new_tensor_2d_fn(ctx, GGML_TYPE_F32, kv_tokens, head_dim);
2563 struct ggml_tensor *prob_tensor = ggml_new_tensor_2d_fn(ctx, GGML_TYPE_F32, kv_tokens, query_block);
2564 if (!v_tensor || !prob_tensor) {
2565 ggml_free_fn(ctx);
2566 return 0;
2567 }
2568
2569 v_tensor->data = (void *) v_cols;
2570 prob_tensor->data = (void *) prob_block;
2571 ggml_set_input_fn(v_tensor);
2572 ggml_set_input_fn(prob_tensor);
2573
2574 struct ggml_tensor *out_tensor = ggml_mul_mat_fn(ctx, v_tensor, prob_tensor);
2575 struct ggml_cgraph *gf = out_tensor ? ggml_new_graph_fn(ctx) : NULL;
2576 if (!out_tensor || !gf) {
2577 ggml_free_fn(ctx);
2578 return 0;
2579 }
2580
2581 ggml_build_forward_expand_fn(gf, out_tensor);
2582 if (ggml_graph_compute_with_ctx_fn(ctx, gf, 1) == GGML_STATUS_SUCCESS) {
2583 memcpy(out_block, out_tensor->data, out_bytes);
2584 ok = 1;
2585 }
2586
2587 ggml_free_fn(ctx);
2588 return ok;
2589}
2590
2591static CK_NOINLINE CK_OPTNONE int attention_head_full_dyn_ggml_regular_graph_out(const float *q_head,
2592 const float *k_head,
2593 const float *v_cols,
2594 int kv_tokens,
2595 int head_dim,
2596 int aligned_head_dim,
2597 float scale,
2598 float *score_row,
2599 float *prob_row,
2600 float *out_head,
2601 ck_ggml_vec_dot_f32_fn dot_fn,
2602 ck_ggml_vec_soft_max_f32_fn softmax_fn,
2603 int layer_id,
2604 int head_id)
2605{
2606 if (!q_head || !k_head || !v_cols || !out_head || !dot_fn || !softmax_fn ||
2607 kv_tokens <= 0 || head_dim <= 0 || aligned_head_dim < head_dim) {
2608 return 0;
2609 }
2610
2611 enum { CK_STRICT_OUT_BLOCK = 64 };
2612 float *prob_block = (float *) alloca((size_t) CK_STRICT_OUT_BLOCK * (size_t) kv_tokens * sizeof(float));
2613 float *out_block = (float *) alloca((size_t) CK_STRICT_OUT_BLOCK * (size_t) head_dim * sizeof(float));
2614 float *raw_dump = (float *) alloca((size_t) kv_tokens * sizeof(float));
2615 for (int q0 = 0; q0 < kv_tokens; q0 += CK_STRICT_OUT_BLOCK) {
2616 const int qn = (q0 + CK_STRICT_OUT_BLOCK <= kv_tokens) ? CK_STRICT_OUT_BLOCK : (kv_tokens - q0);
2617 float *block_probs = prob_block;
2618 float *block_out = out_block;
2619 int have_raw_dump = 0;
2620 int dump_query = -1;
2621 int dump_qi = -1;
2622
2623 for (int qi = 0; qi < qn; ++qi) {
2624 const int query_id = q0 + qi;
2625 const float *q_vec = q_head + (size_t) query_id * (size_t) aligned_head_dim;
2626 float *prob_col = block_probs + (size_t) qi * (size_t) kv_tokens;
2627
2628 for (int j = 0; j < kv_tokens; ++j) {
2629 const float *k_vec = k_head + (size_t) j * (size_t) aligned_head_dim;
2630 float dot = 0.0f;
2631 dot_fn(head_dim, &dot, 0, q_vec, 0, k_vec, 0, 1);
2632 score_row[j] = dot;
2633 }
2634
2635 if (ck_attention_vec_dump_should_emit(layer_id, head_id, query_id)) {
2636 memcpy(raw_dump, score_row, (size_t) kv_tokens * sizeof(float));
2637 have_raw_dump = 1;
2638 dump_query = query_id;
2639 dump_qi = qi;
2640 }
2641
2642 memcpy(prob_row, score_row, (size_t) kv_tokens * sizeof(float));
2643 ck_vec_scale_f32_inplace(prob_row, kv_tokens, scale);
2644 const float max_score = ck_vec_max_f32_contig(prob_row, kv_tokens);
2645 const double sum = softmax_fn(kv_tokens, prob_col, prob_row, max_score);
2646 if (sum > 0.0) {
2647 const float inv_sum = (float) (1.0 / sum);
2648 ck_vec_scale_f32_inplace(prob_col, kv_tokens, inv_sum);
2649 } else {
2650 memset(prob_col, 0, (size_t) kv_tokens * sizeof(float));
2651 }
2652 }
2653
2654 if (!attention_out_mul_mat_graph_block(v_cols, block_probs, kv_tokens, head_dim, qn, block_out)) {
2655 return 0;
2656 }
2657
2658 for (int qi = 0; qi < qn; ++qi) {
2659 float *dst = out_head + (size_t) (q0 + qi) * (size_t) aligned_head_dim;
2660 const float *src = block_out + (size_t) qi * (size_t) head_dim;
2661 memcpy(dst, src, (size_t) head_dim * sizeof(float));
2662 for (int d = head_dim; d < aligned_head_dim; ++d) {
2663 dst[d] = 0.0f;
2664 }
2665 }
2666
2667 if (have_raw_dump && dump_qi >= 0) {
2669 block_probs + (size_t) dump_qi * (size_t) kv_tokens,
2670 block_out + (size_t) dump_qi * (size_t) head_dim,
2671 v_cols,
2672 kv_tokens,
2673 head_dim,
2674 layer_id,
2675 head_id,
2676 dump_query);
2677 }
2678 }
2679
2680 return 1;
2681}
2682
2683static CK_NOINLINE CK_OPTNONE void attention_query_full_dyn_ggml_regular_matmul_out(const float *q_vec,
2684 const float *k_head,
2685 const float *v_cols,
2686 int kv_tokens,
2687 int head_dim,
2688 int aligned_head_dim,
2689 float scale,
2690 float *score_row,
2691 float *prob_row,
2692 float *out_vec,
2693 ck_ggml_vec_dot_f32_fn dot_fn,
2694 ck_ggml_vec_soft_max_f32_fn softmax_fn,
2695 ck_ggml_compute_forward_mul_mat_fn mul_mat_fn,
2696 int layer_id,
2697 int head_id,
2698 int query_id)
2699{
2700 if (kv_tokens <= 0 || !dot_fn || !softmax_fn || !mul_mat_fn || !score_row || !prob_row) {
2701 for (int d = 0; d < aligned_head_dim; ++d) {
2702 out_vec[d] = 0.0f;
2703 }
2704 return;
2705 }
2706
2707 for (int j = 0; j < kv_tokens; ++j) {
2708 const float *k_vec = k_head + (size_t) j * (size_t) aligned_head_dim;
2709 float dot = 0.0f;
2710 dot_fn(head_dim, &dot, 0, q_vec, 0, k_vec, 0, 1);
2711 score_row[j] = dot;
2712 }
2713 float *raw_dump = NULL;
2714 if (ck_attention_vec_dump_should_emit(layer_id, head_id, query_id)) {
2715 raw_dump = (float *) alloca((size_t) kv_tokens * sizeof(float));
2716 memcpy(raw_dump, score_row, (size_t) kv_tokens * sizeof(float));
2717 }
2718
2719 memcpy(prob_row, score_row, (size_t) kv_tokens * sizeof(float));
2720 ck_vec_scale_f32_inplace(prob_row, kv_tokens, scale);
2721 const float max_score = ck_vec_max_f32_contig(prob_row, kv_tokens);
2722 const double sum = softmax_fn(kv_tokens, score_row, prob_row, max_score);
2723 if (sum > 0.0) {
2724 const float inv_sum = (float) (1.0 / sum);
2725 ck_vec_scale_f32_inplace(score_row, kv_tokens, inv_sum);
2726
2727 const size_t prob_row_bytes = (size_t) kv_tokens * sizeof(float);
2728 const size_t out_row_bytes = (size_t) head_dim * sizeof(float);
2729
2730 struct ggml_tensor v_tensor;
2731 struct ggml_tensor prob_tensor;
2732 struct ggml_tensor out_tensor;
2733
2734 ck_ggml_init_tensor_f32(&v_tensor,
2735 kv_tokens, head_dim, 1, 1,
2736 sizeof(float),
2737 prob_row_bytes,
2738 prob_row_bytes * (size_t) head_dim,
2739 prob_row_bytes * (size_t) head_dim,
2740 (void *) v_cols);
2741 ck_ggml_init_tensor_f32(&prob_tensor,
2742 kv_tokens, 1, 1, 1,
2743 sizeof(float),
2744 prob_row_bytes,
2745 prob_row_bytes,
2746 prob_row_bytes,
2747 score_row);
2748 ck_ggml_init_tensor_f32(&out_tensor,
2749 head_dim, 1, 1, 1,
2750 sizeof(float),
2751 out_row_bytes,
2752 out_row_bytes,
2753 out_row_bytes,
2754 out_vec);
2755 out_tensor.src[0] = &v_tensor;
2756 out_tensor.src[1] = &prob_tensor;
2757
2758 memset(out_vec, 0, (size_t) head_dim * sizeof(float));
2759 struct ggml_compute_params mul_params = {
2760 .ith = 0,
2761 .nth = 1,
2762 .wsize = 0,
2763 .wdata = NULL,
2764 .threadpool = NULL,
2765 .use_ref = false,
2766 };
2767 mul_mat_fn(&mul_params, &out_tensor);
2768 } else {
2769 for (int d = 0; d < head_dim; ++d) {
2770 out_vec[d] = 0.0f;
2771 }
2772 }
2773
2774 for (int d = head_dim; d < aligned_head_dim; ++d) {
2775 out_vec[d] = 0.0f;
2776 }
2777 if (raw_dump) {
2778 ck_attention_vec_dump_selected_query(raw_dump, score_row, out_vec, v_cols, kv_tokens, head_dim,
2779 layer_id, head_id, query_id);
2780 }
2781}
2782
2783static CK_NOINLINE CK_OPTNONE void attention_query_full_ggml_compute_regular(const float *q_vec,
2784 const float *k_head,
2785 const float *v_cols,
2786 int kv_tokens,
2787 int head_dim,
2788 int aligned_head_dim,
2789 float scale,
2790 float *score_row,
2791 float *prob_row,
2792 float *out_vec,
2793 ck_ggml_vec_dot_f32_fn dot_fn,
2794 ck_ggml_compute_forward_mul_mat_fn mul_mat_fn,
2795 ck_ggml_compute_forward_soft_max_fn softmax_compute_fn)
2796{
2797 if (kv_tokens <= 0 || !dot_fn || !mul_mat_fn || !softmax_compute_fn) {
2798 for (int d = 0; d < aligned_head_dim; ++d) {
2799 out_vec[d] = 0.0f;
2800 }
2801 return;
2802 }
2803
2804 const size_t k_row_bytes = (size_t) aligned_head_dim * sizeof(float);
2805 const size_t q_row_bytes = (size_t) head_dim * sizeof(float);
2806 const size_t score_row_bytes = (size_t) kv_tokens * sizeof(float);
2807 const size_t softmax_work_elems = (size_t) kv_tokens + 16u;
2808 float *softmax_work = (float *) alloca(softmax_work_elems * sizeof(float));
2809
2810 struct ggml_tensor k_tensor;
2811 struct ggml_tensor q_tensor;
2812 struct ggml_tensor score_tensor;
2813 struct ggml_tensor soft_tensor;
2814
2815 ck_ggml_init_tensor_f32(&k_tensor,
2816 head_dim, kv_tokens, 1, 1,
2817 sizeof(float),
2818 k_row_bytes,
2819 k_row_bytes * (size_t) kv_tokens,
2820 k_row_bytes * (size_t) kv_tokens,
2821 (void *) k_head);
2822 ck_ggml_init_tensor_f32(&q_tensor,
2823 head_dim, 1, 1, 1,
2824 sizeof(float),
2825 q_row_bytes,
2826 q_row_bytes,
2827 q_row_bytes,
2828 (void *) q_vec);
2829 ck_ggml_init_tensor_f32(&score_tensor,
2830 kv_tokens, 1, 1, 1,
2831 sizeof(float),
2832 score_row_bytes,
2833 score_row_bytes,
2834 score_row_bytes,
2835 score_row);
2836 score_tensor.src[0] = &k_tensor;
2837 score_tensor.src[1] = &q_tensor;
2838
2839 struct ggml_compute_params mul_params = {
2840 .ith = 0,
2841 .nth = 1,
2842 .wsize = 0,
2843 .wdata = NULL,
2844 .threadpool = NULL,
2845 .use_ref = false,
2846 };
2847 mul_mat_fn(&mul_params, &score_tensor);
2848
2849 ck_ggml_init_tensor_f32(&soft_tensor,
2850 kv_tokens, 1, 1, 1,
2851 sizeof(float),
2852 score_row_bytes,
2853 score_row_bytes,
2854 score_row_bytes,
2855 prob_row);
2856 soft_tensor.src[0] = &score_tensor;
2857 {
2858 const float max_bias = 0.0f;
2859 memcpy((char *) soft_tensor.op_params + 0, &scale, sizeof(float));
2860 memcpy((char *) soft_tensor.op_params + sizeof(float), &max_bias, sizeof(float));
2861 }
2862
2863 struct ggml_compute_params soft_params = {
2864 .ith = 0,
2865 .nth = 1,
2866 .wsize = softmax_work_elems * sizeof(float),
2867 .wdata = softmax_work,
2868 .threadpool = NULL,
2869 .use_ref = false,
2870 };
2871 softmax_compute_fn(&soft_params, &soft_tensor);
2872
2873 for (int d = 0; d < head_dim; ++d) {
2874 const float *v_col = v_cols + (size_t) d * (size_t) kv_tokens;
2875 float dot = 0.0f;
2876 dot_fn(kv_tokens, &dot, 0, prob_row, 0, v_col, 0, 1);
2877 out_vec[d] = dot;
2878 }
2879
2880 for (int d = head_dim; d < aligned_head_dim; ++d) {
2881 out_vec[d] = 0.0f;
2882 }
2883}
2884#endif
2885
2886/* Strict ggml-backed full-attention oracles live in attention_oracle_ggml.c. */
2887
2888#define CK_GGML_FA_TILE_Q 64
2889#define CK_GGML_FA_TILE_Q_LARGE 336
2890#define CK_GGML_FA_TILE_KV 64
2891#define CK_GGML_FA_TILE_Q_LARGE_MIN_TOKENS 1536
2892
2893static inline void ck_vec_scale_f32_inplace(float *x, int n, float scale)
2894{
2895 for (int i = 0; i < n; ++i) {
2896 x[i] *= scale;
2897 }
2898}
2899
2900static inline float ck_vec_max_f32_contig(const float *x, int n)
2901{
2902 float max_val = -INFINITY;
2903 for (int i = 0; i < n; ++i) {
2904 if (x[i] > max_val) {
2905 max_val = x[i];
2906 }
2907 }
2908 return max_val;
2909}
2910
2911#if defined(__AVX512F__)
2912static inline void ck_attention_simd_gemm_ukernel_4x4(float *c,
2913 const float *a,
2914 const float *b,
2915 int k,
2916 int n)
2917{
2918 __m512 acc[4][4];
2919 for (int i = 0; i < 4; ++i) {
2920 for (int r = 0; r < 4; ++r) {
2921 acc[i][r] = _mm512_loadu_ps(
2922 c + (size_t) i * (size_t) n + (size_t) r * 16u);
2923 }
2924 }
2925
2926 for (int kk = 0; kk < k; ++kk) {
2927 __m512 bv[4];
2928 for (int r = 0; r < 4; ++r) {
2929 bv[r] = _mm512_loadu_ps(
2930 b + (size_t) kk * (size_t) n + (size_t) r * 16u);
2931 }
2932 for (int i = 0; i < 4; ++i) {
2933 const __m512 p = _mm512_set1_ps(
2934 a[(size_t) i * (size_t) k + (size_t) kk]);
2935 for (int r = 0; r < 4; ++r) {
2936 acc[i][r] = _mm512_fmadd_ps(bv[r], p, acc[i][r]);
2937 }
2938 }
2939 }
2940
2941 for (int i = 0; i < 4; ++i) {
2942 for (int r = 0; r < 4; ++r) {
2943 _mm512_storeu_ps(
2944 c + (size_t) i * (size_t) n + (size_t) r * 16u,
2945 acc[i][r]);
2946 }
2947 }
2948}
2949
2950static inline void ck_attention_simd_gemm_ukernel_4x1(float *c,
2951 const float *a,
2952 const float *b,
2953 int k,
2954 int n)
2955{
2956 __m512 acc[4];
2957 for (int i = 0; i < 4; ++i) {
2958 acc[i] = _mm512_loadu_ps(c + (size_t) i * (size_t) n);
2959 }
2960 for (int kk = 0; kk < k; ++kk) {
2961 const __m512 bv = _mm512_loadu_ps(b + (size_t) kk * (size_t) n);
2962 for (int i = 0; i < 4; ++i) {
2963 const __m512 p = _mm512_set1_ps(
2964 a[(size_t) i * (size_t) k + (size_t) kk]);
2965 acc[i] = _mm512_fmadd_ps(bv, p, acc[i]);
2966 }
2967 }
2968 for (int i = 0; i < 4; ++i) {
2969 _mm512_storeu_ps(c + (size_t) i * (size_t) n, acc[i]);
2970 }
2971}
2972
2973static inline void ck_attention_simd_gemm_ukernel_1x4(float *c,
2974 const float *a,
2975 const float *b,
2976 int k,
2977 int n)
2978{
2979 __m512 acc[4];
2980 for (int r = 0; r < 4; ++r) {
2981 acc[r] = _mm512_loadu_ps(c + (size_t) r * 16u);
2982 }
2983 for (int kk = 0; kk < k; ++kk) {
2984 const __m512 p = _mm512_set1_ps(a[kk]);
2985 for (int r = 0; r < 4; ++r) {
2986 const __m512 bv = _mm512_loadu_ps(
2987 b + (size_t) kk * (size_t) n + (size_t) r * 16u);
2988 acc[r] = _mm512_fmadd_ps(bv, p, acc[r]);
2989 }
2990 }
2991 for (int r = 0; r < 4; ++r) {
2992 _mm512_storeu_ps(c + (size_t) r * 16u, acc[r]);
2993 }
2994}
2995
2996static inline void ck_attention_simd_gemm_ukernel_1x1(float *c,
2997 const float *a,
2998 const float *b,
2999 int k,
3000 int n)
3001{
3002 __m512 acc = _mm512_loadu_ps(c);
3003 for (int kk = 0; kk < k; ++kk) {
3004 const __m512 bv = _mm512_loadu_ps(b + (size_t) kk * (size_t) n);
3005 const __m512 p = _mm512_set1_ps(a[kk]);
3006 acc = _mm512_fmadd_ps(bv, p, acc);
3007 }
3008 _mm512_storeu_ps(c, acc);
3009}
3010
3012 const float *a,
3013 const float *b,
3014 int m,
3015 int k,
3016 int n)
3017{
3018 int ii = 0;
3019 for (; ii + 4 <= m; ii += 4) {
3020 int jj = 0;
3021 for (; jj + 64 <= n; jj += 64) {
3022 ck_attention_simd_gemm_ukernel_4x4(c + jj, a, b + jj, k, n);
3023 }
3024 for (; jj + 16 <= n; jj += 16) {
3025 ck_attention_simd_gemm_ukernel_4x1(c + jj, a, b + jj, k, n);
3026 }
3027 for (; jj < n; ++jj) {
3028 for (int i = 0; i < 4; ++i) {
3029 float sum = c[(size_t) i * (size_t) n + (size_t) jj];
3030 for (int kk = 0; kk < k; ++kk) {
3031 sum += a[(size_t) i * (size_t) k + (size_t) kk] *
3032 b[(size_t) kk * (size_t) n + (size_t) jj];
3033 }
3034 c[(size_t) i * (size_t) n + (size_t) jj] = sum;
3035 }
3036 }
3037 a += (size_t) 4 * (size_t) k;
3038 c += (size_t) 4 * (size_t) n;
3039 }
3040
3041 for (; ii < m; ++ii) {
3042 int jj = 0;
3043 for (; jj + 64 <= n; jj += 64) {
3044 ck_attention_simd_gemm_ukernel_1x4(c + jj, a, b + jj, k, n);
3045 }
3046 for (; jj + 16 <= n; jj += 16) {
3047 ck_attention_simd_gemm_ukernel_1x1(c + jj, a, b + jj, k, n);
3048 }
3049 for (; jj < n; ++jj) {
3050 float sum = c[jj];
3051 for (int kk = 0; kk < k; ++kk) {
3052 sum += a[kk] * b[(size_t) kk * (size_t) n + (size_t) jj];
3053 }
3054 c[jj] = sum;
3055 }
3056 a += k;
3057 c += n;
3058 }
3059}
3060#elif defined(__AVX__) || defined(__AVX2__)
3061static inline void ck_attention_simd_gemm_ukernel_6x2(float *c,
3062 const float *a,
3063 const float *b,
3064 int k,
3065 int n)
3066{
3067 __m256 acc[6][2];
3068 for (int i = 0; i < 6; ++i) {
3069 acc[i][0] = _mm256_loadu_ps(c + (size_t) i * (size_t) n + 0);
3070 acc[i][1] = _mm256_loadu_ps(c + (size_t) i * (size_t) n + 8);
3071 }
3072
3073 for (int kk = 0; kk < k; ++kk) {
3074 const __m256 bv0 = _mm256_loadu_ps(b + (size_t) kk * (size_t) n + 0);
3075 const __m256 bv1 = _mm256_loadu_ps(b + (size_t) kk * (size_t) n + 8);
3076 for (int i = 0; i < 6; ++i) {
3077 const __m256 p = _mm256_set1_ps(a[(size_t) i * (size_t) k + (size_t) kk]);
3078#if defined(__FMA__)
3079 acc[i][0] = _mm256_fmadd_ps(bv0, p, acc[i][0]);
3080 acc[i][1] = _mm256_fmadd_ps(bv1, p, acc[i][1]);
3081#else
3082 acc[i][0] = _mm256_add_ps(_mm256_mul_ps(bv0, p), acc[i][0]);
3083 acc[i][1] = _mm256_add_ps(_mm256_mul_ps(bv1, p), acc[i][1]);
3084#endif
3085 }
3086 }
3087
3088 for (int i = 0; i < 6; ++i) {
3089 _mm256_storeu_ps(c + (size_t) i * (size_t) n + 0, acc[i][0]);
3090 _mm256_storeu_ps(c + (size_t) i * (size_t) n + 8, acc[i][1]);
3091 }
3092}
3093
3094static inline void ck_attention_simd_gemm_ukernel_6x1(float *c,
3095 const float *a,
3096 const float *b,
3097 int k,
3098 int n)
3099{
3100 __m256 acc[6];
3101 for (int i = 0; i < 6; ++i) {
3102 acc[i] = _mm256_loadu_ps(c + (size_t) i * (size_t) n);
3103 }
3104
3105 for (int kk = 0; kk < k; ++kk) {
3106 const __m256 bv = _mm256_loadu_ps(b + (size_t) kk * (size_t) n);
3107 for (int i = 0; i < 6; ++i) {
3108 const __m256 p = _mm256_set1_ps(a[(size_t) i * (size_t) k + (size_t) kk]);
3109#if defined(__FMA__)
3110 acc[i] = _mm256_fmadd_ps(bv, p, acc[i]);
3111#else
3112 acc[i] = _mm256_add_ps(_mm256_mul_ps(bv, p), acc[i]);
3113#endif
3114 }
3115 }
3116
3117 for (int i = 0; i < 6; ++i) {
3118 _mm256_storeu_ps(c + (size_t) i * (size_t) n, acc[i]);
3119 }
3120}
3121
3122static inline void ck_attention_simd_gemm_ukernel_1x2(float *c,
3123 const float *a,
3124 const float *b,
3125 int k,
3126 int n)
3127{
3128 __m256 acc0 = _mm256_loadu_ps(c + 0);
3129 __m256 acc1 = _mm256_loadu_ps(c + 8);
3130 for (int kk = 0; kk < k; ++kk) {
3131 const __m256 bv0 = _mm256_loadu_ps(b + (size_t) kk * (size_t) n + 0);
3132 const __m256 bv1 = _mm256_loadu_ps(b + (size_t) kk * (size_t) n + 8);
3133 const __m256 p = _mm256_set1_ps(a[kk]);
3134#if defined(__FMA__)
3135 acc0 = _mm256_fmadd_ps(bv0, p, acc0);
3136 acc1 = _mm256_fmadd_ps(bv1, p, acc1);
3137#else
3138 acc0 = _mm256_add_ps(_mm256_mul_ps(bv0, p), acc0);
3139 acc1 = _mm256_add_ps(_mm256_mul_ps(bv1, p), acc1);
3140#endif
3141 }
3142 _mm256_storeu_ps(c + 0, acc0);
3143 _mm256_storeu_ps(c + 8, acc1);
3144}
3145
3146static inline void ck_attention_simd_gemm_ukernel_1x1(float *c,
3147 const float *a,
3148 const float *b,
3149 int k,
3150 int n)
3151{
3152 __m256 acc = _mm256_loadu_ps(c);
3153 for (int kk = 0; kk < k; ++kk) {
3154 const __m256 bv = _mm256_loadu_ps(b + (size_t) kk * (size_t) n);
3155 const __m256 p = _mm256_set1_ps(a[kk]);
3156#if defined(__FMA__)
3157 acc = _mm256_fmadd_ps(bv, p, acc);
3158#else
3159 acc = _mm256_add_ps(_mm256_mul_ps(bv, p), acc);
3160#endif
3161 }
3162 _mm256_storeu_ps(c, acc);
3163}
3164
3166 const float *a,
3167 const float *b,
3168 int m,
3169 int k,
3170 int n)
3171{
3172 int ii = 0;
3173 for (; ii + 6 <= m; ii += 6) {
3174 int jj = 0;
3175 for (; jj + 16 <= n; jj += 16) {
3176 ck_attention_simd_gemm_ukernel_6x2(c + jj, a, b + jj, k, n);
3177 }
3178 for (; jj + 8 <= n; jj += 8) {
3179 ck_attention_simd_gemm_ukernel_6x1(c + jj, a, b + jj, k, n);
3180 }
3181 for (; jj < n; ++jj) {
3182 for (int i = 0; i < 6; ++i) {
3183 float sum = c[(size_t) i * (size_t) n + (size_t) jj];
3184 for (int kk = 0; kk < k; ++kk) {
3185 sum += a[(size_t) i * (size_t) k + (size_t) kk] * b[(size_t) kk * (size_t) n + (size_t) jj];
3186 }
3187 c[(size_t) i * (size_t) n + (size_t) jj] = sum;
3188 }
3189 }
3190 a += (size_t) 6 * (size_t) k;
3191 c += (size_t) 6 * (size_t) n;
3192 }
3193
3194 for (; ii < m; ++ii) {
3195 int jj = 0;
3196 for (; jj + 16 <= n; jj += 16) {
3197 ck_attention_simd_gemm_ukernel_1x2(c + jj, a, b + jj, k, n);
3198 }
3199 for (; jj + 8 <= n; jj += 8) {
3200 ck_attention_simd_gemm_ukernel_1x1(c + jj, a, b + jj, k, n);
3201 }
3202 for (; jj < n; ++jj) {
3203 float sum = c[jj];
3204 for (int kk = 0; kk < k; ++kk) {
3205 sum += a[kk] * b[(size_t) kk * (size_t) n + (size_t) jj];
3206 }
3207 c[jj] = sum;
3208 }
3209 a += k;
3210 c += n;
3211 }
3212}
3213#elif defined(__SSE2__)
3214static inline void ck_attention_simd_gemm_ukernel_2x2(float *c,
3215 const float *a,
3216 const float *b,
3217 int k,
3218 int n)
3219{
3220 __m128 acc[2][2];
3221 for (int i = 0; i < 2; ++i) {
3222 acc[i][0] = _mm_loadu_ps(c + (size_t) i * (size_t) n + 0);
3223 acc[i][1] = _mm_loadu_ps(c + (size_t) i * (size_t) n + 4);
3224 }
3225
3226 for (int kk = 0; kk < k; ++kk) {
3227 const __m128 bv0 = _mm_loadu_ps(b + (size_t) kk * (size_t) n + 0);
3228 const __m128 bv1 = _mm_loadu_ps(b + (size_t) kk * (size_t) n + 4);
3229 for (int i = 0; i < 2; ++i) {
3230 const __m128 p = _mm_set1_ps(a[(size_t) i * (size_t) k + (size_t) kk]);
3231#if defined(__FMA__)
3232 acc[i][0] = _mm_fmadd_ps(bv0, p, acc[i][0]);
3233 acc[i][1] = _mm_fmadd_ps(bv1, p, acc[i][1]);
3234#else
3235 acc[i][0] = _mm_add_ps(_mm_mul_ps(bv0, p), acc[i][0]);
3236 acc[i][1] = _mm_add_ps(_mm_mul_ps(bv1, p), acc[i][1]);
3237#endif
3238 }
3239 }
3240
3241 for (int i = 0; i < 2; ++i) {
3242 _mm_storeu_ps(c + (size_t) i * (size_t) n + 0, acc[i][0]);
3243 _mm_storeu_ps(c + (size_t) i * (size_t) n + 4, acc[i][1]);
3244 }
3245}
3246
3247static inline void ck_attention_simd_gemm_ukernel_2x1(float *c,
3248 const float *a,
3249 const float *b,
3250 int k,
3251 int n)
3252{
3253 __m128 acc[2];
3254 for (int i = 0; i < 2; ++i) {
3255 acc[i] = _mm_loadu_ps(c + (size_t) i * (size_t) n);
3256 }
3257
3258 for (int kk = 0; kk < k; ++kk) {
3259 const __m128 bv = _mm_loadu_ps(b + (size_t) kk * (size_t) n);
3260 for (int i = 0; i < 2; ++i) {
3261 const __m128 p = _mm_set1_ps(a[(size_t) i * (size_t) k + (size_t) kk]);
3262#if defined(__FMA__)
3263 acc[i] = _mm_fmadd_ps(bv, p, acc[i]);
3264#else
3265 acc[i] = _mm_add_ps(_mm_mul_ps(bv, p), acc[i]);
3266#endif
3267 }
3268 }
3269
3270 for (int i = 0; i < 2; ++i) {
3271 _mm_storeu_ps(c + (size_t) i * (size_t) n, acc[i]);
3272 }
3273}
3274
3275static inline void ck_attention_simd_gemm_ukernel_1x2(float *c,
3276 const float *a,
3277 const float *b,
3278 int k,
3279 int n)
3280{
3281 __m128 acc0 = _mm_loadu_ps(c + 0);
3282 __m128 acc1 = _mm_loadu_ps(c + 4);
3283 for (int kk = 0; kk < k; ++kk) {
3284 const __m128 bv0 = _mm_loadu_ps(b + (size_t) kk * (size_t) n + 0);
3285 const __m128 bv1 = _mm_loadu_ps(b + (size_t) kk * (size_t) n + 4);
3286 const __m128 p = _mm_set1_ps(a[kk]);
3287#if defined(__FMA__)
3288 acc0 = _mm_fmadd_ps(bv0, p, acc0);
3289 acc1 = _mm_fmadd_ps(bv1, p, acc1);
3290#else
3291 acc0 = _mm_add_ps(_mm_mul_ps(bv0, p), acc0);
3292 acc1 = _mm_add_ps(_mm_mul_ps(bv1, p), acc1);
3293#endif
3294 }
3295 _mm_storeu_ps(c + 0, acc0);
3296 _mm_storeu_ps(c + 4, acc1);
3297}
3298
3299static inline void ck_attention_simd_gemm_ukernel_1x1(float *c,
3300 const float *a,
3301 const float *b,
3302 int k,
3303 int n)
3304{
3305 __m128 acc = _mm_loadu_ps(c);
3306 for (int kk = 0; kk < k; ++kk) {
3307 const __m128 bv = _mm_loadu_ps(b + (size_t) kk * (size_t) n);
3308 const __m128 p = _mm_set1_ps(a[kk]);
3309#if defined(__FMA__)
3310 acc = _mm_fmadd_ps(bv, p, acc);
3311#else
3312 acc = _mm_add_ps(_mm_mul_ps(bv, p), acc);
3313#endif
3314 }
3315 _mm_storeu_ps(c, acc);
3316}
3317
3319 const float *a,
3320 const float *b,
3321 int m,
3322 int k,
3323 int n)
3324{
3325 int ii = 0;
3326 for (; ii + 2 <= m; ii += 2) {
3327 int jj = 0;
3328 for (; jj + 8 <= n; jj += 8) {
3329 ck_attention_simd_gemm_ukernel_2x2(c + jj, a, b + jj, k, n);
3330 }
3331 for (; jj + 4 <= n; jj += 4) {
3332 ck_attention_simd_gemm_ukernel_2x1(c + jj, a, b + jj, k, n);
3333 }
3334 for (; jj < n; ++jj) {
3335 for (int i = 0; i < 2; ++i) {
3336 float sum = c[(size_t) i * (size_t) n + (size_t) jj];
3337 for (int kk = 0; kk < k; ++kk) {
3338 sum += a[(size_t) i * (size_t) k + (size_t) kk] * b[(size_t) kk * (size_t) n + (size_t) jj];
3339 }
3340 c[(size_t) i * (size_t) n + (size_t) jj] = sum;
3341 }
3342 }
3343 a += (size_t) 2 * (size_t) k;
3344 c += (size_t) 2 * (size_t) n;
3345 }
3346
3347 for (; ii < m; ++ii) {
3348 int jj = 0;
3349 for (; jj + 8 <= n; jj += 8) {
3350 ck_attention_simd_gemm_ukernel_1x2(c + jj, a, b + jj, k, n);
3351 }
3352 for (; jj + 4 <= n; jj += 4) {
3353 ck_attention_simd_gemm_ukernel_1x1(c + jj, a, b + jj, k, n);
3354 }
3355 for (; jj < n; ++jj) {
3356 float sum = c[jj];
3357 for (int kk = 0; kk < k; ++kk) {
3358 sum += a[kk] * b[(size_t) kk * (size_t) n + (size_t) jj];
3359 }
3360 c[jj] = sum;
3361 }
3362 a += k;
3363 c += n;
3364 }
3365}
3366#else
3368 const float *a,
3369 const float *b,
3370 int m,
3371 int k,
3372 int n)
3373{
3374 for (int i = 0; i < m; ++i) {
3375 float *c_row = c + (size_t) i * (size_t) n;
3376 const float *a_row = a + (size_t) i * (size_t) k;
3377 for (int kk = 0; kk < k; ++kk) {
3378 const float a_ik = a_row[kk];
3379 const float *b_row = b + (size_t) kk * (size_t) n;
3380 for (int j = 0; j < n; ++j) {
3381 c_row[j] += a_ik * b_row[j];
3382 }
3383 }
3384 }
3385}
3386#endif
3387
3389 const float *q,
3390 const float *k,
3391 const float *v,
3392 float *output,
3393 int num_heads,
3394 int num_kv_heads,
3395 int num_tokens,
3396 int head_dim,
3397 int aligned_head_dim,
3398 int kv_stride_tokens,
3399 int query_tile_size,
3400 int ith,
3401 int nth)
3402{
3403 if (!q || !k || !v || !output || num_heads <= 0 || num_kv_heads <= 0 ||
3404 num_tokens <= 0 || head_dim <= 0 || aligned_head_dim < head_dim ||
3405 kv_stride_tokens < num_tokens || num_heads % num_kv_heads != 0 ||
3406 query_tile_size <= 0) {
3407 return;
3408 }
3409
3410 const float scale = ck_attention_strict_scale_f32(head_dim);
3411 const int T = num_tokens;
3412 const size_t kv_head_stride = (size_t) kv_stride_tokens * (size_t) aligned_head_dim;
3413
3414 float *q_tile = (float *) alloca((size_t) query_tile_size * (size_t) head_dim * sizeof(float));
3415 float *k_tile = (float *) alloca((size_t) head_dim * (size_t) CK_GGML_FA_TILE_KV * sizeof(float));
3416 float *v_tile = (float *) alloca((size_t) CK_GGML_FA_TILE_KV * (size_t) head_dim * sizeof(float));
3417 float *kq = (float *) alloca((size_t) query_tile_size * (size_t) CK_GGML_FA_TILE_KV * sizeof(float));
3418 float *vkq = (float *) alloca((size_t) query_tile_size * (size_t) head_dim * sizeof(float));
3419 float *sum_row = (float *) alloca((size_t) query_tile_size * sizeof(float));
3420 float *max_row = (float *) alloca((size_t) query_tile_size * sizeof(float));
3421
3422 const int total_rows = num_heads * T;
3423 const int rows_per_worker = (total_rows + nth - 1) / nth;
3424 int ir = rows_per_worker * ith;
3425 const int ir1 = (ir + rows_per_worker) < total_rows
3426 ? (ir + rows_per_worker)
3427 : total_rows;
3428
3429 while (ir < ir1) {
3430 const int h = ir / T;
3431 const int iq = ir - h * T;
3432 int tile_rows = ir1 - ir;
3433 if (tile_rows > query_tile_size) tile_rows = query_tile_size;
3434 if (tile_rows > T - iq) tile_rows = T - iq;
3435 const int kv_head = (int) ((long long) h * (long long) num_kv_heads / (long long) num_heads);
3436 const float *k_head = k + (size_t) kv_head * kv_head_stride;
3437 const float *v_head = v + (size_t) kv_head * kv_head_stride;
3438
3439 for (int tq = 0; tq < query_tile_size; ++tq) {
3440 sum_row[tq] = 0.0f;
3441 max_row[tq] = -INFINITY;
3442 }
3443
3444 memset(vkq, 0, (size_t) query_tile_size * (size_t) head_dim * sizeof(float));
3445 memset(q_tile, 0, (size_t) query_tile_size * (size_t) head_dim * sizeof(float));
3446 memset(k_tile, 0, (size_t) head_dim * (size_t) CK_GGML_FA_TILE_KV * sizeof(float));
3447 memset(v_tile, 0, (size_t) CK_GGML_FA_TILE_KV * (size_t) head_dim * sizeof(float));
3448
3449 for (int tq = 0; tq < tile_rows; ++tq) {
3450 const float *q_vec = q + qkv_index(h, iq + tq, 0, T, aligned_head_dim);
3451 memcpy(q_tile + (size_t) tq * (size_t) head_dim, q_vec, (size_t) head_dim * sizeof(float));
3452 }
3453
3454 for (int ik = 0; ik < T; ik += CK_GGML_FA_TILE_KV) {
3455 const int kv_tile = (T - ik) < CK_GGML_FA_TILE_KV ? (T - ik) : CK_GGML_FA_TILE_KV;
3456 memset(kq, 0, (size_t) query_tile_size * (size_t) CK_GGML_FA_TILE_KV * sizeof(float));
3457
3458 for (int tk = 0; tk < kv_tile; ++tk) {
3459 const float *k_vec = k_head + (size_t) (ik + tk) * (size_t) aligned_head_dim;
3460 const float *v_vec = v_head + (size_t) (ik + tk) * (size_t) aligned_head_dim;
3461 for (int d = 0; d < head_dim; ++d) {
3462 k_tile[(size_t) d * (size_t) CK_GGML_FA_TILE_KV + (size_t) tk] =
3463 ck_round_fp16_scalar(k_vec[d]);
3464 v_tile[(size_t) tk * (size_t) head_dim + (size_t) d] =
3465 ck_round_fp16_scalar(v_vec[d]);
3466 }
3467 }
3468
3470 q_tile,
3471 k_tile,
3472 query_tile_size,
3473 head_dim,
3476 query_tile_size * CK_GGML_FA_TILE_KV,
3477 scale);
3478
3479 if (kv_tile < CK_GGML_FA_TILE_KV) {
3480 for (int tq = 0; tq < query_tile_size; ++tq) {
3481 float *kq_row = kq + (size_t) tq * (size_t) CK_GGML_FA_TILE_KV;
3482 for (int tk = kv_tile; tk < CK_GGML_FA_TILE_KV; ++tk) {
3483 kq_row[tk] = -INFINITY;
3484 }
3485 }
3486 }
3487
3488 for (int tq = 0; tq < tile_rows; ++tq) {
3489 float *kq_row = kq + (size_t) tq * (size_t) CK_GGML_FA_TILE_KV;
3490 const float tile_max = ck_vec_max_f32_contig(kq_row, CK_GGML_FA_TILE_KV);
3491 if (tile_max == -INFINITY) {
3492 memset(kq_row, 0, (size_t) CK_GGML_FA_TILE_KV * sizeof(float));
3493 continue;
3494 }
3495
3496 const float old_max = max_row[tq];
3497 const float new_max = old_max > tile_max ? old_max : tile_max;
3498 if (new_max > old_max) {
3499 const float ms = ck_attention_reference_expf(old_max - new_max);
3500 ck_vec_scale_f32_inplace(vkq + (size_t) tq * (size_t) head_dim,
3501 head_dim,
3502 ms);
3503 sum_row[tq] *= ms;
3504 }
3505 max_row[tq] = new_max;
3506 sum_row[tq] = (float) (
3507 (double) sum_row[tq] +
3509 CK_GGML_FA_TILE_KV, kq_row, kq_row, new_max));
3510 }
3511
3513 kq,
3514 v_tile,
3515 query_tile_size,
3517 head_dim);
3518 }
3519
3520 for (int tq = 0; tq < tile_rows; ++tq) {
3521 float *out_vec = output + qkv_index(h, iq + tq, 0, T, aligned_head_dim);
3522 const float inv_sum = sum_row[tq] == 0.0f ? 0.0f : (1.0f / sum_row[tq]);
3523 for (int d = 0; d < head_dim; ++d) {
3524 out_vec[d] = vkq[(size_t) tq * (size_t) head_dim + (size_t) d] * inv_sum;
3525 }
3526 for (int d = head_dim; d < aligned_head_dim; ++d) {
3527 out_vec[d] = 0.0f;
3528 }
3529 }
3530 ir += tile_rows;
3531 }
3532}
3533
3534typedef struct {
3535 const float *q;
3536 const float *k;
3537 const float *v;
3538 float *output;
3539 int num_heads;
3540 int num_kv_heads;
3541 int num_tokens;
3542 int head_dim;
3543 int aligned_head_dim;
3544 int kv_stride_tokens;
3545 int query_tile_size;
3546} ck_attention_full_tiled_f16kv_fp32_args_t;
3547
3548static void ck_attention_full_tiled_f16kv_fp32_work(int ith, int nth, void *opaque)
3549{
3550 ck_attention_full_tiled_f16kv_fp32_args_t *args =
3551 (ck_attention_full_tiled_f16kv_fp32_args_t *) opaque;
3553 args->q, args->k, args->v, args->output,
3554 args->num_heads, args->num_kv_heads, args->num_tokens,
3555 args->head_dim, args->aligned_head_dim, args->kv_stride_tokens,
3556 args->query_tile_size,
3557 ith, nth);
3558}
3559
3561 const float *q,
3562 const float *k,
3563 const float *v,
3564 float *output,
3565 int num_heads,
3566 int num_kv_heads,
3567 int num_tokens,
3568 int head_dim,
3569 int aligned_head_dim,
3570 int kv_stride_tokens,
3571 int query_tile_size)
3572{
3573 if (!q || !k || !v || !output || num_heads <= 0 || num_kv_heads <= 0 ||
3574 num_tokens <= 0 || head_dim <= 0 || aligned_head_dim < head_dim ||
3575 kv_stride_tokens < num_tokens || num_heads % num_kv_heads != 0) {
3576 return;
3577 }
3578
3579 ck_attention_full_tiled_f16kv_fp32_args_t args = {
3580 .q = q,
3581 .k = k,
3582 .v = v,
3583 .output = output,
3584 .num_heads = num_heads,
3585 .num_kv_heads = num_kv_heads,
3586 .num_tokens = num_tokens,
3587 .head_dim = head_dim,
3588 .aligned_head_dim = aligned_head_dim,
3589 .kv_stride_tokens = kv_stride_tokens,
3590 .query_tile_size = query_tile_size,
3591 };
3592 ck_threadpool_t *pool = ck_threadpool_global();
3593 const int active = pool ? ck_threadpool_n_threads(pool) : 1;
3594 if (pool && active > 1) {
3596 } else {
3598 }
3599}
3600
3602 const float *q,
3603 const float *k,
3604 const float *v,
3605 float *output,
3606 int num_heads,
3607 int num_kv_heads,
3608 int num_tokens,
3609 int head_dim,
3610 int aligned_head_dim,
3611 int kv_stride_tokens)
3612{
3613 const int query_tile_size = num_tokens >= CK_GGML_FA_TILE_Q_LARGE_MIN_TOKENS
3617 q, k, v, output, num_heads, num_kv_heads, num_tokens,
3618 head_dim, aligned_head_dim, kv_stride_tokens, query_tile_size);
3619}
3620
3622 const float *q,
3623 const float *k,
3624 const float *v,
3625 float *output,
3626 int num_heads,
3627 int num_kv_heads,
3628 int num_tokens,
3629 int head_dim,
3630 int aligned_head_dim,
3631 int kv_stride_tokens)
3632{
3634 q, k, v, output, num_heads, num_kv_heads, num_tokens,
3635 head_dim, aligned_head_dim, kv_stride_tokens, CK_GGML_FA_TILE_Q);
3636}
3637
3639 const float *q,
3640 const float *k,
3641 const float *v,
3642 float *output,
3643 int num_heads,
3644 int num_kv_heads,
3645 int num_tokens,
3646 int head_dim,
3647 int aligned_head_dim,
3648 int kv_stride_tokens)
3649{
3651 q, k, v, output, num_heads, num_kv_heads, num_tokens,
3652 head_dim, aligned_head_dim, kv_stride_tokens, CK_GGML_FA_TILE_Q_LARGE);
3653}
3654
3656 const float *query,
3657 const float *key,
3658 const float *value,
3659 float *output,
3660 int num_heads,
3661 int query_tokens,
3662 int key_tokens,
3663 int head_dim,
3664 float scale)
3665{
3666 if (!query || !key || !value || !output || num_heads <= 0 ||
3667 query_tokens <= 0 || key_tokens != query_tokens || head_dim <= 0) {
3668 return -1;
3669 }
3670
3671 const float contract_scale = ck_attention_strict_scale_f32(head_dim);
3672 if (scale != contract_scale) {
3673 return -1;
3674 }
3675
3676 const int query_tile_size = query_tokens >= CK_GGML_FA_TILE_Q_LARGE_MIN_TOKENS
3680 query, key, value, output,
3681 num_heads, num_heads, query_tokens,
3682 head_dim, head_dim, key_tokens, query_tile_size);
3683 return 0;
3684}
3685
3686/**
3687 * Flash attention forward for GQA (prefill, no score materialization)
3688 * @test test_flash_attention.py::TestFlashAttention::test_flash_forward
3689 * @test test_flash_attention.py::TestFlashAttention::test_flash_vs_score_matrix
3690 * @test test_flash_attention.py::TestFlashAttention::test_flash_gqa
3691 * @test test_attention.py::TestAttentionForward::test_flash_forward
3692 *
3693 * Online softmax with streaming KV. O(N) memory instead of O(N^2).
3694 * For prefill: all tokens attend to previous tokens.
3695 *
3696 * After changes: make test && make llamacpp-parity-full
3697 */
3698
3699typedef struct {
3700 const float *q;
3701 const float *k;
3702 const float *v;
3703 float *output;
3704 int num_heads;
3705 int num_kv_heads;
3706 int num_tokens;
3707 int head_dim;
3708 int aligned_head_dim;
3709 int kv_stride_tokens;
3710 int causal;
3711 int output_token_major;
3712 float scale;
3713} ck_attention_parallel_args_t;
3714
3715static inline void ck_attention_flash_query_auto(const float *q_vec,
3716 const float *k_head,
3717 const float *v_head,
3718 int kv_tokens,
3719 int head_dim,
3720 int aligned_head_dim,
3721 float scale,
3722 float *out_vec)
3723{
3724#if defined(__AVX512F__)
3725 attention_flash_query_causal_avx512(q_vec, k_head, v_head,
3726 kv_tokens, head_dim, aligned_head_dim,
3727 scale, out_vec);
3728#elif defined(__AVX2__)
3729 attention_flash_query_causal_avx2(q_vec, k_head, v_head,
3730 kv_tokens, head_dim, aligned_head_dim,
3731 scale, out_vec);
3732#elif defined(__AVX__)
3733 attention_flash_query_causal_avx(q_vec, k_head, v_head,
3734 kv_tokens, head_dim, aligned_head_dim,
3735 scale, out_vec);
3736#else
3737 attention_flash_query_causal(q_vec, k_head, v_head,
3738 kv_tokens, head_dim, aligned_head_dim,
3739 scale, out_vec);
3740#endif
3741}
3742
3743
3744#if defined(__AVX512F__)
3745static int ck_attention_qblock4_enabled(void)
3746{
3747 static int cached = -1;
3748 if (cached < 0) {
3749 cached = ck_env_truthy_or_qwen3vl_ocr_profile("CK_ATTENTION_QBLOCK4");
3750 }
3751 return cached;
3752}
3753
3754static int ck_attention_qblock8_enabled(void)
3755{
3756 static int cached = -1;
3757 if (cached < 0) {
3758 cached = ck_env_truthy_or_qwen3vl_ocr_profile("CK_ATTENTION_QBLOCK8");
3759 }
3760 return cached;
3761}
3762
3763static int ck_attention_qblock_fast_exp_enabled(void)
3764{
3765 static int cached = -1;
3766 if (cached < 0) {
3767 cached = ck_env_truthy_or_qwen3vl_ocr_profile("CK_ATTENTION_QBLOCK_FAST_EXP");
3768 }
3769 return cached;
3770}
3771
3772static inline float ck_attention_qblock_fast_expf(float x)
3773{
3774 if (x > 88.0f) x = 88.0f;
3775 else if (x < -88.0f) x = -88.0f;
3776
3777 const float log2e = 1.4426950408889634f;
3778 const float z = x * log2e;
3779 const float zf = nearbyintf(z);
3780 const float f = z - zf;
3781
3782 const float c0 = 1.0f;
3783 const float c1 = 0.6931471805599453f;
3784 const float c2 = 0.2402265069591007f;
3785 const float c3 = 0.05550410866482158f;
3786 const float c4 = 0.009618129107628478f;
3787
3788 float poly = ((c4 * f + c3) * f + c2) * f + c1;
3789 poly = poly * f + c0;
3790
3791 union { uint32_t i; float f; } u;
3792 u.i = (uint32_t)((int32_t)zf + 127) << 23;
3793 return poly * u.f;
3794}
3795
3796static inline float ck_attention_qblock_expf(float x)
3797{
3798 return ck_attention_qblock_fast_exp_enabled() ? ck_attention_qblock_fast_expf(x) : expf(x);
3799}
3800
3801static inline float ck_attention_dot72_avx512(const float *q_vec, const float *k_vec)
3802{
3803 __m512 acc = _mm512_setzero_ps();
3804 acc = _mm512_fmadd_ps(_mm512_loadu_ps(q_vec + 0), _mm512_loadu_ps(k_vec + 0), acc);
3805 acc = _mm512_fmadd_ps(_mm512_loadu_ps(q_vec + 16), _mm512_loadu_ps(k_vec + 16), acc);
3806 acc = _mm512_fmadd_ps(_mm512_loadu_ps(q_vec + 32), _mm512_loadu_ps(k_vec + 32), acc);
3807 acc = _mm512_fmadd_ps(_mm512_loadu_ps(q_vec + 48), _mm512_loadu_ps(k_vec + 48), acc);
3808 float dot = _mm512_reduce_add_ps(acc);
3809 for (int d = 64; d < 72; ++d) {
3810 dot += q_vec[d] * k_vec[d];
3811 }
3812 return dot;
3813}
3814
3815static void attention_flash_query4_full_avx512(const float *q_head,
3816 const float *k_head,
3817 const float *v_head,
3818 int q0,
3819 int q_count,
3820 int kv_tokens,
3821 int aligned_head_dim,
3822 float scale,
3823 float *out_head)
3824{
3825 float m[4] = { -INFINITY, -INFINITY, -INFINITY, -INFINITY };
3826 float ssum[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
3827 float out[4][72];
3828 for (int r = 0; r < q_count; ++r) {
3829 for (int d = 0; d < 72; ++d) out[r][d] = 0.0f;
3830 }
3831
3832 const float *qv[4] = { NULL, NULL, NULL, NULL };
3833 for (int r = 0; r < q_count; ++r) {
3834 qv[r] = q_head + (size_t)(q0 + r) * (size_t)aligned_head_dim;
3835 }
3836
3837 for (int j = 0; j < kv_tokens; ++j) {
3838 const float *k_vec = k_head + (size_t)j * (size_t)aligned_head_dim;
3839 const float *v_vec = v_head + (size_t)j * (size_t)aligned_head_dim;
3840 float score[4];
3841 float scale_old[4];
3842 float scale_v[4];
3843
3844 for (int r = 0; r < q_count; ++r) {
3845 score[r] = ck_attention_dot72_avx512(qv[r], k_vec) * scale;
3846 if (score[r] > m[r]) {
3847 scale_old[r] = (m[r] == -INFINITY) ? 0.0f : ck_attention_qblock_expf(m[r] - score[r]);
3848 scale_v[r] = 1.0f;
3849 ssum[r] *= scale_old[r];
3850 ssum[r] += 1.0f;
3851 m[r] = score[r];
3852 } else {
3853 scale_old[r] = 1.0f;
3854 scale_v[r] = ck_attention_qblock_expf(score[r] - m[r]);
3855 ssum[r] += scale_v[r];
3856 }
3857 }
3858
3859 for (int d = 0; d < 72; d += 16) {
3860 const int width = (d + 16 <= 72) ? 16 : (72 - d);
3861 if (width == 16) {
3862 const __m512 vv = _mm512_loadu_ps(v_vec + d);
3863 for (int r = 0; r < q_count; ++r) {
3864 __m512 ov = _mm512_loadu_ps(out[r] + d);
3865 ov = _mm512_fmadd_ps(ov, _mm512_set1_ps(scale_old[r]), _mm512_mul_ps(_mm512_set1_ps(scale_v[r]), vv));
3866 _mm512_storeu_ps(out[r] + d, ov);
3867 }
3868 } else {
3869 for (int r = 0; r < q_count; ++r) {
3870 for (int t = 0; t < width; ++t) {
3871 out[r][d + t] = out[r][d + t] * scale_old[r] + scale_v[r] * v_vec[d + t];
3872 }
3873 }
3874 }
3875 }
3876 }
3877
3878 for (int r = 0; r < q_count; ++r) {
3879 float *dst = out_head + (size_t)(q0 + r) * (size_t)aligned_head_dim;
3880 const float inv = ssum[r] == 0.0f ? 0.0f : (1.0f / ssum[r]);
3881 const __m512 invv = _mm512_set1_ps(inv);
3882 for (int d = 0; d + 16 <= 72; d += 16) {
3883 _mm512_storeu_ps(dst + d, _mm512_mul_ps(_mm512_loadu_ps(out[r] + d), invv));
3884 }
3885 for (int d = 64; d < 72; ++d) dst[d] = out[r][d] * inv;
3886 for (int d = 72; d < aligned_head_dim; ++d) dst[d] = 0.0f;
3887 }
3888}
3889static void attention_flash_query8_full_avx512(const float *q_head,
3890 const float *k_head,
3891 const float *v_head,
3892 int q0,
3893 int q_count,
3894 int kv_tokens,
3895 int aligned_head_dim,
3896 float scale,
3897 float *out_head)
3898{
3899 float m[8] = { -INFINITY, -INFINITY, -INFINITY, -INFINITY, -INFINITY, -INFINITY, -INFINITY, -INFINITY };
3900 float ssum[8] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
3901 float out[8][72];
3902 for (int r = 0; r < q_count; ++r) {
3903 for (int d = 0; d < 72; ++d) out[r][d] = 0.0f;
3904 }
3905
3906 const float *qv[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
3907 for (int r = 0; r < q_count; ++r) {
3908 qv[r] = q_head + (size_t)(q0 + r) * (size_t)aligned_head_dim;
3909 }
3910
3911 for (int j = 0; j < kv_tokens; ++j) {
3912 const float *k_vec = k_head + (size_t)j * (size_t)aligned_head_dim;
3913 const float *v_vec = v_head + (size_t)j * (size_t)aligned_head_dim;
3914 float score[8];
3915 float scale_old[8];
3916 float scale_v[8];
3917
3918 for (int r = 0; r < q_count; ++r) {
3919 score[r] = ck_attention_dot72_avx512(qv[r], k_vec) * scale;
3920 if (score[r] > m[r]) {
3921 scale_old[r] = (m[r] == -INFINITY) ? 0.0f : ck_attention_qblock_expf(m[r] - score[r]);
3922 scale_v[r] = 1.0f;
3923 ssum[r] *= scale_old[r];
3924 ssum[r] += 1.0f;
3925 m[r] = score[r];
3926 } else {
3927 scale_old[r] = 1.0f;
3928 scale_v[r] = ck_attention_qblock_expf(score[r] - m[r]);
3929 ssum[r] += scale_v[r];
3930 }
3931 }
3932
3933 for (int d = 0; d < 72; d += 16) {
3934 const int width = (d + 16 <= 72) ? 16 : (72 - d);
3935 if (width == 16) {
3936 const __m512 vv = _mm512_loadu_ps(v_vec + d);
3937 for (int r = 0; r < q_count; ++r) {
3938 __m512 ov = _mm512_loadu_ps(out[r] + d);
3939 ov = _mm512_fmadd_ps(ov, _mm512_set1_ps(scale_old[r]), _mm512_mul_ps(_mm512_set1_ps(scale_v[r]), vv));
3940 _mm512_storeu_ps(out[r] + d, ov);
3941 }
3942 } else {
3943 for (int r = 0; r < q_count; ++r) {
3944 for (int t = 0; t < width; ++t) {
3945 out[r][d + t] = out[r][d + t] * scale_old[r] + scale_v[r] * v_vec[d + t];
3946 }
3947 }
3948 }
3949 }
3950 }
3951
3952 for (int r = 0; r < q_count; ++r) {
3953 float *dst = out_head + (size_t)(q0 + r) * (size_t)aligned_head_dim;
3954 const float inv = ssum[r] == 0.0f ? 0.0f : (1.0f / ssum[r]);
3955 const __m512 invv = _mm512_set1_ps(inv);
3956 for (int d = 0; d + 16 <= 72; d += 16) {
3957 _mm512_storeu_ps(dst + d, _mm512_mul_ps(_mm512_loadu_ps(out[r] + d), invv));
3958 }
3959 for (int d = 64; d < 72; ++d) dst[d] = out[r][d] * inv;
3960 for (int d = 72; d < aligned_head_dim; ++d) dst[d] = 0.0f;
3961 }
3962}
3963
3964typedef struct {
3965 const float *q;
3966 const float *k;
3967 const float *v;
3968 float *output;
3969 int num_heads;
3970 int num_kv_heads;
3971 int num_tokens;
3972 int aligned_head_dim;
3973 int kv_stride_tokens;
3974 float scale;
3975} ck_attention_qblock4_args_t;
3976
3977static void ck_attention_full_qblock4_work(int ith, int nth, void *opaque)
3978{
3979 ck_attention_qblock4_args_t *args = (ck_attention_qblock4_args_t *) opaque;
3980 const int T = args->num_tokens;
3981 const int q_blocks = (T + 3) / 4;
3982 const int total = args->num_heads * q_blocks;
3983 const size_t head_stride = (size_t)T * (size_t)args->aligned_head_dim;
3984 const size_t kv_head_stride = (size_t)args->kv_stride_tokens * (size_t)args->aligned_head_dim;
3985
3986 for (int idx = ith; idx < total; idx += nth) {
3987 const int h = idx / q_blocks;
3988 const int qb = idx - h * q_blocks;
3989 const int q0 = qb * 4;
3990 const int q_count = (q0 + 4 <= T) ? 4 : (T - q0);
3991 const int kv_head = (int)((long long)h * (long long)args->num_kv_heads / (long long)args->num_heads);
3992 const float *q_head = args->q + (size_t)h * head_stride;
3993 const float *k_head = args->k + (size_t)kv_head * kv_head_stride;
3994 const float *v_head = args->v + (size_t)kv_head * kv_head_stride;
3995 float *out_head = args->output + (size_t)h * head_stride;
3996 attention_flash_query4_full_avx512(q_head, k_head, v_head,
3997 q0, q_count, T,
3998 args->aligned_head_dim,
3999 args->scale,
4000 out_head);
4001 }
4002}
4003
4004static void ck_attention_full_qblock8_work(int ith, int nth, void *opaque)
4005{
4006 ck_attention_qblock4_args_t *args = (ck_attention_qblock4_args_t *) opaque;
4007 const int T = args->num_tokens;
4008 const int q_blocks = (T + 7) / 8;
4009 const int total = args->num_heads * q_blocks;
4010 const size_t head_stride = (size_t)T * (size_t)args->aligned_head_dim;
4011 const size_t kv_head_stride = (size_t)args->kv_stride_tokens * (size_t)args->aligned_head_dim;
4012
4013 for (int idx = ith; idx < total; idx += nth) {
4014 const int h = idx / q_blocks;
4015 const int qb = idx - h * q_blocks;
4016 const int q0 = qb * 8;
4017 const int q_count = (q0 + 8 <= T) ? 8 : (T - q0);
4018 const int kv_head = (int)((long long)h * (long long)args->num_kv_heads / (long long)args->num_heads);
4019 const float *q_head = args->q + (size_t)h * head_stride;
4020 const float *k_head = args->k + (size_t)kv_head * kv_head_stride;
4021 const float *v_head = args->v + (size_t)kv_head * kv_head_stride;
4022 float *out_head = args->output + (size_t)h * head_stride;
4023 attention_flash_query8_full_avx512(q_head, k_head, v_head,
4024 q0, q_count, T,
4025 args->aligned_head_dim,
4026 args->scale,
4027 out_head);
4028 }
4029}
4030
4031#endif
4032
4033static void ck_attention_full_grid_work(int ith, int nth, void *opaque)
4034{
4035 ck_attention_parallel_args_t *args = (ck_attention_parallel_args_t *) opaque;
4036 const int T = args->num_tokens;
4037 const int total = args->num_heads * T;
4038 const size_t kv_head_stride = (size_t) args->kv_stride_tokens * (size_t) args->aligned_head_dim;
4039
4040 for (int idx = ith; idx < total; idx += nth) {
4041 const int h = idx / T;
4042 const int i = idx - h * T;
4043 const int kv_head = (int) ((long long) h * (long long) args->num_kv_heads / (long long) args->num_heads);
4044 const float *k_head = args->k + (size_t) kv_head * kv_head_stride;
4045 const float *v_head = args->v + (size_t) kv_head * kv_head_stride;
4046 const float *q_vec = args->q + qkv_index(h, i, 0, T, args->aligned_head_dim);
4047 float *out_vec = args->output + attention_output_index(
4048 h, i, args->num_heads, T, args->aligned_head_dim,
4049 args->output_token_major);
4050 const int kv_tokens = args->causal ? (i + 1) : T;
4051 ck_attention_flash_query_auto(q_vec, k_head, v_head,
4052 kv_tokens,
4053 args->head_dim,
4054 args->aligned_head_dim,
4055 args->scale,
4056 out_vec);
4057 }
4058}
4059
4060static int ck_attention_parallel_enabled(int total_queries, int num_tokens, int head_dim)
4061{
4062 const char *disable = getenv("CK_DISABLE_ATTENTION_THREADPOOL");
4063 if (disable && disable[0] && strcmp(disable, "0") != 0) return 0;
4064 /*
4065 * A 128-token GQA prefill can fall below the old 2048-query threshold
4066 * even though its causal dot products contain ample independent work.
4067 * Keep smaller prompts serial, but allow the shared pool once at least
4068 * eight 128-query worker grains are available.
4069 */
4070 if (total_queries < 1024 || num_tokens < 128 || head_dim <= 0) return 0;
4071 return 1;
4072}
4073
4074static int ck_attention_pick_active_threads(const ck_threadpool_t *pool, int total_queries, int num_tokens)
4075{
4076 int nth = pool ? ck_threadpool_n_threads(pool) : 1;
4077 if (nth <= 1) return 1;
4078 const char *cap_env = getenv("CK_ATTENTION_THREAD_CAP");
4079 int cap = cap_env && cap_env[0] ? atoi(cap_env) : 24;
4080 if (cap < 1) cap = 1;
4081 if (cap > nth) cap = nth;
4082 int active = (total_queries + 127) / 128;
4083 if (num_tokens >= 1024 && active < 8) active = 8;
4084 if (active > cap) active = cap;
4085 if (active > nth) active = nth;
4086 return active < 1 ? 1 : active;
4087}
4088
4090{
4091 const char *value = getenv("CK_STRICT_ATTN_F16_UNFUSED");
4092 return !value || !value[0] || strcmp(value, "0") != 0;
4093}
4094
4096 const float *q,
4097 const float *k,
4098 const float *v,
4099 float *output,
4100 int num_heads,
4101 int num_kv_heads,
4102 int num_tokens,
4103 int head_dim,
4104 int aligned_head_dim,
4105 int kv_stride_tokens,
4106 int causal,
4107 int output_token_major,
4108 float scale,
4109 int debug_layer_id)
4110{
4111 const int T = num_tokens;
4112 const size_t kv_head_stride = (size_t) kv_stride_tokens * (size_t) aligned_head_dim;
4113 const size_t kv_half_count = (size_t) T * (size_t) aligned_head_dim;
4114 uint16_t *k_half = (uint16_t *) malloc(kv_half_count * sizeof(uint16_t));
4115 uint16_t *v_half = (uint16_t *) malloc(kv_half_count * sizeof(uint16_t));
4116 if (!k_half || !v_half) {
4117 free(k_half);
4118 free(v_half);
4119 return 0;
4120 }
4121 uint16_t *q_half = (uint16_t *) alloca((size_t) aligned_head_dim * sizeof(uint16_t));
4122 uint16_t *prob_half = (uint16_t *) alloca((size_t) T * sizeof(uint16_t));
4123 uint16_t *v_col_half = (uint16_t *) alloca((size_t) T * sizeof(uint16_t));
4124 float *raw_scores = (float *) alloca((size_t) T * sizeof(float));
4125 float *logits = (float *) alloca((size_t) T * sizeof(float));
4126 int cached_kv_head = -1;
4127
4128 for (int h = 0; h < num_heads; ++h) {
4129 const int kv_head = (int) ((long long) h * (long long) num_kv_heads /
4130 (long long) num_heads);
4131 const float *k_head = k + (size_t) kv_head * kv_head_stride;
4132 const float *v_head = v + (size_t) kv_head * kv_head_stride;
4133 if (kv_head != cached_kv_head) {
4134 for (size_t idx = 0; idx < kv_half_count; ++idx) {
4135 k_half[idx] = CK_FP32_TO_FP16(k_head[idx]);
4136 v_half[idx] = CK_FP32_TO_FP16(v_head[idx]);
4137 }
4138 cached_kv_head = kv_head;
4139 }
4140
4141 for (int i = 0; i < T; ++i) {
4142 const float *q_vec = q + qkv_index(h, i, 0, T, aligned_head_dim);
4143 float *out_vec = output + attention_output_index(
4144 h, i, num_heads, T, aligned_head_dim, output_token_major);
4145 const int kv_tokens = causal ? i + 1 : T;
4146 for (int d = 0; d < aligned_head_dim; ++d) {
4147 q_half[d] = CK_FP32_TO_FP16(q_vec[d]);
4148 }
4149 for (int j = 0; j < kv_tokens; ++j) {
4150 raw_scores[j] = ck_attention_dot_f16_unfused_llama(
4151 q_half,
4152 k_half + (size_t) j * (size_t) aligned_head_dim,
4153 head_dim);
4154 logits[j] = raw_scores[j] * scale;
4155 }
4156
4157 const float max_score = ck_vec_max_f32_contig(logits, kv_tokens);
4158 const double sum = ck_ggml_vec_soft_max_row(kv_tokens, logits, logits, max_score);
4159 const float inv_sum = sum > 0.0 ? (float) (1.0 / sum) : 0.0f;
4160 for (int j = 0; j < kv_tokens; ++j) {
4161 logits[j] *= inv_sum;
4162 prob_half[j] = CK_FP32_TO_FP16(logits[j]);
4163 }
4164 for (int d = 0; d < head_dim; ++d) {
4165 for (int j = 0; j < kv_tokens; ++j) {
4166 v_col_half[j] = v_half[(size_t) j * (size_t) aligned_head_dim + (size_t) d];
4167 }
4168 out_vec[d] = ck_attention_dot_f16_unfused_llama(prob_half, v_col_half, kv_tokens);
4169 }
4170 for (int d = head_dim; d < aligned_head_dim; ++d) {
4171 out_vec[d] = 0.0f;
4172 }
4173 ck_attention_vec_dump_selected_query(raw_scores, logits, out_vec, NULL,
4174 kv_tokens, head_dim,
4175 debug_layer_id, h, i);
4176 }
4177 }
4178 free(k_half);
4179 free(v_half);
4180 return 1;
4181}
4182
4184 const float *k,
4185 const float *v,
4186 float *output,
4187 int num_heads,
4188 int num_kv_heads,
4189 int num_tokens,
4190 int head_dim,
4191 int aligned_head_dim,
4192 int kv_stride_tokens,
4193 int causal,
4194 int round_full_kv_fp16,
4195 int output_token_major,
4196 float scale)
4197{
4198 if (!q || !k || !v || !output) {
4199 return;
4200 }
4201 if (num_heads <= 0 || num_kv_heads <= 0 || num_tokens <= 0 ||
4202 head_dim <= 0 || aligned_head_dim < head_dim) {
4203 return;
4204 }
4205 if (kv_stride_tokens < num_tokens) {
4206 return;
4207 }
4208
4209 const int T = num_tokens;
4210 const size_t kv_head_stride = (size_t)kv_stride_tokens * (size_t)aligned_head_dim;
4211
4213 const float strict_scale = ck_attention_strict_scale_f32(head_dim);
4214 const int debug_layer_id = ck_attention_vec_dump_enabled()
4216 : -1;
4219 q, k, v, output,
4220 num_heads, num_kv_heads, num_tokens,
4221 head_dim, aligned_head_dim, kv_stride_tokens,
4222 causal, output_token_major, strict_scale, debug_layer_id)) {
4223 return;
4224 }
4225 }
4226#if CK_ENABLE_LLAMA_CPP_PARITY
4227 if (!causal && !output_token_major &&
4229 k,
4230 v,
4231 output,
4232 num_heads,
4233 num_kv_heads,
4234 num_tokens,
4235 head_dim,
4236 aligned_head_dim,
4237 kv_stride_tokens,
4238 strict_scale)) {
4239 return;
4240 }
4241#endif
4242 float *score_row = (float *) alloca((size_t) T * sizeof(float));
4243 float *logit_row = (float *) alloca((size_t) T * sizeof(float));
4244 float *v_cols = (float *) alloca((size_t) head_dim * (size_t) T * sizeof(float));
4245 for (int h = 0; h < num_heads; ++h) {
4246 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
4247 const float *k_head = k + (size_t)kv_head * kv_head_stride;
4248 const float *v_head = v + (size_t)kv_head * kv_head_stride;
4249
4250 for (int d = 0; d < head_dim; ++d) {
4251 float *dst_col = v_cols + (size_t) d * (size_t) T;
4252 for (int j = 0; j < T; ++j) {
4253 dst_col[j] = v_head[(size_t) j * (size_t) aligned_head_dim + (size_t) d];
4254 }
4255 }
4256
4257 for (int i = 0; i < T; ++i) {
4258 const float *q_vec = q + qkv_index(h, i, 0, T, aligned_head_dim);
4259 float *out_vec = output + qkv_index(h, i, 0, T, aligned_head_dim);
4260 const int kv_tokens = causal ? (i + 1) : T;
4261 if (kv_tokens <= 0) {
4262 for (int d = 0; d < aligned_head_dim; ++d) {
4263 out_vec[d] = 0.0f;
4264 }
4265 continue;
4266 }
4267 for (int j = 0; j < kv_tokens; ++j) {
4268 const float *k_vec = k_head + (size_t) j * (size_t) aligned_head_dim;
4269 score_row[j] = ck_ggml_vec_dot_f32_contig(q_vec, k_vec, head_dim);
4270 }
4271 float *raw_dump = NULL;
4272 if (ck_attention_vec_dump_should_emit(debug_layer_id, h, i)) {
4273 raw_dump = (float *) alloca((size_t) kv_tokens * sizeof(float));
4274 memcpy(raw_dump, score_row, (size_t) kv_tokens * sizeof(float));
4275 }
4276 memcpy(logit_row, score_row, (size_t) kv_tokens * sizeof(float));
4277 ck_vec_scale_f32_inplace(logit_row, kv_tokens, strict_scale);
4278 const float max_score = ck_vec_max_f32_contig(logit_row, kv_tokens);
4279 const double sum = ck_ggml_vec_soft_max_row(kv_tokens, score_row, logit_row, max_score);
4280 if (sum > 0.0) {
4281 const float inv_sum = (float) (1.0 / sum);
4282 ck_vec_scale_f32_inplace(score_row, kv_tokens, inv_sum);
4283 for (int d = 0; d < head_dim; ++d) {
4284 const float *v_col = v_cols + (size_t) d * (size_t) T;
4285 out_vec[d] = ck_ggml_vec_dot_f32_contig(score_row, v_col, kv_tokens);
4286 }
4287 } else {
4288 for (int d = 0; d < head_dim; ++d) {
4289 out_vec[d] = 0.0f;
4290 }
4291 }
4292 for (int d = head_dim; d < aligned_head_dim; ++d) {
4293 out_vec[d] = 0.0f;
4294 }
4295 if (raw_dump) {
4296 ck_attention_vec_dump_selected_query(raw_dump, score_row, out_vec, v_cols,
4297 kv_tokens, head_dim,
4298 debug_layer_id, h, i);
4299 }
4300 }
4301 }
4302 return;
4303 }
4304
4305 /*
4306 * Full GGML-style attention consumes FP32 Q with K/V rounded through
4307 * FP16. Materialize that semantic input once so serial, threaded, and
4308 * ISA-specific implementations all see the same values. Causal callers
4309 * retain their existing FP32 K/V contract.
4310 */
4311 float *rounded_kv = NULL;
4312 const float *compute_k = k;
4313 const float *compute_v = v;
4314 if (!causal && round_full_kv_fp16) {
4315 if ((size_t) kv_stride_tokens > SIZE_MAX / (size_t) num_kv_heads) {
4316 return;
4317 }
4318 const size_t rows = (size_t) num_kv_heads * (size_t) kv_stride_tokens;
4319 if ((size_t) aligned_head_dim > SIZE_MAX / rows) {
4320 return;
4321 }
4322 const size_t elements = rows * (size_t) aligned_head_dim;
4323 if (elements > SIZE_MAX / (2 * sizeof(float))) {
4324 return;
4325 }
4326 rounded_kv = (float *) malloc(2 * elements * sizeof(float));
4327 if (!rounded_kv) {
4328 return;
4329 }
4330 compute_k = rounded_kv;
4331 compute_v = rounded_kv + elements;
4332 ck_round_fp16_buffer(k, rounded_kv, elements);
4333 ck_round_fp16_buffer(v, rounded_kv + elements, elements);
4334 }
4335
4336 // Select SIMD implementation based on compile-time CPU features
4337#if defined(__AVX512F__)
4338 #define FLASH_QUERY_IMPL attention_flash_query_causal_avx512
4339#elif defined(__AVX2__)
4340 #define FLASH_QUERY_IMPL attention_flash_query_causal_avx2
4341#elif defined(__AVX__)
4342 #define FLASH_QUERY_IMPL attention_flash_query_causal_avx
4343#else
4344 #define FLASH_QUERY_IMPL attention_flash_query_causal
4345#endif
4346
4347 const int total_queries = num_heads * T;
4348#if defined(__AVX512F__)
4349 if (!causal && !output_token_major && head_dim == 72 && aligned_head_dim >= 72 && ck_attention_qblock8_enabled()) {
4350 ck_threadpool_t *pool = ck_threadpool_global();
4351 const int q_blocks = (T + 7) / 8;
4352 const int total_blocks = num_heads * q_blocks;
4353 int active = ck_attention_pick_active_threads(pool, total_blocks, T);
4354 if (pool && active > 1) {
4355 ck_attention_qblock4_args_t args = {
4356 .q = q,
4357 .k = compute_k,
4358 .v = compute_v,
4359 .output = output,
4360 .num_heads = num_heads,
4361 .num_kv_heads = num_kv_heads,
4362 .num_tokens = num_tokens,
4363 .aligned_head_dim = aligned_head_dim,
4364 .kv_stride_tokens = kv_stride_tokens,
4365 .scale = scale,
4366 };
4367 ck_threadpool_dispatch_n(pool, active, ck_attention_full_qblock8_work, &args);
4368 free(rounded_kv);
4369 return;
4370 }
4371 }
4372
4373 if (!causal && !output_token_major && head_dim == 72 && aligned_head_dim >= 72 && ck_attention_qblock4_enabled()) {
4374 ck_threadpool_t *pool = ck_threadpool_global();
4375 const int q_blocks = (T + 3) / 4;
4376 const int total_blocks = num_heads * q_blocks;
4377 int active = ck_attention_pick_active_threads(pool, total_blocks, T);
4378 if (pool && active > 1) {
4379 ck_attention_qblock4_args_t args = {
4380 .q = q,
4381 .k = compute_k,
4382 .v = compute_v,
4383 .output = output,
4384 .num_heads = num_heads,
4385 .num_kv_heads = num_kv_heads,
4386 .num_tokens = num_tokens,
4387 .aligned_head_dim = aligned_head_dim,
4388 .kv_stride_tokens = kv_stride_tokens,
4389 .scale = scale,
4390 };
4391 ck_threadpool_dispatch_n(pool, active, ck_attention_full_qblock4_work, &args);
4392 free(rounded_kv);
4393 return;
4394 }
4395 }
4396#endif
4397 if (ck_attention_parallel_enabled(total_queries, T, head_dim)) {
4398 ck_threadpool_t *pool = ck_threadpool_global();
4399 const int active = ck_attention_pick_active_threads(pool, total_queries, T);
4400 if (pool && active > 1) {
4401 ck_attention_parallel_args_t args = {
4402 .q = q,
4403 .k = compute_k,
4404 .v = compute_v,
4405 .output = output,
4406 .num_heads = num_heads,
4407 .num_kv_heads = num_kv_heads,
4408 .num_tokens = num_tokens,
4409 .head_dim = head_dim,
4410 .aligned_head_dim = aligned_head_dim,
4411 .kv_stride_tokens = kv_stride_tokens,
4412 .causal = causal,
4413 .output_token_major = output_token_major,
4414 .scale = scale,
4415 };
4417 free(rounded_kv);
4418 return;
4419 }
4420 }
4421
4422 for (int h = 0; h < num_heads; ++h) {
4423 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
4424 const float *k_head = compute_k + (size_t)kv_head * kv_head_stride;
4425 const float *v_head = compute_v + (size_t)kv_head * kv_head_stride;
4426
4427 for (int i = 0; i < T; ++i) {
4428 const float *q_vec = q + qkv_index(h, i, 0, T, aligned_head_dim);
4429 float *out_vec = output + attention_output_index(
4430 h, i, num_heads, T, aligned_head_dim, output_token_major);
4431 const int kv_tokens = causal ? (i + 1) : T;
4432 FLASH_QUERY_IMPL(q_vec, k_head, v_head,
4433 kv_tokens,
4434 head_dim, aligned_head_dim,
4435 scale, out_vec);
4436 }
4437 }
4438
4439 free(rounded_kv);
4440
4441#undef FLASH_QUERY_IMPL
4442}
4443
4445 const float *k,
4446 const float *v,
4447 float *output,
4448 int num_heads,
4449 int num_kv_heads,
4450 int num_tokens,
4451 int head_dim,
4452 int aligned_head_dim)
4453{
4455 num_heads, num_kv_heads,
4456 num_tokens, head_dim,
4457 aligned_head_dim,
4458 /*kv_stride_tokens=*/num_tokens,
4459 /*causal=*/1,
4460 /*round_full_kv_fp16=*/0,
4461 /*output_token_major=*/0,
4462 1.0f / sqrtf((float)head_dim));
4463}
4464
4466 const float *k,
4467 const float *v,
4468 float *output,
4469 int num_heads,
4470 int num_kv_heads,
4471 int num_tokens,
4472 int head_dim,
4473 int aligned_head_dim)
4474{
4476 num_heads, num_kv_heads,
4477 num_tokens, head_dim,
4478 aligned_head_dim,
4479 /*kv_stride_tokens=*/num_tokens,
4480 /*causal=*/0,
4481 /*round_full_kv_fp16=*/1,
4482 /*output_token_major=*/0,
4483 1.0f / sqrtf((float)head_dim));
4484}
4485
4486/**
4487 * Flash attention forward with custom KV stride (for KV cache)
4488 * @test test_flash_attention.py::TestFlashAttention::test_flash_strided
4489 * @test test_kv_cache_attention.py::TestKVCacheAttention::test_flash_attention
4490 *
4491 * Variant with configurable kv_stride_tokens for KV cache layouts
4492 * where K/V may not be contiguous in memory.
4493 *
4494 * After changes: make test
4495 */
4497 const float *k,
4498 const float *v,
4499 float *output,
4500 int num_heads,
4501 int num_kv_heads,
4502 int num_tokens,
4503 int head_dim,
4504 int aligned_head_dim,
4505 int kv_stride_tokens)
4506{
4508 num_heads, num_kv_heads,
4509 num_tokens, head_dim,
4510 aligned_head_dim,
4511 kv_stride_tokens,
4512 /*causal=*/1,
4513 /*round_full_kv_fp16=*/0,
4514 /*output_token_major=*/0,
4515 1.0f / sqrtf((float)head_dim));
4516}
4517
4519 const float *q,
4520 const float *k,
4521 const float *v,
4522 float *output,
4523 int num_heads,
4524 int num_kv_heads,
4525 int num_tokens,
4526 int head_dim,
4527 int aligned_head_dim,
4528 int kv_stride_tokens)
4529{
4531 num_heads, num_kv_heads,
4532 num_tokens, head_dim,
4533 aligned_head_dim,
4534 kv_stride_tokens,
4535 /*causal=*/1,
4536 /*round_full_kv_fp16=*/0,
4537 /*output_token_major=*/1,
4538 1.0f / sqrtf((float)head_dim));
4539}
4540
4542 const float *k,
4543 const float *v,
4544 float *output,
4545 int num_heads,
4546 int num_kv_heads,
4547 int num_tokens,
4548 int head_dim,
4549 int aligned_head_dim,
4550 int kv_stride_tokens)
4551{
4553 num_heads, num_kv_heads,
4554 num_tokens, head_dim,
4555 aligned_head_dim,
4556 kv_stride_tokens,
4557 /*causal=*/0,
4558 /*round_full_kv_fp16=*/1,
4559 /*output_token_major=*/0,
4560 1.0f / sqrtf((float)head_dim));
4561}
4562
4563
4564static float ck_bf16_dot_contract(const float *a, const float *b, int count)
4565{
4566#if defined(__AVX512BF16__)
4567 __m512 acc = _mm512_setzero_ps();
4568 int i = 0;
4569 for (; i + 32 <= count; i += 32) {
4570 const __m512bh av = _mm512_cvtne2ps_pbh(_mm512_loadu_ps(a + i + 16), _mm512_loadu_ps(a + i));
4571 const __m512bh bv = _mm512_cvtne2ps_pbh(_mm512_loadu_ps(b + i + 16), _mm512_loadu_ps(b + i));
4572 acc = _mm512_dpbf16_ps(acc, av, bv);
4573 }
4574 float sum = _mm512_reduce_add_ps(acc);
4575 for (; i < count; ++i) {
4576 const float av = bf16_to_float(float_to_bf16(a[i]));
4577 const float bv = bf16_to_float(float_to_bf16(b[i]));
4578 sum += av * bv;
4579 }
4580 return sum;
4581#else
4582 float sum = 0.0f;
4583 for (int i = 0; i < count; ++i) {
4584 const float av = bf16_to_float(float_to_bf16(a[i]));
4585 const float bv = bf16_to_float(float_to_bf16(b[i]));
4586 sum += av * bv;
4587 }
4588 return sum;
4589#endif
4590}
4591
4593{
4594 /* PyTorch CPU flash calculate_scale evaluates in FP64, then narrows. */
4595 return head_dim > 0 ? (float)(1.0 / sqrt((double)head_dim)) : 0.0f;
4596}
4597
4598#if defined(__AVX512F__)
4599static float ck_bf16_sdpa_reduce_add_avx512(__m512 value)
4600{
4601 __m512 other = _mm512_shuffle_f32x4(value, value, 0x4E);
4602 value = _mm512_add_ps(value, other);
4603 other = _mm512_shuffle_f32x4(value, value, 0xB1);
4604 value = _mm512_add_ps(value, other);
4605 other = _mm512_shuffle_ps(value, value, 0x4E);
4606 value = _mm512_add_ps(value, other);
4607 other = _mm512_shuffle_ps(value, value, 0xB1);
4608 value = _mm512_add_ps(value, other);
4609 return _mm512_cvtss_f32(value);
4610}
4611
4612static float ck_bf16_sdpa_reduce_max_avx512(__m512 value)
4613{
4614 __m512 other = _mm512_shuffle_f32x4(value, value, 0x4E);
4615 value = _mm512_max_ps(value, other);
4616 other = _mm512_shuffle_f32x4(value, value, 0xB1);
4617 value = _mm512_max_ps(value, other);
4618 other = _mm512_shuffle_ps(value, value, 0x4E);
4619 value = _mm512_max_ps(value, other);
4620 other = _mm512_shuffle_ps(value, value, 0xB1);
4621 value = _mm512_max_ps(value, other);
4622 return _mm512_cvtss_f32(value);
4623}
4624
4625static __m512 ck_bf16_sdpa_exp_u20_avx512(__m512 value)
4626{
4627 const __m512 c1 = _mm512_set1_ps(0.999999701f);
4628 const __m512 c2 = _mm512_set1_ps(0.499991506f);
4629 const __m512 c3 = _mm512_set1_ps(0.166676521f);
4630 const __m512 c4 = _mm512_set1_ps(0.0418978221f);
4631 const __m512 c5 = _mm512_set1_ps(0.00828929059f);
4632 const __m512 log2e = _mm512_castsi512_ps(_mm512_set1_epi32(0x3fb8aa3b));
4633 const __m512 half = _mm512_set1_ps(0.5f);
4634 const __m512 one = _mm512_set1_ps(1.0f);
4635 const __m512 zero = _mm512_setzero_ps();
4636 const __m512 two = _mm512_set1_ps(2.0f);
4637 const __m512 ln2 = _mm512_castsi512_ps(_mm512_set1_epi32(0x3f317218));
4638 const __m512 min_log = _mm512_castsi512_ps(_mm512_set1_epi32(0xc2aeac50));
4639 const __m512 max_log = _mm512_castsi512_ps(_mm512_set1_epi32(0x42b17218));
4640 const __mmask16 underflow = _mm512_cmp_ps_mask(value, min_log, _CMP_LT_OS);
4641 __m512 source = _mm512_max_ps(_mm512_min_ps(value, max_log), min_log);
4642 __m512 exponent = _mm512_fmadd_ps(source, log2e, half);
4643 const __m512i exponent_i = _mm512_cvt_roundps_epi32(
4644 exponent, _MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC);
4645 exponent = _mm512_cvtepi32_ps(exponent_i);
4646 const __m512 reduced = _mm512_fnmadd_ps(exponent, ln2, source);
4647 __m512 result = _mm512_fmadd_ps(reduced, c5, c4);
4648 result = _mm512_fmadd_ps(reduced, result, c3);
4649 result = _mm512_fmadd_ps(reduced, result, c2);
4650 result = _mm512_fmadd_ps(reduced, result, c1);
4651 result = _mm512_fmadd_ps(reduced, result, one);
4652 __m512i power = _mm512_cvtps_epi32(_mm512_sub_ps(exponent, one));
4653 power = _mm512_slli_epi32(_mm512_add_epi32(power, _mm512_set1_epi32(127)), 23);
4654 __m512 scale = _mm512_castsi512_ps(power);
4655 scale = _mm512_mask_blend_ps(underflow, scale, zero);
4656 return _mm512_mul_ps(_mm512_mul_ps(result, scale), two);
4657}
4658
4659static float ck_bf16_sdpa_scale_max_avx512(float *row, int count, float scale)
4660{
4661 __m512 lane_max = _mm512_set1_ps(-INFINITY);
4662 const __m512 scale_v = _mm512_set1_ps(scale);
4663 for (int i = 0; i < count; i += 16) {
4664 const __m512 value = _mm512_mul_ps(_mm512_loadu_ps(row + i), scale_v);
4665 lane_max = _mm512_max_ps(lane_max, value);
4666 _mm512_storeu_ps(row + i, value);
4667 }
4668 return ck_bf16_sdpa_reduce_max_avx512(lane_max);
4669}
4670
4671static float ck_bf16_sdpa_exp_sum_avx512(
4672 const float *scores, uint16_t *probabilities, int count, float maximum)
4673{
4674 __m512 lane_sum = _mm512_setzero_ps();
4675 const __m512 maximum_v = _mm512_set1_ps(maximum);
4676 for (int i = 0; i < count; i += 16) {
4677 const __m512 exponent = ck_bf16_sdpa_exp_u20_avx512(
4678 _mm512_sub_ps(_mm512_loadu_ps(scores + i), maximum_v));
4679 lane_sum = _mm512_add_ps(lane_sum, exponent);
4680 float values[16];
4681 _mm512_storeu_ps(values, exponent);
4682 for (int lane = 0; lane < 16; ++lane) {
4683 probabilities[i + lane] = float_to_bf16(values[lane]);
4684 }
4685 }
4686 return ck_bf16_sdpa_reduce_add_avx512(lane_sum);
4687}
4688#endif
4689
4691 const float *q, const float *k, const float *v, float *output,
4692 int num_heads, int num_kv_heads, int num_tokens,
4693 int head_dim, int aligned_head_dim, int kv_stride_tokens,
4694 int head_begin, int head_step, int output_token_major)
4695{
4696#if defined(__AVX512F__)
4697 /*
4698 * This is a schedule contract, not merely an AMX acceleration of the
4699 * portable kernel. PyTorch CPU flash attention composes FP64 scale
4700 * evaluation, oneDNN-style BF16 BRGEMMs, AVX-512 exp/reductions, BF16
4701 * probability storage, and a second BRGEMM. When parity drifts, compare
4702 * this complete sequence with the PyTorch and oneDNN sources before
4703 * changing an isolated primitive or tolerance.
4704 */
4705 enum { Q_BLOCK = 256, KV_BLOCK = 512, K_PAD = 72, D_PAD = 80 };
4706 if (!ck_gemm_bf16_amx_available() || head_dim != 72 || aligned_head_dim != 72 ||
4707 (num_tokens % 16) != 0 || (kv_stride_tokens < num_tokens) ||
4708 num_heads % num_kv_heads != 0) return 0;
4709 uint16_t *q_block = malloc((size_t)Q_BLOCK * K_PAD * sizeof(uint16_t));
4710 uint16_t *k_packed = malloc((size_t)num_tokens * K_PAD * sizeof(uint16_t));
4711 uint16_t *probabilities = malloc((size_t)Q_BLOCK * KV_BLOCK * sizeof(uint16_t));
4712 uint16_t *v_packed = malloc((size_t)num_tokens * D_PAD * sizeof(uint16_t));
4713 float *scores = malloc((size_t)Q_BLOCK * KV_BLOCK * sizeof(float));
4714 float *destination = malloc((size_t)Q_BLOCK * D_PAD * sizeof(float));
4715 float *row_max = malloc((size_t)Q_BLOCK * sizeof(float));
4716 float *row_sum = malloc((size_t)Q_BLOCK * sizeof(float));
4717 if (!q_block || !k_packed || !probabilities || !v_packed || !scores ||
4718 !destination || !row_max || !row_sum) {
4719 free(q_block); free(k_packed); free(probabilities); free(v_packed);
4720 free(scores); free(destination); free(row_max); free(row_sum);
4721 return 0;
4722 }
4723 const float scale = ck_attention_pytorch_sdpa_scale_f32(head_dim);
4724 const size_t q_stride = (size_t)num_tokens * (size_t)aligned_head_dim;
4725 const size_t kv_stride = (size_t)kv_stride_tokens * (size_t)aligned_head_dim;
4726 for (int h = head_begin; h < num_heads; h += head_step) {
4727 const int kv_head = (int)((long long)h * num_kv_heads / num_heads);
4728 const float *qh = q + (size_t)h * q_stride;
4729 const float *kh = k + (size_t)kv_head * kv_stride;
4730 const float *vh = v + (size_t)kv_head * kv_stride;
4731 /*
4732 * K and V are invariant across all query blocks for this head. Pack
4733 * each 512-key tile once, retaining the exact block-local layouts
4734 * consumed by the two certified BRGEMMs below.
4735 */
4736 for (int n = 0; n < num_tokens; n += KV_BLOCK) {
4737 const int key_count = num_tokens - n < KV_BLOCK ? num_tokens - n : KV_BLOCK;
4738 uint16_t *k_block = k_packed + (size_t)n * K_PAD;
4739 uint16_t *v_block = v_packed + (size_t)n * D_PAD;
4740 memset(k_block, 0, (size_t)key_count * K_PAD * sizeof(uint16_t));
4741 memset(v_block, 0, (size_t)D_PAD * key_count * sizeof(uint16_t));
4742 for (int key = 0; key < key_count; ++key) {
4743 for (int d = 0; d < head_dim; ++d) {
4744 k_block[(size_t)key * K_PAD + d] =
4745 float_to_bf16(kh[(size_t)(n + key) * aligned_head_dim + d]);
4746 v_block[(size_t)d * key_count + key] =
4747 float_to_bf16(vh[(size_t)(n + key) * aligned_head_dim + d]);
4748 }
4749 }
4750 }
4751 for (int m = 0; m < num_tokens; m += Q_BLOCK) {
4752 const int query_count = num_tokens - m < Q_BLOCK ? num_tokens - m : Q_BLOCK;
4753 memset(q_block, 0, (size_t)query_count * K_PAD * sizeof(uint16_t));
4754 memset(destination, 0, (size_t)query_count * D_PAD * sizeof(float));
4755 for (int row = 0; row < query_count; ++row) {
4756 for (int d = 0; d < head_dim; ++d) {
4757 q_block[(size_t)row * K_PAD + d] =
4758 float_to_bf16(qh[(size_t)(m + row) * aligned_head_dim + d]);
4759 }
4760 row_max[row] = -INFINITY;
4761 row_sum[row] = 0.0f;
4762 }
4763 for (int n = 0; n < num_tokens; n += KV_BLOCK) {
4764 const int key_count = num_tokens - n < KV_BLOCK ? num_tokens - n : KV_BLOCK;
4765 const uint16_t *k_block = k_packed + (size_t)n * K_PAD;
4766 const uint16_t *v_block = v_packed + (size_t)n * D_PAD;
4768 q_block, k_block, scores,
4769 query_count, key_count, K_PAD, 0)) goto fail;
4770 for (int row = 0; row < query_count; ++row) {
4771 float *score_row = scores + (size_t)row * key_count;
4772 uint16_t *prob_row = probabilities + (size_t)row * key_count;
4773 const float block_max = ck_bf16_sdpa_scale_max_avx512(
4774 score_row, key_count, scale);
4775 const float merged_max = row_max[row] > block_max
4776 ? row_max[row] : block_max;
4777 const float old_scale = isfinite(row_max[row])
4778 ? expf(row_max[row] - merged_max) : 0.0f;
4779 const float block_sum = ck_bf16_sdpa_exp_sum_avx512(
4780 score_row, prob_row, key_count, merged_max);
4781 row_sum[row] = block_sum + old_scale * row_sum[row];
4782 row_max[row] = merged_max;
4783 if (n > 0) {
4784 __m512 scale_v = _mm512_set1_ps(old_scale);
4785 int d = 0;
4786 for (; d + 16 <= D_PAD; d += 16) {
4787 float *dst = destination + (size_t)row * D_PAD + d;
4788 _mm512_storeu_ps(dst, _mm512_mul_ps(_mm512_loadu_ps(dst), scale_v));
4789 }
4790 }
4791 }
4793 probabilities, v_block, destination,
4794 query_count, D_PAD, key_count, n > 0)) goto fail;
4795 }
4796 for (int row = 0; row < query_count; ++row) {
4797 const float reciprocal = row_sum[row] == 0.0f ? 1.0f : 1.0f / row_sum[row];
4798 float *out_row = output + (
4799 output_token_major
4800 ? ((size_t)(m + row) * (size_t)num_heads + (size_t)h)
4801 * (size_t)aligned_head_dim
4802 : ((size_t)h * (size_t)num_tokens + (size_t)(m + row))
4803 * (size_t)aligned_head_dim);
4804 for (int d = 0; d < head_dim; ++d) {
4805 out_row[d] = bf16_to_float(
4806 float_to_bf16(destination[(size_t)row * D_PAD + d] * reciprocal));
4807 }
4808 }
4809 }
4810 }
4811 free(q_block); free(k_packed); free(probabilities); free(v_packed);
4812 free(scores); free(destination); free(row_max); free(row_sum);
4813 return 1;
4814fail:
4815 free(q_block); free(k_packed); free(probabilities); free(v_packed);
4816 free(scores); free(destination); free(row_max); free(row_sum);
4817 return 0;
4818#else
4819 (void)q; (void)k; (void)v; (void)output; (void)num_heads;
4820 (void)num_kv_heads; (void)num_tokens; (void)head_dim;
4821 (void)aligned_head_dim; (void)kv_stride_tokens; (void)head_begin; (void)head_step;
4822 (void)output_token_major;
4823 return 0;
4824#endif
4825}
4826
4828 const float *q, const float *k, const float *v, float *output,
4829 int num_heads, int num_kv_heads, int num_tokens,
4830 int head_dim, int aligned_head_dim, int kv_stride_tokens,
4831 int head_begin, int head_step)
4832{
4833 if (!q || !k || !v || !output || num_heads <= 0 || num_kv_heads <= 0 ||
4834 head_dim <= 0 || num_tokens <= 0 || aligned_head_dim < head_dim ||
4835 kv_stride_tokens < num_tokens || num_heads % num_kv_heads != 0) return 0;
4836 if ((size_t)num_tokens > SIZE_MAX / (size_t)head_dim) return 0;
4837 const size_t v_col_count = (size_t)head_dim * (size_t)num_tokens;
4838 if (v_col_count > SIZE_MAX / sizeof(float)) return 0;
4839 if ((size_t)num_tokens > SIZE_MAX / (size_t)aligned_head_dim) return 0;
4840 if ((size_t)kv_stride_tokens > SIZE_MAX / (size_t)aligned_head_dim) return 0;
4841 const float scale = ck_attention_pytorch_sdpa_scale_f32(head_dim);
4842 const size_t q_head_stride = (size_t)num_tokens * (size_t)aligned_head_dim;
4843 const size_t kv_head_stride = (size_t)kv_stride_tokens * (size_t)aligned_head_dim;
4844 if ((size_t)num_heads > SIZE_MAX / q_head_stride) return 0;
4845 if ((size_t)num_kv_heads > SIZE_MAX / kv_head_stride) return 0;
4846 float scores[512];
4847 float probs[512];
4848 float *v_cols = (float *)malloc(v_col_count * sizeof(float));
4849 if (!v_cols) return 0;
4850 for (int h = head_begin; h < num_heads; h += head_step) {
4851 const int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
4852 const float *qh = q + (size_t)h * q_head_stride;
4853 const float *kh = k + (size_t)kv_head * kv_head_stride;
4854 const float *vh = v + (size_t)kv_head * kv_head_stride;
4855 float *oh = output + (size_t)h * q_head_stride;
4856 for (int d = 0; d < head_dim; ++d) {
4857 float *col = v_cols + (size_t)d * (size_t)num_tokens;
4858 for (int t = 0; t < num_tokens; ++t) {
4859 col[t] = vh[(size_t)t * (size_t)aligned_head_dim + (size_t)d];
4860 }
4861 }
4862 for (int row = 0; row < num_tokens; ++row) {
4863 const float *qrow = qh + (size_t)row * (size_t)aligned_head_dim;
4864 float *dst = oh + (size_t)row * (size_t)aligned_head_dim;
4865 for (int d = 0; d < head_dim; ++d) dst[d] = 0.0f;
4866 float running_max = -INFINITY;
4867 float running_sum = 0.0f;
4868 for (int n = 0; n < num_tokens; n += 512) {
4869 const int block = num_tokens - n < 512 ? num_tokens - n : 512;
4870 float block_max = -INFINITY;
4871 for (int j = 0; j < block; ++j) {
4872 scores[j] = ck_bf16_dot_contract(
4873 qrow,
4874 kh + (size_t)(n + j) * (size_t)aligned_head_dim,
4875 head_dim) * scale;
4876 if (scores[j] > block_max) block_max = scores[j];
4877 }
4878 const float merged_max = running_max > block_max ? running_max : block_max;
4879 const float old_scale = isfinite(running_max) ? expf(running_max - merged_max) : 0.0f;
4880 float block_sum = 0.0f;
4881 for (int j = 0; j < block; ++j) {
4882 const float p = expf(scores[j] - merged_max);
4883 block_sum += p;
4884 probs[j] = bf16_to_float(float_to_bf16(p));
4885 }
4886 for (int d = 0; d < head_dim; ++d) {
4887 const float partial = ck_bf16_dot_contract(
4888 probs,
4889 v_cols + (size_t)d * (size_t)num_tokens + (size_t)n,
4890 block);
4891 dst[d] = dst[d] * old_scale + partial;
4892 }
4893 running_sum = running_sum * old_scale + block_sum;
4894 running_max = merged_max;
4895 }
4896 const float inv = running_sum > 0.0f ? 1.0f / running_sum : 0.0f;
4897 for (int d = 0; d < head_dim; ++d) dst[d] *= inv;
4898 for (int d = head_dim; d < aligned_head_dim; ++d) dst[d] = 0.0f;
4899 }
4900 }
4901 free(v_cols);
4902 return 1;
4903}
4904
4905typedef struct {
4906 const float *q;
4907 const float *k;
4908 const float *v;
4909 float *output;
4910 int num_heads;
4911 int num_kv_heads;
4912 int num_tokens;
4913 int head_dim;
4914 int aligned_head_dim;
4915 int kv_stride_tokens;
4916 int output_token_major;
4917 int failed;
4918} ck_attention_bf16_sdpa_args_t;
4919
4920static void ck_attention_bf16_sdpa_work(int ith, int nth, void *opaque)
4921{
4922 ck_attention_bf16_sdpa_args_t *args = (ck_attention_bf16_sdpa_args_t *)opaque;
4924 args->q, args->k, args->v, args->output,
4925 args->num_heads, args->num_kv_heads, args->num_tokens,
4926 args->head_dim, args->aligned_head_dim, args->kv_stride_tokens,
4927 ith, nth)) {
4928 __atomic_store_n(&args->failed, 1, __ATOMIC_RELAXED);
4929 }
4930}
4931
4932static void ck_attention_bf16_pytorch_flash_work(int ith, int nth, void *opaque)
4933{
4934 ck_attention_bf16_sdpa_args_t *args = (ck_attention_bf16_sdpa_args_t *)opaque;
4936 args->q, args->k, args->v, args->output,
4937 args->num_heads, args->num_kv_heads, args->num_tokens,
4938 args->head_dim, args->aligned_head_dim, args->kv_stride_tokens,
4939 ith, nth, args->output_token_major)) {
4940 __atomic_store_n(&args->failed, 1, __ATOMIC_RELAXED);
4941 }
4942}
4943
4945 const float *q, const float *k, const float *v, float *output,
4946 int num_heads, int num_kv_heads, int num_tokens,
4947 int head_dim, int aligned_head_dim, int kv_stride_tokens,
4948 int output_token_major)
4949{
4950 ck_attention_bf16_sdpa_args_t args = {
4951 .q=q, .k=k, .v=v, .output=output,
4952 .num_heads=num_heads, .num_kv_heads=num_kv_heads,
4953 .num_tokens=num_tokens, .head_dim=head_dim,
4954 .aligned_head_dim=aligned_head_dim, .kv_stride_tokens=kv_stride_tokens,
4955 .output_token_major=output_token_major,
4956 .failed=0
4957 };
4958 ck_threadpool_t *pool = ck_threadpool_global();
4959 int active = pool ? ck_threadpool_n_threads(pool) : 1;
4960 if (active > num_heads) active = num_heads;
4961 if (pool && active > 1) {
4963 pool, active, ck_attention_bf16_pytorch_flash_work, &args);
4964 } else {
4966 }
4967 return __atomic_load_n(&args.failed, __ATOMIC_RELAXED) == 0;
4968}
4969
4971 const float *q, const float *k, const float *v, float *output,
4972 int num_heads, int num_kv_heads, int num_tokens,
4973 int head_dim, int aligned_head_dim, int kv_stride_tokens)
4974{
4975 ck_attention_bf16_sdpa_args_t args = {
4976 .q=q, .k=k, .v=v, .output=output,
4977 .num_heads=num_heads, .num_kv_heads=num_kv_heads,
4978 .num_tokens=num_tokens, .head_dim=head_dim,
4979 .aligned_head_dim=aligned_head_dim, .kv_stride_tokens=kv_stride_tokens,
4980 .output_token_major=0,
4981 .failed=0
4982 };
4983 ck_threadpool_t *pool = ck_threadpool_global();
4984 int active = pool ? ck_threadpool_n_threads(pool) : 1;
4985 if (active > num_heads) active = num_heads;
4986 if (pool && active > 1) {
4988 } else {
4989 ck_attention_bf16_sdpa_work(0, 1, &args);
4990 }
4991 return __atomic_load_n(&args.failed, __ATOMIC_RELAXED) == 0;
4992}
4993
4995 const float *q,
4996 const float *k,
4997 const float *v,
4998 float *output,
4999 int num_heads,
5000 int num_kv_heads,
5001 int num_tokens,
5002 int head_dim,
5003 int aligned_head_dim,
5004 int kv_stride_tokens)
5005{
5007 q, k, v, output,
5008 num_heads, num_kv_heads, num_tokens, head_dim,
5009 aligned_head_dim, kv_stride_tokens,
5010 /*causal=*/0,
5011 /*round_full_kv_fp16=*/0,
5012 /*output_token_major=*/0,
5013 1.0f / sqrtf((float)head_dim)
5014 );
5015 const size_t count = (size_t)num_heads * (size_t)num_tokens
5016 * (size_t)aligned_head_dim;
5017 for (size_t i = 0; i < count; ++i) {
5018 output[i] = bf16_to_float(float_to_bf16(output[i]));
5019 }
5020}
5021
5023 const float *q,
5024 const float *k,
5025 const float *v,
5026 float *output,
5027 int num_heads,
5028 int num_kv_heads,
5029 int num_tokens,
5030 int head_dim,
5031 int aligned_head_dim,
5032 int kv_stride_tokens)
5033{
5035 q, k, v, output, num_heads, num_kv_heads, num_tokens,
5036 head_dim, aligned_head_dim, kv_stride_tokens)) {
5037 const size_t count = (size_t)num_heads * (size_t)num_tokens
5038 * (size_t)aligned_head_dim;
5039 for (size_t i = 0; i < count; ++i) {
5040 output[i] = bf16_to_float(float_to_bf16(output[i]));
5041 }
5042 return;
5043 }
5044 fprintf(stderr, "CK numerical contract failure: BF16 tiled SDPA received invalid dimensions or could not allocate scratch\n");
5045}
5046
5048 const float *q,
5049 const float *k,
5050 const float *v,
5051 float *output,
5052 int num_heads,
5053 int num_kv_heads,
5054 int num_tokens,
5055 int head_dim,
5056 int aligned_head_dim,
5057 int kv_stride_tokens)
5058{
5060 q, k, v, output, num_heads, num_kv_heads, num_tokens,
5061 head_dim, aligned_head_dim, kv_stride_tokens,
5062 /*output_token_major=*/0)) {
5063 return;
5064 }
5065 fprintf(stderr,
5066 "HARD KERNEL CONTRACT FAULT: PyTorch CPU-flash BF16 attention "
5067 "requires AMX-BF16, AVX-512, D=72/A=72 and a token multiple of 16; "
5068 "no numerically different fallback is permitted\n");
5069 abort();
5070}
5071
5073 const float *q,
5074 const float *k,
5075 const float *v,
5076 float *output,
5077 int num_heads,
5078 int num_kv_heads,
5079 int num_tokens,
5080 int head_dim,
5081 int aligned_head_dim,
5082 int kv_stride_tokens)
5083{
5085 q, k, v, output, num_heads, num_kv_heads, num_tokens,
5086 head_dim, aligned_head_dim, kv_stride_tokens,
5087 /*output_token_major=*/1)) {
5088 return;
5089 }
5090 fprintf(stderr,
5091 "HARD KERNEL CONTRACT FAULT: PyTorch CPU-flash BF16 token-output "
5092 "attention requires AMX-BF16, AVX-512, D=72/A=72 and a token "
5093 "multiple of 16; no numerically different fallback is permitted\n");
5094 abort();
5095}
5096
5097
5099 const float *k,
5100 const float *v,
5101 float *output,
5102 int num_heads,
5103 int num_kv_heads,
5104 int num_tokens,
5105 int head_dim,
5106 int aligned_head_dim,
5107 int kv_stride_tokens)
5108{
5109 (void)head_dim;
5111 num_heads, num_kv_heads,
5112 num_tokens, head_dim,
5113 aligned_head_dim,
5114 kv_stride_tokens,
5115 /*causal=*/1,
5116 /*round_full_kv_fp16=*/0,
5117 /*output_token_major=*/0,
5118 1.0f);
5119}
5120
5122 const float *q,
5123 const float *k,
5124 const float *v,
5125 float *output,
5126 int num_heads,
5127 int num_kv_heads,
5128 int num_tokens,
5129 int head_dim,
5130 int aligned_head_dim,
5131 int kv_stride_tokens)
5132{
5134 num_heads, num_kv_heads,
5135 num_tokens, head_dim,
5136 aligned_head_dim,
5137 kv_stride_tokens,
5138 /*causal=*/1,
5139 /*round_full_kv_fp16=*/0,
5140 /*output_token_major=*/1,
5141 1.0f);
5142}
5143
5145 float *output,
5146 int num_heads,
5147 int num_tokens,
5148 int head_dim,
5149 int aligned_head_dim,
5150 int kv_stride_tokens)
5151{
5153 q, q, q, output, num_heads, num_heads, num_tokens,
5154 head_dim, aligned_head_dim, kv_stride_tokens
5155 );
5156}
5157
5159 const float *k,
5160 const float *v,
5161 float *output,
5162 int num_heads,
5163 int num_kv_heads,
5164 int num_tokens,
5165 int head_dim,
5166 int aligned_head_dim,
5167 int kv_stride_tokens)
5168{
5169 (void)head_dim;
5171 num_heads, num_kv_heads,
5172 num_tokens, head_dim,
5173 aligned_head_dim,
5174 kv_stride_tokens,
5175 /*causal=*/0,
5176 /*round_full_kv_fp16=*/1,
5177 /*output_token_major=*/0,
5178 1.0f);
5179}
5180
5181
5183 const float *q,
5184 const float *k,
5185 const float *v,
5186 float *output,
5187 int num_heads,
5188 int num_kv_heads,
5189 int num_tokens,
5190 int head_dim,
5191 int aligned_head_dim,
5192 int kv_stride_tokens,
5193 int visual_start,
5194 int visual_tokens,
5195 int output_token_major)
5196{
5197 if (!q || !k || !v || !output) {
5198 return;
5199 }
5200 if (num_heads <= 0 || num_kv_heads <= 0 || num_tokens <= 0) {
5201 return;
5202 }
5203 if (kv_stride_tokens < num_tokens || head_dim <= 0 || aligned_head_dim <= 0) {
5204 return;
5205 }
5206 if (visual_start < 0 || visual_tokens <= 0 || visual_start >= num_tokens) {
5208 q, k, v, output, num_heads, num_kv_heads, num_tokens, head_dim,
5209 aligned_head_dim, kv_stride_tokens, /*causal=*/1,
5210 /*round_full_kv_fp16=*/0, output_token_major, 1.0f);
5211 return;
5212 }
5213
5214 int visual_end = visual_start + visual_tokens;
5215 if (visual_end > num_tokens) {
5216 visual_end = num_tokens;
5217 }
5218 if (visual_end <= visual_start) {
5220 q, k, v, output, num_heads, num_kv_heads, num_tokens, head_dim,
5221 aligned_head_dim, kv_stride_tokens, /*causal=*/1,
5222 /*round_full_kv_fp16=*/0, output_token_major, 1.0f);
5223 return;
5224 }
5225
5226 const int T = num_tokens;
5227 const size_t kv_head_stride = (size_t)kv_stride_tokens * (size_t)aligned_head_dim;
5228 const float scale = 1.0f;
5229
5231 const int debug_layer_id = ck_attention_vec_dump_enabled()
5233 : -1;
5234 for (int h = 0; h < num_heads; ++h) {
5235 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
5236 const float *k_head = k + (size_t)kv_head * kv_head_stride;
5237 const float *v_head = v + (size_t)kv_head * kv_head_stride;
5238
5239 for (int i = 0; i < T; ++i) {
5240 const float *q_vec = q + qkv_index(h, i, 0, T, aligned_head_dim);
5241 float *out_vec = output + attention_output_index(
5242 h, i, num_heads, T, aligned_head_dim, output_token_major);
5243 const int in_visual = (i >= visual_start && i < visual_end);
5244 const int kv_tokens = in_visual ? visual_end : (i + 1);
5245 attention_flash_query_causal_exact(q_vec, k_head, v_head,
5246 kv_tokens,
5247 head_dim, aligned_head_dim,
5248 scale, out_vec);
5249 ck_attention_vec_dump_exact_query(q_vec, k_head, out_vec,
5250 kv_tokens,
5251 head_dim, aligned_head_dim,
5252 scale,
5253 debug_layer_id, h, i);
5254 }
5255 }
5256 return;
5257 }
5258
5259#if defined(__AVX512F__)
5260 #define FLASH_QUERY_IMPL attention_flash_query_causal_avx512
5261#elif defined(__AVX2__)
5262 #define FLASH_QUERY_IMPL attention_flash_query_causal_avx2
5263#elif defined(__AVX__)
5264 #define FLASH_QUERY_IMPL attention_flash_query_causal_avx
5265#else
5266 #define FLASH_QUERY_IMPL attention_flash_query_causal
5267#endif
5268
5269 for (int h = 0; h < num_heads; ++h) {
5270 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
5271 const float *k_head = k + (size_t)kv_head * kv_head_stride;
5272 const float *v_head = v + (size_t)kv_head * kv_head_stride;
5273
5274 for (int i = 0; i < T; ++i) {
5275 const float *q_vec = q + qkv_index(h, i, 0, T, aligned_head_dim);
5276 float *out_vec = output + attention_output_index(
5277 h, i, num_heads, T, aligned_head_dim, output_token_major);
5278 const int in_visual = (i >= visual_start && i < visual_end);
5279 const int kv_tokens = in_visual ? visual_end : (i + 1);
5280 FLASH_QUERY_IMPL(q_vec, k_head, v_head,
5281 kv_tokens,
5282 head_dim, aligned_head_dim,
5283 scale, out_vec);
5284 }
5285 }
5286
5287#undef FLASH_QUERY_IMPL
5288}
5289
5291 const float *q, const float *k, const float *v, float *output,
5292 int num_heads, int num_kv_heads, int num_tokens, int head_dim,
5293 int aligned_head_dim, int kv_stride_tokens, int visual_start,
5294 int visual_tokens)
5295{
5297 q, k, v, output, num_heads, num_kv_heads, num_tokens, head_dim,
5298 aligned_head_dim, kv_stride_tokens, visual_start, visual_tokens,
5299 /*output_token_major=*/0);
5300}
5301
5303 const float *q, const float *k, const float *v, float *output,
5304 int num_heads, int num_kv_heads, int num_tokens, int head_dim,
5305 int aligned_head_dim, int kv_stride_tokens, int visual_start,
5306 int visual_tokens)
5307{
5309 q, k, v, output, num_heads, num_kv_heads, num_tokens, head_dim,
5310 aligned_head_dim, kv_stride_tokens, visual_start, visual_tokens,
5311 /*output_token_major=*/1);
5312}
5313
5314typedef struct {
5315 const float *q;
5316 const float *k;
5317 const float *v;
5318 float *output;
5319 int num_heads;
5320 int num_kv_heads;
5321 int num_tokens;
5322 int head_dim;
5323 int aligned_head_dim;
5324 int kv_stride_tokens;
5325 int inputs_prerounded;
5326} ck_attention_causal_f16kv_args_t;
5327
5328static void ck_attention_causal_f16kv_work(int ith, int nth, void *opaque)
5329{
5330 const ck_attention_causal_f16kv_args_t *args =
5331 (const ck_attention_causal_f16kv_args_t *)opaque;
5332 const float scale = 1.0f / sqrtf((float)args->head_dim);
5333 const int T = args->num_tokens;
5334 const size_t kv_head_stride =
5335 (size_t)args->kv_stride_tokens * (size_t)args->aligned_head_dim;
5336
5337 for (int h = ith; h < args->num_heads; h += nth) {
5338 const int kv_head = (int)((long long)h * (long long)args->num_kv_heads /
5339 (long long)args->num_heads);
5340 const float *k_head = args->k + (size_t)kv_head * kv_head_stride;
5341 const float *v_head = args->v + (size_t)kv_head * kv_head_stride;
5342
5343 for (int i = 0; i < T; ++i) {
5344 const float *q_vec = args->q +
5345 qkv_index(h, i, 0, T, args->aligned_head_dim);
5346 float *out_vec = args->output +
5347 qkv_index(h, i, 0, T, args->aligned_head_dim);
5348 if (args->inputs_prerounded) {
5350 q_vec, k_head, v_head, /*kv_tokens=*/i + 1,
5351 args->head_dim, args->aligned_head_dim, scale, out_vec);
5352 } else {
5354 q_vec, k_head, v_head, /*kv_tokens=*/i + 1,
5355 args->head_dim, args->aligned_head_dim, scale, out_vec);
5356 }
5357 }
5358 }
5359}
5360
5362 const float *q, const float *k, const float *v, float *output,
5363 int num_heads, int num_kv_heads, int num_tokens, int head_dim,
5364 int aligned_head_dim, int kv_stride_tokens)
5365{
5366 if (!q || !k || !v || !output || num_heads <= 0 || num_kv_heads <= 0 ||
5367 num_tokens <= 0 || kv_stride_tokens < num_tokens) {
5368 return;
5369 }
5370 ck_attention_causal_f16kv_args_t args = {
5371 q, k, v, output, num_heads, num_kv_heads, num_tokens,
5372 head_dim, aligned_head_dim, kv_stride_tokens, 0,
5373 };
5374 ck_attention_causal_f16kv_work(0, 1, &args);
5375}
5376
5378 const float *q, const float *k, const float *v, float *output,
5379 int num_heads, int num_kv_heads, int num_tokens, int head_dim,
5380 int aligned_head_dim, int kv_stride_tokens,
5381 float *rounded_kv, size_t rounded_kv_bytes)
5382{
5383 if (!q || !k || !v || !output || num_heads <= 0 || num_kv_heads <= 0 ||
5384 num_tokens <= 0 || kv_stride_tokens < num_tokens) {
5385 return;
5386 }
5387 size_t elements = 0;
5388 if ((size_t)num_kv_heads <= SIZE_MAX / (size_t)kv_stride_tokens) {
5389 const size_t rows = (size_t)num_kv_heads * (size_t)kv_stride_tokens;
5390 if (rows <= SIZE_MAX / (size_t)aligned_head_dim) {
5391 elements = rows * (size_t)aligned_head_dim;
5392 }
5393 }
5394 if (elements == 0 || elements > SIZE_MAX / (2 * sizeof(float)) ||
5395 !rounded_kv || rounded_kv_bytes < 2 * elements * sizeof(float)) {
5396 fprintf(stderr,
5397 "HARD KERNEL CONTRACT FAULT: FP16-KV attention workspace is too small\n");
5398 abort();
5399 }
5400 ck_round_fp16_buffer(k, rounded_kv, elements);
5401 ck_round_fp16_buffer(v, rounded_kv + elements, elements);
5402 ck_attention_causal_f16kv_args_t args = {
5403 q, rounded_kv, rounded_kv + elements, output,
5404 num_heads, num_kv_heads, num_tokens,
5405 head_dim, aligned_head_dim, kv_stride_tokens, 1,
5406 };
5407 ck_threadpool_t *pool = ck_threadpool_global();
5408 const int workers = pool ? ck_threadpool_n_threads(pool) : 1;
5409 const int active = workers < num_heads ? workers : num_heads;
5410 if (active > 1) {
5412 } else {
5413 ck_attention_causal_f16kv_work(0, 1, &args);
5414 }
5415}
5416
5418 const float *q, const float *k, const float *v, float *output,
5419 int num_heads, int num_kv_heads, int num_tokens, int head_dim,
5420 int aligned_head_dim, int kv_stride_tokens)
5421{
5422 size_t elements = 0;
5423 if (num_kv_heads > 0 && kv_stride_tokens > 0 && aligned_head_dim > 0 &&
5424 (size_t)num_kv_heads <= SIZE_MAX / (size_t)kv_stride_tokens) {
5425 const size_t rows = (size_t)num_kv_heads * (size_t)kv_stride_tokens;
5426 if (rows <= SIZE_MAX / (size_t)aligned_head_dim) {
5427 elements = rows * (size_t)aligned_head_dim;
5428 }
5429 }
5430 float *workspace = elements > 0 && elements <= SIZE_MAX / (2 * sizeof(float))
5431 ? (float *)malloc(2 * elements * sizeof(float))
5432 : NULL;
5433 if (!workspace) {
5435 q, k, v, output, num_heads, num_kv_heads, num_tokens,
5436 head_dim, aligned_head_dim, kv_stride_tokens);
5437 return;
5438 }
5440 q, k, v, output, num_heads, num_kv_heads, num_tokens, head_dim,
5441 aligned_head_dim, kv_stride_tokens, workspace,
5442 2 * elements * sizeof(float));
5443 free(workspace);
5444}
5445
5447 const float *k,
5448 const float *v,
5449 float *output,
5450 int num_heads,
5451 int num_kv_heads,
5452 int num_tokens,
5453 int head_dim,
5454 int aligned_head_dim,
5455 int kv_stride_tokens)
5456{
5457 if (!q || !k || !v || !output) {
5458 return;
5459 }
5460 if (num_heads <= 0 || num_kv_heads <= 0 || num_tokens <= 0) {
5461 return;
5462 }
5463 if (kv_stride_tokens < num_tokens) {
5464 return;
5465 }
5466
5467 const float scale = ck_strict_parity_enabled()
5469 : 1.0f / sqrtf((float) head_dim);
5470 const int T = num_tokens;
5471 const size_t kv_head_stride = (size_t) kv_stride_tokens * (size_t) aligned_head_dim;
5472 const int debug_layer_id = ck_strict_parity_enabled() ? ck_attention_vec_dump_next_layer_id() : -1;
5473 float *score_row = (float *) alloca((size_t) T * sizeof(float));
5474 float *v_cols = (float *) alloca((size_t) head_dim * (size_t) T * sizeof(float));
5475 for (int h = 0; h < num_heads; ++h) {
5476 const int kv_head = (int) ((long long) h * (long long) num_kv_heads / (long long) num_heads);
5477 const float *k_head = k + (size_t) kv_head * kv_head_stride;
5478 const float *v_head = v + (size_t) kv_head * kv_head_stride;
5479
5480#if CK_ENABLE_LLAMA_CPP_PARITY
5481 float *out_head = output + (size_t) h * (size_t) T * (size_t) aligned_head_dim;
5484 q + (size_t) h * (size_t) T * (size_t) aligned_head_dim,
5485 k_head,
5486 v_head,
5487 out_head,
5488 T,
5489 head_dim,
5490 aligned_head_dim,
5491 scale)) {
5492 ck_attention_trace("regular_graph_oracle", debug_layer_id, h);
5493 continue;
5494 }
5495#endif
5496
5497 for (int d = 0; d < head_dim; ++d) {
5498 float *dst_col = v_cols + (size_t) d * (size_t) T;
5499 for (int j = 0; j < T; ++j) {
5500 dst_col[j] = v_head[(size_t) j * (size_t) aligned_head_dim + (size_t) d];
5501 }
5502 }
5503
5504 for (int i = 0; i < T; ++i) {
5505 const float *q_vec = q + qkv_index(h, i, 0, T, aligned_head_dim);
5506 float *out_vec = output + qkv_index(h, i, 0, T, aligned_head_dim);
5508 k_head,
5509 v_cols,
5510 T,
5511 head_dim,
5512 aligned_head_dim,
5513 scale,
5514 score_row,
5515 out_vec,
5516 debug_layer_id,
5517 h,
5518 i);
5519 }
5520 }
5521}
5522
5524 const float *q,
5525 const float *k,
5526 const float *v,
5527 float *output,
5528 int num_heads,
5529 int num_kv_heads,
5530 int num_tokens,
5531 int head_dim,
5532 int aligned_head_dim,
5533 int kv_stride_tokens,
5534 float *score_rows,
5535 size_t score_rows_bytes,
5536 float *v_columns,
5537 size_t v_columns_bytes,
5538 float *probability_row,
5539 size_t probability_row_bytes)
5540{
5541 if (!q || !k || !v || !output) {
5542 return;
5543 }
5544 if (num_heads <= 0 || num_kv_heads <= 0 || num_tokens <= 0) {
5545 return;
5546 }
5547 if (kv_stride_tokens < num_tokens) {
5548 return;
5549 }
5550
5551 const size_t T = (size_t) num_tokens;
5552 if ((size_t) num_heads > SIZE_MAX / T ||
5553 (size_t) head_dim > SIZE_MAX / T) {
5554 return;
5555 }
5556 const size_t score_elements = (size_t) num_heads * T;
5557 const size_t columns_per_head = (size_t) head_dim * T;
5558 if ((size_t) num_heads > SIZE_MAX / columns_per_head ||
5559 score_elements > SIZE_MAX / sizeof(float) ||
5560 columns_per_head * (size_t) num_heads > SIZE_MAX / sizeof(float) ||
5561 T > SIZE_MAX / sizeof(float)) {
5562 return;
5563 }
5564 const size_t column_elements = columns_per_head * (size_t) num_heads;
5565 if (!score_rows || score_rows_bytes < score_elements * sizeof(float) ||
5566 !v_columns || v_columns_bytes < column_elements * sizeof(float) ||
5567 !probability_row || probability_row_bytes < T * sizeof(float)) {
5568 return;
5569 }
5570
5571 const int strict = ck_strict_parity_enabled();
5572 const float scale = strict
5574 : 1.0f / sqrtf((float) head_dim);
5575 const int token_count = num_tokens;
5576 const size_t kv_head_stride = (size_t) kv_stride_tokens * (size_t) aligned_head_dim;
5577 const int debug_layer_id = strict ? ck_attention_vec_dump_next_layer_id() : -1;
5578#if CK_ENABLE_LLAMA_CPP_PARITY
5579 ck_ggml_vec_dot_f32_fn dot_fn = NULL;
5580 ck_ggml_vec_soft_max_f32_fn softmax_fn = NULL;
5581 ck_ggml_compute_forward_mul_mat_fn mul_mat_fn = NULL;
5582 ck_ggml_compute_forward_soft_max_fn softmax_compute_fn = NULL;
5583 if (strict) {
5585 k,
5586 v,
5587 output,
5588 num_heads,
5589 num_kv_heads,
5590 num_tokens,
5591 head_dim,
5592 aligned_head_dim,
5593 kv_stride_tokens,
5594 scale)) {
5595 return;
5596 }
5597 dot_fn = ck_resolve_ggml_vec_dot_f32();
5598 softmax_fn = ck_resolve_ggml_vec_soft_max_f32();
5599 mul_mat_fn = ck_resolve_ggml_compute_forward_mul_mat();
5600 softmax_compute_fn = ck_resolve_ggml_compute_forward_soft_max();
5601 }
5602#endif
5603 if (strict) {
5604 float *score_row = score_rows;
5605 float *v_cols = v_columns;
5606#if CK_ENABLE_LLAMA_CPP_PARITY
5607 float *prob_row = probability_row;
5608#endif
5609
5610 for (int h = 0; h < num_heads; ++h) {
5611 const int kv_head = (int) ((long long) h * (long long) num_kv_heads / (long long) num_heads);
5612 const float *k_head = k + (size_t) kv_head * kv_head_stride;
5613 const float *v_head = v + (size_t) kv_head * kv_head_stride;
5614
5615#if CK_ENABLE_LLAMA_CPP_PARITY
5616 float *out_head = output + (size_t) h * (size_t) T * (size_t) aligned_head_dim;
5618 q + (size_t) h * (size_t) T * (size_t) aligned_head_dim,
5619 k_head,
5620 v_head,
5621 out_head,
5622 token_count,
5623 head_dim,
5624 aligned_head_dim,
5625 scale)) {
5626 continue;
5627 }
5628#endif
5629
5630 for (int d = 0; d < head_dim; ++d) {
5631 float *dst_col = v_cols + (size_t) d * (size_t) token_count;
5632 for (int j = 0; j < token_count; ++j) {
5633 dst_col[j] = v_head[(size_t) j * (size_t) aligned_head_dim + (size_t) d];
5634 }
5635 }
5636
5637#if CK_ENABLE_LLAMA_CPP_PARITY
5638 if (dot_fn && softmax_fn && ck_attention_ggml_out_graph_enabled()) {
5639 if (attention_head_full_dyn_ggml_regular_graph_out(
5640 q + (size_t) h * (size_t) T * (size_t) aligned_head_dim,
5641 k_head,
5642 v_cols,
5643 token_count,
5644 head_dim,
5645 aligned_head_dim,
5646 scale,
5647 score_row,
5648 prob_row,
5649 out_head,
5650 dot_fn,
5651 softmax_fn,
5652 debug_layer_id,
5653 h)) {
5654 if (token_count > 0) {
5655 ck_attention_trace("dyn_ggml_regular_graph_out", debug_layer_id, h);
5656 ck_attention_trace_float("scale", debug_layer_id, h, scale);
5657 }
5658 continue;
5659 }
5660 }
5661#endif
5662
5663 for (int i = 0; i < token_count; ++i) {
5664 const float *q_vec = q + qkv_index(h, i, 0, token_count, aligned_head_dim);
5665 float *out_vec = output + qkv_index(h, i, 0, token_count, aligned_head_dim);
5666#if CK_ENABLE_LLAMA_CPP_PARITY
5667 if (dot_fn && softmax_fn) {
5668 if (i == 0) {
5669 ck_attention_trace("dyn_ggml_regular", debug_layer_id, h);
5670 ck_attention_trace_float("scale", debug_layer_id, h, scale);
5671 }
5672 attention_query_full_dyn_ggml_regular(q_vec,
5673 k_head,
5674 v_cols,
5675 token_count,
5676 head_dim,
5677 aligned_head_dim,
5678 scale,
5679 score_row,
5680 prob_row,
5681 out_vec,
5682 dot_fn,
5683 softmax_fn,
5684 debug_layer_id,
5685 h,
5686 i);
5687 } else if (dot_fn && mul_mat_fn && softmax_compute_fn) {
5688 if (i == 0) {
5689 ck_attention_trace("ggml_compute_regular", debug_layer_id, h);
5690 }
5691 attention_query_full_ggml_compute_regular(q_vec,
5692 k_head,
5693 v_cols,
5694 token_count,
5695 head_dim,
5696 aligned_head_dim,
5697 scale,
5698 score_row,
5699 prob_row,
5700 out_vec,
5701 dot_fn,
5702 mul_mat_fn,
5703 softmax_compute_fn);
5704 } else
5705#endif
5706 {
5707 if (i == 0) {
5708 ck_attention_trace("ggml_regular", debug_layer_id, h);
5709 }
5711 k_head,
5712 v_cols,
5713 token_count,
5714 head_dim,
5715 aligned_head_dim,
5716 scale,
5717 score_row,
5718 out_vec,
5719 debug_layer_id,
5720 h,
5721 i);
5722 }
5723 }
5724 }
5725 return;
5726 }
5727
5728#pragma omp parallel for schedule(static) if(num_heads > 1)
5729 for (int h = 0; h < num_heads; ++h) {
5730 float *score_row = score_rows + (size_t) h * (size_t) token_count;
5731 float *v_cols = v_columns + (size_t) h * (size_t) head_dim * (size_t) token_count;
5732 const int kv_head = (int) ((long long) h * (long long) num_kv_heads / (long long) num_heads);
5733 const float *k_head = k + (size_t) kv_head * kv_head_stride;
5734 const float *v_head = v + (size_t) kv_head * kv_head_stride;
5735
5736 if (v_cols) {
5737 for (int d = 0; d < head_dim; ++d) {
5738 float *dst_col = v_cols + (size_t) d * (size_t) token_count;
5739 for (int j = 0; j < token_count; ++j) {
5740 dst_col[j] = v_head[(size_t) j * (size_t) aligned_head_dim + (size_t) d];
5741 }
5742 }
5743 }
5744
5745 for (int i = 0; i < token_count; ++i) {
5746 const float *q_vec = q + qkv_index(h, i, 0, token_count, aligned_head_dim);
5747 float *out_vec = output + qkv_index(h, i, 0, token_count, aligned_head_dim);
5749 k_head,
5750 v_cols,
5751 token_count,
5752 head_dim,
5753 aligned_head_dim,
5754 scale,
5755 score_row,
5756 out_vec,
5757 -1,
5758 h,
5759 i);
5760 }
5761 }
5762}
5763
5765 const float *k,
5766 const float *v,
5767 float *output,
5768 int num_heads,
5769 int num_kv_heads,
5770 int num_tokens,
5771 int head_dim,
5772 int aligned_head_dim,
5773 int kv_stride_tokens)
5774{
5775 if (num_heads <= 0 || num_tokens <= 0 || head_dim <= 0 ||
5776 (size_t) num_heads > SIZE_MAX / (size_t) num_tokens ||
5777 (size_t) head_dim > SIZE_MAX / (size_t) num_tokens) {
5778 return;
5779 }
5780 const size_t score_elements = (size_t) num_heads * (size_t) num_tokens;
5781 const size_t columns_per_head = (size_t) head_dim * (size_t) num_tokens;
5782 if ((size_t) num_heads > SIZE_MAX / columns_per_head) return;
5783 const size_t column_elements = (size_t) num_heads * columns_per_head;
5784 if (score_elements > SIZE_MAX - column_elements ||
5785 score_elements + column_elements > SIZE_MAX - (size_t) num_tokens) return;
5786 const size_t total_elements = score_elements + column_elements + (size_t) num_tokens;
5787 if (total_elements > SIZE_MAX / sizeof(float)) return;
5788 float *workspace = (float *) malloc(total_elements * sizeof(float));
5789 if (!workspace) return;
5790 float *score_rows = workspace;
5791 float *v_columns = score_rows + score_elements;
5792 float *probability_row = v_columns + column_elements;
5794 q, k, v, output, num_heads, num_kv_heads, num_tokens, head_dim,
5795 aligned_head_dim, kv_stride_tokens,
5796 score_rows, score_elements * sizeof(float),
5797 v_columns, column_elements * sizeof(float),
5798 probability_row, (size_t) num_tokens * sizeof(float));
5799 free(workspace);
5800}
5801
5802static inline int ck_llama_kv_pad_256(int live_tokens, int capacity)
5803{
5804 if (live_tokens <= 0 || capacity < live_tokens) return 0;
5805 int padded = live_tokens < 256 ? 256 : ((live_tokens + 255) / 256) * 256;
5806 return padded < capacity ? padded : capacity;
5807}
5808
5809#if defined(__AVX512F__)
5810static inline __m512 ck_llama_regular_round_f16x16(__m512 value)
5811{
5812 return _mm512_cvtph_ps(_mm512_cvtps_ph(value, _MM_FROUND_TO_NEAREST_INT));
5813}
5814#endif
5815
5816static inline float ck_llama_regular_dot_f16(const float *a, const float *b, int count)
5817{
5818 int i = 0;
5819#if defined(__AVX512F__)
5820 __m512 acc[4] = {
5821 _mm512_setzero_ps(), _mm512_setzero_ps(),
5822 _mm512_setzero_ps(), _mm512_setzero_ps()
5823 };
5824 for (; i + 64 <= count; i += 64) {
5825 for (int lane = 0; lane < 4; ++lane) {
5826 acc[lane] = _mm512_fmadd_ps(
5827 ck_llama_regular_round_f16x16(_mm512_loadu_ps(a + i + lane * 16)),
5828 ck_llama_regular_round_f16x16(_mm512_loadu_ps(b + i + lane * 16)),
5829 acc[lane]);
5830 }
5831 }
5832 acc[0] = _mm512_add_ps(acc[0], acc[2]);
5833 acc[1] = _mm512_add_ps(acc[1], acc[3]);
5834 float sum = _mm512_reduce_add_ps(_mm512_add_ps(acc[0], acc[1]));
5835#elif defined(__AVX__)
5836 __m256 acc[4] = {
5837 _mm256_setzero_ps(), _mm256_setzero_ps(),
5838 _mm256_setzero_ps(), _mm256_setzero_ps()
5839 };
5840 for (; i + 32 <= count; i += 32) {
5841 for (int lane = 0; lane < 4; ++lane) {
5842#if defined(__FMA__)
5843 acc[lane] = _mm256_fmadd_ps(
5844 _mm256_cvtph_ps(_mm256_cvtps_ph(
5845 _mm256_loadu_ps(a + i + lane * 8), _MM_FROUND_TO_NEAREST_INT)),
5846 _mm256_cvtph_ps(_mm256_cvtps_ph(
5847 _mm256_loadu_ps(b + i + lane * 8), _MM_FROUND_TO_NEAREST_INT)),
5848 acc[lane]);
5849#else
5850 acc[lane] = _mm256_add_ps(acc[lane], _mm256_mul_ps(
5851 _mm256_cvtph_ps(_mm256_cvtps_ph(
5852 _mm256_loadu_ps(a + i + lane * 8), _MM_FROUND_TO_NEAREST_INT)),
5853 _mm256_cvtph_ps(_mm256_cvtps_ph(
5854 _mm256_loadu_ps(b + i + lane * 8), _MM_FROUND_TO_NEAREST_INT))));
5855#endif
5856 }
5857 }
5858 acc[0] = _mm256_add_ps(acc[0], acc[2]);
5859 acc[1] = _mm256_add_ps(acc[1], acc[3]);
5860 const __m256 merged = _mm256_add_ps(acc[0], acc[1]);
5861 __m128 halves = _mm_add_ps(
5862 _mm256_extractf128_ps(merged, 1), _mm256_castps256_ps128(merged));
5863 halves = _mm_add_ps(halves, _mm_movehl_ps(halves, halves));
5864 halves = _mm_add_ss(halves, _mm_movehdup_ps(halves));
5865 float sum = _mm_cvtss_f32(halves);
5866#else
5867 float sums[4] = {0.0f, 0.0f, 0.0f, 0.0f};
5868 for (; i + 4 <= count; i += 4) {
5869 for (int lane = 0; lane < 4; ++lane) {
5870 const float av = CK_FP16_TO_FP32(CK_FP32_TO_FP16(a[i + lane]));
5871 const float bv = CK_FP16_TO_FP32(CK_FP32_TO_FP16(b[i + lane]));
5872 sums[lane] = fmaf(av, bv, sums[lane]);
5873 }
5874 }
5875 float sum = (sums[0] + sums[2]) + (sums[1] + sums[3]);
5876#endif
5877 for (; i < count; ++i) {
5878 const float av = CK_FP16_TO_FP32(CK_FP32_TO_FP16(a[i]));
5879 const float bv = CK_FP16_TO_FP32(CK_FP32_TO_FP16(b[i]));
5880 sum = fmaf(av, bv, sum);
5881 }
5882 return sum;
5883}
5884
5885static inline float ck_llama_regular_gemm_f16(
5886 const float *probability, const float *value_column, int count)
5887{
5888 int i = 0;
5889#if defined(__AVX512F__)
5890 __m512 acc = _mm512_setzero_ps();
5891 for (; i + 16 <= count; i += 16) {
5892 acc = _mm512_fmadd_ps(
5893 ck_llama_regular_round_f16x16(_mm512_loadu_ps(value_column + i)),
5894 ck_llama_regular_round_f16x16(_mm512_loadu_ps(probability + i)), acc);
5895 }
5896 float sum = _mm512_reduce_add_ps(acc);
5897#elif defined(__AVX__)
5898 __m256 acc = _mm256_setzero_ps();
5899 for (; i + 8 <= count; i += 8) {
5900#if defined(__FMA__)
5901 acc = _mm256_fmadd_ps(
5902 _mm256_cvtph_ps(_mm256_cvtps_ph(
5903 _mm256_loadu_ps(value_column + i), _MM_FROUND_TO_NEAREST_INT)),
5904 _mm256_cvtph_ps(_mm256_cvtps_ph(
5905 _mm256_loadu_ps(probability + i), _MM_FROUND_TO_NEAREST_INT)), acc);
5906#else
5907 acc = _mm256_add_ps(acc, _mm256_mul_ps(
5908 _mm256_cvtph_ps(_mm256_cvtps_ph(
5909 _mm256_loadu_ps(value_column + i), _MM_FROUND_TO_NEAREST_INT)),
5910 _mm256_cvtph_ps(_mm256_cvtps_ph(
5911 _mm256_loadu_ps(probability + i), _MM_FROUND_TO_NEAREST_INT))));
5912#endif
5913 }
5914 __m128 halves = _mm_add_ps(
5915 _mm256_extractf128_ps(acc, 1), _mm256_castps256_ps128(acc));
5916 halves = _mm_add_ps(halves, _mm_movehl_ps(halves, halves));
5917 halves = _mm_add_ss(halves, _mm_movehdup_ps(halves));
5918 float sum = _mm_cvtss_f32(halves);
5919#else
5920 float sum = 0.0f;
5921#endif
5922 for (; i < count; ++i) {
5923 const float value = CK_FP16_TO_FP32(CK_FP32_TO_FP16(value_column[i]));
5924 const float probability_value = CK_FP16_TO_FP32(CK_FP32_TO_FP16(probability[i]));
5925 sum = fmaf(value, probability_value, sum);
5926 }
5927 return sum;
5928}
5929
5931 const float *query, const float *key_head, const float *value_columns,
5932 float *output, float *scores, float *scaled_scores,
5933 int live_tokens, int padded_tokens, int query_position,
5934 int head_dim, int aligned_head_dim, int sliding_window,
5935 int batched_prefill)
5936{
5937 const int first = sliding_window > 0 && query_position >= sliding_window
5938 ? query_position - sliding_window + 1 : 0;
5939 const int last = query_position < live_tokens ? query_position : live_tokens - 1;
5940 for (int token = 0; token < padded_tokens; ++token) scores[token] = -INFINITY;
5941 for (int token = first; token <= last; ++token) {
5942 const float *key = key_head + (size_t)token * (size_t)aligned_head_dim;
5943 scores[token] = batched_prefill
5944 ? ck_llama_regular_gemm_f16(query, key, head_dim)
5945 : ck_llama_regular_dot_f16(query, key, head_dim);
5946 }
5947
5948 memcpy(scaled_scores, scores, (size_t)padded_tokens * sizeof(float));
5949 const float scale = 1.0f / sqrtf((float)head_dim);
5950 ck_vec_scale_f32_inplace(scaled_scores, padded_tokens, scale);
5951 const float maximum = ck_vec_max_f32_contig(scaled_scores, padded_tokens);
5952 const double total = ck_ggml_vec_soft_max_row(
5953 padded_tokens, scores, scaled_scores, maximum);
5954 if (total > 0.0) {
5955 ck_vec_scale_f32_inplace(scores, padded_tokens, (float)(1.0 / total));
5956 for (int dim = 0; dim < head_dim; ++dim) {
5957 const float *column = value_columns + (size_t)dim * (size_t)padded_tokens;
5958 output[dim] = batched_prefill
5959 ? ck_llama_regular_gemm_f16(scores, column, padded_tokens)
5960 : ck_llama_regular_dot_f16(scores, column, padded_tokens);
5961 }
5962 } else {
5963 memset(output, 0, (size_t)head_dim * sizeof(float));
5964 }
5965 for (int dim = head_dim; dim < aligned_head_dim; ++dim) output[dim] = 0.0f;
5966}
5967
5969 const float *q, const float *k, const float *v, float *output,
5970 int num_heads, int num_kv_heads, int query_tokens, int live_tokens,
5971 int head_dim, int aligned_head_dim, int kv_stride_tokens, int sliding_window,
5972 float *scores, size_t scores_bytes,
5973 float *value_columns, size_t value_columns_bytes,
5974 float *scaled_scores, size_t scaled_scores_bytes,
5975 int batched_prefill)
5976{
5977 if (!q || !k || !v || !output || !scores || !value_columns || !scaled_scores ||
5978 num_heads <= 0 || num_kv_heads <= 0 || query_tokens <= 0 || live_tokens <= 0 ||
5979 head_dim <= 0 || aligned_head_dim < head_dim || kv_stride_tokens < live_tokens) return;
5980 size_t scratch_capacity = scores_bytes / sizeof(float);
5981 const size_t scaled_capacity = scaled_scores_bytes / sizeof(float);
5982 const size_t value_capacity = value_columns_bytes /
5983 ((size_t)head_dim * sizeof(float));
5984 if (scaled_capacity < scratch_capacity) scratch_capacity = scaled_capacity;
5985 if (value_capacity < scratch_capacity) scratch_capacity = value_capacity;
5986 if (!batched_prefill && (size_t)kv_stride_tokens < scratch_capacity) {
5987 scratch_capacity = (size_t)kv_stride_tokens;
5988 }
5989 const int padded = scratch_capacity > (size_t)INT_MAX ? 0 :
5990 ck_llama_kv_pad_256(live_tokens, (int)scratch_capacity);
5991 if (padded <= 0 || scores_bytes < (size_t)padded * sizeof(float) ||
5992 scaled_scores_bytes < (size_t)padded * sizeof(float) ||
5993 value_columns_bytes < (size_t)head_dim * (size_t)padded * sizeof(float)) return;
5994
5995 const size_t kv_head_stride = (size_t)kv_stride_tokens * (size_t)aligned_head_dim;
5996 const int debug_layer_id = ck_attention_vec_dump_enabled()
5998 int packed_kv_head = -1;
5999 for (int head = 0; head < num_heads; ++head) {
6000 const int kv_head = (int)((long long)head * num_kv_heads / num_heads);
6001 const float *key_head = k + (size_t)kv_head * kv_head_stride;
6002 const float *value_head = v + (size_t)kv_head * kv_head_stride;
6003 if (kv_head != packed_kv_head) {
6004 for (int dim = 0; dim < head_dim; ++dim) {
6005 float *column = value_columns + (size_t)dim * (size_t)padded;
6006 int token = 0;
6007 for (; token < live_tokens; ++token) {
6008 column[token] = value_head[(size_t)token * aligned_head_dim + dim];
6009 }
6010 for (; token < padded; ++token) column[token] = 0.0f;
6011 }
6012 packed_kv_head = kv_head;
6013 }
6014 for (int query = 0; query < query_tokens; ++query) {
6015 const int position = batched_prefill ? query : live_tokens - 1;
6017 q + ((size_t)head * query_tokens + query) * aligned_head_dim,
6018 key_head, value_columns,
6019 output + ((size_t)head * query_tokens + query) * aligned_head_dim,
6020 scores, scaled_scores, live_tokens, padded, position,
6021 head_dim, aligned_head_dim, sliding_window, batched_prefill);
6022 if (debug_layer_id >= 0 &&
6023 ck_attention_vec_dump_should_emit(debug_layer_id, head, query)) {
6025 scaled_scores, scores,
6026 output + ((size_t)head * query_tokens + query) * aligned_head_dim,
6027 value_columns, padded, head_dim,
6028 debug_layer_id, head, query);
6029 }
6030 }
6031 }
6032}
6033
6034typedef struct {
6035 const float *q;
6036 const float *k;
6037 const float *value_columns;
6038 float *output;
6039 float *scores;
6040 float *scaled_scores;
6041 int num_heads;
6042 int num_kv_heads;
6043 int num_tokens;
6044 int head_dim;
6045 int aligned_head_dim;
6046 int kv_stride_tokens;
6047 int sliding_window;
6048 int padded;
6049 _Alignas(CK_CACHE_LINE) atomic_int next_job;
6050} ck_llama_regular_prefill_args_t;
6051
6052static void ck_llama_regular_prefill_work(int ith, int nth, void *opaque)
6053{
6054 ck_llama_regular_prefill_args_t *a =
6055 (ck_llama_regular_prefill_args_t *)opaque;
6056 float *worker_scores = a->scores + (size_t)ith * (size_t)a->padded;
6057 float *worker_scaled =
6058 a->scaled_scores + (size_t)ith * (size_t)a->padded;
6059 const size_t worker_kv_stride =
6060 (size_t)a->kv_stride_tokens * (size_t)a->aligned_head_dim;
6061 const int jobs = a->num_heads * a->num_tokens;
6062 (void)nth;
6063 /* Query rows are independent, but their cost grows with the causal
6064 * prefix. Dynamic claims keep asymmetric workers balanced while each
6065 * worker retains exclusive ownership of its score scratch. */
6066 for (;;) {
6067 const int job = atomic_fetch_add_explicit(
6068 &a->next_job, 1, memory_order_relaxed);
6069 if (job >= jobs) break;
6070 const int head = job / a->num_tokens;
6071 const int query = job - head * a->num_tokens;
6072 const int kv_head =
6073 (int)((long long)head * a->num_kv_heads / a->num_heads);
6075 a->q + ((size_t)head * a->num_tokens + query) * a->aligned_head_dim,
6076 a->k + (size_t)kv_head * worker_kv_stride,
6077 a->value_columns +
6078 (size_t)kv_head * (size_t)a->head_dim * (size_t)a->padded,
6079 a->output +
6080 ((size_t)head * a->num_tokens + query) * a->aligned_head_dim,
6081 worker_scores, worker_scaled, a->num_tokens, a->padded, query,
6082 a->head_dim, a->aligned_head_dim, a->sliding_window, 1);
6083 }
6084}
6085
6087 const float *q, const float *k, const float *v, float *output,
6088 int num_heads, int num_kv_heads, int num_tokens, int head_dim,
6089 int aligned_head_dim, int kv_stride_tokens, int sliding_window,
6090 float *scores, size_t scores_bytes,
6091 float *value_columns, size_t value_columns_bytes,
6092 float *scaled_scores, size_t scaled_scores_bytes)
6093{
6094 if (!q || !k || !v || !output || !scores || !value_columns || !scaled_scores ||
6095 num_heads <= 0 || num_kv_heads <= 0 || num_tokens <= 0 || head_dim <= 0 ||
6096 aligned_head_dim < head_dim || kv_stride_tokens < num_tokens ||
6097 num_heads > INT_MAX / num_tokens) return;
6098
6099 const size_t score_elements = scores_bytes / sizeof(float);
6100 const size_t scaled_elements = scaled_scores_bytes / sizeof(float);
6101 const size_t value_elements = value_columns_bytes / sizeof(float);
6102 if ((size_t)num_kv_heads > SIZE_MAX / (size_t)head_dim) return;
6103 const size_t value_rows = (size_t)num_kv_heads * (size_t)head_dim;
6104 const size_t value_capacity = value_rows > 0 ? value_elements / value_rows : 0;
6105 const size_t padded_size = num_tokens < 256
6106 ? 256 : (((size_t)num_tokens + 255) / 256) * 256;
6107 const int padded = padded_size > (size_t)INT_MAX ? 0 : (int)padded_size;
6108
6109 /* Older direct callers provide one score row and one value bank. Keep that
6110 * ABI usable while generated v8 runtimes provide the parallel workspace. */
6111 if (padded <= 0 || value_capacity < (size_t)padded ||
6112 score_elements < (size_t)padded * 2 ||
6113 scaled_elements < (size_t)padded * 2 ||
6114 value_elements < value_rows * (size_t)padded) {
6116 q, k, v, output, num_heads, num_kv_heads, num_tokens, num_tokens,
6117 head_dim, aligned_head_dim, kv_stride_tokens, sliding_window,
6118 scores, scores_bytes, value_columns, value_columns_bytes,
6119 scaled_scores, scaled_scores_bytes, 1);
6120 return;
6121 }
6122
6125 q, k, v, output, num_heads, num_kv_heads, num_tokens, num_tokens,
6126 head_dim, aligned_head_dim, kv_stride_tokens, sliding_window,
6127 scores, (size_t)padded * sizeof(float),
6128 value_columns, (size_t)head_dim * (size_t)padded * sizeof(float),
6129 scaled_scores, (size_t)padded * sizeof(float), 1);
6130 return;
6131 }
6132
6133 const size_t kv_head_stride =
6134 (size_t)kv_stride_tokens * (size_t)aligned_head_dim;
6135 for (int kv_head = 0; kv_head < num_kv_heads; ++kv_head) {
6136 const float *value_head = v + (size_t)kv_head * kv_head_stride;
6137 float *packed_head = value_columns +
6138 (size_t)kv_head * (size_t)head_dim * (size_t)padded;
6139 for (int dim = 0; dim < head_dim; ++dim) {
6140 float *column = packed_head + (size_t)dim * (size_t)padded;
6141 int token = 0;
6142 for (; token < num_tokens; ++token) {
6143 column[token] =
6144 value_head[(size_t)token * (size_t)aligned_head_dim + (size_t)dim];
6145 }
6146 for (; token < padded; ++token) column[token] = 0.0f;
6147 }
6148 }
6149
6150 ck_llama_regular_prefill_args_t args = {
6151 .q = q,
6152 .k = k,
6153 .value_columns = value_columns,
6154 .output = output,
6155 .scores = scores,
6156 .scaled_scores = scaled_scores,
6157 .num_heads = num_heads,
6158 .num_kv_heads = num_kv_heads,
6159 .num_tokens = num_tokens,
6160 .head_dim = head_dim,
6161 .aligned_head_dim = aligned_head_dim,
6162 .kv_stride_tokens = kv_stride_tokens,
6163 .sliding_window = sliding_window,
6164 .padded = padded,
6165 };
6166 atomic_init(&args.next_job, 0);
6167
6168 size_t scratch_lanes = score_elements / (size_t)padded;
6169 const size_t scaled_lanes = scaled_elements / (size_t)padded;
6170 if (scaled_lanes < scratch_lanes) scratch_lanes = scaled_lanes;
6171 int active = scratch_lanes > (size_t)INT_MAX ? INT_MAX : (int)scratch_lanes;
6172 const int jobs = num_heads * num_tokens;
6173 if (active > jobs) active = jobs;
6174 ck_threadpool_t *pool = ck_threadpool_global();
6175 const int pool_threads = pool ? ck_threadpool_n_threads(pool) : 1;
6176 if (active > pool_threads) active = pool_threads;
6177 if (active < 1) active = 1;
6178 if (pool && active > 1) {
6180 } else {
6181 ck_llama_regular_prefill_work(0, 1, &args);
6182 }
6183}
6184
6186 const float *q, const float *k, const float *v, float *output,
6187 int num_heads, int num_kv_heads, int live_tokens, int kv_stride_tokens,
6188 int head_dim, int aligned_head_dim, int sliding_window,
6189 float *scores, size_t scores_bytes,
6190 float *value_columns, size_t value_columns_bytes,
6191 float *scaled_scores, size_t scaled_scores_bytes)
6192{
6194 q, k, v, output, num_heads, num_kv_heads, 1, live_tokens,
6195 head_dim, aligned_head_dim, kv_stride_tokens, sliding_window,
6196 scores, scores_bytes, value_columns, value_columns_bytes,
6197 scaled_scores, scaled_scores_bytes, 0);
6198}
6199
6200/**
6201 * Flash attention decode (single token attends to KV cache)
6202 * @test test_flash_attention.py::TestFlashAttention::test_flash_decode
6203 * @test test_kv_cache_attention.py::TestKVCacheAttention::test_flash_decode
6204 * @test test_fused_attention_decode.py::TestFusedAttentionDecode::test_flash_decode
6205 * @test test_attention.py::TestAttentionForward::test_flash_decode
6206 *
6207 * Single query token attends to kv_tokens in KV cache.
6208 * Uses true flash attention from attention_flash_true.c.
6209 *
6210 * After changes: make test && make llamacpp-parity-full
6211 */
6213 const float *k_cache,
6214 const float *v_cache,
6215 float *out_token,
6216 int num_heads,
6217 int num_kv_heads,
6218 int kv_tokens,
6219 int cache_capacity,
6220 int head_dim,
6221 int aligned_head_dim)
6222{
6223 if (!q_token || !k_cache || !v_cache || !out_token) {
6224 return;
6225 }
6226 if (num_heads <= 0 || num_kv_heads <= 0 || kv_tokens <= 0 || cache_capacity <= 0) {
6227 return;
6228 }
6229 if (kv_tokens > cache_capacity || head_dim <= 0 || aligned_head_dim <= 0) {
6230 return;
6231 }
6232
6233 const float scale = 1.0f / sqrtf((float)head_dim);
6234 const size_t head_stride = (size_t)cache_capacity * (size_t)aligned_head_dim;
6235
6236 for (int h = 0; h < num_heads; ++h) {
6237 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
6238 const float *q_head = q_token + (size_t)h * (size_t)aligned_head_dim;
6239 const float *k_head = k_cache + (size_t)kv_head * head_stride;
6240 const float *v_head = v_cache + (size_t)kv_head * head_stride;
6241 float *out_head = out_token + (size_t)h * (size_t)aligned_head_dim;
6242
6243 attention_flash_decode(out_head,
6244 q_head,
6245 k_head,
6246 v_head,
6247 1,
6248 kv_tokens,
6249 1,
6250 aligned_head_dim,
6251 scale);
6252 }
6253}
6254
6255
6257 const float *k_cache,
6258 const float *v_cache,
6259 float *out_token,
6260 int num_heads,
6261 int num_kv_heads,
6262 int kv_tokens,
6263 int cache_capacity,
6264 int head_dim,
6265 int aligned_head_dim)
6266{
6267 if (!q_token || !k_cache || !v_cache || !out_token) {
6268 return;
6269 }
6270 if (num_heads <= 0 || num_kv_heads <= 0 || kv_tokens <= 0 || cache_capacity <= 0) {
6271 return;
6272 }
6273 if (kv_tokens > cache_capacity || head_dim <= 0 || aligned_head_dim <= 0) {
6274 return;
6275 }
6276
6277 const float scale = 1.0f;
6278 const size_t head_stride = (size_t)cache_capacity * (size_t)aligned_head_dim;
6279
6280 for (int h = 0; h < num_heads; ++h) {
6281 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
6282 const float *q_head = q_token + (size_t)h * (size_t)aligned_head_dim;
6283 const float *k_head = k_cache + (size_t)kv_head * head_stride;
6284 const float *v_head = v_cache + (size_t)kv_head * head_stride;
6285 float *out_head = out_token + (size_t)h * (size_t)aligned_head_dim;
6286
6287 attention_flash_decode(out_head,
6288 q_head,
6289 k_head,
6290 v_head,
6291 1,
6292 kv_tokens,
6293 1,
6294 aligned_head_dim,
6295 scale);
6296 }
6297}
6298
6300 const float *k_cache,
6301 const float *v_cache,
6302 float *out_chunk,
6303 int num_heads,
6304 int num_kv_heads,
6305 int q_tokens,
6306 int kv_tokens,
6307 int cache_capacity,
6308 int head_dim,
6309 int aligned_head_dim)
6310{
6311 if (!q_chunk || !k_cache || !v_cache || !out_chunk) {
6312 return;
6313 }
6314 if (num_heads <= 0 || num_kv_heads <= 0 || q_tokens <= 0 || kv_tokens <= 0 || cache_capacity <= 0) {
6315 return;
6316 }
6317 if (kv_tokens > cache_capacity || head_dim <= 0 || aligned_head_dim <= 0) {
6318 return;
6319 }
6320
6321 const float scale = 1.0f;
6322 const size_t cache_head_stride = (size_t)cache_capacity * (size_t)aligned_head_dim;
6323 const size_t q_head_stride = (size_t)q_tokens * (size_t)aligned_head_dim;
6324
6325 for (int h = 0; h < num_heads; ++h) {
6326 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
6327 const float *k_head = k_cache + (size_t)kv_head * cache_head_stride;
6328 const float *v_head = v_cache + (size_t)kv_head * cache_head_stride;
6329 for (int t = 0; t < q_tokens; ++t) {
6330 const float *q_head = q_chunk + (size_t)h * q_head_stride + (size_t)t * (size_t)aligned_head_dim;
6331 float *out_head = out_chunk + (size_t)h * q_head_stride + (size_t)t * (size_t)aligned_head_dim;
6332 attention_flash_decode(out_head,
6333 q_head,
6334 k_head,
6335 v_head,
6336 1,
6337 kv_tokens,
6338 1,
6339 aligned_head_dim,
6340 scale);
6341 }
6342 }
6343}
6344
6346 const float *k_cache,
6347 const float *v_cache,
6348 float *out_token,
6349 int num_heads,
6350 int kv_tokens,
6351 int cache_capacity,
6352 int head_dim,
6353 int aligned_head_dim)
6354{
6356 q_token, k_cache, v_cache, out_token, num_heads, num_heads,
6357 kv_tokens, cache_capacity, head_dim, aligned_head_dim
6358 );
6359}
6360
6362 const float *k_cache,
6363 const float *v_cache,
6364 float *out_token,
6365 int num_heads,
6366 int num_kv_heads,
6367 int kv_tokens,
6368 int cache_capacity,
6369 int head_dim,
6370 int aligned_head_dim)
6371{
6372 if (!q_token || !k_cache || !v_cache || !out_token) {
6373 return;
6374 }
6375 if (num_heads <= 0 || num_kv_heads <= 0 || kv_tokens <= 0 || cache_capacity <= 0) {
6376 return;
6377 }
6378 if (kv_tokens > cache_capacity || head_dim <= 0 || aligned_head_dim <= 0) {
6379 return;
6380 }
6381
6382 const float scale = 1.0f / sqrtf((float)head_dim);
6383 const size_t head_stride = (size_t)cache_capacity * (size_t)aligned_head_dim;
6384
6385 for (int h = 0; h < num_heads; ++h) {
6386 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
6387 const float *q_head = q_token + (size_t)h * (size_t)aligned_head_dim;
6388 const float *k_head = k_cache + (size_t)kv_head * head_stride;
6389 const float *v_head = v_cache + (size_t)kv_head * head_stride;
6390 float *out_head = out_token + (size_t)h * (size_t)aligned_head_dim;
6391
6393 k_head,
6394 v_head,
6395 kv_tokens,
6396 head_dim,
6397 aligned_head_dim,
6398 scale,
6399 out_head);
6400 }
6401}
6402
6403typedef struct {
6404 const float *q_token;
6405 const uint16_t *k_cache;
6406 const uint16_t *v_cache;
6407 float *partials;
6408 int num_heads;
6409 int num_kv_heads;
6410 int kv_start;
6411 int kv_tokens;
6412 int cache_capacity;
6413 int head_dim;
6414 int aligned_head_dim;
6415 int split_chunks;
6416 int scheduled_chunks;
6417 int partition_tokens;
6418 float attention_scale;
6419} ck_attention_f16_split_args_t;
6420
6421static inline float ck_attention_f16_reduce_expf(float value)
6422{
6423 return ck_attention_reference_expf(value);
6424}
6425
6426static inline float ck_attention_dot_f16_llama(const uint16_t *x,
6427 const uint16_t *y,
6428 int n)
6429{
6430 int i = 0;
6431#if defined(__AVX512F__)
6432 // Match ggml's AVX-512 FP16-to-FP32 path: four 16-lane
6433 // accumulators per 64 values, then its fixed pairwise tree.
6434 __m512 sum0 = _mm512_setzero_ps();
6435 __m512 sum1 = _mm512_setzero_ps();
6436 __m512 sum2 = _mm512_setzero_ps();
6437 __m512 sum3 = _mm512_setzero_ps();
6438 const int n64 = n & ~63;
6439 for (; i < n64; i += 64) {
6440 const __m512 x0 = _mm512_cvtph_ps(_mm256_loadu_si256((const __m256i *) (x + i)));
6441 const __m512 y0 = _mm512_cvtph_ps(_mm256_loadu_si256((const __m256i *) (y + i)));
6442 const __m512 x1 = _mm512_cvtph_ps(_mm256_loadu_si256((const __m256i *) (x + i + 16)));
6443 const __m512 y1 = _mm512_cvtph_ps(_mm256_loadu_si256((const __m256i *) (y + i + 16)));
6444 const __m512 x2 = _mm512_cvtph_ps(_mm256_loadu_si256((const __m256i *) (x + i + 32)));
6445 const __m512 y2 = _mm512_cvtph_ps(_mm256_loadu_si256((const __m256i *) (y + i + 32)));
6446 const __m512 x3 = _mm512_cvtph_ps(_mm256_loadu_si256((const __m256i *) (x + i + 48)));
6447 const __m512 y3 = _mm512_cvtph_ps(_mm256_loadu_si256((const __m256i *) (y + i + 48)));
6448 sum0 = _mm512_fmadd_ps(x0, y0, sum0);
6449 sum1 = _mm512_fmadd_ps(x1, y1, sum1);
6450 sum2 = _mm512_fmadd_ps(x2, y2, sum2);
6451 sum3 = _mm512_fmadd_ps(x3, y3, sum3);
6452 }
6453 sum0 = _mm512_add_ps(sum0, sum2);
6454 sum1 = _mm512_add_ps(sum1, sum3);
6455 sum0 = _mm512_add_ps(sum0, sum1);
6456 const float vector_result = _mm512_reduce_add_ps(sum0);
6457#elif defined(__AVX2__) && defined(__F16C__)
6458 // Match ggml_vec_dot_f16's AVX reduction contract: four independent
6459 // accumulators per 32 values, followed by its fixed pairwise tree.
6460 __m256 sum0 = _mm256_setzero_ps();
6461 __m256 sum1 = _mm256_setzero_ps();
6462 __m256 sum2 = _mm256_setzero_ps();
6463 __m256 sum3 = _mm256_setzero_ps();
6464 const int n32 = n & ~31;
6465 for (; i < n32; i += 32) {
6466 const __m256 x0 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *) (x + i)));
6467 const __m256 y0 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *) (y + i)));
6468 const __m256 x1 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *) (x + i + 8)));
6469 const __m256 y1 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *) (y + i + 8)));
6470 const __m256 x2 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *) (x + i + 16)));
6471 const __m256 y2 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *) (y + i + 16)));
6472 const __m256 x3 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *) (x + i + 24)));
6473 const __m256 y3 = _mm256_cvtph_ps(_mm_loadu_si128((const __m128i *) (y + i + 24)));
6474#if defined(__FMA__)
6475 sum0 = _mm256_fmadd_ps(x0, y0, sum0);
6476 sum1 = _mm256_fmadd_ps(x1, y1, sum1);
6477 sum2 = _mm256_fmadd_ps(x2, y2, sum2);
6478 sum3 = _mm256_fmadd_ps(x3, y3, sum3);
6479#else
6480 sum0 = _mm256_add_ps(sum0, _mm256_mul_ps(x0, y0));
6481 sum1 = _mm256_add_ps(sum1, _mm256_mul_ps(x1, y1));
6482 sum2 = _mm256_add_ps(sum2, _mm256_mul_ps(x2, y2));
6483 sum3 = _mm256_add_ps(sum3, _mm256_mul_ps(x3, y3));
6484#endif
6485 }
6486 sum0 = _mm256_add_ps(sum0, sum2);
6487 sum1 = _mm256_add_ps(sum1, sum3);
6488 sum0 = _mm256_add_ps(sum0, sum1);
6489 const __m128 pair = _mm_add_ps(
6490 _mm256_castps256_ps128(sum0),
6491 _mm256_extractf128_ps(sum0, 1));
6492 const __m128 half = _mm_hadd_ps(pair, pair);
6493 const float vector_result = _mm_cvtss_f32(_mm_hadd_ps(half, half));
6494#else
6495 const float vector_result = 0.0f;
6496#endif
6497 /* ggml_vec_dot_f16 stores the SIMD reduction in ggml_float (double) and
6498 * accumulates any scalar tail there. This matters for head dimensions
6499 * below or not divisible by the active ISA step (for example D=32 on
6500 * AVX-512 and padded D=80). */
6501 double result = (double) vector_result;
6502 for (; i < n; ++i) {
6503 const float product = CK_FP16_TO_FP32(x[i]) * CK_FP16_TO_FP32(y[i]);
6504 result += (double) product;
6505 }
6506 return (float) result;
6507}
6508
6509static inline void ck_attention_scale_f16_llama(uint16_t *y, float scale, int n)
6510{
6511 int i = 0;
6512#if defined(__AVX512F__)
6513 const __m512 factor = _mm512_set1_ps(scale);
6514 for (; i + 63 < n; i += 64) {
6515 for (int lane = 0; lane < 4; ++lane) {
6516 const int offset = i + lane * 16;
6517 __m512 value = _mm512_cvtph_ps(
6518 _mm256_loadu_si256((const __m256i *) (y + offset)));
6519 value = _mm512_mul_ps(value, factor);
6520 _mm256_storeu_si256(
6521 (__m256i *) (y + offset), _mm512_cvtps_ph(value, 0));
6522 }
6523 }
6524#elif defined(__AVX2__) && defined(__F16C__)
6525 const __m256 factor = _mm256_set1_ps(scale);
6526 for (; i + 31 < n; i += 32) {
6527 for (int lane = 0; lane < 4; ++lane) {
6528 const int offset = i + lane * 8;
6529 __m256 value = _mm256_cvtph_ps(
6530 _mm_loadu_si128((const __m128i *) (y + offset)));
6531 value = _mm256_mul_ps(value, factor);
6532 _mm_storeu_si128(
6533 (__m128i *) (y + offset), _mm256_cvtps_ph(value, 0));
6534 }
6535 }
6536#endif
6537 for (; i < n; ++i) {
6538 const float value = CK_FP16_TO_FP32(y[i]);
6539 y[i] = CK_FP32_TO_FP16(value * scale);
6540 }
6541}
6542
6543static inline void ck_attention_mad_f16_llama(uint16_t *y,
6544 const uint16_t *x,
6545 float scale,
6546 int n)
6547{
6548 int i = 0;
6549#if defined(__AVX512F__)
6550 const __m512 factor = _mm512_set1_ps(scale);
6551 for (; i + 63 < n; i += 64) {
6552 for (int lane = 0; lane < 4; ++lane) {
6553 const int offset = i + lane * 16;
6554 const __m512 xv = _mm512_cvtph_ps(
6555 _mm256_loadu_si256((const __m256i *) (x + offset)));
6556 __m512 yv = _mm512_cvtph_ps(
6557 _mm256_loadu_si256((const __m256i *) (y + offset)));
6558 yv = _mm512_fmadd_ps(xv, factor, yv);
6559 _mm256_storeu_si256(
6560 (__m256i *) (y + offset), _mm512_cvtps_ph(yv, 0));
6561 }
6562 }
6563#elif defined(__AVX2__) && defined(__F16C__)
6564 const __m256 factor = _mm256_set1_ps(scale);
6565 for (; i + 31 < n; i += 32) {
6566 for (int lane = 0; lane < 4; ++lane) {
6567 const int offset = i + lane * 8;
6568 const __m256 xv = _mm256_cvtph_ps(
6569 _mm_loadu_si128((const __m128i *) (x + offset)));
6570 __m256 yv = _mm256_cvtph_ps(
6571 _mm_loadu_si128((const __m128i *) (y + offset)));
6572#if defined(__FMA__)
6573 yv = _mm256_fmadd_ps(xv, factor, yv);
6574#else
6575 yv = _mm256_add_ps(yv, _mm256_mul_ps(xv, factor));
6576#endif
6577 _mm_storeu_si128(
6578 (__m128i *) (y + offset), _mm256_cvtps_ph(yv, 0));
6579 }
6580 }
6581#endif
6582 for (; i < n; ++i) {
6583 const float product = CK_FP16_TO_FP32(x[i]) * scale;
6584 const float updated = CK_FP16_TO_FP32(y[i]) + product;
6585 y[i] = CK_FP32_TO_FP16(updated);
6586 }
6587}
6588
6589static void ck_attention_f16_split_work(int ith, int nth, void *opaque)
6590{
6591 ck_attention_f16_split_args_t *args = (ck_attention_f16_split_args_t *) opaque;
6592 const int partial_stride = args->aligned_head_dim + 2;
6593 const int total_jobs = args->num_heads * args->scheduled_chunks;
6594 const int chunk_size =
6595 (args->partition_tokens + args->split_chunks - 1) / args->split_chunks;
6596 const size_t head_stride = (size_t) args->cache_capacity * (size_t) args->aligned_head_dim;
6597 const float scale = args->attention_scale;
6598 uint16_t *q_half = (uint16_t *) alloca((size_t) args->aligned_head_dim * sizeof(uint16_t));
6599 uint16_t *acc_half = (uint16_t *) alloca((size_t) args->aligned_head_dim * sizeof(uint16_t));
6600
6601 for (int job = ith; job < total_jobs; job += nth) {
6602 const int h = job / args->scheduled_chunks;
6603 const int chunk = job % args->scheduled_chunks;
6604 const int kv_head = (int) ((long long) h * (long long) args->num_kv_heads /
6605 (long long) args->num_heads);
6606 const int begin = args->kv_start + chunk * chunk_size;
6607 const int end = begin < args->kv_tokens
6608 ? (begin + chunk_size < args->kv_tokens ? begin + chunk_size : args->kv_tokens)
6609 : begin;
6610 const float *q_head = args->q_token + (size_t) h * (size_t) args->aligned_head_dim;
6611 const uint16_t *k_head = args->k_cache + (size_t) kv_head * head_stride;
6612 const uint16_t *v_head = args->v_cache + (size_t) kv_head * head_stride;
6613 float *partial = args->partials + (size_t) job * (size_t) partial_stride;
6614
6615 for (int d = 0; d < args->aligned_head_dim; ++d) {
6616 q_half[d] = CK_FP32_TO_FP16(q_head[d]);
6617 acc_half[d] = CK_FP32_TO_FP16(0.0f);
6618 }
6619
6620 float sum = 0.0f;
6621 float max_score = -INFINITY;
6622 for (int j = begin; j < end; ++j) {
6623 const uint16_t *k_vec = k_head + (size_t) j * (size_t) args->aligned_head_dim;
6624 const uint16_t *v_vec = v_head + (size_t) j * (size_t) args->aligned_head_dim;
6625 const float dot = ck_attention_dot_f16_llama(q_half, k_vec, args->head_dim);
6626 const float score = dot * scale;
6627 const float old_max = max_score;
6628 float max_scale = 1.0f;
6629 float value_scale = 1.0f;
6630
6631 if (score > max_score) {
6632 max_score = score;
6633 max_scale = isfinite(old_max)
6634 ? ck_attention_reference_expf(old_max - max_score)
6635 : 0.0f;
6636 ck_attention_scale_f16_llama(acc_half, max_scale, args->head_dim);
6637 } else {
6638 value_scale = ck_attention_reference_expf(score - max_score);
6639 }
6640
6642 acc_half, v_vec, value_scale, args->head_dim);
6643#if defined(__INTEL_LLVM_COMPILER)
6644 sum = ck_attention_mul_add_rounded_f32(
6645 sum, max_scale, value_scale);
6646#else
6647 sum = fmaf(sum, max_scale, value_scale);
6648#endif
6649 }
6650
6651 partial[0] = max_score;
6652 partial[1] = sum;
6653 for (int d = 0; d < args->head_dim; ++d) {
6654 partial[2 + d] = CK_FP16_TO_FP32(acc_half[d]);
6655 }
6656 for (int d = args->head_dim; d < args->aligned_head_dim; ++d) {
6657 partial[2 + d] = 0.0f;
6658 }
6659 }
6660}
6661
6663 const float *q_token,
6664 const uint16_t *k_cache,
6665 const uint16_t *v_cache,
6666 float *out_token,
6667 int num_heads,
6668 int num_kv_heads,
6669 int kv_tokens,
6670 int cache_capacity,
6671 int head_dim,
6672 int aligned_head_dim,
6673 int split_chunks,
6674 int partition_tokens,
6675 int kv_start,
6676 float attention_scale)
6677{
6678 if (!q_token || !k_cache || !v_cache || !out_token ||
6679 num_heads <= 0 || num_kv_heads <= 0 || kv_tokens <= 0 ||
6680 cache_capacity <= 0 || kv_tokens > cache_capacity ||
6681 kv_start < 0 || kv_start >= kv_tokens ||
6682 head_dim <= 0 || aligned_head_dim < head_dim ||
6683 partition_tokens < kv_tokens - kv_start || !isfinite(attention_scale)) {
6684 return;
6685 }
6686
6687 const int live_tokens = kv_tokens - kv_start;
6688
6689 if (split_chunks < 1) {
6690 split_chunks = 1;
6691 }
6692 if (split_chunks > live_tokens) {
6693 split_chunks = live_tokens;
6694 }
6695
6696 const int partial_stride = aligned_head_dim + 2;
6697 const size_t max_partial_bytes = 1024u * 1024u;
6698 while (split_chunks > 1 &&
6699 (size_t) num_heads * (size_t) split_chunks * (size_t) partial_stride * sizeof(float) >
6700 max_partial_bytes) {
6701 split_chunks = (split_chunks + 1) / 2;
6702 }
6703
6704 const int chunk_size = (partition_tokens + split_chunks - 1) / split_chunks;
6705 const int scheduled_chunks = (live_tokens + chunk_size - 1) / chunk_size;
6706 const size_t resolved_count =
6707 (size_t) num_heads * (size_t) scheduled_chunks * (size_t) partial_stride;
6708 float *partials = (float *) alloca(resolved_count * sizeof(float));
6709 ck_attention_f16_split_args_t args = {
6710 .q_token = q_token,
6711 .k_cache = k_cache,
6712 .v_cache = v_cache,
6713 .partials = partials,
6714 .num_heads = num_heads,
6715 .num_kv_heads = num_kv_heads,
6716 .kv_start = kv_start,
6717 .kv_tokens = kv_tokens,
6718 .cache_capacity = cache_capacity,
6719 .head_dim = head_dim,
6720 .aligned_head_dim = aligned_head_dim,
6721 .split_chunks = split_chunks,
6722 .scheduled_chunks = scheduled_chunks,
6723 .partition_tokens = partition_tokens,
6724 .attention_scale = attention_scale,
6725 };
6726
6727 ck_threadpool_t *pool = ck_threadpool_global();
6728 int active_threads = pool ? ck_threadpool_n_threads(pool) : 1;
6729 const int total_jobs = num_heads * scheduled_chunks;
6730 if (active_threads > total_jobs) {
6731 active_threads = total_jobs;
6732 }
6733 if (pool && active_threads > 1) {
6734 ck_threadpool_dispatch_n(pool, active_threads, ck_attention_f16_split_work, &args);
6735 } else {
6736 ck_attention_f16_split_work(0, 1, &args);
6737 }
6738
6739 for (int h = 0; h < num_heads; ++h) {
6740 float *out_head = out_token + (size_t) h * (size_t) aligned_head_dim;
6741 float final_max = -INFINITY;
6742 float final_sum = 0.0f;
6743 for (int d = 0; d < aligned_head_dim; ++d) {
6744 out_head[d] = 0.0f;
6745 }
6746
6747 for (int chunk = 0; chunk < scheduled_chunks; ++chunk) {
6748 const float *partial = partials +
6749 ((size_t) h * (size_t) scheduled_chunks + (size_t) chunk) * (size_t) partial_stride;
6750 const float chunk_max = partial[0];
6751 const float chunk_sum = partial[1];
6752 if (chunk_sum == 0.0f) {
6753 continue;
6754 }
6755 const float new_max = fmaxf(final_max, chunk_max);
6756 const float old_scale = isfinite(final_max)
6757 ? ck_attention_f16_reduce_expf(final_max - new_max)
6758 : 0.0f;
6759 const float chunk_scale =
6760 ck_attention_f16_reduce_expf(chunk_max - new_max);
6761 for (int d = 0; d < head_dim; ++d) {
6762 const float chunk_term = partial[2 + d] * chunk_scale;
6763 out_head[d] = fmaf(out_head[d], old_scale, chunk_term);
6764 }
6765#if defined(__INTEL_LLVM_COMPILER)
6766 const float scaled_chunk_sum = chunk_sum * chunk_scale;
6767 final_sum = fmaf(final_sum, old_scale, scaled_chunk_sum);
6768#else
6769 final_sum = fmaf(
6770 final_sum, old_scale, chunk_sum * chunk_scale);
6771#endif
6772 final_max = new_max;
6773 }
6774
6775 if (final_sum > 0.0f) {
6776 const float inv_sum = 1.0f / final_sum;
6777 for (int d = 0; d < head_dim; ++d) {
6778 out_head[d] *= inv_sum;
6779 }
6780 }
6781 }
6782}
6783
6785 const uint16_t *k_cache,
6786 const uint16_t *v_cache,
6787 float *out_token,
6788 int num_heads,
6789 int num_kv_heads,
6790 int kv_tokens,
6791 int cache_capacity,
6792 int head_dim,
6793 int aligned_head_dim,
6794 int split_chunks)
6795{
6796 const int partition_alignment = 256;
6797 const int partition_tokens =
6798 ((kv_tokens + partition_alignment - 1) / partition_alignment) * partition_alignment;
6800 q_token, k_cache, v_cache, out_token,
6801 num_heads, num_kv_heads, kv_tokens, cache_capacity,
6802 head_dim, aligned_head_dim, split_chunks, partition_tokens, 0,
6804}
6805
6807 const float *q_token,
6808 const uint16_t *k_cache,
6809 const uint16_t *v_cache,
6810 float *out_token,
6811 int num_heads,
6812 int num_kv_heads,
6813 int kv_tokens,
6814 int cache_capacity,
6815 int head_dim,
6816 int aligned_head_dim,
6817 int sliding_window,
6818 ck_attention_reduction_t reduction)
6819{
6820 if (!q_token || !k_cache || !v_cache || !out_token ||
6821 num_heads <= 0 || num_kv_heads <= 0 || kv_tokens <= 0 ||
6822 cache_capacity <= 0 || kv_tokens > cache_capacity ||
6823 head_dim <= 0 || aligned_head_dim < head_dim || sliding_window < 0) {
6825 }
6826 if (reduction == CK_ATTN_REDUCTION_F16_REGULAR_NOSCALE) {
6828 q_token, k_cache, v_cache, out_token, num_heads, num_kv_heads,
6829 kv_tokens, cache_capacity, head_dim, aligned_head_dim, sliding_window);
6830 }
6833 }
6834 const int kv_start = sliding_window > 0 && kv_tokens > sliding_window
6835 ? kv_tokens - sliding_window : 0;
6836 const int live_tokens = kv_tokens - kv_start;
6838 q_token, k_cache, v_cache, out_token,
6839 num_heads, num_kv_heads, kv_tokens, cache_capacity,
6840 head_dim, aligned_head_dim, 1, live_tokens, kv_start, 1.0f);
6842}
6843
6845 const float *q_token,
6846 const uint16_t *k_cache,
6847 const uint16_t *v_cache,
6848 float *out_token,
6849 int num_heads,
6850 int num_kv_heads,
6851 int kv_tokens,
6852 int cache_capacity,
6853 int head_dim,
6854 int aligned_head_dim,
6855 int sliding_window)
6856{
6857 if (!q_token || !k_cache || !v_cache || !out_token ||
6858 num_heads <= 0 || num_kv_heads <= 0 || num_heads % num_kv_heads != 0 ||
6859 kv_tokens <= 0 || cache_capacity <= 0 || kv_tokens > cache_capacity ||
6860 head_dim <= 0 || head_dim > 4096 || aligned_head_dim < head_dim ||
6861 sliding_window < 0 ||
6862 (size_t) cache_capacity > SIZE_MAX / (size_t) aligned_head_dim) {
6864 }
6865
6866 const size_t cache_head_stride = (size_t) cache_capacity * (size_t) aligned_head_dim;
6867 const int first_token = sliding_window > 0 && kv_tokens > sliding_window
6868 ? kv_tokens - sliding_window : 0;
6869 uint16_t *q_half = (uint16_t *) alloca((size_t) head_dim * sizeof(uint16_t));
6870 for (int h = 0; h < num_heads; ++h) {
6871 const int kv_head = h / (num_heads / num_kv_heads);
6872 const float *q_head = q_token + (size_t) h * (size_t) aligned_head_dim;
6873 const uint16_t *k_head = k_cache + (size_t) kv_head * cache_head_stride;
6874 const uint16_t *v_head = v_cache + (size_t) kv_head * cache_head_stride;
6875 float *out_head = out_token + (size_t) h * (size_t) aligned_head_dim;
6876 for (int d = 0; d < head_dim; ++d) q_half[d] = CK_FP32_TO_FP16(q_head[d]);
6877
6878 float maximum = -INFINITY;
6879 for (int token = first_token; token < kv_tokens; ++token) {
6880 const float score = ck_attention_dot_f16_llama(
6881 q_half, k_head + (size_t) token * (size_t) aligned_head_dim, head_dim);
6882 if (score > maximum) maximum = score;
6883 }
6884 if (!isfinite(maximum)) return CK_ATTENTION_STATUS_INVALID_ARGUMENT;
6885
6886 double total = 0.0;
6887 for (int token = first_token; token < kv_tokens; ++token) {
6888 const float score = ck_attention_dot_f16_llama(
6889 q_half, k_head + (size_t) token * (size_t) aligned_head_dim, head_dim);
6890 total += (double) ck_attention_reference_expf(score - maximum);
6891 }
6892 if (!(total > 0.0) || !isfinite(total)) return CK_ATTENTION_STATUS_INVALID_ARGUMENT;
6893
6894 for (int d = 0; d < head_dim; ++d) out_head[d] = 0.0f;
6895 for (int token = first_token; token < kv_tokens; ++token) {
6896 const float score = ck_attention_dot_f16_llama(
6897 q_half, k_head + (size_t) token * (size_t) aligned_head_dim, head_dim);
6898 const float probability = ck_attention_reference_expf(score - maximum) / (float) total;
6899 const float rounded_probability = CK_FP16_TO_FP32(CK_FP32_TO_FP16(probability));
6900 const uint16_t *v_row = v_head + (size_t) token * (size_t) aligned_head_dim;
6901 for (int d = 0; d < head_dim; ++d) {
6902 out_head[d] = fmaf(rounded_probability, CK_FP16_TO_FP32(v_row[d]), out_head[d]);
6903 }
6904 }
6905 for (int d = head_dim; d < aligned_head_dim; ++d) out_head[d] = 0.0f;
6906 }
6908}
6909
6911 const float *q_token, const uint16_t *k_cache, const uint16_t *v_cache,
6912 float *out_token, int num_heads, int num_kv_heads, int kv_tokens,
6913 int cache_capacity, int head_dim, int aligned_head_dim,
6914 ck_attention_reduction_t reduction)
6915{
6917 q_token, k_cache, v_cache, out_token, num_heads, num_kv_heads,
6918 kv_tokens, cache_capacity, head_dim, aligned_head_dim, 0, reduction);
6919}
6920
6922 const float *q_token, const uint16_t *k_cache, const uint16_t *v_cache,
6923 float *out_token, int num_heads, int num_kv_heads, int kv_tokens,
6924 int cache_capacity, int head_dim, int aligned_head_dim, int sliding_window,
6925 ck_attention_reduction_t reduction)
6926{
6928 q_token, k_cache, v_cache, out_token, num_heads, num_kv_heads,
6929 kv_tokens, cache_capacity, head_dim, aligned_head_dim,
6930 sliding_window, reduction);
6931}
6932
6934 const float *q_token,
6935 const uint16_t *k_cache,
6936 const uint16_t *v_cache,
6937 float *out_token,
6938 int num_heads,
6939 int num_kv_heads,
6940 int kv_tokens,
6941 int cache_capacity,
6942 int head_dim,
6943 int aligned_head_dim,
6944 ck_attention_reduction_t reduction)
6945{
6946 if (!q_token || !k_cache || !v_cache || !out_token ||
6947 num_heads <= 0 || num_kv_heads <= 0 || kv_tokens <= 0 ||
6948 cache_capacity <= 0 || kv_tokens > cache_capacity ||
6949 head_dim <= 0 || aligned_head_dim < head_dim) {
6951 }
6952
6953 switch (reduction) {
6956 q_token, k_cache, v_cache, out_token,
6957 num_heads, num_kv_heads, kv_tokens, cache_capacity,
6958 head_dim, aligned_head_dim);
6960
6962 const int partition_alignment = 256;
6963 const int partition_tokens =
6964 ((kv_tokens + partition_alignment - 1) / partition_alignment) * partition_alignment;
6965 const int split_chunks = partition_tokens >= 512 ? ck_get_num_threads() : 1;
6967 q_token, k_cache, v_cache, out_token,
6968 num_heads, num_kv_heads, kv_tokens, cache_capacity,
6969 head_dim, aligned_head_dim, split_chunks);
6971 }
6972
6975 q_token, k_cache, v_cache, out_token,
6976 num_heads, num_kv_heads, kv_tokens, cache_capacity,
6977 head_dim, aligned_head_dim, 1);
6979
6980 default:
6982 }
6983}
6984
6985typedef struct {
6986 const float *q;
6987 const uint16_t *k_cache;
6988 const uint16_t *v_cache;
6989 float *output;
6990 int num_heads;
6991 int num_kv_heads;
6992 int q_tokens;
6993 int past_tokens;
6994 int cache_capacity;
6995 int head_dim;
6996 int aligned_head_dim;
6997 int cache_is_bf16;
6998 float attention_scale;
7000 size_t q_head_stride;
7001 size_t output_head_stride;
7002} ck_attention_f16_prefill_qtile64_args_t;
7003
7004typedef struct {
7005 _Alignas(64) int arrived;
7006 _Alignas(64) int phase;
7007 int lanes;
7008} ck_attention_gqa_team_barrier_t;
7009
7010typedef struct {
7011 ck_attention_f16_prefill_qtile64_args_t base;
7012 float *shared_k_tiles;
7013 float *shared_v_tiles;
7014 ck_attention_gqa_team_barrier_t *barriers;
7015 unsigned char *worker_workspace;
7016 size_t worker_workspace_stride;
7017 int query_tile_size;
7018 int concurrent_query_tiles;
7019} ck_attention_f16_prefill_gqa_reuse_args_t;
7020
7021static inline size_t ck_attention_align64_size(size_t value)
7022{
7023 return (value + 63u) & ~(size_t) 63u;
7024}
7025
7027 int num_heads,
7028 int num_kv_heads,
7029 int head_dim,
7030 int workers,
7031 int query_tile_size,
7032 int concurrent_query_tiles)
7033{
7034 if (num_heads <= 0 || num_kv_heads <= 0 ||
7035 num_heads % num_kv_heads != 0 || workers < num_kv_heads ||
7036 workers % num_kv_heads != 0 || head_dim <= 0 ||
7037 query_tile_size <= 0 || concurrent_query_tiles <= 0) {
7038 return 0;
7039 }
7040 const int lanes = workers / num_kv_heads;
7041 const int group_jobs =
7042 (num_heads / num_kv_heads) * concurrent_query_tiles;
7043 const size_t local_capacity =
7044 (size_t) (group_jobs + lanes - 1) / (size_t) lanes;
7045 size_t bytes = 0;
7046 bytes = ck_attention_align64_size(bytes) +
7047 3u * local_capacity * sizeof(int);
7048 bytes = ck_attention_align64_size(bytes) +
7049 local_capacity * (size_t) query_tile_size * (size_t) head_dim *
7050 sizeof(float);
7051 bytes = ck_attention_align64_size(bytes) +
7052 local_capacity * (size_t) query_tile_size * CK_GGML_FA_TILE_KV *
7053 sizeof(float);
7054 bytes = ck_attention_align64_size(bytes) +
7055 local_capacity * (size_t) query_tile_size * (size_t) head_dim *
7056 sizeof(float);
7057 bytes = ck_attention_align64_size(bytes) +
7058 local_capacity * (size_t) query_tile_size * sizeof(float);
7059 bytes = ck_attention_align64_size(bytes) +
7060 local_capacity * (size_t) query_tile_size * sizeof(float);
7061 return ck_attention_align64_size(bytes);
7062}
7063
7065 int num_heads,
7066 int num_kv_heads,
7067 int head_dim,
7068 int workers,
7069 int query_tile_size,
7070 int concurrent_query_tiles)
7071{
7072 const size_t worker_bytes =
7074 num_heads, num_kv_heads, head_dim, workers,
7075 query_tile_size, concurrent_query_tiles);
7076 if (worker_bytes == 0) return 0;
7077 size_t bytes = 63u;
7078 bytes = ck_attention_align64_size(bytes) +
7079 (size_t) num_kv_heads * (size_t) head_dim * CK_GGML_FA_TILE_KV *
7080 sizeof(float);
7081 bytes = ck_attention_align64_size(bytes) +
7082 (size_t) num_kv_heads * CK_GGML_FA_TILE_KV * (size_t) head_dim *
7083 sizeof(float);
7084 bytes = ck_attention_align64_size(bytes) +
7085 (size_t) num_kv_heads * sizeof(ck_attention_gqa_team_barrier_t);
7086 bytes = ck_attention_align64_size(bytes) +
7087 (size_t) workers * worker_bytes;
7088 return bytes;
7089}
7090
7092 ck_attention_gqa_team_barrier_t *barrier)
7093{
7094 const int phase = __atomic_load_n(&barrier->phase, __ATOMIC_RELAXED);
7095 if (__atomic_fetch_add(&barrier->arrived, 1, __ATOMIC_ACQ_REL) ==
7096 barrier->lanes - 1) {
7097 __atomic_store_n(&barrier->arrived, 0, __ATOMIC_RELAXED);
7098 __atomic_store_n(&barrier->phase, phase + 1, __ATOMIC_RELEASE);
7099 return;
7100 }
7101 while (__atomic_load_n(&barrier->phase, __ATOMIC_ACQUIRE) == phase) {
7102#if defined(__i386__) || defined(__x86_64__)
7103 _mm_pause();
7104#endif
7105 }
7106}
7107
7108static inline float ck_attention_u16_cache_to_f32(uint16_t value, int cache_is_bf16)
7109{
7110 return cache_is_bf16 ? bf16_to_float(value) : CK_FP16_TO_FP32(value);
7111}
7112
7113static void ck_attention_f16_prefill_qtile64_work(int ith, int nth, void *opaque)
7114{
7115 const ck_attention_f16_prefill_qtile64_args_t *args =
7116 (const ck_attention_f16_prefill_qtile64_args_t *) opaque;
7117 const int query_tiles =
7118 (args->q_tokens + CK_GGML_FA_TILE_Q - 1) / CK_GGML_FA_TILE_Q;
7119 const int total_jobs = args->num_heads * query_tiles;
7120 int job_begin = 0;
7121 int job_end = 0;
7122
7123 if (args->schedule == CK_ATTN_PREFILL_SCHEDULE_KV_GROUP_QUERY_TILES &&
7124 nth >= args->num_kv_heads) {
7125 const int kv_group = (ith * args->num_kv_heads) / nth;
7126 const int worker_begin =
7127 (kv_group * nth + args->num_kv_heads - 1) / args->num_kv_heads;
7128 const int worker_end =
7129 ((kv_group + 1) * nth + args->num_kv_heads - 1) /
7130 args->num_kv_heads;
7131 const int lane = ith - worker_begin;
7132 const int lanes = worker_end - worker_begin;
7133 const int head_begin =
7134 (kv_group * args->num_heads + args->num_kv_heads - 1) /
7135 args->num_kv_heads;
7136 const int head_end =
7137 ((kv_group + 1) * args->num_heads + args->num_kv_heads - 1) /
7138 args->num_kv_heads;
7139 const int group_jobs = (head_end - head_begin) * query_tiles;
7140 job_begin = head_begin * query_tiles + (group_jobs * lane) / lanes;
7141 job_end = head_begin * query_tiles + (group_jobs * (lane + 1)) / lanes;
7142 } else if (args->schedule == CK_ATTN_PREFILL_SCHEDULE_QUERY_TILES) {
7143 job_begin = (total_jobs * ith) / nth;
7144 job_end = (total_jobs * (ith + 1)) / nth;
7145 } else {
7146 const int head_begin = (args->num_heads * ith) / nth;
7147 const int head_end = (args->num_heads * (ith + 1)) / nth;
7148 job_begin = head_begin * query_tiles;
7149 job_end = head_end * query_tiles;
7150 }
7151 if (job_begin >= job_end) return;
7152
7153 const int kv_tokens = args->past_tokens + args->q_tokens;
7154 const float scale = args->attention_scale != 0.0f
7155 ? args->attention_scale : ck_attention_strict_scale_f32(args->head_dim);
7156 const size_t kv_head_stride =
7157 (size_t) args->cache_capacity * (size_t) args->aligned_head_dim;
7158
7159 float *q_tile = (float *) alloca(
7160 (size_t) CK_GGML_FA_TILE_Q * (size_t) args->head_dim * sizeof(float));
7161 float *k_tile = (float *) alloca(
7162 (size_t) args->head_dim * (size_t) CK_GGML_FA_TILE_KV * sizeof(float));
7163 float *v_tile = (float *) alloca(
7164 (size_t) CK_GGML_FA_TILE_KV * (size_t) args->head_dim * sizeof(float));
7165 float *kq = (float *) alloca(
7166 (size_t) CK_GGML_FA_TILE_Q * (size_t) CK_GGML_FA_TILE_KV * sizeof(float));
7167 float *vkq = (float *) alloca(
7168 (size_t) CK_GGML_FA_TILE_Q * (size_t) args->head_dim * sizeof(float));
7169
7170 for (int job = job_begin; job < job_end; ++job) {
7171 const int h = job / query_tiles;
7172 const int iq = (job % query_tiles) * CK_GGML_FA_TILE_Q;
7173 const int kv_head =
7174 (int) ((long long) h * (long long) args->num_kv_heads /
7175 (long long) args->num_heads);
7176 const uint16_t *k_head = args->k_cache + (size_t) kv_head * kv_head_stride;
7177 const uint16_t *v_head = args->v_cache + (size_t) kv_head * kv_head_stride;
7178
7179 const int tile_rows =
7180 (args->q_tokens - iq) < CK_GGML_FA_TILE_Q
7181 ? (args->q_tokens - iq)
7183 float sum_row[CK_GGML_FA_TILE_Q];
7184 float max_row[CK_GGML_FA_TILE_Q];
7185
7186 for (int tq = 0; tq < CK_GGML_FA_TILE_Q; ++tq) {
7187 sum_row[tq] = 0.0f;
7188 max_row[tq] = -INFINITY;
7189 }
7190 memset(q_tile, 0,
7191 (size_t) CK_GGML_FA_TILE_Q * (size_t) args->head_dim * sizeof(float));
7192 memset(vkq, 0,
7193 (size_t) CK_GGML_FA_TILE_Q * (size_t) args->head_dim * sizeof(float));
7194
7195 for (int tq = 0; tq < tile_rows; ++tq) {
7196 const float *q_vec = args->q +
7197 (size_t) h * args->q_head_stride +
7198 (size_t) (iq + tq) * (size_t) args->aligned_head_dim;
7199 memcpy(q_tile + (size_t) tq * (size_t) args->head_dim,
7200 q_vec,
7201 (size_t) args->head_dim * sizeof(float));
7202 }
7203
7204 for (int ik = 0; ik < kv_tokens; ik += CK_GGML_FA_TILE_KV) {
7205 const int kv_tile =
7206 (kv_tokens - ik) < CK_GGML_FA_TILE_KV
7207 ? (kv_tokens - ik)
7209 memset(k_tile, 0,
7210 (size_t) args->head_dim * (size_t) CK_GGML_FA_TILE_KV * sizeof(float));
7211 memset(v_tile, 0,
7212 (size_t) CK_GGML_FA_TILE_KV * (size_t) args->head_dim * sizeof(float));
7213 memset(kq, 0,
7214 (size_t) CK_GGML_FA_TILE_Q * (size_t) CK_GGML_FA_TILE_KV * sizeof(float));
7215
7216 for (int tk = 0; tk < kv_tile; ++tk) {
7217 const uint16_t *k_vec = k_head +
7218 (size_t) (ik + tk) * (size_t) args->aligned_head_dim;
7219 const uint16_t *v_vec = v_head +
7220 (size_t) (ik + tk) * (size_t) args->aligned_head_dim;
7221 for (int d = 0; d < args->head_dim; ++d) {
7222 k_tile[(size_t) d * (size_t) CK_GGML_FA_TILE_KV + (size_t) tk] =
7223 ck_attention_u16_cache_to_f32(k_vec[d], args->cache_is_bf16);
7224 v_tile[(size_t) tk * (size_t) args->head_dim + (size_t) d] =
7225 ck_attention_u16_cache_to_f32(v_vec[d], args->cache_is_bf16);
7226 }
7227 }
7228
7230 kq, q_tile, k_tile,
7231 CK_GGML_FA_TILE_Q, args->head_dim, CK_GGML_FA_TILE_KV);
7234
7235 for (int tq = 0; tq < CK_GGML_FA_TILE_Q; ++tq) {
7236 float *kq_row = kq +
7237 (size_t) tq * (size_t) CK_GGML_FA_TILE_KV;
7238 const int last_valid_key = args->past_tokens + iq + tq;
7239 for (int tk = 0; tk < CK_GGML_FA_TILE_KV; ++tk) {
7240 const int key = ik + tk;
7241 if (tk >= kv_tile || tq >= tile_rows || key > last_valid_key) {
7242 kq_row[tk] = -INFINITY;
7243 }
7244 }
7245
7246 const float tile_max =
7248 if (tile_max == -INFINITY) {
7249 memset(kq_row, 0,
7250 (size_t) CK_GGML_FA_TILE_KV * sizeof(float));
7251 continue;
7252 }
7253
7254 const float old_max = max_row[tq];
7255 const float new_max = old_max > tile_max ? old_max : tile_max;
7256 if (new_max > old_max) {
7257 const float ms = ck_attention_reference_expf(old_max - new_max);
7259 vkq + (size_t) tq * (size_t) args->head_dim,
7260 args->head_dim, ms);
7261 sum_row[tq] *= ms;
7262 }
7263 max_row[tq] = new_max;
7264 sum_row[tq] = (float) (
7265 (double) sum_row[tq] +
7267 CK_GGML_FA_TILE_KV, kq_row, kq_row, new_max));
7268 }
7269
7271 vkq, kq, v_tile,
7272 CK_GGML_FA_TILE_Q, CK_GGML_FA_TILE_KV, args->head_dim);
7273 }
7274
7275 for (int tq = 0; tq < tile_rows; ++tq) {
7276 float *out_vec = args->output +
7277 (size_t) h * args->output_head_stride +
7278 (size_t) (iq + tq) * (size_t) args->aligned_head_dim;
7279 const float inv_sum =
7280 sum_row[tq] == 0.0f ? 0.0f : 1.0f / sum_row[tq];
7281 for (int d = 0; d < args->head_dim; ++d) {
7282 out_vec[d] =
7283 vkq[(size_t) tq * (size_t) args->head_dim + (size_t) d] * inv_sum;
7284 }
7285 for (int d = args->head_dim; d < args->aligned_head_dim; ++d) {
7286 out_vec[d] = 0.0f;
7287 }
7288 }
7289 }
7290}
7291
7292static void ck_attention_f16_prefill_gqa_reuse_work(int ith, int nth, void *opaque)
7293{
7294 const ck_attention_f16_prefill_gqa_reuse_args_t *args =
7295 (const ck_attention_f16_prefill_gqa_reuse_args_t *) opaque;
7296 const ck_attention_f16_prefill_qtile64_args_t *base = &args->base;
7297 const int lanes = nth / base->num_kv_heads;
7298 const int kv_group = ith / lanes;
7299 const int lane = ith % lanes;
7300 const int heads_per_group = base->num_heads / base->num_kv_heads;
7301 const int head_begin = kv_group * heads_per_group;
7302 const int query_tiles =
7303 (base->q_tokens + args->query_tile_size - 1) / args->query_tile_size;
7304 const int kv_tokens = base->past_tokens + base->q_tokens;
7305 const float scale = ck_attention_strict_scale_f32(base->head_dim);
7306 const size_t kv_head_stride =
7307 (size_t) base->cache_capacity * (size_t) base->aligned_head_dim;
7308 const uint16_t *k_head =
7309 base->k_cache + (size_t) kv_group * kv_head_stride;
7310 const uint16_t *v_head =
7311 base->v_cache + (size_t) kv_group * kv_head_stride;
7312 float *shared_k = args->shared_k_tiles +
7313 (size_t) kv_group * (size_t) base->head_dim * CK_GGML_FA_TILE_KV;
7314 float *shared_v = args->shared_v_tiles +
7315 (size_t) kv_group * CK_GGML_FA_TILE_KV * (size_t) base->head_dim;
7316 ck_attention_gqa_team_barrier_t *barrier = &args->barriers[kv_group];
7317 const int max_group_jobs =
7318 heads_per_group * args->concurrent_query_tiles;
7319 const int local_capacity = (max_group_jobs + lanes - 1) / lanes;
7320 unsigned char *worker = args->worker_workspace +
7321 (size_t) ith * args->worker_workspace_stride;
7322 size_t cursor = 0;
7323 cursor = ck_attention_align64_size(cursor);
7324 int *local_heads = (int *) (worker + cursor);
7325 cursor += (size_t) local_capacity * sizeof(int);
7326 int *local_iq = (int *) (worker + cursor);
7327 cursor += (size_t) local_capacity * sizeof(int);
7328 int *local_rows = (int *) (worker + cursor);
7329 cursor += (size_t) local_capacity * sizeof(int);
7330 cursor = ck_attention_align64_size(cursor);
7331 float *q_tiles = (float *) (worker + cursor);
7332 cursor += (size_t) local_capacity * (size_t) args->query_tile_size *
7333 (size_t) base->head_dim * sizeof(float);
7334 cursor = ck_attention_align64_size(cursor);
7335 float *kq_tiles = (float *) (worker + cursor);
7336 cursor += (size_t) local_capacity * (size_t) args->query_tile_size *
7337 CK_GGML_FA_TILE_KV * sizeof(float);
7338 cursor = ck_attention_align64_size(cursor);
7339 float *vkq_tiles = (float *) (worker + cursor);
7340 cursor += (size_t) local_capacity * (size_t) args->query_tile_size *
7341 (size_t) base->head_dim * sizeof(float);
7342 cursor = ck_attention_align64_size(cursor);
7343 float *sum_rows = (float *) (worker + cursor);
7344 cursor += (size_t) local_capacity * (size_t) args->query_tile_size *
7345 sizeof(float);
7346 cursor = ck_attention_align64_size(cursor);
7347 float *max_rows = (float *) (worker + cursor);
7348
7349 for (int query_tile_begin = 0; query_tile_begin < query_tiles;
7350 query_tile_begin += args->concurrent_query_tiles) {
7351 int batch_tiles = query_tiles - query_tile_begin;
7352 if (batch_tiles > args->concurrent_query_tiles) {
7353 batch_tiles = args->concurrent_query_tiles;
7354 }
7355 const int group_jobs = heads_per_group * batch_tiles;
7356 int local_jobs = 0;
7357
7358 for (int group_job = lane; group_job < group_jobs; group_job += lanes) {
7359 const int local = local_jobs++;
7360 const int group_head = group_job / batch_tiles;
7361 const int batch_tile = group_job % batch_tiles;
7362 const int h = head_begin + group_head;
7363 const int iq =
7364 (query_tile_begin + batch_tile) * args->query_tile_size;
7365 int tile_rows = base->q_tokens - iq;
7366 if (tile_rows > args->query_tile_size) {
7367 tile_rows = args->query_tile_size;
7368 }
7369 local_heads[local] = h;
7370 local_iq[local] = iq;
7371 local_rows[local] = tile_rows;
7372
7373 float *q_tile = q_tiles +
7374 (size_t) local * (size_t) args->query_tile_size *
7375 (size_t) base->head_dim;
7376 float *vkq = vkq_tiles +
7377 (size_t) local * (size_t) args->query_tile_size *
7378 (size_t) base->head_dim;
7379 float *sum_row = sum_rows +
7380 (size_t) local * (size_t) args->query_tile_size;
7381 float *max_row = max_rows +
7382 (size_t) local * (size_t) args->query_tile_size;
7383 memset(q_tile, 0,
7384 (size_t) args->query_tile_size * (size_t) base->head_dim *
7385 sizeof(float));
7386 memset(vkq, 0,
7387 (size_t) args->query_tile_size * (size_t) base->head_dim *
7388 sizeof(float));
7389 for (int tq = 0; tq < args->query_tile_size; ++tq) {
7390 sum_row[tq] = 0.0f;
7391 max_row[tq] = -INFINITY;
7392 }
7393 for (int tq = 0; tq < tile_rows; ++tq) {
7394 const float *q_vec = base->q +
7395 (size_t) h * base->q_head_stride +
7396 (size_t) (iq + tq) * (size_t) base->aligned_head_dim;
7397 memcpy(q_tile + (size_t) tq * (size_t) base->head_dim,
7398 q_vec, (size_t) base->head_dim * sizeof(float));
7399 }
7400 }
7401
7402 int kv_limit = base->past_tokens +
7403 (query_tile_begin + batch_tiles) * args->query_tile_size;
7404 if (kv_limit > kv_tokens) kv_limit = kv_tokens;
7405
7406 for (int ik = 0; ik < kv_limit; ik += CK_GGML_FA_TILE_KV) {
7407 int kv_tile = kv_limit - ik;
7408 if (kv_tile > CK_GGML_FA_TILE_KV) kv_tile = CK_GGML_FA_TILE_KV;
7409 const int packed_elems = CK_GGML_FA_TILE_KV * base->head_dim;
7410 for (int flat = lane; flat < packed_elems; flat += lanes) {
7411 const int tk = flat / base->head_dim;
7412 const int d = flat % base->head_dim;
7413 float k_value = 0.0f;
7414 float v_value = 0.0f;
7415 if (tk < kv_tile) {
7416 const uint16_t *k_vec = k_head +
7417 (size_t) (ik + tk) * (size_t) base->aligned_head_dim;
7418 const uint16_t *v_vec = v_head +
7419 (size_t) (ik + tk) * (size_t) base->aligned_head_dim;
7421 k_vec[d], base->cache_is_bf16);
7423 v_vec[d], base->cache_is_bf16);
7424 }
7425 shared_k[(size_t) d * CK_GGML_FA_TILE_KV + (size_t) tk] =
7426 k_value;
7427 shared_v[(size_t) tk * (size_t) base->head_dim + (size_t) d] =
7428 v_value;
7429 }
7431
7432 for (int local = 0; local < local_jobs; ++local) {
7433 const int iq = local_iq[local];
7434 const int tile_rows = local_rows[local];
7435 float *q_tile = q_tiles +
7436 (size_t) local * (size_t) args->query_tile_size *
7437 (size_t) base->head_dim;
7438 float *kq = kq_tiles +
7439 (size_t) local * (size_t) args->query_tile_size *
7441 float *vkq = vkq_tiles +
7442 (size_t) local * (size_t) args->query_tile_size *
7443 (size_t) base->head_dim;
7444 float *sum_row = sum_rows +
7445 (size_t) local * (size_t) args->query_tile_size;
7446 float *max_row = max_rows +
7447 (size_t) local * (size_t) args->query_tile_size;
7448 memset(kq, 0,
7449 (size_t) args->query_tile_size * CK_GGML_FA_TILE_KV *
7450 sizeof(float));
7452 kq, q_tile, shared_k, args->query_tile_size,
7453 base->head_dim, CK_GGML_FA_TILE_KV);
7455 kq, args->query_tile_size * CK_GGML_FA_TILE_KV, scale);
7456
7457 for (int tq = 0; tq < args->query_tile_size; ++tq) {
7458 float *kq_row = kq + (size_t) tq * CK_GGML_FA_TILE_KV;
7459 const int last_valid_key = base->past_tokens + iq + tq;
7460 for (int tk = 0; tk < CK_GGML_FA_TILE_KV; ++tk) {
7461 const int key = ik + tk;
7462 if (tk >= kv_tile || tq >= tile_rows || key > last_valid_key) {
7463 kq_row[tk] = -INFINITY;
7464 }
7465 }
7466 const float tile_max =
7468 if (tile_max == -INFINITY) {
7469 memset(kq_row, 0,
7470 CK_GGML_FA_TILE_KV * sizeof(float));
7471 continue;
7472 }
7473 const float old_max = max_row[tq];
7474 const float new_max = old_max > tile_max ? old_max : tile_max;
7475 if (new_max > old_max) {
7476 const float ms =
7477 ck_attention_reference_expf(old_max - new_max);
7479 vkq + (size_t) tq * (size_t) base->head_dim,
7480 base->head_dim, ms);
7481 sum_row[tq] *= ms;
7482 }
7483 max_row[tq] = new_max;
7484 sum_row[tq] = (float) (
7485 (double) sum_row[tq] +
7487 CK_GGML_FA_TILE_KV, kq_row, kq_row, new_max));
7488 }
7490 vkq, kq, shared_v, args->query_tile_size,
7491 CK_GGML_FA_TILE_KV, base->head_dim);
7492 }
7494 }
7495
7496 for (int local = 0; local < local_jobs; ++local) {
7497 const int h = local_heads[local];
7498 const int iq = local_iq[local];
7499 const int tile_rows = local_rows[local];
7500 float *vkq = vkq_tiles +
7501 (size_t) local * (size_t) args->query_tile_size *
7502 (size_t) base->head_dim;
7503 float *sum_row = sum_rows +
7504 (size_t) local * (size_t) args->query_tile_size;
7505 for (int tq = 0; tq < tile_rows; ++tq) {
7506 float *out_vec = base->output +
7507 (size_t) h * base->output_head_stride +
7508 (size_t) (iq + tq) * (size_t) base->aligned_head_dim;
7509 const float inv_sum =
7510 sum_row[tq] == 0.0f ? 0.0f : 1.0f / sum_row[tq];
7511 for (int d = 0; d < base->head_dim; ++d) {
7512 out_vec[d] = vkq[
7513 (size_t) tq * (size_t) base->head_dim + (size_t) d] *
7514 inv_sum;
7515 }
7516 for (int d = base->head_dim; d < base->aligned_head_dim; ++d) {
7517 out_vec[d] = 0.0f;
7518 }
7519 }
7520 }
7521 }
7522}
7523
7525 const float *q,
7526 const uint16_t *k_cache,
7527 const uint16_t *v_cache,
7528 float *output,
7529 int num_heads,
7530 int num_kv_heads,
7531 int q_tokens,
7532 int past_tokens,
7533 int cache_capacity,
7534 int head_dim,
7535 int aligned_head_dim,
7536 int cache_is_bf16,
7537 float attention_scale,
7538 size_t q_head_stride,
7539 size_t output_head_stride,
7541{
7542 if (schedule < CK_ATTN_PREFILL_SCHEDULE_KV_HEADS ||
7545 }
7546 ck_attention_f16_prefill_qtile64_args_t args = {
7547 .q = q,
7548 .k_cache = k_cache,
7549 .v_cache = v_cache,
7550 .output = output,
7551 .num_heads = num_heads,
7552 .num_kv_heads = num_kv_heads,
7553 .q_tokens = q_tokens,
7554 .past_tokens = past_tokens,
7555 .cache_capacity = cache_capacity,
7556 .head_dim = head_dim,
7557 .aligned_head_dim = aligned_head_dim,
7558 .cache_is_bf16 = cache_is_bf16,
7559 .attention_scale = attention_scale,
7560 .schedule = schedule,
7561 .q_head_stride = q_head_stride,
7562 .output_head_stride = output_head_stride,
7563 };
7564 ck_threadpool_t *pool = ck_threadpool_global();
7565 int active = pool ? ck_threadpool_n_threads(pool) : 1;
7566 const int query_tiles =
7567 (q_tokens + CK_GGML_FA_TILE_Q - 1) / CK_GGML_FA_TILE_Q;
7568 int available_jobs = num_heads;
7569 if (schedule == CK_ATTN_PREFILL_SCHEDULE_KV_HEADS) {
7570 available_jobs = num_kv_heads;
7571 } else if (schedule == CK_ATTN_PREFILL_SCHEDULE_QUERY_TILES ||
7573 available_jobs = num_heads * query_tiles;
7574 }
7575 if (active > available_jobs) active = available_jobs;
7576 if (pool && active > 1 && ck_threadpool_thread_id(pool) <= 0) {
7578 pool, active, ck_attention_f16_prefill_qtile64_work, &args);
7579 } else {
7581 }
7583}
7584
7586 const float *q,
7587 const uint16_t *k_cache,
7588 const uint16_t *v_cache,
7589 float *output,
7590 int num_heads,
7591 int num_kv_heads,
7592 int q_tokens,
7593 int past_tokens,
7594 int cache_capacity,
7595 int head_dim,
7596 int aligned_head_dim,
7597 int query_tile_size,
7598 int concurrent_query_tiles,
7599 void *workspace,
7600 size_t workspace_bytes)
7601{
7602 if (!q || !k_cache || !v_cache || !output || !workspace || num_heads <= 0 ||
7603 num_kv_heads <= 0 || num_heads % num_kv_heads != 0 ||
7604 query_tile_size <= 0 || q_tokens < query_tile_size || past_tokens < 0 ||
7605 past_tokens + q_tokens > cache_capacity || head_dim <= 0 ||
7606 aligned_head_dim < head_dim || query_tile_size < 16 ||
7607 query_tile_size > 128 || query_tile_size % 16 != 0 ||
7608 concurrent_query_tiles <= 0 || concurrent_query_tiles > 4) {
7610 }
7611
7612 ck_threadpool_t *pool = ck_threadpool_global();
7613 const int active = pool ? ck_threadpool_n_threads(pool) : 1;
7614 if (!pool || active < num_kv_heads || active % num_kv_heads != 0 ||
7615 ck_threadpool_thread_id(pool) > 0) {
7617 }
7618 const size_t required_workspace =
7620 num_heads, num_kv_heads, head_dim, active,
7621 query_tile_size, concurrent_query_tiles);
7622 if (required_workspace == 0 || workspace_bytes < required_workspace) {
7624 }
7625 const int lanes = active / num_kv_heads;
7626 const size_t tile_elems =
7627 (size_t) num_kv_heads * (size_t) head_dim * CK_GGML_FA_TILE_KV;
7628 uintptr_t workspace_address = (uintptr_t) workspace;
7629 workspace_address = (workspace_address + 63u) & ~(uintptr_t) 63u;
7630 unsigned char *workspace_base = (unsigned char *) workspace_address;
7631 size_t cursor = 0;
7632 float *shared_k_tiles = (float *) (workspace_base + cursor);
7633 cursor += tile_elems * sizeof(float);
7634 cursor = ck_attention_align64_size(cursor);
7635 float *shared_v_tiles = (float *) (workspace_base + cursor);
7636 cursor += tile_elems * sizeof(float);
7637 cursor = ck_attention_align64_size(cursor);
7638 ck_attention_gqa_team_barrier_t *barriers =
7639 (ck_attention_gqa_team_barrier_t *) (workspace_base + cursor);
7640 cursor += (size_t) num_kv_heads * sizeof(*barriers);
7641 cursor = ck_attention_align64_size(cursor);
7642 unsigned char *worker_workspace = workspace_base + cursor;
7643 const size_t worker_workspace_stride =
7645 num_heads, num_kv_heads, head_dim, active,
7646 query_tile_size, concurrent_query_tiles);
7647 for (int group = 0; group < num_kv_heads; ++group) {
7648 __atomic_store_n(&barriers[group].arrived, 0, __ATOMIC_RELAXED);
7649 __atomic_store_n(&barriers[group].phase, 0, __ATOMIC_RELAXED);
7650 barriers[group].lanes = lanes;
7651 }
7652
7653 ck_attention_f16_prefill_gqa_reuse_args_t args = {
7654 .base = {
7655 .q = q,
7656 .k_cache = k_cache,
7657 .v_cache = v_cache,
7658 .output = output,
7659 .num_heads = num_heads,
7660 .num_kv_heads = num_kv_heads,
7661 .q_tokens = q_tokens,
7662 .past_tokens = past_tokens,
7663 .cache_capacity = cache_capacity,
7664 .head_dim = head_dim,
7665 .aligned_head_dim = aligned_head_dim,
7666 .cache_is_bf16 = 0,
7668 .q_head_stride =
7669 (size_t) q_tokens * (size_t) aligned_head_dim,
7670 .output_head_stride =
7671 (size_t) q_tokens * (size_t) aligned_head_dim,
7672 },
7673 .shared_k_tiles = shared_k_tiles,
7674 .shared_v_tiles = shared_v_tiles,
7675 .barriers = barriers,
7676 .worker_workspace = worker_workspace,
7677 .worker_workspace_stride = worker_workspace_stride,
7678 .query_tile_size = query_tile_size,
7679 .concurrent_query_tiles = concurrent_query_tiles,
7680 };
7682 pool, active, ck_attention_f16_prefill_gqa_reuse_work, &args);
7684}
7685
7687 const float *q,
7688 const uint16_t *k_cache,
7689 const uint16_t *v_cache,
7690 float *output,
7691 int num_heads,
7692 int num_kv_heads,
7693 int q_tokens,
7694 int past_tokens,
7695 int cache_capacity,
7696 int head_dim,
7697 int aligned_head_dim,
7698 ck_attention_reduction_t reduction,
7699 float *token_workspace,
7700 size_t token_workspace_bytes,
7701 void *gqa_workspace,
7702 size_t gqa_workspace_bytes,
7703 int route_num_heads,
7704 int route_num_kv_heads,
7705 int route_head_dim,
7706 int route_query_tokens,
7707 int route_min_kv_tokens,
7708 int route_workers,
7709 int route_query_tile_size,
7710 int route_concurrent_query_tiles)
7711{
7712 if (route_num_heads <= 0 || route_num_kv_heads <= 0 ||
7713 route_num_heads % route_num_kv_heads != 0 || route_head_dim <= 0 ||
7714 route_query_tokens <= 0 || route_min_kv_tokens < route_query_tokens ||
7715 route_workers < route_num_kv_heads ||
7716 route_workers % route_num_kv_heads != 0 ||
7717 route_query_tile_size < 16 || route_query_tile_size > 128 ||
7718 route_query_tile_size % 16 != 0 ||
7719 route_concurrent_query_tiles <= 0 ||
7720 route_concurrent_query_tiles > 4) {
7722 }
7723 ck_threadpool_t *pool = ck_threadpool_global();
7724 const int active = pool ? ck_threadpool_n_threads(pool) : 1;
7725 const int kv_tokens = past_tokens + q_tokens;
7727 num_heads == route_num_heads && num_kv_heads == route_num_kv_heads &&
7728 head_dim == route_head_dim && aligned_head_dim == route_head_dim &&
7729 q_tokens == route_query_tokens && kv_tokens >= route_min_kv_tokens &&
7730 active == route_workers) {
7732 q, k_cache, v_cache, output, num_heads, num_kv_heads,
7733 q_tokens, past_tokens, cache_capacity, head_dim, aligned_head_dim,
7734 route_query_tile_size, route_concurrent_query_tiles,
7735 gqa_workspace, gqa_workspace_bytes);
7736 }
7738 q, k_cache, v_cache, output, num_heads, num_kv_heads,
7739 q_tokens, past_tokens, cache_capacity, head_dim, aligned_head_dim,
7740 reduction, token_workspace, token_workspace_bytes);
7741}
7742
7744 const float *q,
7745 const uint16_t *k_cache,
7746 const uint16_t *v_cache,
7747 float *output,
7748 int num_heads,
7749 int num_kv_heads,
7750 int q_tokens,
7751 int past_tokens,
7752 int cache_capacity,
7753 int head_dim,
7754 int aligned_head_dim,
7756{
7757 if (!q || !k_cache || !v_cache || !output || num_heads <= 0 ||
7758 num_kv_heads <= 0 || q_tokens < CK_GGML_FA_TILE_Q || past_tokens < 0 ||
7759 past_tokens + q_tokens > cache_capacity || head_dim <= 0 ||
7760 aligned_head_dim < head_dim) {
7762 }
7764 q, k_cache, v_cache, output, num_heads, num_kv_heads, q_tokens,
7765 past_tokens, cache_capacity, head_dim, aligned_head_dim, 0, 0.0f,
7766 (size_t) q_tokens * (size_t) aligned_head_dim,
7767 (size_t) q_tokens * (size_t) aligned_head_dim, schedule);
7768}
7769
7771 const float *q,
7772 const uint16_t *k_cache,
7773 const uint16_t *v_cache,
7774 float *output,
7775 int num_heads,
7776 int num_kv_heads,
7777 int q_tokens,
7778 int past_tokens,
7779 int cache_capacity,
7780 int head_dim,
7781 int aligned_head_dim,
7782 ck_attention_reduction_t reduction,
7783 float *token_workspace,
7784 size_t token_workspace_bytes)
7785{
7786 if (!q || !k_cache || !v_cache || !output ||
7787 num_heads <= 0 || num_kv_heads <= 0 || q_tokens <= 0 ||
7788 past_tokens < 0 || past_tokens + q_tokens > cache_capacity ||
7789 head_dim <= 0 || aligned_head_dim < head_dim) {
7791 }
7792
7794 q_tokens < CK_GGML_FA_TILE_Q) {
7796 }
7797
7800 q, k_cache, v_cache, output, num_heads, num_kv_heads, q_tokens,
7801 past_tokens, cache_capacity, head_dim, aligned_head_dim, 0, 0.0f,
7802 (size_t) q_tokens * (size_t) aligned_head_dim,
7803 (size_t) q_tokens * (size_t) aligned_head_dim,
7805 }
7806
7809 reduction != CK_ATTN_REDUCTION_FP32_ONLINE) {
7811 }
7812
7813 const size_t token_elems = (size_t) num_heads * (size_t) aligned_head_dim;
7814 if (token_elems > SIZE_MAX / (2 * sizeof(float))) {
7816 }
7817 const size_t required_bytes = 2 * token_elems * sizeof(float);
7818 if (!token_workspace || token_workspace_bytes < required_bytes) {
7820 }
7821 float *q_token = token_workspace;
7822 float *out_token = token_workspace + token_elems;
7823
7825 for (int t = 0; t < q_tokens; ++t) {
7826 for (int h = 0; h < num_heads; ++h) {
7827 const float *src = q +
7828 ((size_t) h * (size_t) q_tokens + (size_t) t) * (size_t) aligned_head_dim;
7829 memcpy(q_token + (size_t) h * (size_t) aligned_head_dim,
7830 src,
7831 (size_t) aligned_head_dim * sizeof(float));
7832 }
7833
7835 q_token, k_cache, v_cache, out_token,
7836 num_heads, num_kv_heads, past_tokens + t + 1, cache_capacity,
7837 head_dim, aligned_head_dim, reduction);
7838 if (status != CK_ATTENTION_STATUS_OK) {
7839 break;
7840 }
7841
7842 for (int h = 0; h < num_heads; ++h) {
7843 float *dst = output +
7844 ((size_t) h * (size_t) q_tokens + (size_t) t) * (size_t) aligned_head_dim;
7845 memcpy(dst,
7846 out_token + (size_t) h * (size_t) aligned_head_dim,
7847 (size_t) aligned_head_dim * sizeof(float));
7848 }
7849 }
7850
7851 return status;
7852}
7853
7855 const float *q,
7856 const uint16_t *k_cache,
7857 const uint16_t *v_cache,
7858 float *output,
7859 int num_heads,
7860 int num_kv_heads,
7861 int q_tokens,
7862 int past_tokens,
7863 int cache_capacity,
7864 int head_dim,
7865 int aligned_head_dim,
7866 int sliding_window,
7867 ck_attention_reduction_t reduction,
7868 float *token_workspace,
7869 size_t token_workspace_bytes,
7870 const int *segment_lengths,
7871 int num_segments)
7872{
7873 if (!q || !k_cache || !v_cache || !output || num_heads <= 0 ||
7874 num_kv_heads <= 0 || q_tokens <= 0 || past_tokens < 0 ||
7875 cache_capacity <= 0 || past_tokens > cache_capacity ||
7876 q_tokens > cache_capacity - past_tokens || head_dim <= 0 ||
7877 aligned_head_dim < head_dim || sliding_window < 0) {
7879 }
7880 int one_segment = q_tokens;
7881 if (!segment_lengths && num_segments == 0) {
7882 segment_lengths = &one_segment;
7883 num_segments = 1;
7884 }
7885 if (!segment_lengths || num_segments <= 0 ||
7886 (num_segments > q_tokens && num_segments > 3)) {
7888 }
7889 int planned_rows = 0;
7890 for (int s = 0; s < num_segments; ++s) {
7891 if (segment_lengths[s] < 0 || segment_lengths[s] > q_tokens - planned_rows) {
7893 }
7894 planned_rows += segment_lengths[s];
7895 }
7896 if (planned_rows != q_tokens) return CK_ATTENTION_STATUS_INVALID_ARGUMENT;
7897 if ((size_t) num_heads > SIZE_MAX / (size_t) aligned_head_dim) {
7899 }
7900 const size_t token_elems = (size_t) num_heads * (size_t) aligned_head_dim;
7901 if (token_elems > SIZE_MAX / (2 * sizeof(float)) || !token_workspace ||
7902 token_workspace_bytes < 2 * token_elems * sizeof(float)) {
7904 }
7905 float *q_token = token_workspace;
7906 float *out_token = token_workspace + token_elems;
7907 int row_offset = 0;
7908 for (int s = 0; s < num_segments; ++s) {
7909 const int rows = segment_lengths[s];
7910 if (rows == 0) continue;
7911 if ((reduction == CK_ATTN_REDUCTION_F16_FLASH_AUTO_QTILE64 ||
7913 rows >= CK_GGML_FA_TILE_Q &&
7914 (sliding_window == 0 || past_tokens + row_offset + rows <= sliding_window)) {
7916 q + (size_t) row_offset * (size_t) aligned_head_dim,
7917 k_cache, v_cache,
7918 output + (size_t) row_offset * (size_t) aligned_head_dim,
7919 num_heads, num_kv_heads, rows, past_tokens + row_offset,
7920 cache_capacity, head_dim, aligned_head_dim, 0, 1.0f,
7921 (size_t) q_tokens * (size_t) aligned_head_dim,
7922 (size_t) q_tokens * (size_t) aligned_head_dim,
7924 if (status != CK_ATTENTION_STATUS_OK) return status;
7925 row_offset += rows;
7926 continue;
7927 }
7928 const ck_attention_reduction_t selected =
7933 for (int local_row = 0; local_row < rows; ++local_row) {
7934 const int t = row_offset + local_row;
7935 for (int h = 0; h < num_heads; ++h) {
7936 const float *src = q +
7937 ((size_t) h * (size_t) q_tokens + (size_t) t) *
7938 (size_t) aligned_head_dim;
7939 memcpy(q_token + (size_t) h * (size_t) aligned_head_dim, src,
7940 (size_t) aligned_head_dim * sizeof(float));
7941 }
7943 q_token, k_cache, v_cache, out_token, num_heads, num_kv_heads,
7944 past_tokens + t + 1, cache_capacity, head_dim, aligned_head_dim,
7945 sliding_window, selected);
7946 if (status != CK_ATTENTION_STATUS_OK) {
7947 return status;
7948 }
7949 for (int h = 0; h < num_heads; ++h) {
7950 float *dst = output +
7951 ((size_t) h * (size_t) q_tokens + (size_t) t) *
7952 (size_t) aligned_head_dim;
7953 memcpy(dst, out_token + (size_t) h * (size_t) aligned_head_dim,
7954 (size_t) aligned_head_dim * sizeof(float));
7955 }
7956 }
7957 row_offset += rows;
7958 }
7960}
7961
7963 const float *q, const uint16_t *k_cache, const uint16_t *v_cache,
7964 float *output, int num_heads, int num_kv_heads, int q_tokens,
7965 int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim,
7966 ck_attention_reduction_t reduction, float *token_workspace,
7967 size_t token_workspace_bytes)
7968{
7970 q, k_cache, v_cache, output, num_heads, num_kv_heads, q_tokens,
7971 past_tokens, cache_capacity, head_dim, aligned_head_dim, 0, reduction,
7972 token_workspace, token_workspace_bytes, NULL, 0);
7973}
7974
7976 const float *q, const uint16_t *k_cache, const uint16_t *v_cache,
7977 float *output, int num_heads, int num_kv_heads, int q_tokens,
7978 int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim,
7979 int sliding_window, ck_attention_reduction_t reduction,
7980 float *token_workspace, size_t token_workspace_bytes)
7981{
7983 q, k_cache, v_cache, output, num_heads, num_kv_heads, q_tokens,
7984 past_tokens, cache_capacity, head_dim, aligned_head_dim, sliding_window,
7985 reduction, token_workspace, token_workspace_bytes, NULL, 0);
7986}
7987
7989 const float *q, const uint16_t *k_cache, const uint16_t *v_cache,
7990 float *output, int num_heads, int num_kv_heads, int q_tokens,
7991 int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim,
7992 int sliding_window, ck_attention_reduction_t reduction,
7993 float *token_workspace, size_t token_workspace_bytes,
7994 const int *segment_lengths, int num_segments)
7995{
7997 q, k_cache, v_cache, output, num_heads, num_kv_heads, q_tokens,
7998 past_tokens, cache_capacity, head_dim, aligned_head_dim, sliding_window,
7999 reduction, token_workspace, token_workspace_bytes,
8000 segment_lengths, num_segments);
8001}
8002
8004 const float *q, const uint16_t *k_cache, const uint16_t *v_cache,
8005 float *output, int num_heads, int num_kv_heads, int q_tokens,
8006 int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim,
8007 ck_attention_reduction_t reduction, float *token_workspace,
8008 size_t token_workspace_bytes, const int *segment_lengths, int num_segments)
8009{
8011 q, k_cache, v_cache, output, num_heads, num_kv_heads, q_tokens,
8012 past_tokens, cache_capacity, head_dim, aligned_head_dim, 0,
8013 reduction, token_workspace, token_workspace_bytes,
8014 segment_lengths, num_segments);
8015}
8016
8018 const float *q,
8019 const uint16_t *k_cache,
8020 const uint16_t *v_cache,
8021 float *output,
8022 int num_heads,
8023 int num_kv_heads,
8024 int q_tokens,
8025 int past_tokens,
8026 int cache_capacity,
8027 int head_dim,
8028 int aligned_head_dim,
8029 ck_attention_reduction_t reduction,
8030 float *token_workspace,
8031 size_t token_workspace_bytes,
8032 const int *segment_lengths,
8033 int num_segments,
8035{
8036 if (!q || !k_cache || !v_cache || !output || !segment_lengths ||
8037 num_segments <= 0 || num_heads <= 0 || num_kv_heads <= 0 ||
8038 q_tokens <= 0 || past_tokens < 0 || past_tokens + q_tokens > cache_capacity ||
8039 head_dim <= 0 || aligned_head_dim < head_dim) {
8041 }
8042 int planned_rows = 0;
8043 for (int s = 0; s < num_segments; ++s) {
8044 if (segment_lengths[s] < 0 || segment_lengths[s] > q_tokens - planned_rows) {
8046 }
8047 planned_rows += segment_lengths[s];
8048 }
8049 if (planned_rows != q_tokens) return CK_ATTENTION_STATUS_INVALID_ARGUMENT;
8050
8051 const size_t head_stride = (size_t) q_tokens * (size_t) aligned_head_dim;
8052 const size_t token_elems = (size_t) num_heads * (size_t) aligned_head_dim;
8053 if (!token_workspace || token_workspace_bytes < 2 * token_elems * sizeof(float)) {
8055 }
8056
8057 int row_offset = 0;
8058 for (int s = 0; s < num_segments; ++s) {
8059 const int rows = segment_lengths[s];
8060 if (rows == 0) continue;
8062 rows >= CK_GGML_FA_TILE_Q) {
8063 ck_attention_f16_prefill_qtile64_args_t args = {
8064 .q = q + (size_t) row_offset * (size_t) aligned_head_dim,
8065 .k_cache = k_cache,
8066 .v_cache = v_cache,
8067 .output = output + (size_t) row_offset * (size_t) aligned_head_dim,
8068 .num_heads = num_heads,
8069 .num_kv_heads = num_kv_heads,
8070 .q_tokens = rows,
8071 .past_tokens = past_tokens + row_offset,
8072 .cache_capacity = cache_capacity,
8073 .head_dim = head_dim,
8074 .aligned_head_dim = aligned_head_dim,
8075 .cache_is_bf16 = 0,
8076 .schedule = schedule,
8077 .q_head_stride = head_stride,
8078 .output_head_stride = head_stride,
8079 };
8080 ck_threadpool_t *pool = ck_threadpool_global();
8081 int active = pool ? ck_threadpool_n_threads(pool) : 1;
8082 const int query_tiles =
8083 (rows + CK_GGML_FA_TILE_Q - 1) / CK_GGML_FA_TILE_Q;
8084 const int available_jobs =
8086 ? num_kv_heads
8087 : num_heads * query_tiles;
8088 if (active > available_jobs) active = available_jobs;
8089 if (pool && active > 1 && ck_threadpool_thread_id(pool) <= 0) {
8091 pool, active, ck_attention_f16_prefill_qtile64_work, &args);
8092 } else {
8094 }
8095 } else {
8096 float *q_token = token_workspace;
8097 float *out_token = token_workspace + token_elems;
8098 ck_attention_reduction_t selected = reduction;
8101 }
8102 for (int t = 0; t < rows; ++t) {
8103 for (int h = 0; h < num_heads; ++h) {
8104 memcpy(
8105 q_token + (size_t) h * (size_t) aligned_head_dim,
8106 q + (size_t) h * head_stride +
8107 (size_t) (row_offset + t) * (size_t) aligned_head_dim,
8108 (size_t) aligned_head_dim * sizeof(float));
8109 }
8110 ck_attention_status_t status =
8112 q_token, k_cache, v_cache, out_token,
8113 num_heads, num_kv_heads, past_tokens + row_offset + t + 1,
8114 cache_capacity, head_dim, aligned_head_dim, selected);
8115 if (status != CK_ATTENTION_STATUS_OK) return status;
8116 for (int h = 0; h < num_heads; ++h) {
8117 memcpy(
8118 output + (size_t) h * head_stride +
8119 (size_t) (row_offset + t) * (size_t) aligned_head_dim,
8120 out_token + (size_t) h * (size_t) aligned_head_dim,
8121 (size_t) aligned_head_dim * sizeof(float));
8122 }
8123 }
8124 }
8125 row_offset += rows;
8126 }
8128}
8129
8131 const float *q,
8132 const uint16_t *k_cache,
8133 const uint16_t *v_cache,
8134 float *output,
8135 int num_heads,
8136 int num_kv_heads,
8137 int q_tokens,
8138 int past_tokens,
8139 int cache_capacity,
8140 int head_dim,
8141 int aligned_head_dim,
8142 ck_attention_reduction_t reduction,
8143 float *token_workspace,
8144 size_t token_workspace_bytes,
8145 const int *segment_lengths,
8146 int num_segments)
8147{
8149 q, k_cache, v_cache, output, num_heads, num_kv_heads, q_tokens,
8150 past_tokens, cache_capacity, head_dim, aligned_head_dim, reduction,
8151 token_workspace, token_workspace_bytes, segment_lengths, num_segments,
8153}
8154
8156 const float *q,
8157 const uint16_t *k_cache,
8158 const uint16_t *v_cache,
8159 float *output,
8160 int num_heads,
8161 int num_kv_heads,
8162 int q_tokens,
8163 int past_tokens,
8164 int cache_capacity,
8165 int head_dim,
8166 int aligned_head_dim,
8167 ck_attention_reduction_t reduction)
8168{
8170 q_tokens >= CK_GGML_FA_TILE_Q) {
8172 q, k_cache, v_cache, output, num_heads, num_kv_heads, q_tokens,
8173 past_tokens, cache_capacity, head_dim, aligned_head_dim, reduction,
8174 NULL, 0);
8175 }
8176
8177 if (num_heads <= 0 || aligned_head_dim <= 0 ||
8178 (size_t) num_heads > SIZE_MAX / (size_t) aligned_head_dim) {
8180 }
8181 const size_t token_elements = (size_t) num_heads * (size_t) aligned_head_dim;
8182 if (token_elements > SIZE_MAX / (2 * sizeof(float))) {
8184 }
8185 const size_t workspace_bytes = 2 * token_elements * sizeof(float);
8186 float *workspace = (float *) malloc(workspace_bytes);
8187 if (!workspace) {
8189 }
8190 const ck_attention_status_t status =
8192 q, k_cache, v_cache, output, num_heads, num_kv_heads, q_tokens,
8193 past_tokens, cache_capacity, head_dim, aligned_head_dim, reduction,
8194 workspace, workspace_bytes);
8195 free(workspace);
8196 return status;
8197}
8198
8199#if defined(__AVX512F__)
8200typedef void (*ck_mkl_gemm_bf16_fn)(
8201 const char *, const char *, const int *, const int *, const int *,
8202 const float *, const uint16_t *, const int *, const uint16_t *, const int *,
8203 const float *, float *, const int *);
8204typedef void (*ck_mkl_cblas_sgemm_fn)(
8205 int, int, int, int, int, int, float,
8206 const float *, int, const float *, int, float, float *, int);
8207typedef void (*ck_mkl_cblas_sgemm_batch_fn)(
8208 int, const int *, const int *, const int *, const int *, const int *,
8209 const float *, const float **, const int *, const float **, const int *,
8210 const float *, float **, const int *, int, const int *);
8211typedef __m512 (*ck_sleef_expf16_fn)(__m512);
8212
8213static ck_mkl_gemm_bf16_fn ck_pytorch_bf16_gemm = NULL;
8214static ck_mkl_cblas_sgemm_fn ck_pytorch_sgemm = NULL;
8215static ck_mkl_cblas_sgemm_batch_fn ck_pytorch_sgemm_batch = NULL;
8216static ck_sleef_expf16_fn ck_pytorch_attention_expf16 = NULL;
8217static void *ck_pytorch_mkl_handle = NULL;
8218static void *ck_pytorch_attention_sleef_handle = NULL;
8219static pthread_once_t ck_pytorch_attention_once = PTHREAD_ONCE_INIT;
8220
8221static void ck_bind_pytorch_attention_primitives(void)
8222{
8223 const char *mkl_library = getenv("CK_MKL_LIBRARY");
8224 if (mkl_library && *mkl_library) {
8225 ck_pytorch_mkl_handle = dlopen(mkl_library, RTLD_NOW | RTLD_LOCAL);
8226 if (ck_pytorch_mkl_handle) {
8227 ck_pytorch_bf16_gemm = (ck_mkl_gemm_bf16_fn)dlsym(
8228 ck_pytorch_mkl_handle, "gemm_bf16bf16f32");
8229 ck_pytorch_sgemm = (ck_mkl_cblas_sgemm_fn)dlsym(
8230 ck_pytorch_mkl_handle, "cblas_sgemm");
8231 ck_pytorch_sgemm_batch = (ck_mkl_cblas_sgemm_batch_fn)dlsym(
8232 ck_pytorch_mkl_handle, "cblas_sgemm_batch");
8233 }
8234 } else {
8235 ck_pytorch_bf16_gemm =
8236 (ck_mkl_gemm_bf16_fn)dlsym(RTLD_DEFAULT, "gemm_bf16bf16f32");
8237 ck_pytorch_sgemm =
8238 (ck_mkl_cblas_sgemm_fn)dlsym(RTLD_DEFAULT, "cblas_sgemm");
8239 ck_pytorch_sgemm_batch =
8240 (ck_mkl_cblas_sgemm_batch_fn)dlsym(RTLD_DEFAULT, "cblas_sgemm_batch");
8241 if (!ck_pytorch_bf16_gemm || !ck_pytorch_sgemm ||
8242 !ck_pytorch_sgemm_batch) {
8243 mkl_library = "libmkl_rt.so.2";
8244 ck_pytorch_mkl_handle = dlopen(mkl_library, RTLD_NOW | RTLD_LOCAL);
8245 if (ck_pytorch_mkl_handle) {
8246 ck_pytorch_bf16_gemm = (ck_mkl_gemm_bf16_fn)dlsym(
8247 ck_pytorch_mkl_handle, "gemm_bf16bf16f32");
8248 ck_pytorch_sgemm = (ck_mkl_cblas_sgemm_fn)dlsym(
8249 ck_pytorch_mkl_handle, "cblas_sgemm");
8250 ck_pytorch_sgemm_batch = (ck_mkl_cblas_sgemm_batch_fn)dlsym(
8251 ck_pytorch_mkl_handle, "cblas_sgemm_batch");
8252 }
8253 }
8254 }
8255
8256 const char *sleef_library = getenv("CK_SLEEF_LIBRARY");
8257 if (sleef_library && *sleef_library) {
8258 ck_pytorch_attention_sleef_handle =
8259 dlopen(sleef_library, RTLD_NOW | RTLD_LOCAL);
8260 if (ck_pytorch_attention_sleef_handle) {
8261 ck_pytorch_attention_expf16 = (ck_sleef_expf16_fn)dlsym(
8262 ck_pytorch_attention_sleef_handle, "Sleef_expf16_u10");
8263 }
8264 } else {
8265 ck_pytorch_attention_expf16 =
8266 (ck_sleef_expf16_fn)dlsym(RTLD_DEFAULT, "Sleef_expf16_u10");
8267 }
8268}
8269
8270static float ck_pytorch_scale_max_f32(float *scores, int count, float scale)
8271{
8272 const __m512 scale_v = _mm512_set1_ps(scale);
8273 __m512 maximum_v = _mm512_set1_ps(-INFINITY);
8274 int i = 0;
8275 for (; i + 16 <= count; i += 16) {
8276 __m512 values = _mm512_mul_ps(_mm512_loadu_ps(scores + i), scale_v);
8277 maximum_v = _mm512_max_ps(maximum_v, values);
8278 _mm512_storeu_ps(scores + i, values);
8279 }
8280 float maximum = _mm512_reduce_max_ps(maximum_v);
8281 for (; i < count; ++i) {
8282 scores[i] *= scale;
8283 maximum = fmaxf(maximum, scores[i]);
8284 }
8285 return maximum;
8286}
8287
8288static float ck_pytorch_reduce_add_f32x16(__m512 values)
8289{
8290 __m512 shuffled = _mm512_shuffle_f32x4(values, values, 0x4e);
8291 values = _mm512_add_ps(values, shuffled);
8292 shuffled = _mm512_shuffle_f32x4(values, values, 0xb1);
8293 values = _mm512_add_ps(values, shuffled);
8294 shuffled = _mm512_shuffle_ps(values, values, 0x4e);
8295 values = _mm512_add_ps(values, shuffled);
8296 shuffled = _mm512_shuffle_ps(values, values, 0xb1);
8297 values = _mm512_add_ps(values, shuffled);
8298 return _mm512_cvtss_f32(values);
8299}
8300
8301static inline __m512 ck_pytorch_flash_fexp_u20_f32x16(__m512 values)
8302{
8303 const __m512 c0 = _mm512_set1_ps(0.00010703434948458272f);
8304 const __m512 c1 = _mm512_set1_ps(0.30354260500649682f);
8305 const __m512 c2 = _mm512_set1_ps(-0.22433836478672356f);
8306 const __m512 c3 = _mm512_set1_ps(-0.079204240219773236f);
8307 const __m512 log2e = _mm512_castsi512_ps(_mm512_set1_epi32(0x3fb8aa3b));
8308 const __m512 exponent_scale = _mm512_set1_ps(8388608.0f);
8309 const __m512 exponent_bias = _mm512_set1_ps(8388608.0f * 127.0f);
8310 const __m512 min_input =
8311 _mm512_castsi512_ps(_mm512_set1_epi32(0xc2aeac50));
8312 const __m512 max_input =
8313 _mm512_castsi512_ps(_mm512_set1_epi32(0x42b17218));
8314
8315 const __mmask16 below_min =
8316 _mm512_cmp_ps_mask(values, min_input, _CMP_LT_OS);
8317 const __mmask16 above_max =
8318 _mm512_cmp_ps_mask(values, max_input, _CMP_GT_OS);
8319 __m512 base2 = _mm512_mul_ps(values, log2e);
8320 const __m512 fractional = _mm512_sub_ps(base2, _mm512_floor_ps(base2));
8321 __m512 correction = _mm512_fmadd_ps(fractional, c3, c2);
8322 correction = _mm512_fmadd_ps(fractional, correction, c1);
8323 correction = _mm512_fmadd_ps(fractional, correction, c0);
8324 base2 = _mm512_sub_ps(base2, correction);
8325 const __m512 encoded =
8326 _mm512_fmadd_ps(exponent_scale, base2, exponent_bias);
8327 __m512i result = _mm512_cvttps_epi32(encoded);
8328 result = _mm512_mask_mov_epi32(result, below_min, _mm512_setzero_epi32());
8329 result = _mm512_mask_mov_epi32(
8330 result, above_max, _mm512_set1_epi32(0x7f800000));
8331 return _mm512_castsi512_ps(result);
8332}
8333
8334static float ck_pytorch_exp_sum_bf16(
8335 float *scores, uint16_t *probabilities, int count, float maximum)
8336{
8337 const __m512 maximum_v = _mm512_set1_ps(maximum);
8338 __m512 sum_lo_v = _mm512_setzero_ps();
8339 __m512 sum_hi_v = _mm512_setzero_ps();
8340 __m512 sum_tail_v = _mm512_setzero_ps();
8341 int i = 0;
8342 for (; i + 32 <= count; i += 32) {
8343 __m512 values_lo = _mm512_sub_ps(
8344 _mm512_loadu_ps(scores + i), maximum_v);
8345 __m512 values_hi = _mm512_sub_ps(
8346 _mm512_loadu_ps(scores + i + 16), maximum_v);
8347 values_lo = ck_pytorch_flash_fexp_u20_f32x16(values_lo);
8348 values_hi = ck_pytorch_flash_fexp_u20_f32x16(values_hi);
8349 sum_lo_v = _mm512_add_ps(sum_lo_v, values_lo);
8350 sum_hi_v = _mm512_add_ps(sum_hi_v, values_hi);
8351 float lanes[16] __attribute__((aligned(64)));
8352 _mm512_store_ps(lanes, values_lo);
8353 for (int lane = 0; lane < 16; ++lane) {
8354 probabilities[i + lane] = float_to_bf16(lanes[lane]);
8355 }
8356 _mm512_store_ps(lanes, values_hi);
8357 for (int lane = 0; lane < 16; ++lane) {
8358 probabilities[i + 16 + lane] = float_to_bf16(lanes[lane]);
8359 }
8360 }
8361 for (; i + 16 <= count; i += 16) {
8362 __m512 values = _mm512_sub_ps(_mm512_loadu_ps(scores + i), maximum_v);
8363 values = ck_pytorch_flash_fexp_u20_f32x16(values);
8364 sum_tail_v = _mm512_add_ps(sum_tail_v, values);
8365 float lanes[16] __attribute__((aligned(64)));
8366 _mm512_store_ps(lanes, values);
8367 for (int lane = 0; lane < 16; ++lane) {
8368 probabilities[i + lane] = float_to_bf16(lanes[lane]);
8369 }
8370 }
8371 sum_lo_v = _mm512_add_ps(sum_lo_v, sum_tail_v);
8372 float sum = ck_pytorch_reduce_add_f32x16(
8373 _mm512_add_ps(sum_lo_v, sum_hi_v));
8374 for (; i < count; ++i) {
8375 const float value = expf(scores[i] - maximum);
8376 sum += value;
8377 probabilities[i] = float_to_bf16(value);
8378 }
8379 return sum;
8380}
8381
8382static inline float ck_pytorch_reduce_max_f32x16(__m512 value)
8383{
8384 __m512 shuffled = _mm512_shuffle_f32x4(value, value, 0x4e);
8385 value = _mm512_max_ps(value, shuffled);
8386 shuffled = _mm512_shuffle_f32x4(value, value, 0xb1);
8387 value = _mm512_max_ps(value, shuffled);
8388 shuffled = _mm512_shuffle_ps(value, value, 0x4e);
8389 value = _mm512_max_ps(value, shuffled);
8390 shuffled = _mm512_shuffle_ps(value, value, 0xb1);
8391 value = _mm512_max_ps(value, shuffled);
8392 return _mm512_cvtss_f32(value);
8393}
8394
8395static inline float ck_pytorch_reduce_sum_f32x16(__m512 value)
8396{
8397 __m512 shuffled = _mm512_shuffle_f32x4(value, value, 0x4e);
8398 value = _mm512_add_ps(value, shuffled);
8399 shuffled = _mm512_shuffle_f32x4(value, value, 0xb1);
8400 value = _mm512_add_ps(value, shuffled);
8401 shuffled = _mm512_shuffle_ps(value, value, 0x4e);
8402 value = _mm512_add_ps(value, shuffled);
8403 shuffled = _mm512_shuffle_ps(value, value, 0xb1);
8404 value = _mm512_add_ps(value, shuffled);
8405 return _mm512_cvtss_f32(value);
8406}
8407
8408static float ck_pytorch_softmax_f32(float *scores, int count)
8409{
8410 /* Match the observed ATen CPU dispatch: sub-vector rows use scalar
8411 * exp/reduction, while larger rows use the AVX-512 accumulator and fold
8412 * a masked tail into its reduction tree. */
8413 if (count < 16) {
8414 float maximum = scores[0];
8415 for (int i = 1; i < count; ++i) {
8416 maximum = fmaxf(maximum, scores[i]);
8417 }
8418 float sum = 0.0f;
8419 for (int i = 0; i < count; ++i) {
8420 scores[i] = expf(scores[i] - maximum);
8421 sum += scores[i];
8422 }
8423 const float reciprocal = 1.0f / sum;
8424 for (int i = 0; i < count; ++i) {
8425 scores[i] *= reciprocal;
8426 }
8427 return sum;
8428 }
8429
8430 __m512 maximum_v = _mm512_set1_ps(-INFINITY);
8431 int i = 0;
8432 for (; i + 16 <= count; i += 16) {
8433 maximum_v = _mm512_max_ps(maximum_v, _mm512_loadu_ps(scores + i));
8434 }
8435 if (i < count) {
8436 const __mmask16 tail_mask = (__mmask16)((1u << (count - i)) - 1u);
8437 maximum_v = _mm512_max_ps(
8438 maximum_v,
8439 _mm512_mask_loadu_ps(_mm512_set1_ps(-INFINITY), tail_mask, scores + i));
8440 }
8441 const float maximum = ck_pytorch_reduce_max_f32x16(maximum_v);
8442
8443 const __m512 maximum_broadcast = _mm512_set1_ps(maximum);
8444 __m512 sum_v = _mm512_setzero_ps();
8445 i = 0;
8446 for (; i + 16 <= count; i += 16) {
8447 __m512 values = _mm512_sub_ps(_mm512_loadu_ps(scores + i), maximum_broadcast);
8448 values = ck_pytorch_attention_expf16(values);
8449 _mm512_storeu_ps(scores + i, values);
8450 sum_v = _mm512_add_ps(sum_v, values);
8451 }
8452 if (i < count) {
8453 const __mmask16 tail_mask = (__mmask16)((1u << (count - i)) - 1u);
8454 __m512 values = _mm512_mask_loadu_ps(
8455 _mm512_set1_ps(-INFINITY), tail_mask, scores + i);
8456 values = ck_pytorch_attention_expf16(
8457 _mm512_sub_ps(values, maximum_broadcast));
8458 values = _mm512_maskz_mov_ps(tail_mask, values);
8459 _mm512_mask_storeu_ps(scores + i, tail_mask, values);
8460 sum_v = _mm512_add_ps(sum_v, values);
8461 }
8462 const float sum = ck_pytorch_reduce_sum_f32x16(sum_v);
8463
8464 const float reciprocal = 1.0f / sum;
8465 const __m512 reciprocal_broadcast = _mm512_set1_ps(reciprocal);
8466 i = 0;
8467 for (; i + 16 <= count; i += 16) {
8468 _mm512_storeu_ps(
8469 scores + i,
8470 _mm512_mul_ps(
8471 _mm512_loadu_ps(scores + i), reciprocal_broadcast));
8472 }
8473 if (i < count) {
8474 const __mmask16 tail_mask = (__mmask16)((1u << (count - i)) - 1u);
8475 const __m512 values = _mm512_maskz_loadu_ps(tail_mask, scores + i);
8476 _mm512_mask_storeu_ps(
8477 scores + i, tail_mask,
8478 _mm512_mul_ps(values, reciprocal_broadcast));
8479 }
8480 return sum;
8481}
8482
8483static ck_attention_status_t ck_attention_decode_bf16_pytorch_math_gqa(
8484 const float *q_token,
8485 const uint16_t *k_cache,
8486 const uint16_t *v_cache,
8487 float *out_token,
8488 int num_heads,
8489 int num_kv_heads,
8490 int kv_tokens,
8491 int cache_capacity,
8492 int head_dim,
8493 int aligned_head_dim)
8494{
8495 enum { CBLAS_ROW_MAJOR = 101, CBLAS_NO_TRANS = 111, CBLAS_TRANS = 112 };
8496 if (!ck_pytorch_sgemm_batch || !ck_pytorch_attention_expf16) {
8497 fprintf(stderr,
8498 "CK BF16 PyTorch GQA math provider requires MKL cblas_sgemm_batch "
8499 "and SLEEF Sleef_expf16_u10\n");
8501 }
8502
8503 const size_t matrix_count =
8504 (size_t)num_heads * (size_t)kv_tokens * (size_t)head_dim;
8505 float *k_scaled = (float *)malloc(matrix_count * sizeof(float));
8506 float *v_f32 = (float *)malloc(matrix_count * sizeof(float));
8507 float *scores = (float *)malloc(
8508 (size_t)num_heads * (size_t)kv_tokens * sizeof(float));
8509 float *q_scaled = (float *)malloc(
8510 (size_t)num_heads * (size_t)head_dim * sizeof(float));
8511 float *destination = (float *)malloc(
8512 (size_t)num_heads * (size_t)head_dim * sizeof(float));
8513 const float **q_batch = (const float **)malloc(
8514 (size_t)num_heads * sizeof(*q_batch));
8515 const float **k_batch = (const float **)malloc(
8516 (size_t)num_heads * sizeof(*k_batch));
8517 float **score_batch = (float **)malloc(
8518 (size_t)num_heads * sizeof(*score_batch));
8519 const float **probability_batch = (const float **)malloc(
8520 (size_t)num_heads * sizeof(*probability_batch));
8521 const float **v_batch = (const float **)malloc(
8522 (size_t)num_heads * sizeof(*v_batch));
8523 float **destination_batch = (float **)malloc(
8524 (size_t)num_heads * sizeof(*destination_batch));
8525 if (!k_scaled || !v_f32 || !scores || !q_scaled || !destination ||
8526 !q_batch || !k_batch || !score_batch || !probability_batch ||
8527 !v_batch || !destination_batch) {
8528 free(destination_batch);
8529 free(v_batch);
8530 free(probability_batch);
8531 free(score_batch);
8532 free(k_batch);
8533 free(q_batch);
8534 free(destination);
8535 free(q_scaled);
8536 free(scores);
8537 free(v_f32);
8538 free(k_scaled);
8540 }
8541
8542 /* aten::_scaled_dot_product_attention_math promotes BF16 Q/K/V to FP32,
8543 * applies sqrt(scale) independently to Q and K, then performs FP32 BMM,
8544 * FP32 softmax and FP32 P*V before the single BF16 output store. */
8545 const float split_scale = (float)sqrt(sqrt(1.0 / (double)head_dim));
8546 const size_t cache_head_stride =
8547 (size_t)cache_capacity * (size_t)aligned_head_dim;
8548 const int heads_per_kv = num_heads / num_kv_heads;
8549
8550 for (int h = 0; h < num_heads; ++h) {
8551 const int kv_head = h / heads_per_kv;
8552 const uint16_t *k_head = k_cache + (size_t)kv_head * cache_head_stride;
8553 const uint16_t *v_head = v_cache + (size_t)kv_head * cache_head_stride;
8554 float *k_repeated = k_scaled +
8555 (size_t)h * (size_t)kv_tokens * (size_t)head_dim;
8556 float *v_repeated = v_f32 +
8557 (size_t)h * (size_t)kv_tokens * (size_t)head_dim;
8558 for (int token = 0; token < kv_tokens; ++token) {
8559 for (int d = 0; d < head_dim; ++d) {
8560 const size_t compact = (size_t)token * (size_t)head_dim + (size_t)d;
8561 const size_t cached =
8562 (size_t)token * (size_t)aligned_head_dim + (size_t)d;
8563 k_repeated[compact] = bf16_to_float(k_head[cached]) * split_scale;
8564 v_repeated[compact] = bf16_to_float(v_head[cached]);
8565 }
8566 }
8567
8568 const float *q_head = q_token + (size_t)h * (size_t)aligned_head_dim;
8569 float *q_row = q_scaled + (size_t)h * (size_t)head_dim;
8570 for (int d = 0; d < head_dim; ++d) {
8571 q_row[d] = bf16_to_float(float_to_bf16(q_head[d])) * split_scale;
8572 }
8573 q_batch[h] = q_row;
8574 k_batch[h] = k_repeated;
8575 score_batch[h] = scores + (size_t)h * (size_t)kv_tokens;
8576 probability_batch[h] = score_batch[h];
8577 v_batch[h] = v_repeated;
8578 destination_batch[h] = destination + (size_t)h * (size_t)head_dim;
8579 }
8580
8581 const int group_count = 1;
8582 const int group_size = num_heads;
8583 const float alpha = 1.0f;
8584 const float beta = 0.0f;
8585 int transpose_a = CBLAS_NO_TRANS;
8586 int transpose_b = CBLAS_TRANS;
8587 int m = 1;
8588 int n = kv_tokens;
8589 int k = head_dim;
8590 int lda = head_dim;
8591 int ldb = head_dim;
8592 int ldc = kv_tokens;
8593 ck_pytorch_sgemm_batch(
8594 CBLAS_ROW_MAJOR, &transpose_a, &transpose_b, &m, &n, &k,
8595 &alpha, q_batch, &lda, k_batch, &ldb, &beta, score_batch, &ldc,
8596 group_count, &group_size);
8597
8598 for (int h = 0; h < num_heads; ++h) {
8599 (void)ck_pytorch_softmax_f32(score_batch[h], kv_tokens);
8600 }
8601
8602 transpose_b = CBLAS_NO_TRANS;
8603 n = head_dim;
8604 k = kv_tokens;
8605 lda = kv_tokens;
8606 ldb = head_dim;
8607 ldc = head_dim;
8608 ck_pytorch_sgemm_batch(
8609 CBLAS_ROW_MAJOR, &transpose_a, &transpose_b, &m, &n, &k,
8610 &alpha, probability_batch, &lda, v_batch, &ldb,
8611 &beta, destination_batch, &ldc, group_count, &group_size);
8612
8613 for (int h = 0; h < num_heads; ++h) {
8614 float *out_head = out_token + (size_t)h * (size_t)aligned_head_dim;
8615 const float *source = destination + (size_t)h * (size_t)head_dim;
8616 for (int d = 0; d < head_dim; ++d) {
8617 out_head[d] = bf16_to_float(float_to_bf16(source[d]));
8618 }
8619 for (int d = head_dim; d < aligned_head_dim; ++d) out_head[d] = 0.0f;
8620 }
8621
8622 free(destination_batch);
8623 free(v_batch);
8624 free(probability_batch);
8625 free(score_batch);
8626 free(k_batch);
8627 free(q_batch);
8628 free(destination);
8629 free(q_scaled);
8630 free(scores);
8631 free(v_f32);
8632 free(k_scaled);
8634}
8635
8636static ck_attention_status_t ck_attention_prefill_bf16_pytorch_math_gqa_full(
8637 const float *q,
8638 const uint16_t *k_cache,
8639 const uint16_t *v_cache,
8640 float *output,
8641 int num_heads,
8642 int num_kv_heads,
8643 int q_tokens,
8644 int past_tokens,
8645 int cache_capacity,
8646 int head_dim,
8647 int aligned_head_dim)
8648{
8649 enum { CBLAS_ROW_MAJOR = 101, CBLAS_NO_TRANS = 111, CBLAS_TRANS = 112 };
8650 if (!ck_pytorch_sgemm_batch || !ck_pytorch_attention_expf16) {
8651 fprintf(stderr,
8652 "CK BF16 PyTorch full-matrix provider requires MKL "
8653 "cblas_sgemm_batch and SLEEF Sleef_expf16_u10\n");
8655 }
8656
8657 const int kv_tokens = past_tokens + q_tokens;
8658 const size_t q_matrix_count =
8659 (size_t)num_heads * (size_t)q_tokens * (size_t)head_dim;
8660 const size_t kv_matrix_count =
8661 (size_t)num_heads * (size_t)kv_tokens * (size_t)head_dim;
8662 const size_t score_count =
8663 (size_t)num_heads * (size_t)q_tokens * (size_t)kv_tokens;
8664 float *q_scaled = (float *)malloc(q_matrix_count * sizeof(float));
8665 float *k_scaled = (float *)malloc(kv_matrix_count * sizeof(float));
8666 float *v_f32 = (float *)malloc(kv_matrix_count * sizeof(float));
8667 float *scores = (float *)malloc(score_count * sizeof(float));
8668 float *destination = (float *)malloc(q_matrix_count * sizeof(float));
8669 const float **q_batch =
8670 (const float **)malloc((size_t)num_heads * sizeof(*q_batch));
8671 const float **k_batch =
8672 (const float **)malloc((size_t)num_heads * sizeof(*k_batch));
8673 float **score_batch =
8674 (float **)malloc((size_t)num_heads * sizeof(*score_batch));
8675 const float **probability_batch =
8676 (const float **)malloc((size_t)num_heads * sizeof(*probability_batch));
8677 const float **v_batch =
8678 (const float **)malloc((size_t)num_heads * sizeof(*v_batch));
8679 float **destination_batch =
8680 (float **)malloc((size_t)num_heads * sizeof(*destination_batch));
8681 if (!q_scaled || !k_scaled || !v_f32 || !scores || !destination ||
8682 !q_batch || !k_batch || !score_batch || !probability_batch ||
8683 !v_batch || !destination_batch) {
8684 free(destination_batch);
8685 free(v_batch);
8686 free(probability_batch);
8687 free(score_batch);
8688 free(k_batch);
8689 free(q_batch);
8690 free(destination);
8691 free(scores);
8692 free(v_f32);
8693 free(k_scaled);
8694 free(q_scaled);
8696 }
8697
8698 const float split_scale = (float)sqrt(sqrt(1.0 / (double)head_dim));
8699 const size_t cache_head_stride =
8700 (size_t)cache_capacity * (size_t)aligned_head_dim;
8701 const int heads_per_kv = num_heads / num_kv_heads;
8702 for (int h = 0; h < num_heads; ++h) {
8703 const int kv_head = h / heads_per_kv;
8704 const uint16_t *k_head =
8705 k_cache + (size_t)kv_head * cache_head_stride;
8706 const uint16_t *v_head =
8707 v_cache + (size_t)kv_head * cache_head_stride;
8708 float *q_head =
8709 q_scaled + (size_t)h * (size_t)q_tokens * (size_t)head_dim;
8710 float *k_head_f32 =
8711 k_scaled + (size_t)h * (size_t)kv_tokens * (size_t)head_dim;
8712 float *v_head_f32 =
8713 v_f32 + (size_t)h * (size_t)kv_tokens * (size_t)head_dim;
8714 for (int token = 0; token < q_tokens; ++token) {
8715 const float *q_row = q +
8716 ((size_t)h * (size_t)q_tokens + (size_t)token) *
8717 (size_t)aligned_head_dim;
8718 float *q_row_f32 =
8719 q_head + (size_t)token * (size_t)head_dim;
8720 for (int d = 0; d < head_dim; ++d) {
8721 q_row_f32[d] =
8722 bf16_to_float(float_to_bf16(q_row[d])) * split_scale;
8723 }
8724 }
8725 for (int token = 0; token < kv_tokens; ++token) {
8726 const uint16_t *k_row =
8727 k_head + (size_t)token * (size_t)aligned_head_dim;
8728 const uint16_t *v_row =
8729 v_head + (size_t)token * (size_t)aligned_head_dim;
8730 float *k_row_f32 =
8731 k_head_f32 + (size_t)token * (size_t)head_dim;
8732 float *v_row_f32 =
8733 v_head_f32 + (size_t)token * (size_t)head_dim;
8734 for (int d = 0; d < head_dim; ++d) {
8735 k_row_f32[d] = bf16_to_float(k_row[d]) * split_scale;
8736 v_row_f32[d] = bf16_to_float(v_row[d]);
8737 }
8738 }
8739 q_batch[h] = q_head;
8740 k_batch[h] = k_head_f32;
8741 score_batch[h] =
8742 scores + (size_t)h * (size_t)q_tokens * (size_t)kv_tokens;
8743 probability_batch[h] = score_batch[h];
8744 v_batch[h] = v_head_f32;
8745 destination_batch[h] =
8746 destination + (size_t)h * (size_t)q_tokens * (size_t)head_dim;
8747 }
8748
8749 const int group_count = 1;
8750 const int group_size = num_heads;
8751 const float alpha = 1.0f;
8752 const float beta = 0.0f;
8753 int transpose_a = CBLAS_NO_TRANS;
8754 int transpose_b = CBLAS_TRANS;
8755 int m = q_tokens;
8756 int n = kv_tokens;
8757 int k = head_dim;
8758 int lda = head_dim;
8759 int ldb = head_dim;
8760 int ldc = kv_tokens;
8761 ck_pytorch_sgemm_batch(
8762 CBLAS_ROW_MAJOR, &transpose_a, &transpose_b, &m, &n, &k,
8763 &alpha, q_batch, &lda, k_batch, &ldb, &beta, score_batch, &ldc,
8764 group_count, &group_size);
8765
8766 /* PyTorch math SDPA reduces each causal row across the complete key
8767 * width. Masked future entries remain -inf and therefore contribute
8768 * zero to the same vector reduction tree as the valid prefix. */
8769 for (int h = 0; h < num_heads; ++h) {
8770 float *score_head = score_batch[h];
8771 for (int token = 0; token < q_tokens; ++token) {
8772 float *score_row =
8773 score_head + (size_t)token * (size_t)kv_tokens;
8774 const int valid = past_tokens + token + 1;
8775 for (int key_token = valid; key_token < kv_tokens; ++key_token) {
8776 score_row[key_token] = -INFINITY;
8777 }
8778 (void)ck_pytorch_softmax_f32(score_row, kv_tokens);
8779 }
8780 }
8781
8782 transpose_b = CBLAS_NO_TRANS;
8783 n = head_dim;
8784 k = kv_tokens;
8785 lda = kv_tokens;
8786 ldb = head_dim;
8787 ldc = head_dim;
8788 ck_pytorch_sgemm_batch(
8789 CBLAS_ROW_MAJOR, &transpose_a, &transpose_b, &m, &n, &k,
8790 &alpha, probability_batch, &lda, v_batch, &ldb,
8791 &beta, destination_batch, &ldc, group_count, &group_size);
8792
8793 for (int h = 0; h < num_heads; ++h) {
8794 const float *source =
8795 destination + (size_t)h * (size_t)q_tokens * (size_t)head_dim;
8796 for (int token = 0; token < q_tokens; ++token) {
8797 float *out_row = output +
8798 ((size_t)h * (size_t)q_tokens + (size_t)token) *
8799 (size_t)aligned_head_dim;
8800 const float *source_row =
8801 source + (size_t)token * (size_t)head_dim;
8802 for (int d = 0; d < head_dim; ++d) {
8803 out_row[d] =
8804 bf16_to_float(float_to_bf16(source_row[d]));
8805 }
8806 for (int d = head_dim; d < aligned_head_dim; ++d) {
8807 out_row[d] = 0.0f;
8808 }
8809 }
8810 }
8811
8812 free(destination_batch);
8813 free(v_batch);
8814 free(probability_batch);
8815 free(score_batch);
8816 free(k_batch);
8817 free(q_batch);
8818 free(destination);
8819 free(scores);
8820 free(v_f32);
8821 free(k_scaled);
8822 free(q_scaled);
8824}
8825
8826static ck_attention_status_t ck_attention_decode_bf16_pytorch_cpu_flash_masked(
8827 const float *q_token,
8828 const uint16_t *k_cache,
8829 const uint16_t *v_cache,
8830 float *out_token,
8831 const float *selected_indices,
8832 int selection_width,
8833 int num_heads,
8834 int num_kv_heads,
8835 int kv_tokens,
8836 int cache_capacity,
8837 int head_dim,
8838 int aligned_head_dim)
8839{
8840 enum { CK_PYTORCH_KV_SPLIT = 512, CK_PYTORCH_MAX_HEAD_DIM = 512 };
8841 if (head_dim > CK_PYTORCH_MAX_HEAD_DIM || aligned_head_dim > CK_PYTORCH_MAX_HEAD_DIM) {
8843 }
8844 pthread_once(&ck_pytorch_attention_once, ck_bind_pytorch_attention_primitives);
8845 if (!ck_pytorch_bf16_gemm || !ck_pytorch_attention_expf16) {
8846 fprintf(stderr,
8847 "CK BF16 PyTorch SDPA provider requires MKL gemm_bf16bf16f32 "
8848 "and SLEEF Sleef_expf16_u10; set CK_MKL_LIBRARY and "
8849 "CK_SLEEF_LIBRARY\n");
8851 }
8852
8853 const float scale = 1.0f / sqrtf((float)head_dim);
8854 const size_t head_stride = (size_t)cache_capacity * (size_t)aligned_head_dim;
8855 for (int h = 0; h < num_heads; ++h) {
8856 const int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
8857 const float *q_head = q_token + (size_t)h * (size_t)aligned_head_dim;
8858 const uint16_t *k_head = k_cache + (size_t)kv_head * head_stride;
8859 const uint16_t *v_head = v_cache + (size_t)kv_head * head_stride;
8860 float *out_head = out_token + (size_t)h * (size_t)aligned_head_dim;
8861 uint16_t q_bf16[CK_PYTORCH_MAX_HEAD_DIM] __attribute__((aligned(64)));
8862 uint16_t probabilities[CK_PYTORCH_KV_SPLIT] __attribute__((aligned(64)));
8863 float scores[CK_PYTORCH_KV_SPLIT] __attribute__((aligned(64)));
8864 float destination[CK_PYTORCH_MAX_HEAD_DIM] __attribute__((aligned(64)));
8865 for (int d = 0; d < head_dim; ++d) q_bf16[d] = float_to_bf16(q_head[d]);
8866
8867 float running_max = -INFINITY;
8868 float running_sum = 0.0f;
8869 int has_destination = 0;
8870 for (int start = 0; start < kv_tokens; start += CK_PYTORCH_KV_SPLIT) {
8871 const int block = kv_tokens - start < CK_PYTORCH_KV_SPLIT
8872 ? kv_tokens - start : CK_PYTORCH_KV_SPLIT;
8873 const char transpose = 'T';
8874 const char no_transpose = 'N';
8875 const int one = 1;
8876 const float one_f = 1.0f;
8877 const float zero_f = 0.0f;
8878 ck_pytorch_bf16_gemm(
8879 &transpose, &no_transpose, &block, &one, &head_dim,
8880 &one_f, k_head + (size_t)start * (size_t)aligned_head_dim,
8881 &aligned_head_dim, q_bf16, &aligned_head_dim,
8882 &zero_f, scores, &block);
8883
8884 if (selected_indices) {
8885 int selected = 0;
8886 int cursor = 0;
8887 while (cursor < selection_width &&
8888 (int)selected_indices[cursor] < start) {
8889 ++cursor;
8890 }
8891 for (int token = 0; token < block; ++token) {
8892 const int absolute_token = start + token;
8893 while (cursor < selection_width &&
8894 selected_indices[cursor] >= 0.0f &&
8895 (int)selected_indices[cursor] < absolute_token) {
8896 ++cursor;
8897 }
8898 if (cursor < selection_width &&
8899 (int)selected_indices[cursor] == absolute_token) {
8900 ++selected;
8901 ++cursor;
8902 } else {
8903 scores[token] = -INFINITY;
8904 }
8905 }
8906 if (selected == 0 && !has_destination) continue;
8907 }
8908
8909 const float block_max = ck_pytorch_scale_max_f32(scores, block, scale);
8910 const float maximum = running_max > block_max ? running_max : block_max;
8911 const float block_sum = ck_pytorch_exp_sum_bf16(
8912 scores, probabilities, block, maximum);
8913 const float previous_scale = expf(running_max - maximum);
8914 running_sum = block_sum + previous_scale * running_sum;
8915 if (has_destination) {
8916 const __m512 previous_scale_v = _mm512_set1_ps(previous_scale);
8917 int d = 0;
8918 for (; d + 16 <= head_dim; d += 16) {
8919 _mm512_storeu_ps(
8920 destination + d,
8921 _mm512_mul_ps(_mm512_loadu_ps(destination + d), previous_scale_v));
8922 }
8923 for (; d < head_dim; ++d) destination[d] *= previous_scale;
8924 }
8925 const float beta = has_destination ? 1.0f : 0.0f;
8926 ck_pytorch_bf16_gemm(
8927 &no_transpose, &no_transpose, &head_dim, &one, &block,
8928 &one_f, v_head + (size_t)start * (size_t)aligned_head_dim,
8929 &aligned_head_dim, probabilities, &block,
8930 &beta, destination, &head_dim);
8931 running_max = maximum;
8932 has_destination = 1;
8933 }
8934
8935 if (!has_destination) {
8937 }
8938 const float reciprocal = running_sum != 0.0f ? 1.0f / running_sum : 1.0f;
8939 for (int d = 0; d < head_dim; ++d) {
8940 out_head[d] = bf16_to_float(float_to_bf16(destination[d] * reciprocal));
8941 }
8942 for (int d = head_dim; d < aligned_head_dim; ++d) out_head[d] = 0.0f;
8943 }
8945}
8946
8947static ck_attention_status_t ck_attention_decode_bf16_pytorch_cpu_flash(
8948 const float *q_token,
8949 const uint16_t *k_cache,
8950 const uint16_t *v_cache,
8951 float *out_token,
8952 int num_heads,
8953 int num_kv_heads,
8954 int kv_tokens,
8955 int cache_capacity,
8956 int head_dim,
8957 int aligned_head_dim)
8958{
8959 return ck_attention_decode_bf16_pytorch_cpu_flash_masked(
8960 q_token, k_cache, v_cache, out_token, NULL, 0,
8961 num_heads, num_kv_heads, kv_tokens, cache_capacity,
8962 head_dim, aligned_head_dim);
8963}
8964#endif
8965
8967 const float *query,
8968 const uint16_t *key_cache,
8969 const uint16_t *value_cache,
8970 const float *selected_indices,
8971 float *output,
8972 float *score_scratch,
8973 int rows,
8974 int query_heads,
8975 int kv_heads,
8976 int head_dim,
8977 int selection_width,
8978 int context_length,
8979 int position)
8980{
8981 (void)score_scratch;
8982 if (!query || !key_cache || !value_cache || !selected_indices || !output ||
8983 rows <= 0 || query_heads <= 0 || kv_heads <= 0 || head_dim <= 0 ||
8984 selection_width <= 0 || context_length <= 0 || position < 0 ||
8985 position + rows > context_length || query_heads % kv_heads != 0) {
8986 fprintf(stderr, "CK sparse BF16 CPU-flash attention: invalid contract\n");
8987 abort();
8988 }
8989
8990#if defined(__AVX512F__)
8991 for (int row = 0; row < rows; ++row) {
8992 const int visible_tokens = position + row + 1;
8993 const ck_attention_status_t status =
8994 ck_attention_decode_bf16_pytorch_cpu_flash_masked(
8995 query + (size_t)row * (size_t)query_heads * (size_t)head_dim,
8996 key_cache, value_cache,
8997 output + (size_t)row * (size_t)query_heads * (size_t)head_dim,
8998 selected_indices + (size_t)row * (size_t)selection_width,
8999 selection_width, query_heads, kv_heads, visible_tokens,
9000 context_length, head_dim, head_dim);
9001 if (status != CK_ATTENTION_STATUS_OK) {
9002 fprintf(stderr,
9003 "CK sparse BF16 CPU-flash attention unavailable (status=%d)\n",
9004 (int)status);
9005 abort();
9006 }
9007 }
9008#else
9009 fprintf(stderr,
9010 "CK sparse BF16 CPU-flash attention requires AVX-512F; "
9011 "no numerically different fallback is permitted\n");
9012 abort();
9013#endif
9014}
9015
9017{
9018#if defined(__AVX512F__)
9020#else
9021 return 0;
9022#endif
9023}
9024
9026{
9027#if defined(__AVX512F__)
9028 pthread_once(&ck_pytorch_attention_once, ck_bind_pytorch_attention_primitives);
9029 return ck_pytorch_sgemm_batch != NULL &&
9030 ck_pytorch_attention_expf16 != NULL;
9031#else
9032 return 1;
9033#endif
9034}
9035
9037 size_t left, size_t right, const char *label)
9038{
9039 if (left != 0 && right > SIZE_MAX / left) {
9040 fprintf(stderr,
9041 "HARD KERNEL CONTRACT FAULT: Muse eager attention %s "
9042 "size overflow\n", label);
9043 abort();
9044 }
9045 return left * right;
9046}
9047
9048/* Keep the full softmax width while bounding the number of query rows held
9049 * in planner-owned scratch. The first tile retains the eager arithmetic for
9050 * prompts of at most this many tokens. */
9051#define CK_MUSE_PREFILL_QUERY_TILE 1024
9052
9054 const float *q,
9055 const float *k,
9056 const float *v,
9057 float *output,
9058 int num_heads,
9059 int num_kv_heads,
9060 int query_tokens,
9061 int kv_tokens,
9062 int kv_stride_tokens,
9063 int head_dim,
9064 int aligned_head_dim,
9065 int sliding_window,
9066 float *scores,
9067 size_t scores_bytes,
9068 uint16_t *key_rows,
9069 size_t key_rows_bytes,
9070 uint16_t *value_columns,
9071 size_t value_columns_bytes,
9072 uint16_t *gemm_rows,
9073 size_t gemm_rows_bytes,
9074 uint16_t *gemm_scores,
9075 size_t gemm_scores_bytes)
9076{
9077#if defined(__AVX512F__) && defined(USE_ONEDNN)
9078 if (!q || !k || !v || !output || num_heads <= 0 || num_kv_heads <= 0 ||
9079 query_tokens <= 0 || kv_tokens < query_tokens ||
9080 kv_stride_tokens < kv_tokens || head_dim <= 0 ||
9081 aligned_head_dim != head_dim || num_heads % num_kv_heads != 0) {
9082 fprintf(stderr, "HARD KERNEL CONTRACT FAULT: invalid Muse eager attention dimensions\n");
9083 abort();
9084 }
9085
9086 const int workspace_rows = query_tokens < CK_MUSE_PREFILL_QUERY_TILE
9087 ? query_tokens : CK_MUSE_PREFILL_QUERY_TILE;
9088 const size_t score_count = ck_muse_attention_checked_product(
9089 (size_t)workspace_rows, (size_t)kv_tokens, "score");
9090 const size_t kv_matrix_count = ck_muse_attention_checked_product(
9091 (size_t)kv_tokens, (size_t)head_dim, "KV matrix");
9092 const size_t row_count = ck_muse_attention_checked_product(
9093 (size_t)workspace_rows, (size_t)head_dim, "row matrix");
9094 if (score_count > SIZE_MAX / sizeof(*scores) ||
9095 kv_matrix_count > SIZE_MAX / sizeof(*key_rows) ||
9096 row_count > SIZE_MAX / sizeof(*gemm_rows)) {
9097 fprintf(stderr,
9098 "HARD KERNEL CONTRACT FAULT: Muse eager attention workspace "
9099 "byte size overflow\n");
9100 abort();
9101 }
9102 if (!scores || scores_bytes < score_count * sizeof(*scores) ||
9103 !key_rows || key_rows_bytes < kv_matrix_count * sizeof(*key_rows) ||
9104 !value_columns ||
9105 value_columns_bytes < kv_matrix_count * sizeof(*value_columns) ||
9106 !gemm_rows || gemm_rows_bytes < row_count * sizeof(*gemm_rows) ||
9107 !gemm_scores ||
9108 gemm_scores_bytes < score_count * sizeof(*gemm_scores)) {
9109 fprintf(stderr,
9110 "HARD KERNEL CONTRACT FAULT: Muse eager attention received "
9111 "an undersized planner-owned workspace\n");
9112 abort();
9113 }
9114 pthread_once(&ck_pytorch_attention_once, ck_bind_pytorch_attention_primitives);
9115 if (!ck_pytorch_attention_expf16) {
9116 fprintf(stderr,
9117 "HARD KERNEL CONTRACT FAULT: Muse eager attention requires "
9118 "Sleef_expf16_u10 from CK_SLEEF_LIBRARY\n");
9119 abort();
9120 }
9121
9122 const float scale = 1.0f / sqrtf((float)head_dim);
9123 const int heads_per_kv = num_heads / num_kv_heads;
9124 const int query_position_offset = kv_tokens - query_tokens;
9125 const size_t query_head_stride = ck_muse_attention_checked_product(
9126 (size_t)query_tokens, (size_t)aligned_head_dim, "query stride");
9127 const size_t kv_head_stride = ck_muse_attention_checked_product(
9128 (size_t)kv_stride_tokens, (size_t)aligned_head_dim, "KV stride");
9130 (size_t)num_heads, query_head_stride, "query tensor");
9132 (size_t)num_kv_heads, kv_head_stride, "KV tensor");
9133
9134 for (int head = 0; head < num_heads; ++head) {
9135 const int kv_head = head / heads_per_kv;
9136 const float *key_head = k + (size_t)kv_head * kv_head_stride;
9137 const float *value_head = v + (size_t)kv_head * kv_head_stride;
9138 for (int token = 0; token < kv_tokens; ++token) {
9139 const float *key_row = key_head +
9140 (size_t)token * (size_t)aligned_head_dim;
9141 const float *value_row = value_head +
9142 (size_t)token * (size_t)aligned_head_dim;
9143 for (int dim = 0; dim < head_dim; ++dim) {
9144 key_rows[(size_t)token * (size_t)head_dim + (size_t)dim] =
9145 float_to_bf16(key_row[dim]);
9146 value_columns[(size_t)dim * (size_t)kv_tokens + (size_t)token] =
9147 float_to_bf16(value_row[dim]);
9148 }
9149 }
9150
9151 const float *query_head = q + (size_t)head * query_head_stride;
9152 float *output_head = output + (size_t)head * query_head_stride;
9153 for (int query_start = 0; query_start < query_tokens;
9154 query_start += CK_MUSE_PREFILL_QUERY_TILE) {
9155 const int tile_rows = query_tokens - query_start < CK_MUSE_PREFILL_QUERY_TILE
9156 ? query_tokens - query_start : CK_MUSE_PREFILL_QUERY_TILE;
9158 query_head + (size_t)query_start * (size_t)head_dim,
9159 key_rows, NULL, scores,
9160 tile_rows, kv_tokens, head_dim,
9161 gemm_rows, gemm_rows_bytes, gemm_scores, gemm_scores_bytes,
9162 NULL, 0);
9163
9164 for (int query_token = 0; query_token < tile_rows; ++query_token) {
9165 const int query_position = query_position_offset + query_start + query_token;
9166 const int visible_end = query_position + 1;
9167 int visible_begin = 0;
9168 if (sliding_window > 0 && visible_end > sliding_window) {
9169 visible_begin = visible_end - sliding_window;
9170 }
9171 float *score_row = scores +
9172 (size_t)query_token * (size_t)kv_tokens;
9173 for (int token = 0; token < kv_tokens; ++token) {
9174 score_row[token] = bf16_to_float(
9175 float_to_bf16(score_row[token] * scale));
9176 if (token < visible_begin || token >= visible_end) {
9177 score_row[token] = -INFINITY;
9178 }
9179 }
9180 (void)ck_pytorch_softmax_f32(score_row, kv_tokens);
9181 }
9183 scores, value_columns, NULL,
9184 output_head + (size_t)query_start * (size_t)head_dim,
9185 tile_rows, head_dim, kv_tokens,
9186 gemm_scores, gemm_scores_bytes, gemm_rows, gemm_rows_bytes,
9187 NULL, 0);
9188 }
9189 }
9190#else
9191 (void)q; (void)k; (void)v; (void)output; (void)num_heads;
9192 (void)num_kv_heads; (void)query_tokens; (void)kv_tokens;
9193 (void)kv_stride_tokens; (void)head_dim; (void)aligned_head_dim;
9194 (void)sliding_window;
9195 (void)scores; (void)scores_bytes; (void)key_rows; (void)key_rows_bytes;
9196 (void)value_columns; (void)value_columns_bytes;
9197 (void)gemm_rows; (void)gemm_rows_bytes;
9198 (void)gemm_scores; (void)gemm_scores_bytes;
9199 fprintf(stderr,
9200 "HARD KERNEL CONTRACT FAULT: Muse eager attention requires "
9201 "AVX-512 and USE_ONEDNN=1\n");
9202 abort();
9203#endif
9204}
9205
9207 const float *q, const float *k, const float *v, float *output,
9208 int num_heads, int num_kv_heads, int num_tokens, int head_dim,
9209 int aligned_head_dim, int kv_stride_tokens,
9210 float *scores, size_t scores_bytes,
9211 uint16_t *key_rows, size_t key_rows_bytes,
9212 uint16_t *value_columns, size_t value_columns_bytes,
9213 uint16_t *gemm_rows, size_t gemm_rows_bytes,
9214 uint16_t *gemm_scores, size_t gemm_scores_bytes)
9215{
9217 q, k, v, output, num_heads, num_kv_heads, num_tokens, num_tokens,
9218 kv_stride_tokens, head_dim, aligned_head_dim, 0,
9219 scores, scores_bytes, key_rows, key_rows_bytes,
9220 value_columns, value_columns_bytes, gemm_rows, gemm_rows_bytes,
9221 gemm_scores, gemm_scores_bytes);
9222}
9223
9225 const float *q, const float *k, const float *v, float *output,
9226 int num_heads, int num_kv_heads, int num_tokens, int head_dim,
9227 int aligned_head_dim, int kv_stride_tokens, int sliding_window,
9228 float *scores, size_t scores_bytes,
9229 uint16_t *key_rows, size_t key_rows_bytes,
9230 uint16_t *value_columns, size_t value_columns_bytes,
9231 uint16_t *gemm_rows, size_t gemm_rows_bytes,
9232 uint16_t *gemm_scores, size_t gemm_scores_bytes)
9233{
9235 q, k, v, output, num_heads, num_kv_heads, num_tokens, num_tokens,
9236 kv_stride_tokens, head_dim, aligned_head_dim, sliding_window,
9237 scores, scores_bytes, key_rows, key_rows_bytes,
9238 value_columns, value_columns_bytes, gemm_rows, gemm_rows_bytes,
9239 gemm_scores, gemm_scores_bytes);
9240}
9241
9243 const float *q_token, const float *k_cache, const float *v_cache,
9244 float *out_token, int num_heads, int num_kv_heads, int kv_tokens,
9245 int cache_capacity, int head_dim, int aligned_head_dim,
9246 float *scores, size_t scores_bytes,
9247 uint16_t *key_rows, size_t key_rows_bytes,
9248 uint16_t *value_columns, size_t value_columns_bytes,
9249 uint16_t *gemm_rows, size_t gemm_rows_bytes,
9250 uint16_t *gemm_scores, size_t gemm_scores_bytes)
9251{
9253 q_token, k_cache, v_cache, out_token, num_heads, num_kv_heads,
9254 1, kv_tokens, cache_capacity, head_dim, aligned_head_dim, 0,
9255 scores, scores_bytes, key_rows, key_rows_bytes,
9256 value_columns, value_columns_bytes, gemm_rows, gemm_rows_bytes,
9257 gemm_scores, gemm_scores_bytes);
9258}
9259
9261 const float *q_token, const float *k_cache, const float *v_cache,
9262 float *out_token, int num_heads, int num_kv_heads, int kv_tokens,
9263 int cache_capacity, int head_dim, int aligned_head_dim, int sliding_window,
9264 float *scores, size_t scores_bytes,
9265 uint16_t *key_rows, size_t key_rows_bytes,
9266 uint16_t *value_columns, size_t value_columns_bytes,
9267 uint16_t *gemm_rows, size_t gemm_rows_bytes,
9268 uint16_t *gemm_scores, size_t gemm_scores_bytes)
9269{
9271 q_token, k_cache, v_cache, out_token, num_heads, num_kv_heads,
9272 1, kv_tokens, cache_capacity, head_dim, aligned_head_dim,
9273 sliding_window, scores, scores_bytes, key_rows, key_rows_bytes,
9274 value_columns, value_columns_bytes, gemm_rows, gemm_rows_bytes,
9275 gemm_scores, gemm_scores_bytes);
9276}
9277
9279 const float *q_token,
9280 const uint16_t *k_cache,
9281 const uint16_t *v_cache,
9282 float *out_token,
9283 int num_heads,
9284 int num_kv_heads,
9285 int kv_tokens,
9286 int cache_capacity,
9287 int head_dim,
9288 int aligned_head_dim,
9289 ck_attention_reduction_t reduction)
9290{
9291 if (!q_token || !k_cache || !v_cache || !out_token ||
9292 num_heads <= 0 || num_kv_heads <= 0 || kv_tokens <= 0 ||
9293 cache_capacity <= 0 || kv_tokens > cache_capacity ||
9294 head_dim <= 0 || aligned_head_dim < head_dim ||
9295 num_heads % num_kv_heads != 0) {
9297 }
9298 if (reduction != CK_ATTN_REDUCTION_BF16_PYTORCH_SDPA) {
9300 }
9301
9302#if defined(__AVX512F__)
9303 pthread_once(&ck_pytorch_attention_once, ck_bind_pytorch_attention_primitives);
9304 return ck_attention_decode_bf16_pytorch_math_gqa(
9305 q_token, k_cache, v_cache, out_token, num_heads, num_kv_heads,
9306 kv_tokens, cache_capacity, head_dim, aligned_head_dim);
9307#else
9308 const float scale = 1.0f / sqrtf((float)head_dim);
9309 const size_t head_stride = (size_t)cache_capacity * (size_t)aligned_head_dim;
9310 for (int h = 0; h < num_heads; ++h) {
9311 const int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
9312 const float *q_head = q_token + (size_t)h * (size_t)aligned_head_dim;
9313 const uint16_t *k_head = k_cache + (size_t)kv_head * head_stride;
9314 const uint16_t *v_head = v_cache + (size_t)kv_head * head_stride;
9315 float *out_head = out_token + (size_t)h * (size_t)aligned_head_dim;
9316
9317 float maximum = -INFINITY;
9318 for (int token = 0; token < kv_tokens; ++token) {
9319 const uint16_t *k_row = k_head + (size_t)token * (size_t)aligned_head_dim;
9320 float dot = 0.0f;
9321 for (int d = 0; d < head_dim; ++d) {
9322 dot = fmaf(q_head[d], bf16_to_float(k_row[d]), dot);
9323 }
9324 const float score = dot * scale;
9325 if (score > maximum) maximum = score;
9326 }
9327
9328 for (int d = 0; d < aligned_head_dim; ++d) out_head[d] = 0.0f;
9329 float denominator = 0.0f;
9330 for (int token = 0; token < kv_tokens; ++token) {
9331 const uint16_t *k_row = k_head + (size_t)token * (size_t)aligned_head_dim;
9332 const uint16_t *v_row = v_head + (size_t)token * (size_t)aligned_head_dim;
9333 float dot = 0.0f;
9334 for (int d = 0; d < head_dim; ++d) {
9335 dot = fmaf(q_head[d], bf16_to_float(k_row[d]), dot);
9336 }
9337 const float probability = expf(dot * scale - maximum);
9338 denominator += probability;
9339 for (int d = 0; d < head_dim; ++d) {
9340 out_head[d] = fmaf(probability, bf16_to_float(v_row[d]), out_head[d]);
9341 }
9342 }
9343 const float inverse = denominator > 0.0f ? 1.0f / denominator : 0.0f;
9344 for (int d = 0; d < head_dim; ++d) {
9345 out_head[d] = bf16_to_float(float_to_bf16(out_head[d] * inverse));
9346 }
9347 }
9349#endif
9350}
9351
9353 const float *q,
9354 const uint16_t *k_cache,
9355 const uint16_t *v_cache,
9356 float *output,
9357 int num_heads,
9358 int num_kv_heads,
9359 int q_tokens,
9360 int past_tokens,
9361 int cache_capacity,
9362 int head_dim,
9363 int aligned_head_dim,
9364 ck_attention_reduction_t reduction,
9365 float *token_workspace,
9366 size_t token_workspace_bytes)
9367{
9368 if (!q || !k_cache || !v_cache || !output ||
9369 num_heads <= 0 || num_kv_heads <= 0 || q_tokens <= 0 ||
9370 past_tokens < 0 || past_tokens + q_tokens > cache_capacity ||
9371 head_dim <= 0 || aligned_head_dim < head_dim) {
9373 }
9374 if (reduction != CK_ATTN_REDUCTION_BF16_PYTORCH_SDPA) {
9376 }
9377
9378 if (q_tokens >= CK_GGML_FA_TILE_Q) {
9379 ck_attention_f16_prefill_qtile64_args_t args = {
9380 .q = q, .k_cache = k_cache, .v_cache = v_cache, .output = output,
9381 .num_heads = num_heads, .num_kv_heads = num_kv_heads,
9382 .q_tokens = q_tokens, .past_tokens = past_tokens,
9383 .cache_capacity = cache_capacity, .head_dim = head_dim,
9384 .aligned_head_dim = aligned_head_dim, .cache_is_bf16 = 1,
9385 };
9386 ck_threadpool_t *pool = ck_threadpool_global();
9387 int active = pool ? ck_threadpool_n_threads(pool) : 1;
9388 if (active > num_kv_heads) active = num_kv_heads;
9389 if (pool && active > 1 && ck_threadpool_thread_id(pool) <= 0) {
9391 } else {
9393 }
9394 const size_t count = (size_t)num_heads * (size_t)q_tokens * (size_t)aligned_head_dim;
9395 for (size_t i = 0; i < count; ++i) {
9396 output[i] = bf16_to_float(float_to_bf16(output[i]));
9397 }
9399 }
9400
9401 const size_t token_elems = (size_t)num_heads * (size_t)aligned_head_dim;
9402 if (token_elems > SIZE_MAX / (2 * sizeof(float)) ||
9403 !token_workspace || token_workspace_bytes < 2 * token_elems * sizeof(float)) {
9405 }
9406 float *q_token = token_workspace;
9407 float *out_token = token_workspace + token_elems;
9409 for (int t = 0; t < q_tokens; ++t) {
9410 for (int h = 0; h < num_heads; ++h) {
9411 memcpy(q_token + (size_t)h * (size_t)aligned_head_dim,
9412 q + ((size_t)h * (size_t)q_tokens + (size_t)t) * (size_t)aligned_head_dim,
9413 (size_t)aligned_head_dim * sizeof(float));
9414 }
9416 q_token, k_cache, v_cache, out_token,
9417 num_heads, num_kv_heads, past_tokens + t + 1, cache_capacity,
9418 head_dim, aligned_head_dim, reduction);
9419 if (status != CK_ATTENTION_STATUS_OK) break;
9420 for (int h = 0; h < num_heads; ++h) {
9421 memcpy(output + ((size_t)h * (size_t)q_tokens + (size_t)t) * (size_t)aligned_head_dim,
9422 out_token + (size_t)h * (size_t)aligned_head_dim,
9423 (size_t)aligned_head_dim * sizeof(float));
9424 }
9425 }
9426 return status;
9427}
9428
9430 const float *q,
9431 const uint16_t *k_cache,
9432 const uint16_t *v_cache,
9433 float *output,
9434 int num_heads,
9435 int num_kv_heads,
9436 int q_tokens,
9437 int past_tokens,
9438 int cache_capacity,
9439 int head_dim,
9440 int aligned_head_dim,
9441 ck_attention_reduction_t reduction)
9442{
9443 if (q_tokens >= CK_GGML_FA_TILE_Q) {
9445 q, k_cache, v_cache, output, num_heads, num_kv_heads, q_tokens,
9446 past_tokens, cache_capacity, head_dim, aligned_head_dim, reduction,
9447 NULL, 0);
9448 }
9449 if (num_heads <= 0 || aligned_head_dim <= 0 ||
9450 (size_t) num_heads > SIZE_MAX / (size_t) aligned_head_dim) {
9452 }
9453 const size_t token_elements = (size_t) num_heads * (size_t) aligned_head_dim;
9454 if (token_elements > SIZE_MAX / (2 * sizeof(float))) {
9456 }
9457 const size_t workspace_bytes = 2 * token_elements * sizeof(float);
9458 float *workspace = (float *) malloc(workspace_bytes);
9459 if (!workspace) return CK_ATTENTION_STATUS_INVALID_ARGUMENT;
9460 const ck_attention_status_t status =
9462 q, k_cache, v_cache, output, num_heads, num_kv_heads, q_tokens,
9463 past_tokens, cache_capacity, head_dim, aligned_head_dim, reduction,
9464 workspace, workspace_bytes);
9465 free(workspace);
9466 return status;
9467}
9468
9470 const float *q,
9471 const uint16_t *k_cache,
9472 const uint16_t *v_cache,
9473 float *output,
9474 int num_heads,
9475 int num_kv_heads,
9476 int q_tokens,
9477 int past_tokens,
9478 int cache_capacity,
9479 int head_dim,
9480 int aligned_head_dim,
9481 ck_attention_reduction_t reduction)
9482{
9483 if (!q || !k_cache || !v_cache || !output ||
9484 num_heads <= 0 || num_kv_heads <= 0 ||
9485 num_heads % num_kv_heads != 0 ||
9486 q_tokens <= 0 || past_tokens < 0 ||
9487 past_tokens + q_tokens > cache_capacity ||
9488 head_dim <= 0 || aligned_head_dim < head_dim) {
9490 }
9491 if (reduction != CK_ATTN_REDUCTION_BF16_PYTORCH_SDPA) {
9493 }
9494
9495#if defined(__AVX512F__)
9496 pthread_once(&ck_pytorch_attention_once, ck_bind_pytorch_attention_primitives);
9497 return ck_attention_prefill_bf16_pytorch_math_gqa_full(
9498 q, k_cache, v_cache, output,
9499 num_heads, num_kv_heads, q_tokens, past_tokens, cache_capacity,
9500 head_dim, aligned_head_dim);
9501#else
9503#endif
9504}
9505
9507 const uint16_t *k_cache,
9508 const uint16_t *v_cache,
9509 float *out_token,
9510 int num_heads,
9511 int num_kv_heads,
9512 int kv_tokens,
9513 int cache_capacity,
9514 int head_dim,
9515 int aligned_head_dim)
9516{
9517 if (!q_token || !k_cache || !v_cache || !out_token) {
9518 return;
9519 }
9520 if (num_heads <= 0 || num_kv_heads <= 0 || kv_tokens <= 0 || cache_capacity <= 0) {
9521 return;
9522 }
9523 if (kv_tokens > cache_capacity || head_dim <= 0 || aligned_head_dim <= 0) {
9524 return;
9525 }
9526
9527 const float scale = 1.0f / sqrtf((float)head_dim);
9528 const size_t head_stride = (size_t)cache_capacity * (size_t)aligned_head_dim;
9529 const size_t scratch_elems = (size_t)kv_tokens * (size_t)aligned_head_dim;
9530 const size_t scratch_bytes = scratch_elems * sizeof(float);
9531 const size_t max_stack_bytes = 1024u * 1024u;
9532
9533 if (scratch_bytes * 2u > max_stack_bytes) {
9534 for (int h = 0; h < num_heads; ++h) {
9535 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
9536 const float *q_head = q_token + (size_t)h * (size_t)aligned_head_dim;
9537 const uint16_t *k_head = k_cache + (size_t)kv_head * head_stride;
9538 const uint16_t *v_head = v_cache + (size_t)kv_head * head_stride;
9539 float *out_head = out_token + (size_t)h * (size_t)aligned_head_dim;
9540
9541 for (int d = 0; d < aligned_head_dim; ++d) {
9542 out_head[d] = 0.0f;
9543 }
9544
9545 float max_score = -INFINITY;
9546 for (int j = 0; j < kv_tokens; ++j) {
9547 const uint16_t *k_vec = k_head + (size_t)j * (size_t)aligned_head_dim;
9548 float dot = 0.0f;
9549 for (int d = 0; d < head_dim; ++d) {
9550 dot += q_head[d] * CK_FP16_TO_FP32(k_vec[d]);
9551 }
9552 const float score = dot * scale;
9553 if (score > max_score) {
9554 max_score = score;
9555 }
9556 }
9557
9558 float sum = 0.0f;
9559 for (int j = 0; j < kv_tokens; ++j) {
9560 const uint16_t *k_vec = k_head + (size_t)j * (size_t)aligned_head_dim;
9561 const uint16_t *v_vec = v_head + (size_t)j * (size_t)aligned_head_dim;
9562 float dot = 0.0f;
9563 for (int d = 0; d < head_dim; ++d) {
9564 dot += q_head[d] * CK_FP16_TO_FP32(k_vec[d]);
9565 }
9566 const float w = expf(dot * scale - max_score);
9567 sum += w;
9568 for (int d = 0; d < head_dim; ++d) {
9569 out_head[d] += w * CK_FP16_TO_FP32(v_vec[d]);
9570 }
9571 }
9572
9573 if (sum > 0.0f) {
9574 const float inv_sum = 1.0f / sum;
9575 for (int d = 0; d < head_dim; ++d) {
9576 out_head[d] *= inv_sum;
9577 }
9578 }
9579 for (int d = head_dim; d < aligned_head_dim; ++d) {
9580 out_head[d] = 0.0f;
9581 }
9582 }
9583 return;
9584 }
9585
9586 float *k_head_fp32 = (float *)alloca(scratch_bytes);
9587 float *v_head_fp32 = (float *)alloca(scratch_bytes);
9588
9589 for (int kv_head = 0; kv_head < num_kv_heads; ++kv_head) {
9590 const uint16_t *k_head = k_cache + (size_t)kv_head * head_stride;
9591 const uint16_t *v_head = v_cache + (size_t)kv_head * head_stride;
9592 const int q_begin = (int)((long long)kv_head * (long long)num_heads / (long long)num_kv_heads);
9593 const int q_end = (int)((long long)(kv_head + 1) * (long long)num_heads / (long long)num_kv_heads);
9594
9595 ck_local_fp16_to_fp32_2d(k_head, k_head_fp32, kv_tokens, aligned_head_dim, aligned_head_dim, aligned_head_dim);
9596 ck_local_fp16_to_fp32_2d(v_head, v_head_fp32, kv_tokens, aligned_head_dim, aligned_head_dim, aligned_head_dim);
9597
9598 for (int h = q_begin; h < q_end; ++h) {
9599 const float *q_head = q_token + (size_t)h * (size_t)aligned_head_dim;
9600 float *out_head = out_token + (size_t)h * (size_t)aligned_head_dim;
9601
9602 attention_flash_decode(out_head,
9603 q_head,
9604 k_head_fp32,
9605 v_head_fp32,
9606 1,
9607 kv_tokens,
9608 1,
9609 aligned_head_dim,
9610 scale);
9611 }
9612 }
9613}
9614
9615/**
9616 * @brief WARNING: This is NOT true flash attention!
9617 *
9618 * This function is named "flash" but implements regular attention with O(n) complexity.
9619 * It's kept for reference and as a fallback.
9620 *
9621 * TRUE flash attention is implemented in attention_flash_true.c
9622 * @test test_kv_cache_attention.py::TestKVCacheAttention::test_regular_decode
9623 * @test test_attention.py::TestAttentionForward::test_regular_decode
9624 *
9625 * Regular attention decode (score-matrix version) for fallback.
9626 *
9627 * After changes: make test
9628 */
9630 const float *k_cache,
9631 const float *v_cache,
9632 float *out_token,
9633 int num_heads,
9634 int num_kv_heads,
9635 int kv_tokens,
9636 int cache_capacity,
9637 int head_dim,
9638 int aligned_head_dim)
9639{
9640 if (!q_token || !k_cache || !v_cache || !out_token) {
9641 return;
9642 }
9643 if (num_heads <= 0 || num_kv_heads <= 0 || kv_tokens <= 0 || cache_capacity <= 0) {
9644 return;
9645 }
9646 if (kv_tokens > cache_capacity) {
9647 return;
9648 }
9649
9650 const int strict = ck_strict_parity_enabled();
9651 const float scale = strict
9653 : 1.0f / sqrtf((float) head_dim);
9654 const size_t head_stride = (size_t)cache_capacity * (size_t)aligned_head_dim;
9655
9656 // Select SIMD implementation based on compile-time CPU features
9657#if defined(__AVX512F__)
9658 #define FLASH_QUERY_IMPL_DECODE attention_flash_query_causal_avx512
9659#elif defined(__AVX2__)
9660 #define FLASH_QUERY_IMPL_DECODE attention_flash_query_causal_avx2
9661#elif defined(__AVX__)
9662 #define FLASH_QUERY_IMPL_DECODE attention_flash_query_causal_avx
9663#else
9664 #define FLASH_QUERY_IMPL_DECODE attention_flash_query_causal
9665#endif
9666
9667#pragma omp parallel for schedule(static) if(num_heads > 1)
9668 for (int h = 0; h < num_heads; ++h) {
9669 int kv_head = (int)((long long)h * (long long)num_kv_heads / (long long)num_heads);
9670 const float *q_vec = q_token + (size_t)h * (size_t)aligned_head_dim;
9671 const float *k_head = k_cache + (size_t)kv_head * head_stride;
9672 const float *v_head = v_cache + (size_t)kv_head * head_stride;
9673 float *out_vec = out_token + (size_t)h * (size_t)aligned_head_dim;
9674
9675 if (strict) {
9677 k_head,
9678 v_head,
9679 kv_tokens,
9680 head_dim,
9681 aligned_head_dim,
9682 scale,
9683 out_vec);
9684 continue;
9685 }
9686
9687 FLASH_QUERY_IMPL_DECODE(q_vec, k_head, v_head,
9688 kv_tokens, head_dim, aligned_head_dim,
9689 scale, out_vec);
9690 }
9691
9692#undef FLASH_QUERY_IMPL_DECODE
9693}
9694
9695// ============================================================================
9696// ATTENTION BACKWARD - Causal, Head-Major, GQA-aware
9697// ============================================================================
9698//
9699// Backward pass for scaled dot-product attention with causal mask.
9700//
9701// Given:
9702// d_output: gradient from the layer above [num_heads, T, head_dim]
9703// q, k, v: saved activations from forward pass
9704// attn_weights: saved softmax output from forward [num_heads, T, T]
9705//
9706// Computes:
9707// d_q: gradient w.r.t. queries [num_heads, T, head_dim]
9708// d_k: gradient w.r.t. keys [num_kv_heads, T, head_dim]
9709// d_v: gradient w.r.t. values [num_kv_heads, T, head_dim]
9710//
9711// Math derivation:
9712// Forward: scores = Q @ K^T / sqrt(d)
9713// weights = causal_softmax(scores)
9714// output = weights @ V
9715//
9716// Backward through V multiply:
9717// d_weights = d_output @ V^T [H, T, T]
9718// d_v = weights^T @ d_output [H_kv, T, d]
9719//
9720// Backward through softmax:
9721// d_scores = softmax_backward(d_weights, weights)
9722//
9723// Backward through Q @ K^T:
9724// d_q = d_scores @ K / sqrt(d) [H, T, d]
9725// d_k = d_scores^T @ Q / sqrt(d) [H_kv, T, d]
9726//
9727// For GQA: multiple query heads share the same KV head, so we accumulate
9728// gradients from all query heads that map to each KV head.
9729//
9730/**
9731 * BF16 attention backward with caller-provided scratch buffers
9732 * @test bf16/test_attention_bf16.py::TestAttentionBF16::test_bf16_backward
9733 *
9734 * Accepts BF16 inputs, converts to FP32, runs FP32 backward.
9735 * Caller provides scratch buffers (no per-call malloc).
9736 *
9737 * After changes: make test
9738 */
9740 const uint16_t *d_output, // [num_heads, T, aligned_head_dim]
9741 float *d_x, // [num_heads, T, aligned_head_dim]
9742 const uint16_t *q, // [num_heads, T, aligned_head_dim]
9743 const uint16_t *k, // [num_kv_heads, T, aligned_head_dim]
9744 const uint16_t *v, // [num_kv_heads, T, aligned_head_dim]
9745 const float *attn_weights, // [num_heads, T, aligned_context_window]
9746 float *d_q, // [num_heads, T, aligned_head_dim] output
9747 float *d_k, // [num_kv_heads, T, aligned_head_dim] output
9748 float *d_v, // [num_kv_heads, T, aligned_head_dim] output
9749 float *d_scores, // [num_heads, T, aligned_context_window] scratch
9750 int num_heads,
9751 int num_kv_heads,
9752 int num_tokens,
9753 int head_dim,
9754 int aligned_head_dim,
9755 int aligned_context_window,
9756 float *scratch_d_output,
9757 float *scratch_q,
9758 float *scratch_k,
9759 float *scratch_v)
9760{
9761 (void)d_x;
9762 const size_t head_elems = (size_t)num_heads * (size_t)num_tokens * (size_t)aligned_head_dim;
9763 const size_t kv_elems = (size_t)num_kv_heads * (size_t)num_tokens * (size_t)aligned_head_dim;
9764
9765 if (!scratch_d_output || !scratch_q || !scratch_k || !scratch_v) return;
9766
9767 convert_bf16_tensor_to_buf(d_output, scratch_d_output, head_elems);
9768 convert_bf16_tensor_to_buf(q, scratch_q, head_elems);
9769 convert_bf16_tensor_to_buf(k, scratch_k, kv_elems);
9770 convert_bf16_tensor_to_buf(v, scratch_v, kv_elems);
9771
9772 attention_backward_causal_head_major_gqa(scratch_d_output, scratch_q, scratch_k, scratch_v,
9773 attn_weights,
9774 d_q, d_k, d_v, d_scores,
9775 num_heads, num_kv_heads,
9776 num_tokens, head_dim,
9777 aligned_head_dim, aligned_context_window);
9778 /* No free - caller owns scratch buffers */
9779}
9780
9781/**
9782 * GQA causal attention backward (score-matrix version)
9783 * @test test_attention_backward.py::TestAttentionBackwardGQA::test_gqa_backward
9784 * @test test_attention_backward.py::TestAttentionBackwardGQA::test_gqa_vs_separate
9785 * @test test_parity.py::test_attention_backward_parity
9786 *
9787 * Computes dQ, dK, dV given dOutput and attention weights.
9788 * Supports grouped-query attention with head broadcasting.
9789 *
9790 * After changes: make test && make llamacpp-parity-full
9791 */
9793 const float *d_output, // [num_heads, T, aligned_head_dim]
9794 const float *q, // [num_heads, T, aligned_head_dim]
9795 const float *k, // [num_kv_heads, T, aligned_head_dim]
9796 const float *v, // [num_kv_heads, T, aligned_head_dim]
9797 const float *attn_weights, // [num_heads, T, aligned_context_window]
9798 float *d_q, // [num_heads, T, aligned_head_dim] output
9799 float *d_k, // [num_kv_heads, T, aligned_head_dim] output
9800 float *d_v, // [num_kv_heads, T, aligned_head_dim] output
9801 float *d_scores, // [num_heads, T, aligned_context_window] scratch
9802 int num_heads,
9803 int num_kv_heads,
9804 int num_tokens,
9805 int head_dim,
9806 int aligned_head_dim,
9807 int aligned_context_window)
9808{
9809 const float scale = 1.0f / sqrtf((float)head_dim);
9810 int T = num_tokens;
9811 int H = num_heads;
9812 int H_kv = num_kv_heads;
9813 int hd = head_dim;
9814 int ad = aligned_head_dim;
9815 int aw = aligned_context_window;
9816
9817 const size_t d_q_elems = (size_t)H * (size_t)T * (size_t)ad;
9818 const size_t kv_elems = (size_t)H_kv * (size_t)T * (size_t)ad;
9819 /* Zero the aligned outputs so padded lanes never leak garbage to downstream GEMMs. */
9820 for (size_t idx = 0; idx < d_q_elems; ++idx) {
9821 d_q[idx] = 0.0f;
9822 }
9823 for (size_t idx = 0; idx < kv_elems; ++idx) {
9824 d_k[idx] = 0.0f;
9825 d_v[idx] = 0.0f;
9826 }
9827
9828 // Process each query head
9829 for (int h = 0; h < H; ++h) {
9830 // Which KV head does this query head use?
9831 int kv_h = (int)((long long)h * (long long)H_kv / (long long)H);
9832
9833 // ----------------------------------------------------------------
9834 // Step 1: d_weights = d_output @ V^T and d_v += weights^T @ d_output
9835 // ----------------------------------------------------------------
9836 // For each query position i, compute d_weights[i, j] for j <= i
9837 // and accumulate d_v[j] contributions
9838
9839 for (int i = 0; i < T; ++i) {
9840 size_t d_out_base = qkv_index(h, i, 0, T, ad);
9841
9842 for (int j = 0; j <= i; ++j) {
9843 size_t v_base = qkv_index(kv_h, j, 0, T, ad);
9844 size_t w_idx = score_index(h, i, j, aw);
9845 float w = attn_weights[w_idx];
9846
9847 // d_weights[h, i, j] = d_output[h, i, :] @ v[kv_h, j, :]^T
9848 float dot = 0.0f;
9849 for (int dd = 0; dd < hd; ++dd) {
9850 dot += d_output[d_out_base + dd] * v[v_base + dd];
9851 }
9852 d_scores[w_idx] = dot;
9853
9854 // d_v[kv_h, j, :] += weights[h, i, j] * d_output[h, i, :]
9855 for (int dd = 0; dd < hd; ++dd) {
9856 d_v[v_base + dd] += w * d_output[d_out_base + dd];
9857 }
9858 }
9859
9860 // Zero out upper triangle of d_scores
9861 for (int j = i + 1; j < T; ++j) {
9862 d_scores[score_index(h, i, j, aw)] = 0.0f;
9863 }
9864 /* Scores scratch uses aligned_context_window, zero the padded columns. */
9865 for (int j = T; j < aw; ++j) {
9866 d_scores[score_index(h, i, j, aw)] = 0.0f;
9867 }
9868 }
9869
9870 // ----------------------------------------------------------------
9871 // Step 2: Backward through softmax (in-place on d_scores for this head)
9872 // ----------------------------------------------------------------
9873 // d_scores = softmax_backward(d_scores, attn_weights)
9874 // Formula: d_score[i,j] = w[i,j] * (d_w[i,j] - sum_k(w[i,k] * d_w[i,k]))
9875
9876 for (int i = 0; i < T; ++i) {
9877 int base = h * aw * aw + i * aw;
9878
9879 // Compute dot product: sum_j w[i,j] * d_w[i,j]
9880 float dot_product = 0.0f;
9881 for (int j = 0; j <= i; ++j) {
9882 float wt = attn_weights[base + j];
9883 float dw = d_scores[base + j];
9884 dot_product += wt * dw;
9885 }
9886
9887 // Apply softmax backward formula
9888 for (int j = 0; j <= i; ++j) {
9889 float wt = attn_weights[base + j];
9890 float dw = d_scores[base + j];
9891 d_scores[base + j] = wt * (dw - dot_product);
9892 }
9893 }
9894
9895 // ----------------------------------------------------------------
9896 // Step 3: d_q = d_scores @ K * scale
9897 // d_k += d_scores^T @ Q * scale
9898 // ----------------------------------------------------------------
9899
9900 for (int i = 0; i < T; ++i) {
9901 size_t d_q_base = qkv_index(h, i, 0, T, ad);
9902 size_t q_base = qkv_index(h, i, 0, T, ad);
9903
9904 // d_q[h, i, :] = sum_j d_scores[h, i, j] * k[kv_h, j, :] * scale
9905 // d_k[kv_h, j, :] += d_scores[h, i, j] * q[h, i, :] * scale
9906 for (int j = 0; j <= i; ++j) {
9907 size_t k_base = qkv_index(kv_h, j, 0, T, ad);
9908 size_t d_k_base = qkv_index(kv_h, j, 0, T, ad);
9909 float ds = d_scores[score_index(h, i, j, aw)] * scale;
9910
9911 for (int dd = 0; dd < hd; ++dd) {
9912 d_q[d_q_base + dd] += ds * k[k_base + dd];
9913 d_k[d_k_base + dd] += ds * q[q_base + dd];
9914 }
9915 }
9916 }
9917 }
9918}
9919
9920/**
9921 * Causal attention backward (non-GQA version)
9922 * @test test_attention_backward.py::TestAttentionBackward::test_backward
9923 * @test test_attention_backward.py::TestAttentionBackward::test_backward_vs_separate
9924 * @test test_parity.py::test_attention_backward_parity
9925 *
9926 * Non-GQA version where num_heads == num_kv_heads.
9927 * Simpler than GQA, no head broadcasting needed.
9928 *
9929 * After changes: make test && make llamacpp-parity-full
9930 */
9932 const float *d_output,
9933 const float *q,
9934 const float *k,
9935 const float *v,
9936 const float *attn_weights,
9937 float *d_q,
9938 float *d_k,
9939 float *d_v,
9940 float *d_scores,
9941 int num_heads,
9942 int num_tokens,
9943 int head_dim,
9944 int aligned_head_dim,
9945 int aligned_context_window)
9946{
9948 d_output, q, k, v, attn_weights,
9949 d_q, d_k, d_v, d_scores,
9950 num_heads, num_heads, // num_kv_heads == num_heads
9951 num_tokens, head_dim, aligned_head_dim, aligned_context_window);
9952}
static size_t qkv_index(int h, int t, int d, int num_tokens, int aligned_head_dim)
ck_attention_status_t attention_forward_causal_head_major_gqa_prefill_segmented_f16cache_sliding_gemma4_workspace(const float *q, const uint16_t *k_cache, const uint16_t *v_cache, float *output, int num_heads, int num_kv_heads, int q_tokens, int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim, int sliding_window, ck_attention_reduction_t reduction, float *token_workspace, size_t token_workspace_bytes, const int *segment_lengths, int num_segments)
static double ck_ggml_vec_soft_max_row(int n, float *y, const float *x, float max)
float ck_attention_pytorch_sdpa_scale_f32(int head_dim)
void attention_forward_causal_head_major_gqa_bf16(const uint16_t *q, const uint16_t *k, const uint16_t *v, float *scores, float *output, int num_heads, int num_kv_heads, int num_tokens, int head_dim, int aligned_head_dim, int aligned_context_window, float *scratch_q, float *scratch_k, float *scratch_v)
ck_attention_status_t attention_forward_causal_head_major_gqa_prefill_append_f16cache_sliding_gemma4_workspace(const float *q, const uint16_t *k_cache, const uint16_t *v_cache, float *output, int num_heads, int num_kv_heads, int q_tokens, int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim, int sliding_window, ck_attention_reduction_t reduction, float *token_workspace, size_t token_workspace_bytes)
void attention_forward_causal_head_major_gqa_exact(const float *q, const float *k, const float *v, float *scores, float *output, int num_heads, int num_kv_heads, int num_tokens, int head_dim, int aligned_head_dim, int aligned_context_window)
#define CK_GGML_FA_TILE_KV
static void attention_forward_decode_head_major_gqa_flash_f16cache_split_partitioned(const float *q_token, const uint16_t *k_cache, const uint16_t *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 split_chunks, int partition_tokens, int kv_start, float attention_scale)
void attention_forward_decode_head_major_gqa_flash_f16cache(const float *q_token, const uint16_t *k_cache, const uint16_t *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)
ck_attention_status_t attention_forward_causal_head_major_gqa_prefill_append_f16cache_qtile64_schedule(const float *q, const uint16_t *k_cache, const uint16_t *v_cache, float *output, int num_heads, int num_kv_heads, int q_tokens, int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim, ck_attention_prefill_schedule_t schedule)
static void ck_attention_vec_dump_exact_query(const float *q_vec, const float *k_head, const float *out_vec, int kv_tokens, int head_dim, int aligned_head_dim, float scale, int layer_id, int head_id, int query_id)
static void ck_attention_query_key_f32_head_work(int ith, int nth, void *opaque)
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_shared_kv_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)
static int ck_attention_vec_dump_next_layer_id(void)
static ck_attention_status_t ck_attention_forward_decode_f16cache_gemma4(const float *q_token, const uint16_t *k_cache, const uint16_t *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, ck_attention_reduction_t reduction)
static void ck_llama_regular_prefill_work(int ith, int nth, void *opaque)
static void attention_query_full_exact_regular(const float *q_vec, const float *k_head, const float *v_cols, int kv_tokens, int head_dim, int aligned_head_dim, float scale, float *score_row, float *out_vec, int layer_id, int head_id, int query_id)
static void ck_attention_query_key_f32_transpose_work(int ith, int nth, void *opaque)
void attention_forward_full_head_major_gqa_tiled336_f16kv_fp32_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)
static float ck_attention_strict_scale_f32(int head_dim)
ck_attention_status_t attention_forward_causal_head_major_gqa_prefill_append_bf16cache_pytorch_contract(const float *q, const uint16_t *k_cache, const uint16_t *v_cache, float *output, int num_heads, int num_kv_heads, int q_tokens, int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim, ck_attention_reduction_t reduction)
static const char ck_attention_vec_dump_magic[8]
static float ck_llama_regular_gemm_f16(const float *probability, const float *value_column, int count)
static void ck_attention_vec_dump_tensor(const char *name, int layer_id, int query_id, const float *data, size_t elem_count)
ck_attention_status_t attention_forward_decode_head_major_gqa_f16cache_regular_noscale(const float *q_token, const uint16_t *k_cache, const uint16_t *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_f16kv_workspace(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, float *rounded_kv, size_t rounded_kv_bytes)
static void attention_flash_query_causal_exact(const float *q_vec, const float *k_head, const float *v_head, int kv_tokens, int head_dim, int aligned_head_dim, float scale, float *out_vec)
void attention_forward_full_head_major_gqa_flash_strided_bf16_storage(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)
static void ck_attention_trace_float(const char *tag, int layer_id, int head_id, float value)
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_decode_head_major_gqa_flash_f16kv(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 attention_forward_query_key_head_major_f32_decode_heads(const float *query, const float *key, const float *value, float *output, float *score_scratch, int num_heads, int query_tokens, int key_tokens, int head_dim, float scale)
static float ck_vec_dot_f32_strict(const float *x, const float *y, int n)
ck_attention_status_t attention_forward_causal_head_major_gqa_prefill_append_f16cache_auto_workspace(const float *q, const uint16_t *k_cache, const uint16_t *v_cache, float *output, int num_heads, int num_kv_heads, int q_tokens, int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim, ck_attention_reduction_t reduction, float *token_workspace, size_t token_workspace_bytes, void *gqa_workspace, size_t gqa_workspace_bytes, int route_num_heads, int route_num_kv_heads, int route_head_dim, int route_query_tokens, int route_min_kv_tokens, int route_workers, int route_query_tile_size, int route_concurrent_query_tiles)
void attention_backward_causal_head_major_gqa_bf16(const uint16_t *d_output, float *d_x, const uint16_t *q, const uint16_t *k, const uint16_t *v, const float *attn_weights, float *d_q, float *d_k, float *d_v, float *d_scores, int num_heads, int num_kv_heads, int num_tokens, int head_dim, int aligned_head_dim, int aligned_context_window, float *scratch_d_output, float *scratch_q, float *scratch_k, float *scratch_v)
static void ck_attention_f16_prefill_gqa_reuse_work(int ith, int nth, void *opaque)
static void ck_vec_scale_f32_inplace(float *x, int n, float scale)
static size_t attention_output_index(int h, int token, int num_heads, int num_tokens, int aligned_head_dim, int token_major)
void attention_forward_full_head_major_gqa_exact_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)
#define CK_GGML_FA_TILE_Q
void attention_forward_causal_head_major(const float *q, const float *k, const float *v, float *scores, float *output, int num_heads, int num_tokens, int head_dim, int aligned_head_dim, int aligned_context_window)
#define CK_NOINLINE
void attention_forward_mixed_visual_chunk_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, int visual_start, int visual_tokens)
void attention_forward_causal_head_major_gqa(const float *q, const float *k, const float *v, float *scores, float *output, int num_heads, int num_kv_heads, int num_tokens, int head_dim, int aligned_head_dim, int aligned_context_window)
static float ck_attention_u16_cache_to_f32(uint16_t value, int cache_is_bf16)
static void ck_attention_flash_query_auto(const float *q_vec, const float *k_head, const float *v_head, int kv_tokens, int head_dim, int aligned_head_dim, float scale, float *out_vec)
ck_attention_status_t attention_forward_decode_head_major_gqa_f16cache_gemma4_contract(const float *q_token, const uint16_t *k_cache, const uint16_t *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, ck_attention_reduction_t reduction)
void attention_forward_full_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)
static float ck_vec_dot_f32x_f32_to_f32_via_f64(const float *x, const float *y, int n)
static float ck_vec_max_f32_contig(const float *x, int n)
static void ck_attention_mad_f16_llama(uint16_t *y, const uint16_t *x, float scale, int n)
static int ck_attention_strict_unfused_f16_enabled(void)
int attention_forward_query_key_head_major_f32_packed_k(const float *query, const float *key, const float *value, float *output, float *score_scratch, float *key_transpose_scratch, int num_heads, int query_tokens, int key_tokens, int head_dim, float scale)
ck_attention_status_t attention_forward_causal_head_major_gqa_prefill_append_bf16cache_pytorch_contract_workspace(const float *q, const uint16_t *k_cache, const uint16_t *v_cache, float *output, int num_heads, int num_kv_heads, int q_tokens, int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim, ck_attention_reduction_t reduction, float *token_workspace, size_t token_workspace_bytes)
static ck_attention_status_t ck_attention_forward_causal_head_major_gqa_prefill_segmented_f16cache_schedule_workspace(const float *q, const uint16_t *k_cache, const uint16_t *v_cache, float *output, int num_heads, int num_kv_heads, int q_tokens, int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim, ck_attention_reduction_t reduction, float *token_workspace, size_t token_workspace_bytes, const int *segment_lengths, int num_segments, ck_attention_prefill_schedule_t schedule)
static int ck_attention_vec_dump_enabled(void)
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)
#define CK_OPTNONE
void attention_forward_causal_head_major_gqa_muse_eager_bf16_storage(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, float *scores, size_t scores_bytes, uint16_t *key_rows, size_t key_rows_bytes, uint16_t *value_columns, size_t value_columns_bytes, uint16_t *gemm_rows, size_t gemm_rows_bytes, uint16_t *gemm_scores, size_t gemm_scores_bytes)
void attention_forward_full_head_major_gqa_flash(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)
void attention_forward_chunk_head_major_gqa_flash_gemma4(const float *q_chunk, const float *k_cache, const float *v_cache, float *out_chunk, int num_heads, int num_kv_heads, int q_tokens, int kv_tokens, int cache_capacity, int head_dim, int aligned_head_dim)
void attention_forward_causal_head_major_exact(const float *q, const float *k, const float *v, float *scores, float *output, int num_heads, int num_tokens, int head_dim, int aligned_head_dim, int aligned_context_window)
#define RTLD_DEFAULT
int attention_forward_query_key_head_major_f32(const float *query, const float *key, const float *value, float *output, float *score_scratch, int num_heads, int query_tokens, int key_tokens, int head_dim, float scale)
static void ck_attention_bf16_sdpa_work(int ith, int nth, void *opaque)
static void ck_round_fp16_buffer(const float *src, float *dst, size_t count)
static void ck_attention_full_tiled_f16kv_fp32_work(int ith, int nth, void *opaque)
void attention_forward_causal_head_major_shared_kv_gemma4(const float *q, float *output, int num_heads, int num_tokens, int head_dim, int aligned_head_dim, int kv_stride_tokens)
void attention_forward_full_head_major_gqa_tiled_f16kv_fp32_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)
ck_attention_status_t attention_forward_causal_head_major_gqa_prefill_append_f16cache_contract(const float *q, const uint16_t *k_cache, const uint16_t *v_cache, float *output, int num_heads, int num_kv_heads, int q_tokens, int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim, ck_attention_reduction_t reduction)
#define FLASH_QUERY_IMPL
static void ck_local_fp16_to_fp32_row(const uint16_t *src, float *dst, int n)
ck_attention_status_t attention_forward_causal_head_major_gqa_prefill_segmented_f16cache_gemma4_workspace(const float *q, const uint16_t *k_cache, const uint16_t *v_cache, float *output, int num_heads, int num_kv_heads, int q_tokens, int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim, ck_attention_reduction_t reduction, float *token_workspace, size_t token_workspace_bytes, const int *segment_lengths, int num_segments)
static int ck_attention_reverse_out_dot_enabled(void)
void attention_forward_full_head_major_gqa_ggml_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)
static float ck_attention_reference_expf(float value)
ck_attention_status_t attention_forward_causal_head_major_gqa_prefill_full_bf16cache_pytorch_contract(const float *q, const uint16_t *k_cache, const uint16_t *v_cache, float *output, int num_heads, int num_kv_heads, int q_tokens, int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim, ck_attention_reduction_t reduction)
static void ck_attention_matmul_f32_accum(float *c, const float *a, const float *b, int m, int k, int n)
void attention_forward_full_head_major_gqa_pytorch_cpu_flash_bf16_storage(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_muse_eager_bf16_storage(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, float *scores, size_t scores_bytes, uint16_t *key_rows, size_t key_rows_bytes, uint16_t *value_columns, size_t value_columns_bytes, uint16_t *gemm_rows, size_t gemm_rows_bytes, uint16_t *gemm_scores, size_t gemm_scores_bytes)
static int ck_attention_ggml_out_graph_enabled(void)
static size_t score_index(int h, int i, int j, int aligned_context_window)
static float ck_round_fp16_scalar(float x)
static int ck_llama_kv_pad_256(int live_tokens, int capacity)
void attention_forward_full_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)
static float ck_attention_dot_f16_unfused_llama(const uint16_t *x, const uint16_t *y, int n)
static void attention_query_full_ggml_regular(const float *q_vec, const float *k_head, const float *v_cols, int kv_tokens, int head_dim, int aligned_head_dim, float scale, float *score_row, float *out_vec, int layer_id, int head_id, int query_id)
void attention_forward_causal_head_major_gqa_llama_regular_strided_sliding_workspace(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, float *scores, size_t scores_bytes, float *value_columns, size_t value_columns_bytes, float *scaled_scores, size_t scaled_scores_bytes)
ck_attention_status_t attention_forward_causal_head_major_gqa_prefill_append_f16cache_gqa_reuse_config(const float *q, const uint16_t *k_cache, const uint16_t *v_cache, float *output, int num_heads, int num_kv_heads, int q_tokens, int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim, int query_tile_size, int concurrent_query_tiles, void *workspace, size_t workspace_bytes)
static void ck_attention_scale_f16_llama(uint16_t *y, float scale, int n)
static void attention_forward_mixed_visual_chunk_head_major_gqa_flash_strided_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 visual_start, int visual_tokens, int output_token_major)
static void ck_attention_bf16_pytorch_flash_work(int ith, int nth, void *opaque)
static void attention_forward_head_major_gqa_flash_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 causal, int round_full_kv_fp16, int output_token_major, float scale)
static int ck_attention_pick_active_threads(const ck_threadpool_t *pool, int total_queries, int num_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)
static float ck_attention_dot_f16_llama(const uint16_t *x, const uint16_t *y, int n)
static int ck_attention_full_bf16_sdpa_tiled(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)
static void ck_attention_llama_regular_query(const float *query, const float *key_head, const float *value_columns, float *output, float *scores, float *scaled_scores, int live_tokens, int padded_tokens, int query_position, int head_dim, int aligned_head_dim, int sliding_window, int batched_prefill)
static void ck_attention_query_key_f32_work(int ith, int nth, void *opaque)
static int ck_attention_full_bf16_sdpa_tiled_range(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 head_begin, int head_step)
size_t attention_forward_causal_head_major_gqa_prefill_append_f16cache_gqa_reuse_workspace_bytes(int num_heads, int num_kv_heads, int head_dim, int workers, int query_tile_size, int concurrent_query_tiles)
static void ck_attention_trace_query(const char *tag, int layer_id, int head_id, int query_id, int value)
static int ck_attention_vec_dump_parse_env_int(const char *name, int *out)
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 attention_forward_causal_head_major_gqa_flash_strided_f16kv(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)
static void ck_attention_f16_prefill_qtile64_work(int ith, int nth, void *opaque)
static float ck_ggml_vec_dot_f32_contig(const float *x, const float *y, int n)
static void ck_attention_full_tiled_f16kv_fp32_range(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 query_tile_size, int ith, int nth)
#define CK_GGML_FA_TILE_Q_LARGE
void attention_forward_decode_head_major_gqa_muse_eager_bf16_storage_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, float *scores, size_t scores_bytes, uint16_t *key_rows, size_t key_rows_bytes, uint16_t *value_columns, size_t value_columns_bytes, uint16_t *gemm_rows, size_t gemm_rows_bytes, uint16_t *gemm_scores, size_t gemm_scores_bytes)
static ck_attention_status_t ck_attention_f16_prefill_qtile64_dispatch(const float *q, const uint16_t *k_cache, const uint16_t *v_cache, float *output, int num_heads, int num_kv_heads, int q_tokens, int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim, int cache_is_bf16, float attention_scale, size_t q_head_stride, size_t output_head_stride, ck_attention_prefill_schedule_t schedule)
static ck_attention_status_t ck_attention_prefill_append_f16cache_gemma4_workspace(const float *q, const uint16_t *k_cache, const uint16_t *v_cache, float *output, int num_heads, int num_kv_heads, int q_tokens, int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim, int sliding_window, ck_attention_reduction_t reduction, float *token_workspace, size_t token_workspace_bytes, const int *segment_lengths, int num_segments)
float(* ck_attention_math_f32_fn)(float)
static void attention_flash_query_causal(const float *q_vec, const float *k_head, const float *v_head, int kv_tokens, int head_dim, int aligned_head_dim, float scale, float *out_vec)
#define CK_MUSE_PREFILL_QUERY_TILE
void attention_forward_sparse_token_major_gqa_bf16cache_pytorch_cpu_flash_contract(const float *query, const uint16_t *key_cache, const uint16_t *value_cache, const float *selected_indices, float *output, float *score_scratch, int rows, int query_heads, int kv_heads, int head_dim, int selection_width, int context_length, int position)
static void ck_attention_trace(const char *branch, int layer_id, int head_id)
static int ck_attention_full_bf16_pytorch_flash(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 output_token_major)
void attention_forward_decode_head_major_gqa_llama_regular_sliding_workspace(const float *q, const float *k, const float *v, float *output, int num_heads, int num_kv_heads, int live_tokens, int kv_stride_tokens, int head_dim, int aligned_head_dim, int sliding_window, float *scores, size_t scores_bytes, float *value_columns, size_t value_columns_bytes, float *scaled_scores, size_t scaled_scores_bytes)
int attention_forward_query_key_head_major_tiled_f16kv_fp32(const float *query, const float *key, const float *value, float *output, int num_heads, int query_tokens, int key_tokens, int head_dim, float scale)
static float ck_llama_regular_dot_f16(const float *a, const float *b, int count)
static const uint32_t ck_attention_vec_dump_version
static float ck_vec_dot_f32_reverse_strict(const float *x, const float *y, int n)
static void ck_attention_gqa_team_barrier_wait(ck_attention_gqa_team_barrier_t *barrier)
void attention_forward_causal_head_major_gqa_flash(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)
static void ck_attention_vec_dump_selected_query(const float *raw_scores, const float *probs, const float *out_vec, const float *v_cols, int kv_tokens, int head_dim, int layer_id, int head_id, int query_id)
ck_attention_status_t attention_forward_decode_head_major_gqa_bf16cache_pytorch_contract(const float *q_token, const uint16_t *k_cache, const uint16_t *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, ck_attention_reduction_t reduction)
void attention_forward_full_head_major_gqa_ggml_strided_workspace(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, float *score_rows, size_t score_rows_bytes, float *v_columns, size_t v_columns_bytes, float *probability_row, size_t probability_row_bytes)
ck_attention_status_t attention_forward_causal_head_major_gqa_prefill_append_f16cache_gemma4_workspace(const float *q, const uint16_t *k_cache, const uint16_t *v_cache, float *output, int num_heads, int num_kv_heads, int q_tokens, int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim, ck_attention_reduction_t reduction, float *token_workspace, size_t token_workspace_bytes)
static void attention_flash_query_causal_exact_f16kv(const float *q_vec, const float *k_head, const float *v_head, int kv_tokens, int head_dim, int aligned_head_dim, float scale, float *out_vec)
void attention_forward_mixed_visual_chunk_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, int visual_start, int visual_tokens)
static size_t ck_attention_f16_prefill_gqa_reuse_worker_bytes(int num_heads, int num_kv_heads, int head_dim, int workers, int query_tile_size, int concurrent_query_tiles)
#define FLASH_QUERY_IMPL_DECODE
static int ck_attention_vec_dump_vcols_enabled(void)
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)
ck_attention_status_t attention_forward_decode_head_major_gqa_f16cache_sliding_gemma4_contract(const float *q_token, const uint16_t *k_cache, const uint16_t *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, ck_attention_reduction_t reduction)
static void ck_attention_muse_eager_bf16_storage_impl(const float *q, const float *k, const float *v, float *output, int num_heads, int num_kv_heads, int query_tokens, int kv_tokens, int kv_stride_tokens, int head_dim, int aligned_head_dim, int sliding_window, float *scores, size_t scores_bytes, uint16_t *key_rows, size_t key_rows_bytes, uint16_t *value_columns, size_t value_columns_bytes, uint16_t *gemm_rows, size_t gemm_rows_bytes, uint16_t *gemm_scores, size_t gemm_scores_bytes)
void attention_forward_full_head_major_gqa_tiled64_f16kv_fp32_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)
#define CK_GGML_FA_TILE_Q_LARGE_MIN_TOKENS
void attention_forward_causal_head_major_gqa_muse_eager_bf16_storage_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, float *scores, size_t scores_bytes, uint16_t *key_rows, size_t key_rows_bytes, uint16_t *value_columns, size_t value_columns_bytes, uint16_t *gemm_rows, size_t gemm_rows_bytes, uint16_t *gemm_scores, size_t gemm_scores_bytes)
ck_attention_status_t attention_forward_causal_head_major_gqa_prefill_append_f16cache_contract_workspace(const float *q, const uint16_t *k_cache, const uint16_t *v_cache, float *output, int num_heads, int num_kv_heads, int q_tokens, int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim, ck_attention_reduction_t reduction, float *token_workspace, size_t token_workspace_bytes)
static void attention_flash_query_causal_exact_prerounded_f16kv(const float *q_vec, const float *k_head, const float *v_head, int kv_tokens, int head_dim, int aligned_head_dim, float scale, float *out_vec)
static void ck_attention_full_grid_work(int ith, int nth, void *opaque)
void attention_backward_causal_head_major(const float *d_output, const float *q, const float *k, const float *v, const float *attn_weights, float *d_q, float *d_k, float *d_v, float *d_scores, int num_heads, int num_tokens, int head_dim, int aligned_head_dim, int aligned_context_window)
void attention_forward_decode_head_major_gqa_flash_f16cache_split(const float *q_token, const uint16_t *k_cache, const uint16_t *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 split_chunks)
void attention_forward_causal_head_major_gqa_flash_strided_f16kv_serial(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 ck_attention_sparse_bf16_pytorch_gqa_available(void)
void attention_backward_causal_head_major_gqa(const float *d_output, const float *q, const float *k, const float *v, const float *attn_weights, float *d_q, float *d_k, float *d_v, float *d_scores, int num_heads, int num_kv_heads, int num_tokens, int head_dim, int aligned_head_dim, int aligned_context_window)
static float ck_attention_f16_reduce_expf(float value)
static int ck_attention_full_bf16_sdpa_amx_range(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 head_begin, int head_step, int output_token_major)
ck_attention_status_t attention_forward_causal_head_major_gqa_prefill_segmented_f16cache_contract_workspace(const float *q, const uint16_t *k_cache, const uint16_t *v_cache, float *output, int num_heads, int num_kv_heads, int q_tokens, int past_tokens, int cache_capacity, int head_dim, int aligned_head_dim, ck_attention_reduction_t reduction, float *token_workspace, size_t token_workspace_bytes, const int *segment_lengths, int num_segments)
void attention_forward_full_head_major_gqa_sdpa_bf16_storage(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)
static void ck_attention_llama_regular_impl(const float *q, const float *k, const float *v, float *output, int num_heads, int num_kv_heads, int query_tokens, int live_tokens, int head_dim, int aligned_head_dim, int kv_stride_tokens, int sliding_window, float *scores, size_t scores_bytes, float *value_columns, size_t value_columns_bytes, float *scaled_scores, size_t scaled_scores_bytes, int batched_prefill)
static void ck_local_fp16_to_fp32_2d(const uint16_t *src, float *dst, int rows, int cols, int src_stride, int dst_stride)
static size_t ck_attention_align64_size(size_t value)
ck_attention_status_t attention_forward_decode_head_major_gqa_flash_f16cache_contract(const float *q_token, const uint16_t *k_cache, const uint16_t *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, ck_attention_reduction_t reduction)
static int attention_forward_head_major_gqa_unfused_f16_strict(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 causal, int output_token_major, float scale, int debug_layer_id)
static int ck_attention_vec_dump_layer_seq
static void ck_attention_causal_f16kv_work(int ith, int nth, void *opaque)
void attention_forward_causal_head_major_gqa_flash_strided_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)
static int ck_attention_vec_dump_should_emit(int layer_id, int head_id, int query_id)
static float ck_bf16_dot_contract(const float *a, const float *b, int count)
void attention_forward_full_head_major_gqa_pytorch_cpu_flash_bf16_storage_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)
static void ck_attention_forward_full_head_major_gqa_tiled_f16kv_fp32_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, int query_tile_size)
static void convert_bf16_tensor_to_buf(const uint16_t *src, float *dst, size_t count)
static void ck_attention_f16_split_work(int ith, int nth, void *opaque)
int ck_attention_bf16_pytorch_gqa_available(void)
static int ck_attention_forward_query_key_head_major_f32_run(const float *query, const float *key, const float *value, float *output, float *score_scratch, float *key_transpose_scratch, int num_heads, int query_tokens, int key_tokens, int head_dim, float scale)
static int ck_attention_parallel_enabled(int total_queries, int num_tokens, int head_dim)
static size_t ck_muse_attention_checked_product(size_t left, size_t right, const char *label)
static ck_ggml_mul_mat_graph_fn ck_resolve_ggml_mul_mat_graph(void)
void(* ck_ggml_cpu_init_fn)(void)
struct ggml_tensor *(* ck_ggml_new_tensor_2d_fn)(struct ggml_context *, enum ggml_type, int64_t, int64_t)
struct ggml_cgraph *(* ck_ggml_new_graph_fn)(struct ggml_context *)
void(* ck_ggml_set_input_fn)(struct ggml_tensor *)
enum ggml_status(* ck_ggml_graph_compute_with_ctx_fn)(struct ggml_context *, struct ggml_cgraph *, int)
static ck_ggml_new_tensor_2d_fn ck_resolve_ggml_new_tensor_2d(void)
static ck_ggml_build_forward_expand_fn ck_resolve_ggml_build_forward_expand(void)
void(* ck_ggml_free_fn)(struct ggml_context *)
void(* ck_ggml_build_forward_expand_fn)(struct ggml_cgraph *, struct ggml_tensor *)
struct ggml_tensor *(* ck_ggml_mul_mat_graph_fn)(struct ggml_context *, struct ggml_tensor *, struct ggml_tensor *)
static ck_ggml_graph_compute_with_ctx_fn ck_resolve_ggml_graph_compute_with_ctx(void)
static ck_ggml_set_input_fn ck_resolve_ggml_set_input(void)
int ck_attention_head_full_ggml_graph_oracle_regular(const float *q_head, const float *k_head, const float *v_head, float *out_head, int num_tokens, int head_dim, int aligned_head_dim, float scale)
static ck_ggml_new_graph_fn ck_resolve_ggml_new_graph(void)
static ck_ggml_free_fn ck_resolve_ggml_free(void)
struct ggml_context *(* ck_ggml_init_fn)(struct ggml_init_params)
int ck_attention_full_ggml_graph_oracle_multihead(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, float scale)
static ck_ggml_init_fn ck_resolve_ggml_init(void)
static ck_ggml_cpu_init_fn ck_resolve_ggml_cpu_init(void)
static int product(size_t a, size_t b, size_t *out)
static uint16_t float_to_bf16(float f)
Definition bf16_utils.h:90
static float bf16_to_float(uint16_t v)
Definition bf16_utils.h:38
static void bf16_tensor_to_float(const uint16_t *src, float *dst, size_t count)
Definition bf16_utils.h:250
static int ck_env_truthy_or_qwen3vl_ocr_profile(const char *name)
Persistent pthread thread pool for CK-Engine inference.
#define CK_CACHE_LINE
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)
static const char * op_name(CKOpType op)
ck_attention_prefill_schedule_t
@ CK_ATTN_PREFILL_SCHEDULE_QUERY_TILES
@ CK_ATTN_PREFILL_SCHEDULE_KV_HEADS
@ CK_ATTN_PREFILL_SCHEDULE_GQA_SHARED_KV_TILES
@ CK_ATTN_PREFILL_SCHEDULE_KV_GROUP_QUERY_TILES
void attention_flash_decode(float *out, const float *q, const float *k, const float *v, int T_q, int T_k, int H, int D_h, float scale)
Main flash attention function with SIMD dispatch.
void gemm_nt_bf16_pytorch_onednn_3_12_brgemm_bf16_storage_workspace(const float *A, const void *B, const float *bias, float *C, int M, int N, int K, uint16_t *input_bf16, size_t input_bf16_bytes, uint16_t *output_bf16, size_t output_bf16_bytes, uint16_t *bias_bf16, size_t bias_bf16_bytes)
int ck_gemm_bf16_amx_available(void)
void causal_softmax_head_major_exact(float *scores, int num_heads, int num_tokens, int aligned_context_window)
void causal_softmax_head_major(float *scores, int num_heads, int num_tokens, int aligned_context_window)
int ck_gemm_bf16_fp32out_amx_raw(const uint16_t *A, const uint16_t *B, float *C, int M, int N, int K, int accumulate)
ck_attention_reduction_t
@ CK_ATTN_REDUCTION_BF16_PYTORCH_SDPA
@ CK_ATTN_REDUCTION_F16_FLASH_AUTO_QTILE64_REGULAR_TAIL_NOSCALE
@ CK_ATTN_REDUCTION_F16_REGULAR_NOSCALE
@ CK_ATTN_REDUCTION_F16_ONLINE_FP32_MERGE
@ CK_ATTN_REDUCTION_F16_FLASH_AUTO_QTILE64
@ CK_ATTN_REDUCTION_F16_ONLINE_SINGLE_RANGE
@ CK_ATTN_REDUCTION_FP32_ONLINE
ck_attention_status_t
@ CK_ATTENTION_STATUS_UNSUPPORTED_CONTRACT
@ CK_ATTENTION_STATUS_OK
@ CK_ATTENTION_STATUS_INVALID_ARGUMENT
@ CK_ATTENTION_STATUS_INSUFFICIENT_WORKSPACE
int ck_get_num_threads(void)
int ck_strict_parity_enabled(void)
#define CK_FP16_TO_FP32(x)
#define CK_FP32_TO_FP16(x)
@ GGML_STATUS_SUCCESS
@ GGML_TYPE_F32
__attribute__((visibility("default"))) CKTokenizer *ck_tokenizer_create(CKTokenizerType type)
const char * token
Definition tokenizer.h:307
int32_t float * score
Definition tokenizer.h:328
const char * left
Definition true_bpe.h:138
const uint8_t size_t count
Definition true_bpe.h:209
const char const char * right
Definition true_bpe.h:139
uint32_t end
Definition utf8.c:215
uint32_t start
Definition utf8.c:214