1#![allow(missing_docs)]
2use super::{f16_to_f32, SUPER_BLOCK_BYTES, SUPER_BLOCK_SIZE};
8
9#[inline(always)]
12fn extract_q6k_scalar(ql: &[u8], qh: &[u8], idx: usize) -> i8 {
13 let ql_byte = ql[idx / 2];
14 let low4 = if idx % 2 == 0 { ql_byte & 0x0F } else { ql_byte >> 4 };
15 let qh_byte = qh[idx / 4];
16 let high2 = (qh_byte >> ((idx % 4) * 2)) & 0x03;
17 (low4 | (high2 << 4)) as i8 - 32
18}
19
20#[inline(always)]
22fn process_q6k_superblock_scalar(
23 sb_data: &[u8],
24 input: &[f32],
25 input_offset: usize,
26 in_dim: usize,
27) -> f32 {
28 let ql = sb_data.get(0..128).expect("Q6_K: need ≥128 bytes for ql");
29 let qh = sb_data.get(128..192).expect("Q6_K: need ≥192 bytes for qh");
30 let scales = sb_data.get(192..208).expect("Q6_K: need ≥208 bytes for scales");
31 let d = f16_to_f32(u16::from_le_bytes([sb_data[208], sb_data[209]]));
32 let mut sum = 0.0f32;
33
34 for group in 0..16 {
35 let scale = (scales[group] as i8) as f32;
36 let group_offset = group * 16;
37
38 for j in 0..16 {
39 let idx = group_offset + j;
40 let input_idx = input_offset + idx;
41 if input_idx >= in_dim {
42 continue;
43 }
44 let q6 = extract_q6k_scalar(ql, qh, idx);
45 sum += d * scale * q6 as f32 * input[input_idx];
46 }
47 }
48 sum
49}
50
51pub fn matmul_q6k_f32_scalar(
52 q6k_data: &[u8],
53 input: &[f32],
54 out_dim: usize,
55 in_dim: usize,
56) -> Vec<f32> {
57 assert_eq!(input.len(), in_dim, "Input length mismatch");
58
59 let num_blocks_per_row = (in_dim + SUPER_BLOCK_SIZE - 1) / SUPER_BLOCK_SIZE;
60 let row_bytes = num_blocks_per_row * SUPER_BLOCK_BYTES;
61
62 let mut output: Vec<f32> = Vec::with_capacity(out_dim);
64 unsafe {
66 output.set_len(out_dim);
67 }
68
69 for out_idx in 0..out_dim {
70 let row_start = out_idx * row_bytes;
71 let mut sum = 0.0f32;
72
73 for sb_idx in 0..num_blocks_per_row {
74 let sb_start = row_start + sb_idx * SUPER_BLOCK_BYTES;
75 if sb_start + SUPER_BLOCK_BYTES > q6k_data.len() {
76 break;
77 }
78 let sb_data = &q6k_data[sb_start..sb_start + SUPER_BLOCK_BYTES];
79 let input_offset = sb_idx * SUPER_BLOCK_SIZE;
80 sum += process_q6k_superblock_scalar(sb_data, input, input_offset, in_dim);
81 }
82
83 output[out_idx] = sum;
84 }
85
86 output
87}
88
89#[cfg(target_arch = "x86_64")]
91#[inline(always)]
92fn extract_q6k_values(ql: &[u8], qh: &[u8], idx_base: usize) -> [i32; 8] {
93 let mut q6_vals = [0i32; 8];
94 for i in 0..8 {
95 let idx = idx_base + i;
96 let ql_byte = ql[idx / 2];
97 let low4 = if idx % 2 == 0 { ql_byte & 0x0F } else { ql_byte >> 4 };
98 let qh_byte = qh[idx / 4];
99 let qh_shift = (idx % 4) * 2;
100 let high2 = (qh_byte >> qh_shift) & 0x03;
101 q6_vals[i] = ((low4 | (high2 << 4)) as i32) - 32;
102 }
103 q6_vals
104}
105
106#[cfg(target_arch = "x86_64")]
108#[target_feature(enable = "avx2")]
109unsafe fn hsum_q6k_avx2(acc: std::arch::x86_64::__m256) -> f32 {
111 use std::arch::x86_64::*;
112 let hi128 = _mm256_extractf128_ps(acc, 1);
113 let lo128 = _mm256_castps256_ps128(acc);
114 let sum128 = _mm_add_ps(lo128, hi128);
115 let hi64 = _mm_movehl_ps(sum128, sum128);
116 let sum64 = _mm_add_ps(sum128, hi64);
117 let hi32 = _mm_shuffle_ps(sum64, sum64, 1);
118 let sum32 = _mm_add_ss(sum64, hi32);
119 _mm_cvtss_f32(sum32)
120}
121
122#[cfg(target_arch = "x86_64")]
124#[target_feature(enable = "avx2", enable = "fma")]
125unsafe fn process_q6k_superblock_avx2(
127 sb_data: &[u8],
128 input: &[f32],
129 input_offset: usize,
130 in_dim: usize,
131 acc: &mut std::arch::x86_64::__m256,
132) {
133 unsafe {
134 use std::arch::x86_64::*;
135
136 let ql = sb_data.get(0..128).expect("Q6_K: need ≥128 bytes for ql");
137 let qh = sb_data.get(128..192).expect("Q6_K: need ≥192 bytes for qh");
138 let scales = sb_data.get(192..208).expect("Q6_K: need ≥208 bytes for scales");
139 let d = f16_to_f32(u16::from_le_bytes([sb_data[208], sb_data[209]]));
140 let d_vec = _mm256_set1_ps(d);
141
142 for group in 0..16 {
143 let scale = (scales[group] as i8) as f32;
144 let ds_vec = _mm256_mul_ps(d_vec, _mm256_set1_ps(scale));
145 let group_offset = group * 16;
146 let input_group = input_offset + group_offset;
147
148 for half in 0..2 {
149 let half_offset = half * 8;
150 let input_base = input_group + half_offset;
151 if input_base + 8 > in_dim {
152 continue;
153 }
154
155 let q6_vals = extract_q6k_values(ql, qh, group_offset + half_offset);
156 let q6_i32 = _mm256_loadu_si256(q6_vals.as_ptr() as *const __m256i);
157 let q6_f32 = _mm256_cvtepi32_ps(q6_i32);
158 let x = _mm256_loadu_ps(input.as_ptr().add(input_base));
159 let dequant = _mm256_mul_ps(ds_vec, q6_f32);
160 *acc = _mm256_fmadd_ps(dequant, x, *acc);
161 }
162 }
163 }
164}
165
166#[cfg(target_arch = "x86_64")]
171#[target_feature(enable = "avx2", enable = "fma")]
172unsafe fn matmul_q6k_f32_avx2(
174 q6k_data: &[u8],
175 input: &[f32],
176 out_dim: usize,
177 in_dim: usize,
178) -> Vec<f32> {
179 unsafe {
180 use std::arch::x86_64::*;
181
182 let num_blocks_per_row = (in_dim + SUPER_BLOCK_SIZE - 1) / SUPER_BLOCK_SIZE;
183 let row_bytes = num_blocks_per_row * SUPER_BLOCK_BYTES;
184
185 let mut output: Vec<f32> = Vec::with_capacity(out_dim);
187 output.set_len(out_dim);
189
190 for out_idx in 0..out_dim {
191 let row_start = out_idx * row_bytes;
192 let mut acc = _mm256_setzero_ps();
193
194 for sb_idx in 0..num_blocks_per_row {
195 let sb_start = row_start + sb_idx * SUPER_BLOCK_BYTES;
196 if sb_start + SUPER_BLOCK_BYTES > q6k_data.len() {
197 break;
198 }
199 let sb_data = &q6k_data[sb_start..sb_start + SUPER_BLOCK_BYTES];
200 let input_offset = sb_idx * SUPER_BLOCK_SIZE;
201 process_q6k_superblock_avx2(sb_data, input, input_offset, in_dim, &mut acc);
202 }
203
204 output[out_idx] = hsum_q6k_avx2(acc);
205 }
206
207 output
208 }
209}
210
211#[inline]
224pub fn matmul_q6k_f32_dispatch(
225 q6k_data: &[u8],
226 input: &[f32],
227 out_dim: usize,
228 in_dim: usize,
229) -> Vec<f32> {
230 debug_assert_eq!(input.len(), in_dim, "Q6K dispatch: input length mismatch");
234 debug_assert!(
235 q6k_data.len() >= crate::contracts::Q6_K.expected_bytes(out_dim, in_dim),
236 "Q6K dispatch: buffer too small: {} bytes for [{}, {}] (need {})",
237 q6k_data.len(),
238 out_dim,
239 in_dim,
240 crate::contracts::Q6_K.expected_bytes(out_dim, in_dim),
241 );
242
243 let total_work = out_dim * in_dim;
247 if total_work >= 8_000_000 {
248 return matmul_q6k_f32_parallel(q6k_data, input, out_dim, in_dim);
249 }
250
251 #[cfg(target_arch = "x86_64")]
252 {
253 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
254 return unsafe { matmul_q6k_f32_avx2(q6k_data, input, out_dim, in_dim) };
256 }
257 }
258 matmul_q6k_f32_scalar(q6k_data, input, out_dim, in_dim)
259}
260
261#[cfg(target_arch = "x86_64")]
263fn matmul_q6k_f32_parallel(
264 q6k_data: &[u8],
265 input: &[f32],
266 out_dim: usize,
267 in_dim: usize,
268) -> Vec<f32> {
269 use std::thread;
270
271 let num_threads = thread::available_parallelism().map(|p| p.get()).unwrap_or(4).min(12); let chunk_size = (out_dim + num_threads - 1) / num_threads;
275 let num_blocks_per_row = (in_dim + SUPER_BLOCK_SIZE - 1) / SUPER_BLOCK_SIZE;
276 let row_bytes = num_blocks_per_row * SUPER_BLOCK_BYTES;
277
278 let mut output: Vec<f32> = Vec::with_capacity(out_dim);
280 unsafe {
282 output.set_len(out_dim);
283 }
284 let has_avx2 = is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma");
285
286 thread::scope(|s| {
287 let input_ref = input;
288 let q6k_ref = q6k_data;
289 for (chunk_idx, chunk) in output.chunks_mut(chunk_size).enumerate() {
291 let start_row = chunk_idx * chunk_size;
292
293 s.spawn(move || {
294 if has_avx2 {
295 unsafe {
298 compute_chunk_avx2(
299 q6k_ref,
300 input_ref,
301 chunk,
302 start_row,
303 out_dim,
304 in_dim,
305 num_blocks_per_row,
306 row_bytes,
307 );
308 }
309 } else {
310 compute_chunk_scalar(
311 q6k_ref,
312 input_ref,
313 chunk,
314 start_row,
315 out_dim,
316 in_dim,
317 num_blocks_per_row,
318 row_bytes,
319 );
320 }
321 });
322 }
323 });
324
325 output
326}
327
328#[cfg(not(target_arch = "x86_64"))]
330fn matmul_q6k_f32_parallel(
331 q6k_data: &[u8],
332 input: &[f32],
333 out_dim: usize,
334 in_dim: usize,
335) -> Vec<f32> {
336 matmul_q6k_f32_scalar(q6k_data, input, out_dim, in_dim)
337}
338
339#[cfg(target_arch = "x86_64")]
340#[target_feature(enable = "avx2", enable = "fma")]
341unsafe fn compute_chunk_avx2(
343 q6k_data: &[u8],
344 input: &[f32],
345 chunk: &mut [f32],
346 start_row: usize,
347 out_dim: usize,
348 in_dim: usize,
349 num_blocks_per_row: usize,
350 row_bytes: usize,
351) {
352 unsafe {
353 use std::arch::x86_64::*;
354
355 for (local_idx, out_val) in chunk.iter_mut().enumerate() {
356 let out_idx = start_row + local_idx;
357 if out_idx >= out_dim {
358 break;
359 }
360
361 let row_start = out_idx * row_bytes;
362 let mut acc = _mm256_setzero_ps();
363
364 for sb_idx in 0..num_blocks_per_row {
365 let sb_start = row_start + sb_idx * SUPER_BLOCK_BYTES;
366 if sb_start + SUPER_BLOCK_BYTES > q6k_data.len() {
367 break;
368 }
369 let sb_data = &q6k_data[sb_start..sb_start + SUPER_BLOCK_BYTES];
370 let input_offset = sb_idx * SUPER_BLOCK_SIZE;
371 process_q6k_superblock_avx2(sb_data, input, input_offset, in_dim, &mut acc);
372 }
373
374 *out_val = hsum_q6k_avx2(acc);
375 }
376 }
377}
378
379#[cfg(any(target_arch = "x86_64", test))]
380pub(crate) fn compute_chunk_scalar(
381 q6k_data: &[u8],
382 input: &[f32],
383 chunk: &mut [f32],
384 start_row: usize,
385 out_dim: usize,
386 in_dim: usize,
387 num_blocks_per_row: usize,
388 row_bytes: usize,
389) {
390 for (local_idx, out_val) in chunk.iter_mut().enumerate() {
391 let out_idx = start_row + local_idx;
392 if out_idx >= out_dim {
393 break;
394 }
395
396 let row_start = out_idx * row_bytes;
397 let mut sum = 0.0f32;
398
399 for sb_idx in 0..num_blocks_per_row {
400 let sb_start = row_start + sb_idx * SUPER_BLOCK_BYTES;
401 if sb_start + SUPER_BLOCK_BYTES > q6k_data.len() {
402 break;
403 }
404 let sb_data = &q6k_data[sb_start..sb_start + SUPER_BLOCK_BYTES];
405 let input_offset = sb_idx * SUPER_BLOCK_SIZE;
406 sum += process_q6k_superblock_scalar(sb_data, input, input_offset, in_dim);
407 }
408
409 *out_val = sum;
410 }
411}
412
413pub fn matmul_q6k_f32(q6k_data: &[u8], input: &[f32], out_dim: usize, in_dim: usize) -> Vec<f32> {
415 matmul_q6k_f32_dispatch(q6k_data, input, out_dim, in_dim)
416}