← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
gemm_kernels_q4k_sse.c
Go to the documentation of this file.
1/**
2 * @file gemm_kernels_q4k_sse.c
3 * @brief SSE4.1 Q4_K x Q8_K dot product kernels
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 * Compatible with Sandy Bridge/Ivy Bridge and later.
15 */
16
17#include <immintrin.h>
18#include <stdint.h>
19#include <string.h>
20
21#include "ckernel_quant.h"
22
23/* SSE4.1 implementation of Q4_K * Q8_K dot product */
24
25static inline int32_t hsum_epi32_sse(__m128i v) {
26 __m128i shuf = _mm_shuffle_epi32(v, _MM_SHUFFLE(1, 0, 3, 2));
27 __m128i sums = _mm_add_epi32(v, shuf);
28 shuf = _mm_shuffle_epi32(sums, _MM_SHUFFLE(2, 3, 0, 1));
29 sums = _mm_add_epi32(sums, shuf);
30 return _mm_cvtsi128_si32(sums);
31}
32
33void gemv_q4_k_q8_k_sse(float *y,
34 const void *W,
35 const void *x_q8,
36 int M, int K)
37{
38 const block_q4_K *blocks = (const block_q4_K *)W;
39 const block_q8_K *x = (const block_q8_K *)x_q8;
40 const int blocks_per_row = K / QK_K;
41
42 const __m128i mask_low = _mm_set1_epi8(0x0F);
43
44 for (int row = 0; row < M; ++row) {
45 float sumf = 0.0f;
46 const block_q4_K *w_row = blocks + row * blocks_per_row;
47
48 for (int i = 0; i < blocks_per_row; ++i) {
49 const block_q4_K *b4 = &w_row[i];
50 const block_q8_K *b8 = &x[i];
51
52 // Unpack scales (same as ref)
53 uint8_t sc[8], m_val[8];
54 unpack_q4_k_scales(b4->scales, sc, m_val);
55
56 float d = CK_FP16_TO_FP32(b4->d) * b8->d;
57 float dmin = CK_FP16_TO_FP32(b4->dmin) * b8->d;
58
59 int is = 0;
60 int q_offset = 0;
61
62 // Process 4 chunks of 64 elements (256 total)
63 for (int j = 0; j < QK_K; j += 64) {
64 // We process 32 bytes of qs (covering 64 elements via low/high nibbles)
65 // We access qs[0..31] relative to q_offset
66
67 // Accumulators for this 64-element chunk
68 __m128i acc_lo = _mm_setzero_si128();
69 __m128i acc_hi = _mm_setzero_si128();
70
71 // Inner loop: 2 iters of 16 bytes (32 elements)
72 for (int l = 0; l < 32; l += 16) {
73 // Load 16 bytes of Q4
74 __m128i q4_vec = _mm_loadu_si128((const __m128i *)(b4->qs + q_offset + l));
75
76 // Low nibbles -> correspond to q8_lo (elements j+l .. j+l+15)
77 __m128i q4_lo = _mm_and_si128(q4_vec, mask_low);
78
79 // High nibbles -> correspond to q8_hi (elements j+32+l .. j+32+l+15)
80 __m128i q4_hi = _mm_and_si128(_mm_srli_epi16(q4_vec, 4), mask_low);
81
82 // Load Q8
83 __m128i q8_lo_vec = _mm_loadu_si128((const __m128i *)(b8->qs + j + l));
84 __m128i q8_hi_vec = _mm_loadu_si128((const __m128i *)(b8->qs + j + 32 + l));
85
86 // Expand and Multiply-Add: Q4(u8) * Q8(s8) -> i32
87 // Since Q4 is u8 and Q8 is s8, we use intermediate i16
88
89 // LO PART
90 __m128i q4_lo_16_L = _mm_cvtepu8_epi16(q4_lo); // lower 8 -> 16
91 __m128i q8_lo_16_L = _mm_cvtepi8_epi16(q8_lo_vec);
92 __m128i prod_lo_L = _mm_madd_epi16(q4_lo_16_L, q8_lo_16_L); // i32
93 acc_lo = _mm_add_epi32(acc_lo, prod_lo_L);
94
95 __m128i q4_lo_16_H = _mm_cvtepu8_epi16(_mm_srli_si128(q4_lo, 8)); // upper 8 -> 16
96 __m128i q8_lo_16_H = _mm_cvtepi8_epi16(_mm_srli_si128(q8_lo_vec, 8));
97 __m128i prod_lo_H = _mm_madd_epi16(q4_lo_16_H, q8_lo_16_H); // i32
98 acc_lo = _mm_add_epi32(acc_lo, prod_lo_H);
99
100 // HI PART
101 __m128i q4_hi_16_L = _mm_cvtepu8_epi16(q4_hi);
102 __m128i q8_hi_16_L = _mm_cvtepi8_epi16(q8_hi_vec);
103 __m128i prod_hi_L = _mm_madd_epi16(q4_hi_16_L, q8_hi_16_L);
104 acc_hi = _mm_add_epi32(acc_hi, prod_hi_L);
105
106 __m128i q4_hi_16_H = _mm_cvtepu8_epi16(_mm_srli_si128(q4_hi, 8));
107 __m128i q8_hi_16_H = _mm_cvtepi8_epi16(_mm_srli_si128(q8_hi_vec, 8));
108 __m128i prod_hi_H = _mm_madd_epi16(q4_hi_16_H, q8_hi_16_H);
109 acc_hi = _mm_add_epi32(acc_hi, prod_hi_H);
110 }
111
112 int32_t sum_q4q8_lo = hsum_epi32_sse(acc_lo);
113 int32_t sum_q4q8_hi = hsum_epi32_sse(acc_hi);
114
115 /* bsums: each bsum is 16 elements */
116 int32_t bsum_lo = (int32_t)b8->bsums[j / 16] +
117 (int32_t)b8->bsums[j / 16 + 1];
118 int32_t bsum_hi = (int32_t)b8->bsums[(j + 32) / 16] +
119 (int32_t)b8->bsums[(j + 32) / 16 + 1];
120
121 sumf += d * (float)sc[is] * (float)sum_q4q8_lo;
122 sumf -= dmin * (float)m_val[is] * (float)bsum_lo;
123 sumf += d * (float)sc[is + 1] * (float)sum_q4q8_hi;
124 sumf -= dmin * (float)m_val[is + 1] * (float)bsum_hi;
125
126 q_offset += 32;
127 is += 2;
128 }
129 }
130 y[row] = sumf;
131 }
132}
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
static int32_t hsum_epi32_sse(__m128i v)
void gemv_q4_k_q8_k_sse(float *y, const void *W, const void *x_q8, int M, int K)
uint8_t scales[12]
uint8_t qs[256/2]
int8_t qs[256]
int16_t bsums[256/16]