← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
gemm_kernels_q4k_q8k.c
Go to the documentation of this file.
1/**
2 * @file gemm_kernels_q4k_q8k.c
3 * @brief Q4_K (weights) x Q8_K (activations) kernels for inference
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 * Implements decode-style matvec/matmul where weights are Q4_K and the
15 * activations are quantized on-the-fly to Q8_K. This is inference-only;
16 * no backward pass is provided here.
17 */
18
19#include <assert.h>
20#if defined(__x86_64__) || defined(__i386__)
21#include <immintrin.h>
22#endif
23#if defined(__ARM_NEON) || defined(__aarch64__)
24#include <arm_neon.h>
25#endif
26#include <math.h>
27#include <stdlib.h>
28#include <string.h>
29
30#include "ck_threadpool.h"
31#include "ckernel_quant.h"
32
33void gemv_q4_k_q8_k_avx2(float *y,
34 const void *W,
35 const void *x_q8,
36 int M, int K);
37
38void gemv_q4_k_q8_k_vnni(float *y,
39 const void *W,
40 const void *x_q8,
41 int M, int K);
42
43void gemv_q4_k_q8_k_avx(float *y,
44 const void *W,
45 const void *x_q8,
46 int M, int K);
47
48void gemv_q4_k_q8_k_sse(float *y,
49 const void *W,
50 const void *x_q8,
51 int M, int K);
52
53static inline int ck_nearest_int(float fval) {
54 /* Bit-level round-to-nearest from llama.cpp (fast + deterministic). */
55 float val = fval + 12582912.f;
56 int i;
57 memcpy(&i, &val, sizeof(int));
58 return (i & 0x007fffff) - 0x00400000;
59}
60
61#if defined(__clang__)
62#pragma clang fp contract(off)
63#elif defined(__GNUC__)
64__attribute__((optimize("fp-contract=off")))
65#endif
66void quantize_row_q8_k_ref(const float *x, void *vy, int k) {
67 if (!x || !vy || k <= 0) {
68 return;
69 }
70 assert(k % QK_K == 0);
71 const int nb = k / QK_K;
72 block_q8_K *y = (block_q8_K *)vy;
73
74 for (int i = 0; i < nb; ++i) {
75 float max = 0.0f;
76 float amax = 0.0f;
77 for (int j = 0; j < QK_K; ++j) {
78 float ax = fabsf(x[j]);
79 if (ax > amax) {
80 amax = ax;
81 max = x[j];
82 }
83 }
84 if (!amax) {
85 y[i].d = 0.0f;
86 memset(y[i].qs, 0, sizeof(y[i].qs));
87 memset(y[i].bsums, 0, sizeof(y[i].bsums));
88 x += QK_K;
89 continue;
90 }
91
92 const float iscale = -127.0f / max;
93 for (int j = 0; j < QK_K; ++j) {
94 /* llama.cpp rounds the multiply before adding nearest_int's magic
95 * constant. Contracting both operations changes Q8_K tie cases. */
96 float scaled = iscale * x[j];
97 int v = ck_nearest_int(scaled);
98 if (v > 127) {
99 v = 127;
100 }
101 if (v < -128) {
102 v = -128;
103 }
104 y[i].qs[j] = (int8_t)v;
105 }
106
107 for (int j = 0; j < QK_K / 16; ++j) {
108 int sum = 0;
109 const int8_t *qs = &y[i].qs[j * 16];
110 for (int ii = 0; ii < 16; ++ii) {
111 sum += qs[ii];
112 }
113 y[i].bsums[j] = (int16_t)sum;
114 }
115
116 y[i].d = 1.0f / iscale;
117 x += QK_K;
118 }
119}
120
121#if defined(__ARM_NEON) || defined(__aarch64__)
122/* NEON Q8_K row quantizer. Byte-exact against quantize_row_q8_k_ref:
123 * - amax via IEEE max reduction (exact), then the first element whose |x|
124 * equals amax, matching the scalar strict-> tie-break.
125 * - Plain single-precision multiply (fp-contract off), then the same
126 * magic-constant round-to-nearest-even as ck_nearest_int, vectorized.
127 * - bsums recomputed from the stored int8 payload, integer-exact. */
128#if defined(__clang__)
129#pragma clang fp contract(off)
130#elif defined(__GNUC__)
131__attribute__((optimize("fp-contract=off")))
132#endif
133void quantize_row_q8_k_neon(const float *x, void *vy, int k) {
134 if (!x || !vy || k <= 0) {
135 return;
136 }
137 assert(k % QK_K == 0);
138 const int nb = k / QK_K;
139 block_q8_K *y = (block_q8_K *)vy;
140
141 for (int i = 0; i < nb; ++i) {
142 float32x4_t vmax = vdupq_n_f32(0.0f);
143 for (int j = 0; j < QK_K; j += 16) {
144 const float32x4_t a0 = vabsq_f32(vld1q_f32(x + j + 0));
145 const float32x4_t a1 = vabsq_f32(vld1q_f32(x + j + 4));
146 const float32x4_t a2 = vabsq_f32(vld1q_f32(x + j + 8));
147 const float32x4_t a3 = vabsq_f32(vld1q_f32(x + j + 12));
148 vmax = vmaxq_f32(vmax, vmaxq_f32(vmaxq_f32(a0, a1),
149 vmaxq_f32(a2, a3)));
150 }
151 float lanes[4];
152 vst1q_f32(lanes, vmax);
153 const float amax = fmaxf(fmaxf(lanes[0], lanes[1]),
154 fmaxf(lanes[2], lanes[3]));
155 if (!amax) {
156 y[i].d = 0.0f;
157 memset(y[i].qs, 0, sizeof(y[i].qs));
158 memset(y[i].bsums, 0, sizeof(y[i].bsums));
159 x += QK_K;
160 continue;
161 }
162
163 /* Scalar ref keeps the FIRST element reaching amax (strict >). */
164 float max = 0.0f;
165 for (int j = 0; j < QK_K; ++j) {
166 if (fabsf(x[j]) == amax) {
167 max = x[j];
168 break;
169 }
170 }
171
172 const float iscale = -127.0f / max;
173 const float32x4_t viscale = vdupq_n_f32(iscale);
174 const float32x4_t vmagic = vdupq_n_f32(12582912.0f);
175 const int32x4_t mmant = vdupq_n_s32(0x007fffff);
176 const int32x4_t mbias = vdupq_n_s32(0x00400000);
177 const int32x4_t vlo = vdupq_n_s32(-128);
178 const int32x4_t vhi = vdupq_n_s32(127);
179
180 for (int j = 0; j < QK_K; j += 16) {
181 /* ck_nearest_int(iscale * x[j]): one rounded multiply, then the
182 * magic-constant trick (round-to-nearest-even), vectorized. */
183 int32x4_t v0 = vreinterpretq_s32_f32(vaddq_f32(
184 vmulq_f32(viscale, vld1q_f32(x + j + 0)), vmagic));
185 int32x4_t v1 = vreinterpretq_s32_f32(vaddq_f32(
186 vmulq_f32(viscale, vld1q_f32(x + j + 4)), vmagic));
187 int32x4_t v2 = vreinterpretq_s32_f32(vaddq_f32(
188 vmulq_f32(viscale, vld1q_f32(x + j + 8)), vmagic));
189 int32x4_t v3 = vreinterpretq_s32_f32(vaddq_f32(
190 vmulq_f32(viscale, vld1q_f32(x + j + 12)), vmagic));
191 v0 = vsubq_s32(vandq_s32(v0, mmant), mbias);
192 v1 = vsubq_s32(vandq_s32(v1, mmant), mbias);
193 v2 = vsubq_s32(vandq_s32(v2, mmant), mbias);
194 v3 = vsubq_s32(vandq_s32(v3, mmant), mbias);
195 v0 = vminq_s32(vmaxq_s32(v0, vlo), vhi);
196 v1 = vminq_s32(vmaxq_s32(v1, vlo), vhi);
197 v2 = vminq_s32(vmaxq_s32(v2, vlo), vhi);
198 v3 = vminq_s32(vmaxq_s32(v3, vlo), vhi);
199 const int16x8_t p0 = vcombine_s16(vmovn_s32(v0), vmovn_s32(v1));
200 const int16x8_t p1 = vcombine_s16(vmovn_s32(v2), vmovn_s32(v3));
201 vst1q_s8(&y[i].qs[j], vcombine_s8(vmovn_s16(p0), vmovn_s16(p1)));
202 }
203
204 for (int j = 0; j < QK_K / 16; ++j) {
205 const int8x16_t q = vld1q_s8(&y[i].qs[j * 16]);
206 const int32x4_t s0 = vpaddlq_s16(vmovl_s8(vget_low_s8(q)));
207 const int32x4_t s1 = vpaddlq_s16(vmovl_s8(vget_high_s8(q)));
208 const int32x4_t s = vaddq_s32(s0, s1);
209 int32_t sl[4];
210 vst1q_s32(sl, s);
211 y[i].bsums[j] = (int16_t)(sl[0] + sl[1] + sl[2] + sl[3]);
212 }
213
214 y[i].d = 1.0f / iscale;
215 x += QK_K;
216 }
217}
218#endif /* __ARM_NEON || __aarch64__ */
219
220void quantize_row_q8_k_sse(const float *x, void *vy, int k);
221void quantize_row_q8_k_avx(const float *x, void *vy, int k);
222void quantize_row_q8_k_avx2(const float *x, void *vy, int k);
223void quantize_row_q8_k_avx512(const float *x, void *vy, int k);
224
225#if defined(__ARM_NEON) || defined(__aarch64__)
226void quantize_row_q8_k_neon(const float *x, void *vy, int k);
227#endif
228
229void quantize_row_q8_k(const float *x, void *vy, int k) {
230 const char *ref_env = getenv("CK_DEBUG_Q8K_REF");
231 if (ref_env && atoi(ref_env) != 0) {
232 quantize_row_q8_k_ref(x, vy, k);
233 return;
234 }
235#if defined(__AVX512F__) && defined(__AVX512BW__)
236 quantize_row_q8_k_avx512(x, vy, k);
237#elif defined(__AVX2__)
238 quantize_row_q8_k_avx2(x, vy, k);
239#elif defined(__AVX__)
240 quantize_row_q8_k_avx(x, vy, k);
241#elif defined(__SSE4_1__)
242 quantize_row_q8_k_sse(x, vy, k);
243#elif defined(__ARM_NEON) || defined(__aarch64__)
244 quantize_row_q8_k_neon(x, vy, k);
245#else
246 quantize_row_q8_k_ref(x, vy, k);
247#endif
248}
249
251 const char *ref_env = getenv("CK_DEBUG_Q8K_REF");
252 if (ref_env && atoi(ref_env) != 0) {
253 return "q8_k_quantize_ref";
254 }
255#if defined(__AVX512F__) && defined(__AVX512BW__)
256 return "q8_k_quantize_avx512";
257#elif defined(__AVX2__)
258 return "q8_k_quantize_avx2";
259#elif defined(__AVX__)
260 return "q8_k_quantize_avx";
261#elif defined(__SSE4_1__)
262 return "q8_k_quantize_sse";
263#elif defined(__ARM_NEON) || defined(__aarch64__)
264 return "q8_k_quantize_neon";
265#else
266 return "q8_k_quantize_ref";
267#endif
268}
269
270void quantize_batch_q8_k_4row_nearest_even(const float *x, void *vy,
271 int num_rows, int k) {
272 if (!x || !vy || num_rows <= 0 || k <= 0) {
273 return;
274 }
275 assert(k % QK_K == 0);
276
277 block_q8_K *y = (block_q8_K *)vy;
278 const int blocks_per_row = k / QK_K;
279
280 /* Q8_K bytes are a numerical ABI. The previous four-row AVX2 path used
281 * a different max/scale evaluation order and changed real Qwen3-VL
282 * visual-prefix scales by one FP32 ULP. Keep this public grouped ABI on
283 * the canonical row provider until an optimized implementation is
284 * byte-exact against llama.cpp on both synthetic and production inputs. */
285 for (int row = 0; row < num_rows; ++row) {
287 x + (size_t)row * (size_t)k,
288 y + (size_t)row * (size_t)blocks_per_row,
289 k);
290 }
291}
292
293static float dot_q4_k_q8_k_ref(const block_q4_K *w,
294 const block_q8_K *x,
295 int k)
296{
297 const int nb = k / QK_K;
298 float sumf = 0.0f;
299
300 for (int i = 0; i < nb; ++i) {
301 uint8_t sc[8], m_val[8];
302 unpack_q4_k_scales(w[i].scales, sc, m_val);
303
304 const float d = CK_FP16_TO_FP32(w[i].d) * x[i].d;
305 const float dmin = CK_FP16_TO_FP32(w[i].dmin) * x[i].d;
306
307 int sumi = 0;
308 for (int j = 0; j < QK_K / 16; ++j) {
309 sumi += (int)x[i].bsums[j] * (int)m_val[j / 2];
310 }
311
312 int32_t scaled_sum = 0;
313 for (int group = 0; group < 4; ++group) {
314 const uint8_t *qs = &w[i].qs[group * 32];
315 const int8_t *q8_lo = &x[i].qs[group * 64];
316 const int8_t *q8_hi = q8_lo + 32;
317 int32_t lo = 0;
318 int32_t hi = 0;
319 for (int l = 0; l < 32; ++l) {
320 lo += (int32_t)(qs[l] & 0x0F) * (int32_t)q8_lo[l];
321 hi += (int32_t)(qs[l] >> 4) * (int32_t)q8_hi[l];
322 }
323 scaled_sum += (int32_t)sc[2 * group] * lo;
324 scaled_sum += (int32_t)sc[2 * group + 1] * hi;
325 }
326 sumf += d * (float)scaled_sum - dmin * (float)sumi;
327 }
328 return sumf;
329}
330
331#if defined(__ARM_FEATURE_DOTPROD)
332static int32_t reduce_s32x4(int32x4_t v) {
333 int32_t lanes[4];
334 vst1q_s32(lanes, v);
335 return lanes[0] + lanes[1] + lanes[2] + lanes[3];
336}
337
338/* Q4_K x Q8_K dot using the AArch64 dot-product extension (SDOT). Nibbles
339 * are in [0,15], so they are valid non-negative int8 values for vdotq_s32.
340 * Integer arithmetic matches the scalar reference exactly; the FP32
341 * per-super-block epilogue keeps the reference expression.
342 *
343 * Gated on __ARM_FEATURE_DOTPROD rather than plain NEON: on cores without
344 * SDOT the widening (vmovl/vmlal) variant is no faster than the compiler's
345 * own autovectorization of the scalar reference, so those hosts keep the
346 * reference path (baseline armv8-a builds are unaffected). */
347static float dot_q4_k_q8_k_neon(const block_q4_K *w,
348 const block_q8_K *x,
349 int k)
350{
351 const int nb = k / QK_K;
352 float sumf = 0.0f;
353 const uint8x16_t m4 = vdupq_n_u8(0x0F);
354
355 for (int i = 0; i < nb; ++i) {
356 uint8_t sc[8], m_val[8];
357 unpack_q4_k_scales(w[i].scales, sc, m_val);
358
359 const float d = CK_FP16_TO_FP32(w[i].d) * x[i].d;
360 const float dmin = CK_FP16_TO_FP32(w[i].dmin) * x[i].d;
361
362 /* sumi = sum_j bsums[j] * m[j/2] */
363 int16_t mv[16];
364 for (int j = 0; j < 16; ++j) {
365 mv[j] = (int16_t)m_val[j / 2];
366 }
367 const int16x8_t b0 = vld1q_s16(x[i].bsums);
368 const int16x8_t b1 = vld1q_s16(x[i].bsums + 8);
369 const int16x8_t m0 = vld1q_s16(mv);
370 const int16x8_t m1 = vld1q_s16(mv + 8);
371 int32x4_t sumi_v = vmull_s16(vget_low_s16(b0), vget_low_s16(m0));
372 sumi_v = vmlal_s16(sumi_v, vget_high_s16(b0), vget_high_s16(m0));
373 sumi_v = vmlal_s16(sumi_v, vget_low_s16(b1), vget_low_s16(m1));
374 sumi_v = vmlal_s16(sumi_v, vget_high_s16(b1), vget_high_s16(m1));
375 const int32_t sumi = reduce_s32x4(sumi_v);
376
377 /* Scale each group's partial dots in vector form so only one
378 * horizontal reduction is needed per super-block. */
379 int32x4_t scaled_v = vdupq_n_s32(0);
380 for (int group = 0; group < 4; ++group) {
381 const uint8_t *qs = &w[i].qs[group * 32];
382 const int8_t *q8_lo = &x[i].qs[group * 64];
383 const int8_t *q8_hi = q8_lo + 32;
384
385 const uint8x16_t q0 = vld1q_u8(qs);
386 const uint8x16_t q1 = vld1q_u8(qs + 16);
387
388 const int8x16_t lo0 = vreinterpretq_s8_u8(vandq_u8(q0, m4));
389 const int8x16_t lo1 = vreinterpretq_s8_u8(vandq_u8(q1, m4));
390 const int8x16_t hi0 = vreinterpretq_s8_u8(vshrq_n_u8(q0, 4));
391 const int8x16_t hi1 = vreinterpretq_s8_u8(vshrq_n_u8(q1, 4));
392
393 int32x4_t lo = vdotq_s32(vdupq_n_s32(0), lo0, vld1q_s8(q8_lo));
394 lo = vdotq_s32(lo, lo1, vld1q_s8(q8_lo + 16));
395 int32x4_t hi = vdotq_s32(vdupq_n_s32(0), hi0, vld1q_s8(q8_hi));
396 hi = vdotq_s32(hi, hi1, vld1q_s8(q8_hi + 16));
397
398 scaled_v = vmlaq_s32(scaled_v, lo,
399 vdupq_n_s32((int32_t)sc[2 * group]));
400 scaled_v = vmlaq_s32(scaled_v, hi,
401 vdupq_n_s32((int32_t)sc[2 * group + 1]));
402 }
403 const int32_t scaled_sum = reduce_s32x4(scaled_v);
404 sumf += d * (float)scaled_sum - dmin * (float)sumi;
405 }
406 return sumf;
407}
408
409static void gemv_q4_k_q8_k_neon(float *y,
410 const void *W,
411 const void *x_q8,
412 int M, int K)
413{
414 const block_q4_K *blocks = (const block_q4_K *)W;
415 const block_q8_K *x = (const block_q8_K *)x_q8;
416 const int blocks_per_row = K / QK_K;
417
418 for (int row = 0; row < M; ++row) {
419 const block_q4_K *w_row = blocks + (size_t)row * (size_t)blocks_per_row;
420 y[row] = dot_q4_k_q8_k_neon(w_row, x, K);
421 }
422}
423#endif /* __ARM_FEATURE_DOTPROD */
424
425void gemv_q4_k_q8_k_ref(float *y,
426 const void *W,
427 const void *x_q8,
428 int M, int K)
429{
430 if (!y || !W || !x_q8 || M <= 0 || K <= 0) {
431 return;
432 }
433
434 const block_q4_K *blocks = (const block_q4_K *)W;
435 const block_q8_K *x = (const block_q8_K *)x_q8;
436 const int blocks_per_row = K / QK_K;
437
438 for (int row = 0; row < M; ++row) {
439 const block_q4_K *w_row = blocks + (size_t)row * (size_t)blocks_per_row;
440 y[row] = dot_q4_k_q8_k_ref(w_row, x, K);
441 }
442}
443
444static int ck_q4k_q8k_force_ref(void)
445{
446 static int cached = -1;
447 if (cached < 0) {
448 const char *env = getenv("CK_DEBUG_Q4K_Q8_REF");
449 cached = (env && env[0] && env[0] != '0') ? 1 : 0;
450 }
451 return cached;
452}
453
454/* ============================================================================
455 * PARALLEL VERSIONS (for parallel orchestration)
456 *
457 * These receive ith (thread index) and nth (total threads) from orchestration.
458 * OpenMP lives in orchestration layer, NOT here.
459 *
460 * Naming: *_parallel = receives ith/nth, processes only its portion
461 * *_ref/_avx = single-threaded, processes all rows
462 * ============================================================================ */
463
465 const void *W,
466 const void *x_q8,
467 int M, int K,
468 int ith, int nth)
469{
470 if (!y || !W || !x_q8 || M <= 0 || K <= 0) {
471 return;
472 }
473 if (ith < 0 || nth <= 0 || ith >= nth) {
474 return;
475 }
476
477 /* Compute row range for this thread */
478 const int dr = (M + nth - 1) / nth;
479 const int r0 = dr * ith;
480 const int r1 = (r0 + dr < M) ? (r0 + dr) : M;
481
482 if (r0 >= M) {
483 return; /* This thread has no work */
484 }
485
486 const block_q4_K *blocks = (const block_q4_K *)W;
487 const block_q8_K *x = (const block_q8_K *)x_q8;
488 const int blocks_per_row = K / QK_K;
489
490 /* Only process rows [r0, r1) */
491 for (int row = r0; row < r1; ++row) {
492 const block_q4_K *w_row = blocks + (size_t)row * (size_t)blocks_per_row;
493 y[row] = dot_q4_k_q8_k_ref(w_row, x, K);
494 }
495}
496
497void gemv_q4_k_q8_k(float *y,
498 const void *W,
499 const void *x_q8,
500 int M, int K)
501{
502 if (ck_q4k_q8k_force_ref()) {
503 gemv_q4_k_q8_k_ref(y, W, x_q8, M, K);
504 return;
505 }
506#if defined(__AVX512VNNI__) && defined(__AVX512VL__) && !defined(CK_NO_AVX512_VNNI)
507 /* VNNI: Best for decode (single token) - INT8 dot product acceleration */
508 gemv_q4_k_q8_k_vnni(y, W, x_q8, M, K);
509#elif defined(__AVX2__)
510 gemv_q4_k_q8_k_avx2(y, W, x_q8, M, K);
511#elif defined(__AVX__)
512 /* AVX version uses maddubs_epi16 (more efficient than SSE) */
513 gemv_q4_k_q8_k_avx(y, W, x_q8, M, K);
514#elif defined(__SSE4_1__)
515 gemv_q4_k_q8_k_sse(y, W, x_q8, M, K);
516#elif defined(__ARM_FEATURE_DOTPROD)
517 /* SDOT NEON is integer-exact against the scalar reference (identical
518 * block arithmetic and FP32 epilogue); CK_DEBUG_Q4K_Q8_REF=1 still
519 * forces the reference above. Baseline armv8-a builds (no dot-product
520 * extension) keep the reference path, which the compiler already
521 * autovectorizes well. */
522 gemv_q4_k_q8_k_neon(y, W, x_q8, M, K);
523#else
524 gemv_q4_k_q8_k_ref(y, W, x_q8, M, K);
525#endif
526}
527
529{
530 if (ck_q4k_q8k_force_ref()) {
531 return "q4_k_q8_k_ref";
532 }
533#if defined(__AVX512VNNI__) && defined(__AVX512VL__) && !defined(CK_NO_AVX512_VNNI)
534 return "q4_k_q8_k_vnni";
535#elif defined(__AVX2__)
536 return "q4_k_q8_k_avx2";
537#elif defined(__AVX__)
538 return "q4_k_q8_k_avx";
539#elif defined(__SSE4_1__)
540 return "q4_k_q8_k_sse";
541#elif defined(__ARM_FEATURE_DOTPROD)
542 return "q4_k_q8_k_neon";
543#else
544 return "q4_k_q8_k_ref";
545#endif
546}
547
548void gemm_q4_k_q8_k_ref(float *Y,
549 const void *W,
550 const void *X_q8,
551 int M, int N, int K)
552{
553 if (!Y || !W || !X_q8 || M <= 0 || N <= 0 || K <= 0) {
554 return;
555 }
556
557 const block_q8_K *X = (const block_q8_K *)X_q8;
558 const int blocks_per_vec = K / QK_K;
559
560 for (int n = 0; n < N; ++n) {
561 const block_q8_K *x_row = X + (size_t)n * (size_t)blocks_per_vec;
562 gemv_q4_k_q8_k_ref(&Y[n * M], W, x_row, M, K);
563 }
564}
565
566typedef struct {
567 float *Y;
568 const void *W;
569 const block_q8_K *X;
570 int M_out;
571 int N_batch;
572 int K;
573 int blocks_per_vec;
574 int blocks_per_row;
575} gemm_q4_k_q8_k_work_t;
576
577static void gemm_q4_k_q8_k_thread_fn(int ith, int nth, void *args)
578{
579 gemm_q4_k_q8_k_work_t *a = (gemm_q4_k_q8_k_work_t *)args;
580 if (!a || ith < 0 || nth <= 0 || ith >= nth) {
581 return;
582 }
583
584 const int dr = (a->M_out + nth - 1) / nth;
585 const int r0 = dr * ith;
586 const int r1 = (r0 + dr < a->M_out) ? (r0 + dr) : a->M_out;
587 if (r0 >= a->M_out) {
588 return;
589 }
590
591 const block_q4_K *blocks = (const block_q4_K *)a->W;
592 const block_q4_K *w_start = blocks + (size_t)r0 * (size_t)a->blocks_per_row;
593 const int rows = r1 - r0;
594
595 for (int n = 0; n < a->N_batch; ++n) {
596 const block_q8_K *x_row = a->X + (size_t)n * (size_t)a->blocks_per_vec;
597 gemv_q4_k_q8_k(a->Y + (size_t)n * (size_t)a->M_out + (size_t)r0,
598 w_start,
599 x_row,
600 rows,
601 a->K);
602 }
603}
604
605void gemm_q4_k_q8_k(float *Y,
606 const void *W,
607 const void *X_q8,
608 int M, int N, int K)
609{
610 if (!Y || !W || !X_q8 || M <= 0 || N <= 0 || K <= 0) {
611 return;
612 }
613
614 const block_q8_K *X = (const block_q8_K *)X_q8;
615 const int blocks_per_vec = K / QK_K;
616 const int blocks_per_row = K / QK_K;
617 const size_t work_items = (size_t)M * (size_t)N;
618
619 if (work_items >= 4096u && M >= 512 && N > 1) {
620 ck_threadpool_t *pool = ck_threadpool_global();
621 const int pool_threads = pool ? ck_threadpool_n_threads(pool) : 1;
622 int active_threads = pool_threads;
623 if (active_threads > M) {
624 active_threads = M;
625 }
626 if (active_threads > 1) {
627 gemm_q4_k_q8_k_work_t work = {
628 .Y = Y,
629 .W = W,
630 .X = X,
631 .M_out = M,
632 .N_batch = N,
633 .K = K,
634 .blocks_per_vec = blocks_per_vec,
635 .blocks_per_row = blocks_per_row,
636 };
637 ck_threadpool_dispatch_n(pool, active_threads, gemm_q4_k_q8_k_thread_fn, &work);
638 return;
639 }
640 }
641
642 for (int n = 0; n < N; ++n) {
643 const block_q8_K *x_row = X + (size_t)n * (size_t)blocks_per_vec;
644 gemv_q4_k_q8_k(&Y[n * M], W, x_row, M, K);
645 }
646}
647
648void gemm_nt_q4_k_q8_k(const void *A_q8,
649 const void *B,
650 const float *bias,
651 float *C,
652 int M, int N, int K)
653{
654 if (!A_q8 || !B || !C) {
655 return;
656 }
657 if (M <= 0 || N <= 0 || K <= 0) {
658 return;
659 }
660
661 gemm_q4_k_q8_k(C, B, A_q8, /*M_out=*/N, /*N_batch=*/M, K);
662
663 if (!bias) {
664 return;
665 }
666
667 for (int i = 0; i < M; ++i) {
668 float *row = C + (size_t)i * (size_t)N;
669 for (int j = 0; j < N; ++j) {
670 row[j] += bias[j];
671 }
672 }
673}
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_n_threads(const ck_threadpool_t *pool)
Quantization block structures for weight-only quantization.
#define CK_FP16_TO_FP32(x)
static void unpack_q4_k_scales(const uint8_t *scales, uint8_t *sc, uint8_t *m)
Unpack Q4_K sub-block scales and mins.
#define QK_K
void gemv_q4_k_q8_k_avx2(float *y, const void *W, const void *x_q8, int M, int K)
void quantize_row_q8_k_avx512(const float *x, void *vy, int k)
void quantize_row_q8_k_avx2(const float *x, void *vy, int k)
void gemv_q4_k_q8_k_vnni(float *y, const void *W, const void *x_q8, int M, int K)
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 gemv_q4_k_q8_k_parallel(float *y, const void *W, const void *x_q8, int M, int K, int ith, int nth)
void quantize_batch_q8_k_4row_nearest_even(const float *x, void *vy, int num_rows, int k)
void gemm_q4_k_q8_k_ref(float *Y, const void *W, const void *X_q8, int M, int N, int K)
static int ck_nearest_int(float fval)
void quantize_row_q8_k_avx(const float *x, void *vy, int k)
void gemv_q4_k_q8_k(float *y, const void *W, const void *x_q8, int M, int K)
void gemv_q4_k_q8_k_ref(float *y, const void *W, const void *x_q8, int M, int K)
void gemm_q4_k_q8_k(float *Y, const void *W, const void *X_q8, int M, int N, int K)
const char * ck_q4_k_q8_k_provider_name(void)
static int ck_q4k_q8k_force_ref(void)
const char * ck_quantize_row_q8_k_provider_name(void)
void quantize_row_q8_k_sse(const float *x, void *vy, int k)
static float dot_q4_k_q8_k_ref(const block_q4_K *w, const block_q8_K *x, int k)
static void gemm_q4_k_q8_k_thread_fn(int ith, int nth, void *args)
void quantize_row_q8_k_ref(const float *x, void *vy, int k)
void gemv_q4_k_q8_k_avx(float *y, const void *W, const void *x_q8, int M, int K)
void gemv_q4_k_q8_k_sse(float *y, const void *W, const void *x_q8, int M, int K)
#define C(color)
Definition show_config.c:39
uint8_t qs[256/2]
int8_t qs[256]
int16_t bsums[256/16]
__attribute__((visibility("default"))) CKTokenizer *ck_tokenizer_create(CKTokenizerType type)