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