← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
gemm_batch_int8.c
Go to the documentation of this file.
1/**
2 * @file gemm_batch_int8.c
3 * @brief Batch GEMM kernels for quantized weights with INT8 activations
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 batch matrix multiplication where:
15 * - Activations (A): Q8_0 quantized (INT8 + scale)
16 * - Weights (B): Q5_0 or Q8_0 quantized
17 * - Output (C): FP32
18 *
19 * Operation: C[M,N] = A[M,K] @ B[N,K]^T (B is transposed/row-major weights)
20 *
21 * Instruction Set Implementations:
22 * - Scalar: Reference implementation for correctness verification
23 * - AVX: 256-bit SIMD (8 floats, or 32 int8s)
24 * - AVX-512: 512-bit SIMD (16 floats, or 64 int8s)
25 * - AMX: Intel Advanced Matrix Extensions (tile-based, requires Sapphire Rapids+)
26 *
27 * Design Philosophy:
28 * - Every kernel MUST produce bit-identical results to scalar reference
29 * - Comprehensive testing against llama.cpp ensures correctness
30 * - Performance optimizations never compromise accuracy
31 *
32 * @author C-Kernel-Engine Team
33 * @date 2024
34 */
35
36#include <stdint.h>
37#include <stddef.h>
38#include <string.h>
39#include <math.h>
40#include "ckernel_quant.h"
41
42/* SIMD headers */
43#if defined(__AVX512F__) || defined(__AVX2__) || defined(__AVX__) || defined(__SSE4_1__)
44#include <immintrin.h>
45#endif
46
47/* AMX headers (requires specific compiler support) */
48#if defined(__AMX_INT8__) && defined(__AVX512VNNI__)
49#include <immintrin.h>
50#define HAS_AMX 1
51#else
52#define HAS_AMX 0
53#endif
54
55/* ============================================================================
56 * Constants and Block Sizes
57 * ============================================================================ */
58
59#define QK8_0 32 /* Q8_0: 32 weights per block */
60#define QK5_0 32 /* Q5_0: 32 weights per block */
61
62/* AMX tile dimensions */
63#define AMX_TILE_M 16
64#define AMX_TILE_N 16
65#define AMX_TILE_K 64
66
67/* Certified Q8_0 dot provider from gemm_kernels_q8_0.c. */
68void gemv_q8_0_q8_0_x4(float *y, const void *W, const void *x_q8,
69 int M, int K);
70void gemm_q8_0_q8_0_m2n4(float *C, const void *W, const void *A_q8,
71 int M, int N, int K);
72void gemm_q8_0_q8_0_m2n4_strided(float *C, int ldc, const void *W,
73 const void *A_q8, int M, int N, int K);
74
75#if defined(__AVX512VNNI__) && defined(__AVX512VL__)
76static inline int32_t hsum256_epi32_q8_batch(__m256i v)
77{
78 __m128i lo = _mm256_castsi256_si128(v);
79 __m128i hi = _mm256_extracti128_si256(v, 1);
80 __m128i sum = _mm_add_epi32(lo, hi);
81 sum = _mm_add_epi32(sum, _mm_srli_si128(sum, 8));
82 sum = _mm_add_epi32(sum, _mm_srli_si128(sum, 4));
83 return _mm_cvtsi128_si32(sum);
84}
85
86static inline int32_t dot_q8_0_q8_0_32_vnni_i32(const int8_t *a, const int8_t *b)
87{
88 const __m256i va = _mm256_loadu_si256((const __m256i *)a);
89 const __m256i vb = _mm256_loadu_si256((const __m256i *)b);
90 const __m256i va_u = _mm256_xor_si256(va, _mm256_set1_epi8((char)0x80));
91 const __m256i dot_u_s = _mm256_dpbusd_epi32(_mm256_setzero_si256(), va_u, vb);
92 const __m256i sum_b = _mm256_dpbusd_epi32(_mm256_setzero_si256(), _mm256_set1_epi8(1), vb);
93 const __m256i correction = _mm256_slli_epi32(sum_b, 7);
94 return hsum256_epi32_q8_batch(_mm256_sub_epi32(dot_u_s, correction));
95}
96#endif
97
98/* ============================================================================
99 * SECTION 1: GEMM Q8_0 x Q8_0 -> FP32
100 *
101 * Both weights and activations are Q8_0 quantized.
102 * This is the simplest case - direct INT8 x INT8 -> INT32 accumulation.
103 * ============================================================================ */
104
105/**
106 * @brief Scalar reference: gemm_nt_q8_0_q8_0
107 *
108 * C[m,n] = sum_k( dequant(A[m,k]) * dequant(B[n,k]) )
109 * = sum_blocks( d_a * d_b * sum_j(a_qs[j] * b_qs[j]) )
110 *
111 * @param A Input activations [M, K] in Q8_0 format
112 * @param B Weight matrix [N, K] in Q8_0 format (row-major, each row is one output)
113 * @param C Output matrix [M, N] in FP32
114 * @param M Number of tokens (batch size)
115 * @param N Number of output features (rows in B)
116 * @param K Number of input features (must be multiple of 32)
117 */
119 const void *A,
120 const void *B,
121 float *C,
122 int M, int N, int K)
123{
124 const int nb = K / QK8_0; /* Number of blocks per row */
125 const block_q8_0 *a_blocks = (const block_q8_0 *)A;
126 const block_q8_0 *b_blocks = (const block_q8_0 *)B;
127
128 for (int m = 0; m < M; m++) {
129 const block_q8_0 *a_row = a_blocks + (size_t)m * nb;
130
131 for (int n = 0; n < N; n++) {
132 const block_q8_0 *b_row = b_blocks + (size_t)n * nb;
133 float sum = 0.0f;
134
135 for (int ib = 0; ib < nb; ib++) {
136 const float d_a = CK_FP16_TO_FP32(a_row[ib].d);
137 const float d_b = CK_FP16_TO_FP32(b_row[ib].d);
138 const float d = d_a * d_b;
139
140 int32_t sumi = 0;
141 for (int j = 0; j < QK8_0; j++) {
142 sumi += (int32_t)a_row[ib].qs[j] * (int32_t)b_row[ib].qs[j];
143 }
144
145 sum += d * (float)sumi;
146 }
147
148 C[(size_t)m * N + n] = sum;
149 }
150 }
151}
152
153#if defined(__AVX2__)
154/**
155 * @brief AVX2 implementation: gemm_nt_q8_0_q8_0
156 *
157 * Uses 256-bit vectors to process 32 int8 values at once.
158 * Requires AVX2 for _mm256_cvtepi8_epi16, _mm256_madd_epi16, etc.
159 * Accumulates in INT32, then scales by d_a * d_b.
160 */
161void gemm_nt_q8_0_q8_0_avx2(
162 const void *A,
163 const void *B,
164 float *C,
165 int M, int N, int K)
166{
167 const int nb = K / QK8_0;
168 const block_q8_0 *a_blocks = (const block_q8_0 *)A;
169
170 for (int m = 0; m < M; m++) {
171 const block_q8_0 *a_row = a_blocks + (size_t)m * nb;
172 gemv_q8_0_q8_0_x4(C + (size_t)m * (size_t)N, B, a_row, N, K);
173 }
174}
175#endif /* __AVX2__ */
176
177#if defined(__AVX__) && !defined(__AVX2__)
178/**
179 * @brief AVX (SSE4.1) implementation: gemm_nt_q8_0_q8_0
180 *
181 * Uses 128-bit SSE4.1 intrinsics to process 32 int8 values per block
182 * in 4 chunks of 8. Available on all AVX-capable CPUs (Sandy Bridge+).
183 * Fills the gap between AVX2 and scalar fallback.
184 */
185void gemm_nt_q8_0_q8_0_avx(
186 const void *A,
187 const void *B,
188 float *C,
189 int M, int N, int K)
190{
191 const int nb = K / QK8_0;
192 const block_q8_0 *a_blocks = (const block_q8_0 *)A;
193 const block_q8_0 *b_blocks = (const block_q8_0 *)B;
194
195 for (int m = 0; m < M; m++) {
196 const block_q8_0 *a_row = a_blocks + (size_t)m * nb;
197 for (int n = 0; n < N; n++) {
198 const block_q8_0 *b_row = b_blocks + (size_t)n * nb;
199 float sum = 0.0f;
200
201 for (int ib = 0; ib < nb; ib++) {
202 const float d = CK_FP16_TO_FP32(a_row[ib].d)
203 * CK_FP16_TO_FP32(b_row[ib].d);
204 const int8_t *a_qs = a_row[ib].qs;
205 const int8_t *b_qs = b_row[ib].qs;
206
207 /* 4 chunks of 8 int8 values: load, sign-extend to int16, madd to int32 */
208 __m128i d0 = _mm_madd_epi16(
209 _mm_cvtepi8_epi16(_mm_loadl_epi64((const __m128i *)(a_qs + 0))),
210 _mm_cvtepi8_epi16(_mm_loadl_epi64((const __m128i *)(b_qs + 0))));
211 __m128i d1 = _mm_madd_epi16(
212 _mm_cvtepi8_epi16(_mm_loadl_epi64((const __m128i *)(a_qs + 8))),
213 _mm_cvtepi8_epi16(_mm_loadl_epi64((const __m128i *)(b_qs + 8))));
214 __m128i d2 = _mm_madd_epi16(
215 _mm_cvtepi8_epi16(_mm_loadl_epi64((const __m128i *)(a_qs + 16))),
216 _mm_cvtepi8_epi16(_mm_loadl_epi64((const __m128i *)(b_qs + 16))));
217 __m128i d3 = _mm_madd_epi16(
218 _mm_cvtepi8_epi16(_mm_loadl_epi64((const __m128i *)(a_qs + 24))),
219 _mm_cvtepi8_epi16(_mm_loadl_epi64((const __m128i *)(b_qs + 24))));
220
221 /* Reduce 4x4 int32 lanes to single int32 */
222 __m128i s4 = _mm_add_epi32(_mm_add_epi32(d0, d1),
223 _mm_add_epi32(d2, d3));
224 s4 = _mm_add_epi32(s4, _mm_srli_si128(s4, 8));
225 s4 = _mm_add_epi32(s4, _mm_srli_si128(s4, 4));
226 sum += d * (float)_mm_cvtsi128_si32(s4);
227 }
228 C[(size_t)m * N + n] = sum;
229 }
230 }
231}
232#endif /* __AVX__ && !__AVX2__ */
233
234#if defined(__AVX512F__)
235/**
236 * @brief AVX-512 implementation: gemm_nt_q8_0_q8_0
237 *
238 * Uses 512-bit vectors to process 64 int8 values at once.
239 * With VNNI, can use _mm512_dpbusd for even faster int8 dot products.
240 */
241void gemm_nt_q8_0_q8_0_avx512(
242 const void *A,
243 const void *B,
244 float *C,
245 int M, int N, int K)
246{
247 const int nb = K / QK8_0;
248 const block_q8_0 *a_blocks = (const block_q8_0 *)A;
249 const block_q8_0 *b_blocks = (const block_q8_0 *)B;
250
251 for (int m = 0; m < M; m++) {
252 const block_q8_0 *a_row = a_blocks + (size_t)m * nb;
253
254 for (int n = 0; n < N; n++) {
255 const block_q8_0 *b_row = b_blocks + (size_t)n * nb;
256 float sum = 0.0f;
257
258 for (int ib = 0; ib < nb; ib++) {
259 const float d_a = CK_FP16_TO_FP32(a_row[ib].d);
260 const float d_b = CK_FP16_TO_FP32(b_row[ib].d);
261 const float d = d_a * d_b;
262
263 /* Load 32 int8 values - use 256-bit load, extend to 512-bit for processing */
264 __m256i va_256 = _mm256_loadu_si256((const __m256i *)a_row[ib].qs);
265 __m256i vb_256 = _mm256_loadu_si256((const __m256i *)b_row[ib].qs);
266
267 /* Extend int8 to int16 for multiplication */
268 __m512i va_16 = _mm512_cvtepi8_epi16(va_256);
269 __m512i vb_16 = _mm512_cvtepi8_epi16(vb_256);
270
271 /* Multiply 32 pairs of int16 -> int16 (no overflow for int8*int8) */
272 __m512i prod = _mm512_mullo_epi16(va_16, vb_16);
273
274 /* Sum adjacent pairs to int32: madd adds pairs of int16 products */
275 __m512i sum_32 = _mm512_madd_epi16(prod, _mm512_set1_epi16(1));
276
277 /* Reduce all 16 int32 lanes to single int32 */
278 int32_t sumi = _mm512_reduce_add_epi32(sum_32);
279
280 sum += d * (float)sumi;
281 }
282
283 C[(size_t)m * N + n] = sum;
284 }
285 }
286}
287
288#if defined(__AVX512VNNI__) && defined(__AVX512VL__)
289/**
290 * @brief AVX-512 VNNI implementation: gemm_nt_q8_0_q8_0
291 *
292 * Uses VNNI instructions (_mm512_dpbusd_epi32) for optimal int8 dot products.
293 * VNNI computes: acc += sum(a[i] * b[i]) for 4 int8 pairs at once.
294 *
295 * Note: VNNI expects unsigned * signed for dpbusd, so we need to handle
296 * signed * signed carefully using dpbssd or offset trick.
297 */
298void gemm_nt_q8_0_q8_0_vnni(
299 const void *A,
300 const void *B,
301 float *C,
302 int M, int N, int K)
303{
304 const int nb = K / QK8_0;
305 const block_q8_0 *a_blocks = (const block_q8_0 *)A;
306 const block_q8_0 *b_blocks = (const block_q8_0 *)B;
307
308 for (int m = 0; m < M; m++) {
309 const block_q8_0 *a_row = a_blocks + (size_t)m * nb;
310
311 for (int n = 0; n < N; n++) {
312 const block_q8_0 *b_row = b_blocks + (size_t)n * nb;
313 float sum = 0.0f;
314
315 for (int ib = 0; ib < nb; ib++) {
316 const float d_a = CK_FP16_TO_FP32(a_row[ib].d);
317 const float d_b = CK_FP16_TO_FP32(b_row[ib].d);
318 const int32_t sumi = dot_q8_0_q8_0_32_vnni_i32(a_row[ib].qs, b_row[ib].qs);
319 sum += (d_a * d_b) * (float)sumi;
320 }
321
322 C[(size_t)m * N + n] = sum;
323 }
324 }
325}
326#endif /* __AVX512VNNI__ && __AVX512VL__ */
327#endif /* __AVX512F__ */
328
329/**
330 * @brief Dispatcher for gemm_nt_q8_0_q8_0
331 *
332 * Selects the best available implementation at runtime.
333 */
334/* Dispatcher is now gemm_nt_q8_0_q8_0_bias in Section 5 */
335
336
337/* ============================================================================
338 * SECTION 2: GEMM Q5_0 x Q8_0 -> FP32
339 *
340 * Weights are Q5_0 (5-bit), activations are Q8_0 (8-bit).
341 * Q5_0 requires unpacking: 4 bits from qs[] + 1 bit from qh[].
342 * ============================================================================ */
343
344/**
345 * @brief Scalar reference: gemm_nt_q5_0_q8_0
346 *
347 * Q5_0 weight reconstruction:
348 * weight[j] = d * ((qs_nibble | (qh_bit << 4)) - 16)
349 *
350 * For j in 0..15: use low nibble + qh bit j
351 * For j in 16..31: use high nibble + qh bit (j+16) -> actually bit (j) for j=16..31
352 *
353 * @param A Input activations [M, K] in Q8_0 format
354 * @param B Weight matrix [N, K] in Q5_0 format
355 * @param C Output matrix [M, N] in FP32
356 * @param M Number of tokens (batch size)
357 * @param N Number of output features
358 * @param K Number of input features (must be multiple of 32)
359 */
361 const void *A,
362 const void *B,
363 float *C,
364 int M, int N, int K)
365{
366 const int nb = K / QK5_0;
367 const block_q8_0 *a_blocks = (const block_q8_0 *)A;
368 const block_q5_0 *b_blocks = (const block_q5_0 *)B;
369
370 for (int m = 0; m < M; m++) {
371 const block_q8_0 *a_row = a_blocks + (size_t)m * nb;
372
373 for (int n = 0; n < N; n++) {
374 const block_q5_0 *b_row = b_blocks + (size_t)n * nb;
375 float sum = 0.0f;
376
377 for (int ib = 0; ib < nb; ib++) {
378 const float d_a = CK_FP16_TO_FP32(a_row[ib].d);
379 const float d_b = CK_FP16_TO_FP32(b_row[ib].d);
380 const float d = d_a * d_b;
381
382 /* Load high bits as 32-bit value */
383 uint32_t qh;
384 memcpy(&qh, b_row[ib].qh, sizeof(qh));
385
386 int32_t sumi = 0;
387
388 /* Process 32 weights: j=0..15 uses low nibble, j=16..31 uses high nibble */
389 for (int j = 0; j < 16; j++) {
390 /* First 16 weights: low nibble + qh bit j */
391 const uint8_t xh_0 = ((qh >> j) & 1) << 4;
392 const int8_t w0 = (int8_t)(((b_row[ib].qs[j] & 0x0F) | xh_0) - 16);
393
394 /* Second 16 weights: high nibble + qh bit (j+16) */
395 const uint8_t xh_1 = ((qh >> (j + 16)) & 1) << 4;
396 const int8_t w1 = (int8_t)(((b_row[ib].qs[j] >> 4) | xh_1) - 16);
397
398 /* Accumulate with activation values */
399 sumi += (int32_t)w0 * (int32_t)a_row[ib].qs[j];
400 sumi += (int32_t)w1 * (int32_t)a_row[ib].qs[j + 16];
401 }
402
403 sum += d * (float)sumi;
404 }
405
406 C[(size_t)m * N + n] = sum;
407 }
408 }
409}
410
411/* ============================================================================
412 * SECTION 3: AMX Implementation (Intel Advanced Matrix Extensions)
413 *
414 * AMX uses tile registers (TMM0-TMM7) for matrix operations.
415 * Each tile can hold up to 16 rows x 64 bytes (1KB).
416 *
417 * Key operations:
418 * _tile_loadd: Load tile from memory
419 * _tile_dpbssd: Signed int8 dot product accumulate (A signed, B signed)
420 * _tile_stored: Store tile to memory
421 *
422 * Requirements:
423 * - Sapphire Rapids or later CPU
424 * - __AMX_INT8__ defined
425 * - OS support (XSAVE/XRSTOR for tiles)
426 * ============================================================================ */
427
428#if HAS_AMX
429
430/* AMX tile configuration */
431typedef struct {
432 uint8_t palette_id;
433 uint8_t start_row;
434 uint8_t reserved[14];
435 uint16_t colsb[8];
436 uint8_t rows[8];
437} tile_config_t;
438
439static void amx_tile_config_init(void)
440{
441 static __thread int initialized = 0;
442 if (initialized) return;
443
444 tile_config_t tc = {0};
445 tc.palette_id = 1;
446
447 /* Configure tiles for our GEMM pattern:
448 * TMM0: accumulator C (16 rows x 16 cols of int32)
449 * TMM1: A tile (16 rows x 64 bytes = 64 int8 per row)
450 * TMM2: B tile (16 rows x 64 bytes)
451 */
452 tc.rows[0] = 16; tc.colsb[0] = 64; /* TMM0: 16x16 int32 */
453 tc.rows[1] = 16; tc.colsb[1] = 64; /* TMM1: A */
454 tc.rows[2] = 16; tc.colsb[2] = 64; /* TMM2: B */
455 tc.rows[3] = 16; tc.colsb[3] = 64; /* TMM3: spare */
456 tc.rows[4] = 16; tc.colsb[4] = 64; /* TMM4: spare */
457 tc.rows[5] = 16; tc.colsb[5] = 64; /* TMM5: spare */
458 tc.rows[6] = 16; tc.colsb[6] = 64; /* TMM6: spare */
459 tc.rows[7] = 16; tc.colsb[7] = 64; /* TMM7: spare */
460
461 _tile_loadconfig(&tc);
462 initialized = 1;
463}
464
465/**
466 * @brief AMX implementation: gemm_nt_q8_0_q8_0
467 *
468 * Uses AMX tiles for matrix multiplication.
469 * This is a simplified version - full implementation would tile the problem.
470 *
471 * Note: AMX requires specific data layout and tiling strategy.
472 * This implementation focuses on correctness; optimization is future work.
473 */
474void gemm_nt_q8_0_q8_0_amx(
475 const void *A,
476 const void *B,
477 float *C,
478 int M, int N, int K)
479{
480 amx_tile_config_init();
481
482 /* For now, fall back to AVX-512 implementation.
483 * Full AMX implementation requires:
484 * 1. Repacking data for tile-friendly layout
485 * 2. Proper tile blocking (16x16 tiles)
486 * 3. Scale factor handling after tile operations
487 *
488 * TODO: Implement full AMX path when we have test infrastructure
489 */
490 gemm_nt_q8_0_q8_0_avx512(A, B, C, M, N, K);
491}
492
493void gemm_nt_q5_0_q8_0_amx(
494 const void *A,
495 const void *B,
496 float *C,
497 int M, int N, int K)
498{
499 amx_tile_config_init();
500
501 /* Q5_0 requires unpacking before AMX can process.
502 * Strategy:
503 * 1. Unpack Q5_0 to int8 buffer
504 * 2. Use AMX for the actual GEMM
505 * 3. Apply scales
506 *
507 * For now, fall back to scalar reference.
508 */
509 gemm_nt_q5_0_q8_0_ref(A, B, C, M, N, K);
510}
511
512#endif /* HAS_AMX */
513
514
515/* ============================================================================
516 * SECTION 4: API Functions with Full Dispatch
517 * ============================================================================ */
518
519/**
520 * @brief Get the best implementation name for logging/debugging
521 */
523{
524#if defined(__AVX512VNNI__) && defined(__AVX512VL__)
525 return "AVX-512 VNNI";
526#elif HAS_AMX
527 /* The AMX entry points in this file currently fall back to AVX-512/ref. */
528 return "AMX fallback";
529#elif defined(__AVX512F__)
530 return "AVX-512";
531#elif defined(__AVX2__)
532 return "AVX2";
533#elif defined(__AVX__)
534 return "AVX";
535#else
536 return "Scalar";
537#endif
538}
539
540
541/* ============================================================================
542 * SECTION 5: API Wrappers with Bias Support
543 *
544 * These match the existing API signature in ckernel_quant.h
545 * ============================================================================ */
546
547/**
548 * @brief gemm_nt_q8_0_q8_0 with optional bias (matches header signature)
549 *
550 * C[m,n] = A[m,K] @ B[n,K]^T + bias[n]
551 */
553 const void *A,
554 const void *B,
555 const float *bias,
556 float *C,
557 int M, int N, int K)
558{
559 /* First compute GEMM */
560#if defined(__AVX2__)
561 /*
562 * The production contract is bit-exact with llama.cpp's eight-lane
563 * FP32 accumulation tree. AVX-512/VNNI changes the integer dot, but it
564 * must not silently replace that FP32 reduction with the scalar-per-block
565 * candidate above. The AVX2-named entry point delegates each activation
566 * row to the certified x4 provider, which also uses VNNI instructions when
567 * they are available while preserving the declared reduction order.
568 */
569 gemm_nt_q8_0_q8_0_avx2(A, B, C, M, N, K);
570#elif defined(__AVX__)
571 gemm_nt_q8_0_q8_0_avx(A, B, C, M, N, K);
572#else
573 gemm_nt_q8_0_q8_0_ref(A, B, C, M, N, K);
574#endif
575
576 /* Add bias if provided */
577 if (bias != NULL) {
578 for (int m = 0; m < M; m++) {
579 for (int n = 0; n < N; n++) {
580 C[(size_t)m * N + n] += bias[n];
581 }
582 }
583 }
584}
585
587 const void *A,
588 const void *B,
589 const float *bias,
590 float *C,
591 int M, int N, int K)
592{
593 gemm_q8_0_q8_0_m2n4(C, B, A, M, N, K);
594 if (bias != NULL) {
595 for (int m = 0; m < M; ++m) {
596 for (int n = 0; n < N; ++n) {
597 C[(size_t)m * (size_t)N + n] += bias[n];
598 }
599 }
600 }
601}
602
604 const void *A,
605 const void *B,
606 const float *bias,
607 float *C,
608 int M, int N, int K, int ldc)
609{
610 gemm_q8_0_q8_0_m2n4_strided(C, ldc, B, A, M, N, K);
611 if (bias != NULL) {
612 for (int m = 0; m < M; ++m) {
613 for (int n = 0; n < N; ++n) {
614 C[(size_t)m * (size_t)ldc + n] += bias[n];
615 }
616 }
617 }
618}
Quantization block structures for weight-only quantization.
#define CK_FP16_TO_FP32(x)
#define QK5_0
void gemm_nt_q5_0_q8_0_ref(const void *A, const void *B, float *C, int M, int N, int K)
Dispatcher for gemm_nt_q8_0_q8_0.
void gemm_nt_q8_0_q8_0_ref(const void *A, const void *B, float *C, int M, int N, int K)
Scalar reference: gemm_nt_q8_0_q8_0.
void gemv_q8_0_q8_0_x4(float *y, const void *W, const void *x_q8, int M, int K)
void gemm_nt_q8_0_q8_0(const void *A, const void *B, const float *bias, float *C, int M, int N, int K)
gemm_nt_q8_0_q8_0 with optional bias (matches header signature)
void gemm_q8_0_q8_0_m2n4(float *C, const void *W, const void *A_q8, int M, int N, int K)
const char * gemm_batch_int8_impl_name(void)
Get the best implementation name for logging/debugging.
void gemm_nt_q8_0_q8_0_m2n4_tile(const void *A, const void *B, const float *bias, float *C, int M, int N, int K, int ldc)
void gemm_q8_0_q8_0_m2n4_strided(float *C, int ldc, const void *W, const void *A_q8, int M, int N, int K)
void gemm_nt_q8_0_q8_0_m2n4(const void *A, const void *B, const float *bias, float *C, int M, int N, int K)
#define QK8_0
#define C(color)
Definition show_config.c:39
int8_t qs[32]