← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
ck_parity_api.c
Go to the documentation of this file.
1/**
2 * @file ck_parity_api.c
3 * @brief C-Kernel-Engine Parity Testing API Implementation
4 *
5 * Wraps CK kernels for parity testing against llama.cpp/ggml.
6 */
7
8#include "ck_parity_api.h"
9#include "ckernel_quant.h"
10#include <math.h>
11#include <stdlib.h>
12#include <string.h>
13
14/* External kernel function declarations */
15
16/* Dequantization kernels (from dequant_kernels.c) */
17extern void dequant_q4_k_row(const void *src, float *dst, size_t n_elements);
18extern void dequant_q6_k_row(const void *src, float *dst, size_t n_elements);
19extern void dequant_q4_0_row(const void *src, float *dst, size_t n_elements);
20
21/* Quantization kernels (from gemm_kernels_q4k_q8k.c) */
22extern void quantize_row_q8_k(const float *x, void *vy, int k);
23
24/* GEMV/GEMM kernels */
25extern void gemv_q4_k_q8_k(float *y, const void *W, const void *x_q8, int M, int K);
26extern void gemm_nt_q4_k_q8_k(const void *A_q8, const void *B, const float *bias,
27 float *C, int M, int N, int K);
28
29/* Q6_K x Q8_K kernels (from gemm_kernels_q6k_q8k.c) */
30extern void gemv_q6_k_q8_k(float *y, const void *W, const void *x_q8, int M, int K);
31extern void gemm_nt_q6_k_q8_k(const void *A_q8, const void *B, const float *bias,
32 float *C, int M, int N, int K);
33
34/* Q8_0 x Q8_0 batch GEMM (from gemm_batch_int8.c) */
35extern void gemm_nt_q8_0_q8_0(const void *A_q8, const void *B_q8, const float *bias,
36 float *C, int M, int N, int K);
37
38/* Q5_0 x Q8_0 batch GEMM (from gemm_kernels_q5_0.c) */
39extern void gemm_nt_q5_0_q8_0(const void *A_q8, const void *B_q5, const float *bias,
40 float *C, int M, int N, int K);
41
42/* Q5_0 and Q8_0 GEMV kernels (from gemm_kernels_q5_0.c, gemm_kernels_q8_0.c) */
43extern void gemv_q5_0(float *y, const void *W, const float *x, int M, int K);
44extern void gemv_q8_0(float *y, const void *W, const float *x, int M, int K);
45
46/* Quantized dot product kernels for parity with llama.cpp */
47extern void gemv_q5_0_q8_0(float *y, const void *W, const void *x_q8, int M, int K);
48extern void gemv_q8_0_q8_0(float *y, const void *W, const void *x_q8, int M, int K);
49
50/* Direct vec_dot kernels (single dot product, not GEMV) */
51extern void vec_dot_q5_0_q8_0(int n, float *s, const void *vx, const void *vy);
52extern void vec_dot_q8_0_q8_0(int n, float *s, const void *vx, const void *vy);
53extern void vec_dot_q6_k_q8_k(int n, float *s, const void *vx, const void *vy);
54
55/* Q8_0 quantization (for input) */
56extern void quantize_row_q8_0(const float *x, void *vy, int k);
57
58/* RMSNorm kernel (from rmsnorm_kernels.c) */
59extern void rmsnorm_forward(const float *input, const float *gamma,
60 float *output, float *rstd_cache,
61 int tokens, int d_model, int aligned_embed_dim, float eps);
62
63/* RoPE kernels (from rope_kernels.c) */
64extern void rope_forward_qk(float *q, float *k,
65 const float *cos_cache, const float *sin_cache,
66 int num_heads, int num_kv_heads, int num_tokens,
67 int head_dim, int aligned_head_dim, int pos_offset);
68extern void rope_precompute_cache(float *cos_cache, float *sin_cache,
69 int max_seq_len, int head_dim, float base,
70 int rotary_dim, const char *scaling_type,
71 float scaling_factor);
72
73/* SwiGLU kernel (from swiglu_kernels.c) */
74extern void swiglu_forward(const float *input, float *output, int tokens, int dim);
75
76/* Attention kernels (from attention_kernels.c / attention_flash_true.c) */
78 const float *q, const float *k, const float *v, float *output,
79 int num_heads, int num_kv_heads, int num_tokens,
80 int head_dim, int aligned_head_dim, int kv_stride_tokens);
81
82/* SSM convolution kernel (from ssm_kernels.c) */
83extern void ssm_conv1d_forward(
84 const float *conv_x,
85 const float *kernel,
86 float *out,
87 int kernel_size,
88 int num_channels,
89 int num_tokens,
90 int num_seqs);
91
92/* Full-attention hybrid helper kernels (from hybrid_attention_kernels.c) */
93extern void split_q_gate_forward(
94 const float *packed_qg,
95 float *q,
96 float *gate,
97 int rows,
98 int q_dim,
99 int gate_dim,
100 int group_dim);
102 const float *x,
103 const float *gate,
104 float *out,
105 int rows,
106 int num_heads,
107 int state_dim);
108
109/* Recurrent packed QKV split kernel (from recurrent_split_kernels.c) */
111 const float *packed_qkv,
112 float *q,
113 float *k,
114 float *v,
115 int rows,
116 int q_dim,
117 int k_dim,
118 int v_dim);
119
120/* Recurrent dt gate kernel (from recurrent_gate_kernels.c) */
121extern void recurrent_dt_gate_forward(
122 const float *alpha,
123 const float *dt_bias,
124 const float *a,
125 float *gate,
126 int rows,
127 int num_heads,
128 int state_dim);
130 const float *state_in,
131 const float *q,
132 const float *k,
133 const float *v,
134 float *conv_x,
135 float *state_out,
136 int history_len,
137 int num_seqs,
138 int num_tokens,
139 int q_dim,
140 int k_dim,
141 int v_dim);
142extern void recurrent_silu_forward(
143 const float *x,
144 float *out,
145 int rows,
146 int dim);
148 const float *packed_qkv,
149 float *q,
150 float *k,
151 float *v,
152 int rows,
153 int q_dim,
154 int k_dim,
155 int v_dim);
157 float *q,
158 float *k,
159 int rows,
160 int q_dim,
161 int k_dim,
162 int head_dim,
163 float eps);
165 const float *x,
166 const float *gate,
167 const float *weight,
168 float *out,
169 int rows,
170 int num_heads,
171 int head_dim,
172 float eps);
173
174/* Gated DeltaNet kernel (from deltanet_kernels.c) */
176 const float *q,
177 const float *k,
178 const float *v,
179 const float *g,
180 const float *beta,
181 const float *state_in,
182 float *state_out,
183 float *out,
184 int num_heads,
185 int state_dim,
186 float norm_eps);
187
188/* ============================================================================
189 * Dequantization Tests
190 * ============================================================================ */
191
192void ck_test_dequant_q4_k(const void *src, float *dst, int n)
193{
194 dequant_q4_k_row(src, dst, (size_t)n);
195}
196
197void ck_test_dequant_q6_k(const void *src, float *dst, int n)
198{
199 dequant_q6_k_row(src, dst, (size_t)n);
200}
201
202void ck_test_dequant_q4_0(const void *src, float *dst, int n)
203{
204 dequant_q4_0_row(src, dst, (size_t)n);
205}
206
207/* ============================================================================
208 * Quantization Tests
209 * ============================================================================ */
210
211void ck_test_quantize_q8_k(const float *src, void *dst, int n)
212{
213 quantize_row_q8_k(src, dst, n);
214}
215
216/* ============================================================================
217 * GEMV Tests
218 * ============================================================================ */
219
220void ck_test_gemv_q4_k(const void *weight_q4k,
221 const float *input_f32,
222 float *output,
223 int cols)
224{
225 /* Allocate Q8_K buffer for quantized activations */
226 int n_blocks = cols / CK_QK_K;
227 block_q8_K *q8_data = (block_q8_K *)malloc(n_blocks * sizeof(block_q8_K));
228 if (!q8_data) {
229 *output = 0.0f;
230 return;
231 }
232
233 /* Quantize input to Q8_K */
234 quantize_row_q8_k(input_f32, q8_data, cols);
235
236 /* Compute dot product using GEMV with M=1 */
237 gemv_q4_k_q8_k(output, weight_q4k, q8_data, 1, cols);
238
239 free(q8_data);
240}
241
242void ck_test_gemv_q6_k(const void *weight_q6k,
243 const float *input_f32,
244 float *output,
245 int cols)
246{
247 /* Match runtime decode path:
248 * 1) quantize FP32 activation to Q8_K
249 * 2) run Q6_K x Q8_K GEMV kernel with M=1
250 */
251 int n_blocks = cols / CK_QK_K;
252 block_q8_K *q8_data = (block_q8_K *)malloc(n_blocks * sizeof(block_q8_K));
253 if (!q8_data) {
254 *output = 0.0f;
255 return;
256 }
257
258 quantize_row_q8_k(input_f32, q8_data, cols);
259 gemv_q6_k_q8_k(output, weight_q6k, q8_data, 1, cols);
260 free(q8_data);
261}
262
263void ck_test_gemv_q5_0(const void *weight_q5_0,
264 const float *input_f32,
265 float *output,
266 int rows, int cols)
267{
268 /* Match llama.cpp's test_gemv_q5_0:
269 * 1. Quantize input to Q8_0 format
270 * 2. Use quantized dot product (vec_dot_q5_0_q8_0)
271 *
272 * This ensures parity with llama.cpp which always uses the
273 * quantized path, NOT the FP32 dequantization path.
274 */
275 int n_blocks = cols / CK_QK8_0;
276 block_q8_0 *q8_data = (block_q8_0 *)malloc(n_blocks * sizeof(block_q8_0));
277 if (!q8_data) {
278 for (int r = 0; r < rows; r++) output[r] = 0.0f;
279 return;
280 }
281
282 /* Quantize input to Q8_0 */
283 quantize_row_q8_0(input_f32, q8_data, cols);
284
285 /* Call the quantized GEMV kernel (same as ck_test_gemv_q5_0_q8_0) */
286 gemv_q5_0_q8_0(output, weight_q5_0, q8_data, rows, cols);
287
288 free(q8_data);
289}
290
291void ck_test_gemv_q8_0(const void *weight_q8_0,
292 const float *input_f32,
293 float *output,
294 int rows, int cols)
295{
296 /* Match llama.cpp's test_gemv_q8_0:
297 * 1. Quantize input to Q8_0 format
298 * 2. Use quantized dot product (vec_dot_q8_0_q8_0)
299 *
300 * This ensures parity with llama.cpp which always uses the
301 * quantized path, NOT the FP32 dequantization path.
302 */
303 int n_blocks = cols / CK_QK8_0;
304 block_q8_0 *q8_data = (block_q8_0 *)malloc(n_blocks * sizeof(block_q8_0));
305 if (!q8_data) {
306 for (int r = 0; r < rows; r++) output[r] = 0.0f;
307 return;
308 }
309
310 /* Quantize input to Q8_0 */
311 quantize_row_q8_0(input_f32, q8_data, cols);
312
313 /* Call the quantized GEMV kernel (same as ck_test_gemv_q8_0_q8_0) */
314 gemv_q8_0_q8_0(output, weight_q8_0, q8_data, rows, cols);
315
316 free(q8_data);
317}
318
319void ck_test_gemv_q5_0_q8_0(const void *weight_q5_0,
320 const float *input_f32,
321 float *output,
322 int rows, int cols)
323{
324 /* This matches llama.cpp's approach:
325 * 1. Quantize input to Q8_0 format
326 * 2. Use quantized dot product (integer math)
327 * 3. Scale at the end
328 */
329 int n_blocks = cols / CK_QK8_0;
330 block_q8_0 *q8_data = (block_q8_0 *)malloc(n_blocks * sizeof(block_q8_0));
331 if (!q8_data) {
332 for (int r = 0; r < rows; r++) output[r] = 0.0f;
333 return;
334 }
335
336 /* Quantize input to Q8_0 */
337 quantize_row_q8_0(input_f32, q8_data, cols);
338
339 /* Call the quantized GEMV kernel */
340 gemv_q5_0_q8_0(output, weight_q5_0, q8_data, rows, cols);
341
342 free(q8_data);
343}
344
345void ck_test_gemv_q8_0_q8_0(const void *weight_q8_0,
346 const float *input_f32,
347 float *output,
348 int rows, int cols)
349{
350 /* This matches llama.cpp's approach:
351 * 1. Quantize input to Q8_0 format
352 * 2. Use quantized dot product (integer math)
353 * 3. Scale at the end
354 */
355 int n_blocks = cols / CK_QK8_0;
356 block_q8_0 *q8_data = (block_q8_0 *)malloc(n_blocks * sizeof(block_q8_0));
357 if (!q8_data) {
358 for (int r = 0; r < rows; r++) output[r] = 0.0f;
359 return;
360 }
361
362 /* Quantize input to Q8_0 */
363 quantize_row_q8_0(input_f32, q8_data, cols);
364
365 /* Call the quantized GEMV kernel */
366 gemv_q8_0_q8_0(output, weight_q8_0, q8_data, rows, cols);
367
368 free(q8_data);
369}
370
371/* ============================================================================
372 * Direct Vec Dot Tests (pre-quantized inputs, no FP32 conversion)
373 * ============================================================================ */
374
375void ck_test_vec_dot_q4_k_q8_k(const void *weight_q4_k,
376 const void *input_q8_k,
377 float *output,
378 int cols)
379{
380 /* M=1 reaches the production Q4 dispatch without requantizing input. */
381 gemv_q4_k_q8_k(output, weight_q4_k, input_q8_k, 1, cols);
382}
383
384void ck_test_vec_dot_q6_k_q8_k(const void *weight_q6_k,
385 const void *input_q8_k,
386 float *output,
387 int cols)
388{
389 /* Exercise the production M=1 provider. The scalar vec_dot helper is an
390 * internal architecture-neutral oracle and does not preserve the x86
391 * provider's declared lane reduction order. */
392 gemv_q6_k_q8_k(output, weight_q6_k, input_q8_k, 1, cols);
393}
394
395/**
396 * @brief Direct Q5_0 x Q8_0 dot product test (takes pre-quantized Q8_0 input)
397 *
398 * This is a "direct" test that bypasses FP32-to-Q8_0 conversion.
399 * Useful for isolating kernel bugs from quantization bugs.
400 *
401 * @param weight_q5_0 Q5_0 quantized weights [cols]
402 * @param input_q8_0 Q8_0 quantized input [cols] (pre-quantized!)
403 * @param output Output scalar [1]
404 * @param cols Number of elements (must be multiple of 32)
405 */
406void ck_test_vec_dot_q5_0_q8_0(const void *weight_q5_0,
407 const void *input_q8_0,
408 float *output,
409 int cols)
410{
411 vec_dot_q5_0_q8_0(cols, output, weight_q5_0, input_q8_0);
412}
413
414/**
415 * @brief Direct Q8_0 x Q8_0 dot product test (takes pre-quantized Q8_0 input)
416 *
417 * @param weight_q8_0 Q8_0 quantized weights [cols]
418 * @param input_q8_0 Q8_0 quantized input [cols] (pre-quantized!)
419 * @param output Output scalar [1]
420 * @param cols Number of elements (must be multiple of 32)
421 */
422void ck_test_vec_dot_q8_0_q8_0(const void *weight_q8_0,
423 const void *input_q8_0,
424 float *output,
425 int cols)
426{
427 vec_dot_q8_0_q8_0(cols, output, weight_q8_0, input_q8_0);
428}
429
430/* ============================================================================
431 * GEMM Tests
432 * ============================================================================ */
433
434void ck_test_gemm_q4_k(const void *weight_q4k,
435 const float *input_f32,
436 float *output,
437 int rows, int cols, int n_tokens)
438{
439 /* Allocate Q8_K buffer for quantized activations */
440 int n_blocks_per_row = cols / CK_QK_K;
441 block_q8_K *q8_data = (block_q8_K *)malloc(n_tokens * n_blocks_per_row * sizeof(block_q8_K));
442 if (!q8_data) {
443 memset(output, 0, n_tokens * rows * sizeof(float));
444 return;
445 }
446
447 /* Quantize all input tokens */
448 for (int t = 0; t < n_tokens; t++) {
449 quantize_row_q8_k(input_f32 + t * cols,
450 q8_data + t * n_blocks_per_row, cols);
451 }
452
453 /* Use gemm_nt_q4_k_q8_k: C[M,N] = A[M,K] * B[N,K]^T
454 * Our layout: output[n_tokens, rows] = input[n_tokens, cols] * weight[rows, cols]^T
455 * So: M = n_tokens, N = rows, K = cols
456 */
457 gemm_nt_q4_k_q8_k(q8_data, weight_q4k, NULL, output, n_tokens, rows, cols);
458
459 free(q8_data);
460}
461
462/**
463 * @brief Test Q6_K x Q8_K GEMM (batch matrix multiply)
464 *
465 * Used for MLP W2 (down projection) with Q6_K weights.
466 */
467void ck_test_gemm_q6_k(const void *weight_q6k,
468 const float *input_f32,
469 float *output,
470 int rows, int cols, int n_tokens)
471{
472 /* Allocate Q8_K buffer for quantized activations */
473 int n_blocks_per_row = cols / CK_QK_K;
474 block_q8_K *q8_data = (block_q8_K *)malloc(n_tokens * n_blocks_per_row * sizeof(block_q8_K));
475 if (!q8_data) {
476 memset(output, 0, n_tokens * rows * sizeof(float));
477 return;
478 }
479
480 /* Quantize all input tokens */
481 for (int t = 0; t < n_tokens; t++) {
482 quantize_row_q8_k(input_f32 + t * cols,
483 q8_data + t * n_blocks_per_row, cols);
484 }
485
486 /* Use gemm_nt_q6_k_q8_k: C[M,N] = A[M,K] * B[N,K]^T
487 * Our layout: output[n_tokens, rows] = input[n_tokens, cols] * weight[rows, cols]^T
488 * So: M = n_tokens, N = rows, K = cols
489 */
490 gemm_nt_q6_k_q8_k(q8_data, weight_q6k, NULL, output, n_tokens, rows, cols);
491
492 free(q8_data);
493}
494
495/**
496 * @brief Test Q8_0 x Q8_0 GEMM (batch matrix multiply)
497 *
498 * Used for attention V projection with Q8_0 weights.
499 */
500void ck_test_gemm_q8_0(const void *weight_q8_0,
501 const float *input_f32,
502 float *output,
503 int rows, int cols, int n_tokens)
504{
505 /* Allocate Q8_0 buffer for quantized activations */
506 int n_blocks_per_row = cols / CK_QK8_0;
507 block_q8_0 *q8_data = (block_q8_0 *)malloc(n_tokens * n_blocks_per_row * sizeof(block_q8_0));
508 if (!q8_data) {
509 memset(output, 0, n_tokens * rows * sizeof(float));
510 return;
511 }
512
513 /* Quantize all input tokens */
514 for (int t = 0; t < n_tokens; t++) {
515 quantize_row_q8_0(input_f32 + t * cols,
516 q8_data + t * n_blocks_per_row, cols);
517 }
518
519 /* Use gemm_nt_q8_0_q8_0: C[M,N] = A[M,K] * B[N,K]^T
520 * Our layout: output[n_tokens, rows] = input[n_tokens, cols] * weight[rows, cols]^T
521 * So: M = n_tokens, N = rows, K = cols
522 */
523 gemm_nt_q8_0_q8_0(q8_data, weight_q8_0, NULL, output, n_tokens, rows, cols);
524
525 free(q8_data);
526}
527
528/**
529 * @brief Test Q5_0 x Q8_0 GEMM (batch matrix multiply)
530 *
531 * Used for MLP W1 (gate/up projection) and attention Q/K with Q5_0 weights.
532 */
533void ck_test_gemm_q5_0(const void *weight_q5_0,
534 const float *input_f32,
535 float *output,
536 int rows, int cols, int n_tokens)
537{
538 /* Allocate Q8_0 buffer for quantized activations */
539 int n_blocks_per_row = cols / CK_QK8_0;
540 block_q8_0 *q8_data = (block_q8_0 *)malloc(n_tokens * n_blocks_per_row * sizeof(block_q8_0));
541 if (!q8_data) {
542 memset(output, 0, n_tokens * rows * sizeof(float));
543 return;
544 }
545
546 /* Quantize all input tokens */
547 for (int t = 0; t < n_tokens; t++) {
548 quantize_row_q8_0(input_f32 + t * cols,
549 q8_data + t * n_blocks_per_row, cols);
550 }
551
552 /* Use gemm_nt_q5_0_q8_0: C[M,N] = A[M,K] * B[N,K]^T
553 * Our layout: output[n_tokens, rows] = input[n_tokens, cols] * weight[rows, cols]^T
554 * So: M = n_tokens, N = rows, K = cols
555 */
556 gemm_nt_q5_0_q8_0(q8_data, weight_q5_0, NULL, output, n_tokens, rows, cols);
557
558 free(q8_data);
559}
560
561/* ============================================================================
562 * Activation Kernels
563 * ============================================================================ */
564
565void ck_test_rmsnorm(const float *input,
566 const float *weight,
567 float *output,
568 int n_tokens, int dim, float eps)
569{
570 /* CK rmsnorm_forward has aligned_embed_dim parameter
571 * For testing, use dim as aligned_embed_dim (no padding) */
572 rmsnorm_forward(input, weight, output, NULL, n_tokens, dim, dim, eps);
573}
574
575void ck_test_rope(float *q, float *k,
576 int n_tokens, int n_heads, int n_heads_kv, int head_dim,
577 int pos_offset, float theta)
578{
579 /* Precompute cos/sin cache */
580 int half_dim = head_dim / 2;
581 int max_seq = pos_offset + n_tokens;
582
583 float *cos_cache = (float *)malloc(max_seq * half_dim * sizeof(float));
584 float *sin_cache = (float *)malloc(max_seq * half_dim * sizeof(float));
585 if (!cos_cache || !sin_cache) {
586 free(cos_cache);
587 free(sin_cache);
588 return;
589 }
590
591 rope_precompute_cache(cos_cache, sin_cache, max_seq, head_dim, theta,
592 head_dim, "none", 1.0f);
593
594 /* CK RoPE expects layout [num_heads, num_tokens, head_dim]
595 * Reshape from [n_tokens, n_heads * head_dim] to [n_heads, n_tokens, head_dim]
596 */
597 float *q_reorder = (float *)malloc(n_heads * n_tokens * head_dim * sizeof(float));
598 float *k_reorder = (float *)malloc(n_heads_kv * n_tokens * head_dim * sizeof(float));
599
600 if (q_reorder && k_reorder) {
601 /* Reorder Q: [T, H*D] -> [H, T, D] */
602 for (int t = 0; t < n_tokens; t++) {
603 for (int h = 0; h < n_heads; h++) {
604 for (int d = 0; d < head_dim; d++) {
605 q_reorder[h * n_tokens * head_dim + t * head_dim + d] =
606 q[t * n_heads * head_dim + h * head_dim + d];
607 }
608 }
609 }
610
611 /* Reorder K: [T, H_kv*D] -> [H_kv, T, D] */
612 for (int t = 0; t < n_tokens; t++) {
613 for (int h = 0; h < n_heads_kv; h++) {
614 for (int d = 0; d < head_dim; d++) {
615 k_reorder[h * n_tokens * head_dim + t * head_dim + d] =
616 k[t * n_heads_kv * head_dim + h * head_dim + d];
617 }
618 }
619 }
620
621 /* Apply RoPE */
622 rope_forward_qk(q_reorder, k_reorder,
623 cos_cache, sin_cache,
624 n_heads, n_heads_kv, n_tokens,
625 head_dim, head_dim, pos_offset);
626
627 /* Reorder back: [H, T, D] -> [T, H*D] */
628 for (int t = 0; t < n_tokens; t++) {
629 for (int h = 0; h < n_heads; h++) {
630 for (int d = 0; d < head_dim; d++) {
631 q[t * n_heads * head_dim + h * head_dim + d] =
632 q_reorder[h * n_tokens * head_dim + t * head_dim + d];
633 }
634 }
635 }
636
637 for (int t = 0; t < n_tokens; t++) {
638 for (int h = 0; h < n_heads_kv; h++) {
639 for (int d = 0; d < head_dim; d++) {
640 k[t * n_heads_kv * head_dim + h * head_dim + d] =
641 k_reorder[h * n_tokens * head_dim + t * head_dim + d];
642 }
643 }
644 }
645 }
646
647 free(q_reorder);
648 free(k_reorder);
649 free(cos_cache);
650 free(sin_cache);
651}
652
653void ck_test_rope_interleaved(float *q, float *k,
654 int n_tokens, int n_heads, int n_heads_kv, int head_dim,
655 int pos_offset, float theta)
656{
657 /* Interleaved RoPE format (matches llama.cpp):
658 * (x0, x1) -> (x0*cos - x1*sin, x0*sin + x1*cos)
659 * Applied to consecutive pairs of elements
660 */
661
662 /* Precompute inverse frequencies */
663 float *inv_freq = (float *)malloc((head_dim / 2) * sizeof(float));
664 if (!inv_freq) return;
665
666 for (int i = 0; i < head_dim / 2; i++) {
667 inv_freq[i] = 1.0f / powf(theta, (float)(2 * i) / head_dim);
668 }
669
670 /* Apply RoPE to Q */
671 for (int t = 0; t < n_tokens; t++) {
672 int pos = pos_offset + t;
673 for (int h = 0; h < n_heads; h++) {
674 float *qh = q + t * n_heads * head_dim + h * head_dim;
675
676 for (int i = 0; i < head_dim / 2; i++) {
677 float freq = pos * inv_freq[i];
678 float cos_val = cosf(freq);
679 float sin_val = sinf(freq);
680
681 /* Interleaved format */
682 float x0 = qh[i * 2];
683 float x1 = qh[i * 2 + 1];
684 qh[i * 2] = x0 * cos_val - x1 * sin_val;
685 qh[i * 2 + 1] = x0 * sin_val + x1 * cos_val;
686 }
687 }
688 }
689
690 /* Apply RoPE to K */
691 for (int t = 0; t < n_tokens; t++) {
692 int pos = pos_offset + t;
693 for (int h = 0; h < n_heads_kv; h++) {
694 float *kh = k + t * n_heads_kv * head_dim + h * head_dim;
695
696 for (int i = 0; i < head_dim / 2; i++) {
697 float freq = pos * inv_freq[i];
698 float cos_val = cosf(freq);
699 float sin_val = sinf(freq);
700
701 float x0 = kh[i * 2];
702 float x1 = kh[i * 2 + 1];
703 kh[i * 2] = x0 * cos_val - x1 * sin_val;
704 kh[i * 2 + 1] = x0 * sin_val + x1 * cos_val;
705 }
706 }
707 }
708
709 free(inv_freq);
710}
711
712void ck_test_swiglu(const float *gate_up,
713 float *output,
714 int n_tokens, int intermediate_dim)
715{
716 swiglu_forward(gate_up, output, n_tokens, intermediate_dim);
717}
718
719void ck_test_softmax(const float *input, float *output, int n)
720{
721 /* Find max for numerical stability */
722 float max_val = input[0];
723 for (int i = 1; i < n; i++) {
724 if (input[i] > max_val) max_val = input[i];
725 }
726
727 /* Compute exp and sum */
728 float sum = 0.0f;
729 for (int i = 0; i < n; i++) {
730 output[i] = expf(input[i] - max_val);
731 sum += output[i];
732 }
733
734 /* Normalize */
735 float inv_sum = 1.0f / sum;
736 for (int i = 0; i < n; i++) {
737 output[i] *= inv_sum;
738 }
739}
740
742 const float *k,
743 const float *v,
744 const float *g,
745 const float *beta,
746 const float *state_in,
747 float *state_out,
748 float *out,
749 int num_heads,
750 int state_dim,
751 float norm_eps)
752{
754 q,
755 k,
756 v,
757 g,
758 beta,
759 state_in,
760 state_out,
761 out,
762 num_heads,
763 state_dim,
764 norm_eps);
765}
766
767void ck_test_ssm_conv1d(const float *conv_x,
768 const float *kernel,
769 float *out,
770 int kernel_size,
771 int num_channels,
772 int num_tokens,
773 int num_seqs)
774{
775 ssm_conv1d_forward(conv_x, kernel, out, kernel_size, num_channels, num_tokens, num_seqs);
776}
777
778void ck_test_split_q_gate(const float *packed_qg,
779 float *q,
780 float *gate,
781 int rows,
782 int q_dim,
783 int gate_dim,
784 int group_dim)
785{
786 split_q_gate_forward(packed_qg, q, gate, rows, q_dim, gate_dim, group_dim);
787}
788
789void ck_test_recurrent_split_qkv(const float *packed_qkv,
790 float *q,
791 float *k,
792 float *v,
793 int rows,
794 int q_dim,
795 int k_dim,
796 int v_dim)
797{
798 recurrent_split_qkv_forward(packed_qkv, q, k, v, rows, q_dim, k_dim, v_dim);
799}
800
801void ck_test_recurrent_dt_gate(const float *alpha,
802 const float *dt_bias,
803 const float *a,
804 float *gate,
805 int rows,
806 int dim)
807{
808 recurrent_dt_gate_forward(alpha, dt_bias, a, gate, rows, 1, dim);
809}
810
811void ck_test_recurrent_conv_state_update(const float *state_in,
812 const float *q,
813 const float *k,
814 const float *v,
815 float *conv_x,
816 float *state_out,
817 int history_len,
818 int num_seqs,
819 int num_tokens,
820 int q_dim,
821 int k_dim,
822 int v_dim)
823{
825 state_in, q, k, v, conv_x, state_out, history_len, num_seqs, num_tokens, q_dim, k_dim, v_dim);
826}
827
828void ck_test_recurrent_silu(const float *x,
829 float *out,
830 int rows,
831 int dim)
832{
833 recurrent_silu_forward(x, out, rows, dim);
834}
835
836void ck_test_recurrent_split_conv_qkv(const float *packed_qkv,
837 float *q,
838 float *k,
839 float *v,
840 int rows,
841 int q_dim,
842 int k_dim,
843 int v_dim)
844{
845 recurrent_split_conv_qkv_forward(packed_qkv, q, k, v, rows, q_dim, k_dim, v_dim);
846}
847
849 float *k,
850 int rows,
851 int q_dim,
852 int k_dim,
853 int head_dim,
854 float eps)
855{
856 recurrent_qk_l2_norm_forward(q, k, rows, q_dim, k_dim, head_dim, eps);
857}
858
860 const float *gate,
861 float *out,
862 int rows,
863 int dim)
864{
865 attn_gate_sigmoid_mul_forward(x, gate, out, rows, 1, dim);
866}
867
868void ck_test_recurrent_norm_gate(const float *x,
869 const float *gate,
870 const float *weight,
871 float *out,
872 int rows,
873 int num_heads,
874 int head_dim,
875 float eps)
876{
877 recurrent_norm_gate_forward(x, gate, weight, out, rows, num_heads, head_dim, eps);
878}
879
880/* ============================================================================
881 * Attention Kernels
882 * ============================================================================ */
883
884void ck_test_attention_causal(const float *q,
885 const float *k,
886 const float *v,
887 float *out,
888 int num_heads,
889 int num_kv_heads,
890 int tokens,
891 int seq_len,
892 int head_dim)
893{
894 /* For prefill, seq_len == tokens, and kv_stride == tokens.
895 * The CK kernel expects strided KV layout with kv_stride_tokens parameter.
896 * For parity testing with contiguous tensors, kv_stride = seq_len.
897 */
899 q, k, v, out,
900 num_heads, num_kv_heads, tokens,
901 head_dim, head_dim, /* aligned_head_dim = head_dim for testing */
902 seq_len /* kv_stride_tokens = seq_len for contiguous KV */
903 );
904}
905
906/* ============================================================================
907 * Mega-Fused OutProj + MLP Kernels
908 * ============================================================================ */
909
910/* External declaration for mega_fused_outproj_mlp_prefill */
912 float *output,
913 const float *attn_out,
914 const float *residual,
915 const float *ln2_gamma,
916 const void *wo, const float *bo, int wo_dt,
917 const void *w1, const float *b1, int w1_dt,
918 const void *w2, const float *b2, int w2_dt,
919 int tokens,
920 int embed_dim,
921 int aligned_embed_dim,
922 int num_heads,
923 int aligned_head_dim,
924 int intermediate_dim,
925 int aligned_intermediate_dim,
926 float eps,
927 void *scratch);
928
930 int tokens,
931 int aligned_embed_dim,
932 int num_heads,
933 int aligned_head_dim,
934 int aligned_intermediate_dim);
935
936/**
937 * @brief Test mega-fused OutProj + MLP kernel (Q5_0 weights)
938 *
939 * This is a simplified wrapper for parity testing that:
940 * - Uses Q5_0 for W_o and W1 weights
941 * - Uses Q4_K for W2 weights
942 * - Allocates scratch internally
943 *
944 * @param attn_out Attention output [num_heads, tokens, head_dim] (FP32, head-major)
945 * @param residual Residual input [tokens, embed_dim] (FP32)
946 * @param ln2_gamma RMSNorm gamma [embed_dim] (FP32)
947 * @param wo OutProj weights [embed_dim, embed_dim] (Q5_0)
948 * @param w1 MLP W1 weights [2*intermediate, embed_dim] (Q5_0)
949 * @param w2 MLP W2 weights [embed_dim, intermediate] (Q4_K or Q6_K)
950 * @param output Output [tokens, embed_dim] (FP32)
951 * @param tokens Number of tokens
952 * @param num_heads Number of attention heads
953 * @param head_dim Dimension per head
954 * @param embed_dim Embedding dimension (= num_heads * head_dim)
955 * @param intermediate MLP intermediate dimension
956 * @param eps RMSNorm epsilon
957 * @param w2_is_q6k If true, W2 is Q6_K; if false, W2 is Q4_K
958 */
960 const float *attn_out,
961 const float *residual,
962 const float *ln2_gamma,
963 const void *wo,
964 const void *w1,
965 const void *w2,
966 float *output,
967 int tokens,
968 int num_heads,
969 int head_dim,
970 int embed_dim,
971 int intermediate,
972 float eps,
973 int w2_is_q6k)
974{
975 /* CK uses dtype enum: CK_DT_Q5_0 = 11, CK_DT_Q4_K = 7, CK_DT_Q6_K = 8 */
976 const int CK_DT_Q5_0_VAL = 11;
977 const int CK_DT_Q4_K_VAL = 7;
978 const int CK_DT_Q6_K_VAL = 8;
979
980 /* For parity testing, aligned = actual (no padding) */
981 int aligned_embed_dim = embed_dim;
982 int aligned_head_dim = head_dim;
983 int aligned_intermediate = intermediate;
984
985 /* Ensure intermediate is multiple of 256 (QK_K) for K-quants */
986 if ((intermediate % 256) != 0) {
987 aligned_intermediate = ((intermediate + 255) / 256) * 256;
988 }
989
990 /* Allocate scratch */
992 tokens, aligned_embed_dim, num_heads, aligned_head_dim, aligned_intermediate);
993
994 void *scratch = malloc(scratch_size);
995 if (!scratch) {
996 return;
997 }
998
999 /* Call the mega-fused kernel */
1001 output,
1002 attn_out,
1003 residual,
1004 ln2_gamma,
1005 wo, NULL, CK_DT_Q5_0_VAL, /* W_o with Q5_0 */
1006 w1, NULL, CK_DT_Q5_0_VAL, /* W1 with Q5_0 */
1007 w2, NULL, w2_is_q6k ? CK_DT_Q6_K_VAL : CK_DT_Q4_K_VAL, /* W2 with Q4_K or Q6_K */
1008 tokens,
1009 embed_dim,
1010 aligned_embed_dim,
1011 num_heads,
1012 aligned_head_dim,
1013 intermediate,
1014 aligned_intermediate,
1015 eps,
1016 scratch
1017 );
1018
1019 free(scratch);
1020}
1021
1022/* ============================================================================
1023 * Utility Functions
1024 * ============================================================================ */
1025
1027{
1028 return sizeof(block_q4_K);
1029}
1030
1032{
1033 return sizeof(block_q6_K);
1034}
1035
1037{
1038 return sizeof(block_q8_K);
1039}
1040
1042{
1043 return QK_K;
1044}
void ck_test_quantize_q8_k(const float *src, void *dst, int n)
Quantize FP32 to Q8_K (for activations)
void dequant_q4_0_row(const void *src, float *dst, size_t n_elements)
Dequantize Q4_0 row (multiple blocks)
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 ck_test_gemm_q4_k(const void *weight_q4k, const float *input_f32, float *output, int rows, int cols, int n_tokens)
Q4_K GEMM - batched matrix multiply with quantized weights.
int ck_get_block_q8_k_size(void)
Get Q8_K block size in bytes.
void ck_test_dequant_q6_k(const void *src, float *dst, int n)
Dequantize Q6_K data to FP32.
void ck_test_rope(float *q, float *k, int n_tokens, int n_heads, int n_heads_kv, int head_dim, int pos_offset, float theta)
RoPE (Rotary Position Embedding)
void ck_test_recurrent_silu(const float *x, float *out, int rows, int dim)
Apply SiLU elementwise to recurrent rows.
void recurrent_qk_l2_norm_forward(float *q, float *k, int rows, int q_dim, int k_dim, int head_dim, float eps)
void swiglu_forward(const float *input, float *output, int tokens, int dim)
void ck_test_recurrent_dt_gate(const float *alpha, const float *dt_bias, const float *a, float *gate, int rows, int dim)
Transform recurrent alpha rows into the DeltaNet gate.
void ck_test_gated_deltanet_autoregressive(const float *q, const float *k, const float *v, const float *g, const float *beta, const float *state_in, float *state_out, float *out, int num_heads, int state_dim, float norm_eps)
Gated DeltaNet autoregressive update.
void ck_test_attn_gate_sigmoid_mul(const float *x, const float *gate, float *out, int rows, int dim)
Multiply attention output rows by sigmoid(gate) elementwise.
void ck_test_split_q_gate(const float *packed_qg, float *q, float *gate, int rows, int q_dim, int gate_dim, int group_dim)
Split a packed full-attention Q+gate matrix into Q rows and gate rows.
void ck_test_gemv_q5_0_q8_0(const void *weight_q5_0, const float *input_f32, float *output, int rows, int cols)
Q5_0 x Q8_0 quantized GEMV - matches llama.cpp's approach.
void ck_test_recurrent_conv_state_update(const float *state_in, const float *q, const float *k, const float *v, float *conv_x, float *state_out, int history_len, int num_seqs, int num_tokens, int q_dim, int k_dim, int v_dim)
Build the recurrent convolution input history window.
void gemv_q8_0(float *y, const void *W, const float *x, int M, int K)
Auto-dispatch GEMV for Q8_0 weights based on CPU features.
void ck_test_recurrent_split_conv_qkv(const float *packed_qkv, float *q, float *k, float *v, int rows, int q_dim, int k_dim, int v_dim)
Split the post-convolution recurrent packed QKV rows.
void ck_test_dequant_q4_0(const void *src, float *dst, int n)
Dequantize Q4_0 data to FP32.
void ck_test_softmax(const float *input, float *output, int n)
Softmax (simple, non-causal)
void ck_test_rmsnorm(const float *input, const float *weight, float *output, int n_tokens, int dim, float eps)
RMSNorm.
void gemv_q5_0_q8_0(float *y, const void *W, const void *x_q8, int M, int K)
Matrix-vector multiply with Q5_0 weights and Q8_0 input.
void attn_gate_sigmoid_mul_forward(const float *x, const float *gate, float *out, int rows, int num_heads, int state_dim)
void recurrent_split_conv_qkv_forward(const float *packed_qkv, float *q, float *k, float *v, int rows, int q_dim, int k_dim, int v_dim)
void recurrent_conv_state_update_forward(const float *state_in, const float *q, const float *k, const float *v, float *conv_x, float *state_out, int history_len, int num_seqs, int num_tokens, int q_dim, int k_dim, int v_dim)
void gemv_q5_0(float *y, const void *W, const float *x, int M, int K)
Auto-dispatch GEMV for Q5_0 weights based on CPU features.
void ck_test_gemm_q5_0(const void *weight_q5_0, const float *input_f32, float *output, int rows, int cols, int n_tokens)
Test Q5_0 x Q8_0 GEMM (batch matrix multiply)
void ck_test_dequant_q4_k(const void *src, float *dst, int n)
Dequantize Q4_K data to FP32.
void ck_test_attention_causal(const float *q, const float *k, const float *v, float *out, int num_heads, int num_kv_heads, int tokens, int seq_len, int head_dim)
Multi-head causal attention for prefill (head-major layout)
void ck_test_gemv_q4_k(const void *weight_q4k, const float *input_f32, float *output, int cols)
Q4_K GEMV - dot product of quantized weights and FP32 input.
void mega_fused_outproj_mlp_prefill(float *output, const float *attn_out, const float *residual, const float *ln2_gamma, const void *wo, const float *bo, int wo_dt, const void *w1, const float *b1, int w1_dt, const void *w2, const float *b2, int w2_dt, int tokens, int embed_dim, int aligned_embed_dim, int num_heads, int aligned_head_dim, int intermediate_dim, int aligned_intermediate_dim, float eps, void *scratch)
void quantize_row_q8_k(const float *x, void *vy, int k)
void gemm_nt_q4_k_q8_k(const void *A_q8, const void *B, const float *bias, float *C, int M, int N, int K)
void ck_test_recurrent_split_qkv(const float *packed_qkv, float *q, float *k, float *v, int rows, int q_dim, int k_dim, int v_dim)
Split a packed recurrent QKV matrix into explicit Q, K, and V outputs.
void ck_test_vec_dot_q6_k_q8_k(const void *weight_q6_k, const void *input_q8_k, float *output, int cols)
void ck_test_gemv_q8_0_q8_0(const void *weight_q8_0, const float *input_f32, float *output, int rows, int cols)
Q8_0 x Q8_0 quantized GEMV - matches llama.cpp's approach.
void ck_test_vec_dot_q8_0_q8_0(const void *weight_q8_0, const void *input_q8_0, float *output, int cols)
Direct Q8_0 x Q8_0 dot product test (takes pre-quantized Q8_0 input)
void ck_test_swiglu(const float *gate_up, float *output, int n_tokens, int intermediate_dim)
SwiGLU activation.
void ck_test_gemm_q6_k(const void *weight_q6k, const float *input_f32, float *output, int rows, int cols, int n_tokens)
Test Q6_K x Q8_K GEMM (batch matrix multiply)
void ck_test_vec_dot_q5_0_q8_0(const void *weight_q5_0, const void *input_q8_0, float *output, int cols)
Direct Q5_0 x Q8_0 dot product test (takes pre-quantized Q8_0 input)
void gemv_q6_k_q8_k(float *y, const void *W, const void *x_q8, int M, int K)
GEMV: y = W @ x where W is Q6_K and x is Q8_K.
void ck_test_recurrent_norm_gate(const float *x, const float *gate, const float *weight, float *out, int rows, int num_heads, int head_dim, float eps)
Per-head RMSNorm followed by SiLU(z) gating for recurrent outputs.
void vec_dot_q5_0_q8_0(int n, float *s, const void *vx, const void *vy)
Auto-dispatch quantized dot product Q5_0 x Q8_0.
void vec_dot_q6_k_q8_k(int n, float *s, const void *vx, const void *vy)
Q6_K x Q8_K dot product (single row)
void recurrent_silu_forward(const float *x, float *out, int rows, int dim)
int ck_get_qk_k(void)
Get QK_K (elements per super-block)
void rmsnorm_forward(const float *input, const float *gamma, float *output, float *rstd_cache, int tokens, int d_model, int aligned_embed_dim, float eps)
void ck_test_gemm_q8_0(const void *weight_q8_0, const float *input_f32, float *output, int rows, int cols, int n_tokens)
Test Q8_0 x Q8_0 GEMM (batch matrix multiply)
void rope_precompute_cache(float *cos_cache, float *sin_cache, int max_seq_len, int head_dim, float base, int rotary_dim, const char *scaling_type, float scaling_factor)
void gemm_nt_q8_0_q8_0(const void *A_q8, const void *B_q8, const float *bias, float *C, int M, int N, int K)
gemm_nt_q8_0_q8_0 with optional bias (matches header signature)
void gated_deltanet_autoregressive_forward(const float *q, const float *k, const float *v, const float *g, const float *beta, const float *state_in, float *state_out, float *out, int num_heads, int state_dim, float norm_eps)
void rope_forward_qk(float *q, float *k, const float *cos_cache, const float *sin_cache, int num_heads, int num_kv_heads, int num_tokens, int head_dim, int aligned_head_dim, int pos_offset)
void gemv_q8_0_q8_0(float *y, const void *W, const void *x_q8, int M, int K)
Matrix-vector multiply with Q8_0 weights and Q8_0 input.
void gemm_nt_q5_0_q8_0(const void *A_q8, const void *B_q5, const float *bias, float *C, int M, int N, int K)
Batch GEMM with Q5_0 weights and Q8_0 activations for prefill.
void gemv_q4_k_q8_k(float *y, const void *W, const void *x_q8, int M, int K)
void ssm_conv1d_forward(const float *conv_x, const float *kernel, float *out, int kernel_size, int num_channels, int num_tokens, int num_seqs)
void ck_test_gemv_q5_0(const void *weight_q5_0, const float *input_f32, float *output, int rows, int cols)
Q5_0 GEMV - matrix-vector multiply with Q5_0 weights.
void gemm_nt_q6_k_q8_k(const void *A_q8, const void *B, const float *bias, float *C, int M, int N, int K)
NT GEMM: C = A @ B^T where A is Q8_K and B is Q6_K.
size_t mega_fused_outproj_mlp_prefill_scratch_size(int tokens, int aligned_embed_dim, int num_heads, int aligned_head_dim, int aligned_intermediate_dim)
Get scratch buffer size for mega_fused_outproj_mlp_prefill.
void ck_test_ssm_conv1d(const float *conv_x, const float *kernel, float *out, int kernel_size, int num_channels, int num_tokens, int num_seqs)
qwen3next/Qwen3.5 SSM causal depthwise convolution.
void ck_test_recurrent_qk_l2_norm(float *q, float *k, int rows, int q_dim, int k_dim, int head_dim, float eps)
Apply per-head L2 normalization to recurrent Q/K rows in-place.
void recurrent_split_qkv_forward(const float *packed_qkv, float *q, float *k, float *v, int rows, int q_dim, int k_dim, int v_dim)
void dequant_q6_k_row(const void *src, float *dst, size_t n_elements)
Dequantize Q6_K row (multiple blocks)
void quantize_row_q8_0(const float *x, void *vy, int k)
Quantize FP32 to Q8_0 format (scalar reference)
void ck_test_rope_interleaved(float *q, float *k, int n_tokens, int n_heads, int n_heads_kv, int head_dim, int pos_offset, float theta)
RoPE with interleaved format (for llama.cpp compatibility)
void split_q_gate_forward(const float *packed_qg, float *q, float *gate, int rows, int q_dim, int gate_dim, int group_dim)
int ck_get_block_q4_k_size(void)
Get Q4_K block size in bytes.
int ck_get_block_q6_k_size(void)
Get Q6_K block size in bytes.
void ck_test_gemv_q8_0(const void *weight_q8_0, const float *input_f32, float *output, int rows, int cols)
Q8_0 GEMV - matrix-vector multiply with Q8_0 weights.
void vec_dot_q8_0_q8_0(int n, float *s, const void *vx, const void *vy)
Auto-dispatch quantized dot product Q8_0 x Q8_0.
void dequant_q4_k_row(const void *src, float *dst, size_t n_elements)
Dequantize Q4_K row (multiple blocks)
void recurrent_norm_gate_forward(const float *x, const float *gate, const float *weight, float *out, int rows, int num_heads, int head_dim, float eps)
void recurrent_dt_gate_forward(const float *alpha, const float *dt_bias, const float *a, float *gate, int rows, int num_heads, int state_dim)
void ck_test_gemv_q6_k(const void *weight_q6k, const float *input_f32, float *output, int cols)
Q6_K GEMV.
void ck_test_vec_dot_q4_k_q8_k(const void *weight_q4_k, const void *input_q8_k, float *output, int cols)
void ck_test_outproj_mlp_fused_q5_0(const float *attn_out, const float *residual, const float *ln2_gamma, const void *wo, const void *w1, const void *w2, float *output, int tokens, int num_heads, int head_dim, int embed_dim, int intermediate, float eps, int w2_is_q6k)
Test mega-fused OutProj + MLP kernel (Q5_0 weights)
C-Kernel-Engine Parity Testing API.
#define CK_QK_K
#define CK_QK8_0
Quantization block structures for weight-only quantization.
#define QK_K
#define C(color)
Definition show_config.c:39