← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
gemm_kernels_q5_1.c
Go to the documentation of this file.
1/**
2 * @file gemm_kernels_q5_1.c
3 * @brief GEMM/GEMV kernels with Q5_1 quantized weights
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 * Q5_1 Format:
15 * - 32 weights per block
16 * - 1 FP16 scale (d) per block
17 * - 1 FP16 minimum (m) per block
18 * - Low 4-bits stored like Q4_1 (16 bytes)
19 * - High 1-bit packed separately (4 bytes)
20 * - 24 bytes per 32 weights = 6.0 bits/weight
21 *
22 * Dequantization: w = d * q5 + m
23 * where q5 = low4bit | (highbit << 4), giving values 0-31
24 *
25 * Operations:
26 * Forward: Y = W @ X (W is Q5_1, X and Y are FP32)
27 * Backward: dX = W^T @ dY (gradient w.r.t. input)
28 */
29
30#include <stdint.h>
31#include <stddef.h>
32#include <string.h>
33#include "ckernel_quant.h"
34
35#ifdef __AVX512F__
36#include <immintrin.h>
37#endif
38
39/* ============================================================================
40 * Forward Pass: GEMV y = W @ x
41 * ============================================================================ */
42
43/**
44 * @brief Matrix-vector multiply with Q5_1 weights (scalar reference)
45 *
46 * @param y Output vector [M]
47 * @param W Weight matrix in Q5_1 format [M x K]
48 * @param x Input vector [K]
49 * @param M Number of output rows
50 * @param K Number of columns (must be multiple of 32)
51 */
52void gemv_q5_1_ref(float *y,
53 const void *W,
54 const float *x,
55 int M, int K)
56{
57 const block_q5_1 *blocks = (const block_q5_1 *)W;
58 const int blocks_per_row = K / QK5_1;
59
60 for (int row = 0; row < M; row++) {
61 float sum = 0.0f;
62
63 for (int b = 0; b < blocks_per_row; b++) {
64 const block_q5_1 *block = &blocks[row * blocks_per_row + b];
65 const float d = CK_FP16_TO_FP32(block->d);
66 const float m = CK_FP16_TO_FP32(block->m);
67 const float *xp = &x[b * QK5_1];
68
69 /* Get high bits as 32-bit integer */
70 uint32_t qh;
71 memcpy(&qh, block->qh, sizeof(qh));
72
73 for (int i = 0; i < QK5_1 / 2; i++) {
74 const uint8_t packed = block->qs[i];
75
76 /* Extract low 4 bits */
77 const int lo0 = (packed & 0x0F);
78 const int lo1 = (packed >> 4);
79
80 /* Extract high bits */
81 const int hi0 = ((qh >> (2 * i + 0)) & 1) << 4;
82 const int hi1 = ((qh >> (2 * i + 1)) & 1) << 4;
83
84 /* Combine to 5-bit unsigned value */
85 const int q0 = lo0 | hi0;
86 const int q1 = lo1 | hi1;
87
88 /* Dequantize: w = d * q + m */
89 const float w0 = d * (float)q0 + m;
90 const float w1 = d * (float)q1 + m;
91
92 sum += w0 * xp[2 * i + 0];
93 sum += w1 * xp[2 * i + 1];
94 }
95 }
96
97 y[row] = sum;
98 }
99}
100
101#ifdef __AVX512F__
102/**
103 * @brief Matrix-vector multiply with Q5_1 weights (AVX-512)
104 */
105void gemv_q5_1_avx512(float *y,
106 const void *W,
107 const float *x,
108 int M, int K)
109{
110 const block_q5_1 *blocks = (const block_q5_1 *)W;
111 const int blocks_per_row = K / QK5_1;
112 const __m512i mask_lo = _mm512_set1_epi32(0x0F);
113
114 for (int row = 0; row < M; row++) {
115 __m512 acc = _mm512_setzero_ps();
116
117 for (int b = 0; b < blocks_per_row; b++) {
118 const block_q5_1 *block = &blocks[row * blocks_per_row + b];
119 const __m512 vscale = _mm512_set1_ps(CK_FP16_TO_FP32(block->d));
120 const __m512 vmin = _mm512_set1_ps(CK_FP16_TO_FP32(block->m));
121 const float *xp = &x[b * QK5_1];
122
123 /* Load high bits */
124 uint32_t qh;
125 memcpy(&qh, block->qh, sizeof(qh));
126
127 /* Load 16 bytes = 32 x 4-bit low weights */
128 __m128i packed = _mm_loadu_si128((const __m128i *)block->qs);
129 __m512i bytes = _mm512_cvtepu8_epi32(packed);
130
131 /* Extract low nibbles */
132 __m512i lo = _mm512_and_epi32(bytes, mask_lo);
133 __m512i hi_shift = _mm512_srli_epi32(bytes, 4);
134
135 /* Build high bit contribution for first 16 weights (indices 0,2,4,...,30) */
136 __m512i qh_lo = _mm512_set_epi32(
137 ((qh >> 30) & 1) << 4, ((qh >> 28) & 1) << 4,
138 ((qh >> 26) & 1) << 4, ((qh >> 24) & 1) << 4,
139 ((qh >> 22) & 1) << 4, ((qh >> 20) & 1) << 4,
140 ((qh >> 18) & 1) << 4, ((qh >> 16) & 1) << 4,
141 ((qh >> 14) & 1) << 4, ((qh >> 12) & 1) << 4,
142 ((qh >> 10) & 1) << 4, ((qh >> 8) & 1) << 4,
143 ((qh >> 6) & 1) << 4, ((qh >> 4) & 1) << 4,
144 ((qh >> 2) & 1) << 4, ((qh >> 0) & 1) << 4
145 );
146
147 /* Build high bit contribution for second 16 weights (indices 1,3,5,...,31) */
148 __m512i qh_hi = _mm512_set_epi32(
149 ((qh >> 31) & 1) << 4, ((qh >> 29) & 1) << 4,
150 ((qh >> 27) & 1) << 4, ((qh >> 25) & 1) << 4,
151 ((qh >> 23) & 1) << 4, ((qh >> 21) & 1) << 4,
152 ((qh >> 19) & 1) << 4, ((qh >> 17) & 1) << 4,
153 ((qh >> 15) & 1) << 4, ((qh >> 13) & 1) << 4,
154 ((qh >> 11) & 1) << 4, ((qh >> 9) & 1) << 4,
155 ((qh >> 7) & 1) << 4, ((qh >> 5) & 1) << 4,
156 ((qh >> 3) & 1) << 4, ((qh >> 1) & 1) << 4
157 );
158
159 /* Combine low + high bits (no subtraction for Q5_1, it uses min instead) */
160 __m512i q_lo = _mm512_or_epi32(lo, qh_lo);
161 __m512i q_hi = _mm512_or_epi32(hi_shift, qh_hi);
162
163 /* Dequantize: w = d * q + m */
164 __m512 w_lo = _mm512_fmadd_ps(_mm512_cvtepi32_ps(q_lo), vscale, vmin);
165 __m512 w_hi = _mm512_fmadd_ps(_mm512_cvtepi32_ps(q_hi), vscale, vmin);
166
167 /* Load interleaved input */
168 __m512 x_even = _mm512_set_ps(
169 xp[30], xp[28], xp[26], xp[24], xp[22], xp[20], xp[18], xp[16],
170 xp[14], xp[12], xp[10], xp[8], xp[6], xp[4], xp[2], xp[0]);
171 __m512 x_odd = _mm512_set_ps(
172 xp[31], xp[29], xp[27], xp[25], xp[23], xp[21], xp[19], xp[17],
173 xp[15], xp[13], xp[11], xp[9], xp[7], xp[5], xp[3], xp[1]);
174
175 acc = _mm512_fmadd_ps(w_lo, x_even, acc);
176 acc = _mm512_fmadd_ps(w_hi, x_odd, acc);
177 }
178
179 y[row] = _mm512_reduce_add_ps(acc);
180 }
181}
182#endif
183
184/**
185 * @brief Auto-dispatch GEMV
186 */
187void gemv_q5_1(float *y,
188 const void *W,
189 const float *x,
190 int M, int K)
191{
192#ifdef __AVX512F__
193 gemv_q5_1_avx512(y, W, x, M, K);
194#else
195 gemv_q5_1_ref(y, W, x, M, K);
196#endif
197}
198
199/* ============================================================================
200 * Forward Pass: GEMM Y = W @ X
201 * ============================================================================ */
202
203/**
204 * @brief Matrix-matrix multiply with Q5_1 weights
205 */
206void gemm_q5_1(float *Y,
207 const void *W,
208 const float *X,
209 int M, int N, int K)
210{
211 for (int n = 0; n < N; n++) {
212 gemv_q5_1(&Y[n * M], W, &X[n * K], M, K);
213 }
214}
215
216/* ============================================================================
217 * Backward Pass: Gradient w.r.t. Input
218 * ============================================================================ */
219
220/**
221 * @brief Backward pass: compute input gradient
222 *
223 * @param dX Output gradient w.r.t. input [K]
224 * @param W Weight matrix in Q5_1 format [M x K]
225 * @param dY Gradient w.r.t. output [M]
226 * @param M Number of output rows
227 * @param K Number of columns (input dimension)
228 */
230 const void *W,
231 const float *dY,
232 int M, int K)
233{
234 const block_q5_1 *blocks = (const block_q5_1 *)W;
235 const int blocks_per_row = K / QK5_1;
236
237 /* Zero output gradient */
238 memset(dX, 0, K * sizeof(float));
239
240 /* Accumulate: dX += W^T @ dY */
241 for (int row = 0; row < M; row++) {
242 const float dy = dY[row];
243
244 for (int b = 0; b < blocks_per_row; b++) {
245 const block_q5_1 *block = &blocks[row * blocks_per_row + b];
246 const float d = CK_FP16_TO_FP32(block->d);
247 const float m = CK_FP16_TO_FP32(block->m);
248 float *dxp = &dX[b * QK5_1];
249
250 /* Get high bits */
251 uint32_t qh;
252 memcpy(&qh, block->qh, sizeof(qh));
253
254 for (int i = 0; i < QK5_1 / 2; i++) {
255 const uint8_t packed = block->qs[i];
256
257 /* Extract and reconstruct 5-bit values */
258 const int lo0 = (packed & 0x0F);
259 const int lo1 = (packed >> 4);
260 const int hi0 = ((qh >> (2 * i + 0)) & 1) << 4;
261 const int hi1 = ((qh >> (2 * i + 1)) & 1) << 4;
262 const int q0 = lo0 | hi0;
263 const int q1 = lo1 | hi1;
264
265 const float w0 = d * (float)q0 + m;
266 const float w1 = d * (float)q1 + m;
267
268 dxp[2 * i + 0] += w0 * dy;
269 dxp[2 * i + 1] += w1 * dy;
270 }
271 }
272 }
273}
274
275/**
276 * @brief Auto-dispatch backward
277 */
278void gemv_q5_1_backward(float *dX,
279 const void *W,
280 const float *dY,
281 int M, int K)
282{
283 gemv_q5_1_backward_ref(dX, W, dY, M, K);
284}
285
286/**
287 * @brief Batched backward pass
288 */
289void gemm_q5_1_backward(float *dX,
290 const void *W,
291 const float *dY,
292 int M, int N, int K)
293{
294 for (int n = 0; n < N; n++) {
295 gemv_q5_1_backward(&dX[n * K], W, &dY[n * M], M, K);
296 }
297}
298
299/* ============================================================================
300 * GEMM NT (Non-Transpose A, Transpose B) - C = A @ B^T
301 * ============================================================================ */
302
303/**
304 * @brief GEMM with transposed Q5_1 weights: C = A @ B^T
305 *
306 * @param A Input activations [M x K], row-major FP32
307 * @param B Weight matrix in Q5_1 format [N x K], row-major quantized
308 * @param bias Optional bias [N], NULL if not used
309 * @param C Output [M x N], row-major FP32
310 * @param M Batch size (number of tokens)
311 * @param N Output dimension
312 * @param K Input dimension
313 */
314void gemm_nt_q5_1(const float *A,
315 const void *B,
316 const float *bias,
317 float *C,
318 int M, int N, int K)
319{
320 const block_q5_1 *blocks = (const block_q5_1 *)B;
321 const int blocks_per_row = K / QK5_1;
322
323 for (int m = 0; m < M; m++) {
324 const float *a_row = &A[m * K];
325
326 for (int n = 0; n < N; n++) {
327 float sum = 0.0f;
328
329 for (int b = 0; b < blocks_per_row; b++) {
330 const block_q5_1 *block = &blocks[n * blocks_per_row + b];
331 const float d = CK_FP16_TO_FP32(block->d);
332 const float min = CK_FP16_TO_FP32(block->m);
333 const float *ap = &a_row[b * QK5_1];
334
335 uint32_t qh;
336 memcpy(&qh, block->qh, sizeof(qh));
337
338 for (int i = 0; i < QK5_1 / 2; i++) {
339 const uint8_t packed = block->qs[i];
340 const int lo0 = (packed & 0x0F);
341 const int lo1 = (packed >> 4);
342 const int hi0 = ((qh >> (2 * i + 0)) & 1) << 4;
343 const int hi1 = ((qh >> (2 * i + 1)) & 1) << 4;
344 const int q0 = lo0 | hi0;
345 const int q1 = lo1 | hi1;
346
347 const float w0 = d * (float)q0 + min;
348 const float w1 = d * (float)q1 + min;
349
350 sum += w0 * ap[2 * i + 0];
351 sum += w1 * ap[2 * i + 1];
352 }
353 }
354
355 C[m * N + n] = sum + (bias ? bias[n] : 0.0f);
356 }
357 }
358}
359
360/* ============================================================================
361 * Dot Product Utility
362 * ============================================================================ */
363
364float dot_q5_1(const void *w_q5_1, const float *x, int K)
365{
366 float result;
367 gemv_q5_1(&result, w_q5_1, x, 1, K);
368 return result;
369}
Quantization block structures for weight-only quantization.
#define CK_FP16_TO_FP32(x)
#define QK5_1
void gemm_q5_1_backward(float *dX, const void *W, const float *dY, int M, int N, int K)
Batched backward pass.
float dot_q5_1(const void *w_q5_1, const float *x, int K)
void gemv_q5_1(float *y, const void *W, const float *x, int M, int K)
Auto-dispatch GEMV.
void gemv_q5_1_ref(float *y, const void *W, const float *x, int M, int K)
Matrix-vector multiply with Q5_1 weights (scalar reference)
void gemm_nt_q5_1(const float *A, const void *B, const float *bias, float *C, int M, int N, int K)
GEMM with transposed Q5_1 weights: C = A @ B^T.
void gemv_q5_1_backward_ref(float *dX, const void *W, const float *dY, int M, int K)
Backward pass: compute input gradient.
void gemv_q5_1_backward(float *dX, const void *W, const float *dY, int M, int K)
Auto-dispatch backward.
void gemm_q5_1(float *Y, const void *W, const float *X, int M, int N, int K)
Matrix-matrix multiply with Q5_1 weights.
#define C(color)
Definition show_config.c:39
uint8_t qs[32/2]
uint8_t qh[4]