1use crate::hadamard::fwht_normalized;
51use crate::math;
52use alloc::vec;
53use alloc::vec::Vec;
54
55pub const TERNARY_RECORD_BITS: u8 = 5;
58
59pub const TERNARY_CENTROID: f32 = 1.2240064;
62
63pub const CODEBOOK_WIDTHS: [u8; 3] = [2, 3, 4];
66
67pub const CODEBOOK_LEN: usize = 4 + 8 + 16;
69
70pub const MAX_GROUP: usize = 1024;
74
75#[derive(Debug, Clone, PartialEq, Eq)]
77pub enum CqError {
78 UnsupportedBits(u8),
80 BadGroup(usize),
83 BadCodebookLen(usize),
85 ShortBlob { need: usize, got: usize },
87 EmptyShape,
89}
90
91pub fn codebook_slice(codebook: &[f32], bits: u8) -> Result<&[f32], CqError> {
94 if codebook.len() != CODEBOOK_LEN {
95 return Err(CqError::BadCodebookLen(codebook.len()));
96 }
97 match bits {
98 2 => Ok(&codebook[0..4]),
99 3 => Ok(&codebook[4..12]),
100 4 => Ok(&codebook[12..28]),
101 other => Err(CqError::UnsupportedBits(other)),
102 }
103}
104
105#[inline]
107pub fn packed_row_bytes(in_padded: usize, bits: u8) -> usize {
108 let effective = if bits == TERNARY_RECORD_BITS {
109 2
110 } else {
111 bits as usize
112 };
113 in_padded * effective / 8
114}
115
116pub struct CqWeight {
118 packed: Vec<u8>,
120 norms: Vec<f32>,
122 lut: Vec<f32>,
124 per_byte: usize,
127 row_bytes: usize,
128 num_groups: usize,
129 pub out_feat: usize,
130 pub in_feat: usize,
132 pub in_padded: usize,
134 pub group: usize,
135 pub bits: u8,
137 levels: Vec<f32>,
139}
140
141impl CqWeight {
142 pub fn select_rows(&self, rows: &[usize]) -> Option<Self> {
152 let mut packed = Vec::with_capacity(rows.len() * self.row_bytes);
153 let mut norms = Vec::with_capacity(rows.len() * self.num_groups);
154 for &r in rows {
155 if r >= self.out_feat {
156 return None;
157 }
158 packed.extend_from_slice(&self.packed[r * self.row_bytes..(r + 1) * self.row_bytes]);
159 norms.extend_from_slice(&self.norms[r * self.num_groups..(r + 1) * self.num_groups]);
160 }
161 Some(Self {
162 packed,
163 norms,
164 lut: self.lut.clone(),
165 per_byte: self.per_byte,
166 row_bytes: self.row_bytes,
167 num_groups: self.num_groups,
168 out_feat: rows.len(),
169 in_feat: self.in_feat,
170 in_padded: self.in_padded,
171 group: self.group,
172 bits: self.bits,
173 levels: self.levels.clone(),
174 })
175 }
176
177 pub fn from_blob(
182 blob: &[u8],
183 out_feat: usize,
184 in_feat: usize,
185 group: usize,
186 bits: u8,
187 codebook: &[f32],
188 ) -> Result<Self, CqError> {
189 if out_feat == 0 || in_feat == 0 {
190 return Err(CqError::EmptyShape);
191 }
192 if group == 0 || !group.is_multiple_of(8) || !group.is_power_of_two() {
193 return Err(CqError::BadGroup(group));
196 }
197 let levels: Vec<f32> = if bits == TERNARY_RECORD_BITS {
198 let s = TERNARY_CENTROID / math::sqrt(group as f32);
199 vec![-s, 0.0, s]
200 } else {
201 codebook_slice(codebook, bits)?.to_vec()
202 };
203
204 let in_padded = in_feat.div_ceil(group) * group;
205 let num_groups = in_padded / group;
206 let row_bytes = packed_row_bytes(in_padded, bits);
207 let n_packed = out_feat * row_bytes;
208 let n_norm_bytes = out_feat * num_groups * 2;
209 if blob.len() < n_packed + n_norm_bytes {
210 return Err(CqError::ShortBlob {
211 need: n_packed + n_norm_bytes,
212 got: blob.len(),
213 });
214 }
215
216 let packed = blob[..n_packed].to_vec();
217 let mut norms = Vec::with_capacity(out_feat * num_groups);
218 for c in blob[n_packed..n_packed + n_norm_bytes].chunks_exact(2) {
219 norms.push(math::f16_to_f32(u16::from_le_bytes([c[0], c[1]])));
220 }
221
222 let (per_byte, lut) = build_lut(bits, &levels);
223
224 Ok(Self {
225 packed,
226 norms,
227 lut,
228 per_byte,
229 row_bytes,
230 num_groups,
231 out_feat,
232 in_feat,
233 in_padded,
234 group,
235 bits,
236 levels,
237 })
238 }
239
240 pub fn packed_bytes(&self) -> usize {
242 self.packed.len()
243 }
244
245 pub fn effective_bits(&self) -> f32 {
247 let total_bits = (self.packed.len() + self.norms.len() * 2) * 8;
248 total_bits as f32 / (self.out_feat * self.in_padded) as f32
249 }
250
251 #[inline]
253 pub fn prepared_len(&self) -> usize {
254 self.in_padded
255 }
256
257 pub fn prepare_input(&self, x: &[f32], xh: &mut [f32]) {
265 debug_assert_eq!(x.len(), self.in_feat);
266 debug_assert_eq!(xh.len(), self.in_padded);
267 xh[..self.in_feat].copy_from_slice(x);
268 xh[self.in_feat..].fill(0.0);
271 for g in xh.chunks_exact_mut(self.group) {
272 fwht_normalized(g);
273 }
274 }
275
276 pub fn matvec(&self, x: &[f32], y: &mut [f32]) {
284 let mut xh = vec![0.0f32; self.in_padded];
285 self.prepare_input(x, &mut xh);
286 self.matvec_prepared(&xh, y);
287 }
288
289 pub fn matvec_prepared(&self, xh: &[f32], y: &mut [f32]) {
293 self.matvec_rows_prepared(xh, 0, y)
294 }
295
296 pub fn matvec_rows_prepared(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
302 #[cfg(feature = "parallel")]
303 if self.should_parallelise(y.len()) {
304 return self.matvec_rows_par(xh, row_start, y);
305 }
306 self.matvec_rows_serial(xh, row_start, y)
307 }
308
309 #[cfg(feature = "parallel")]
314 fn matvec_rows_par(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
315 use rayon::prelude::*;
316 let rows = y.len();
317 let chunk = rows
318 .div_ceil(rayon::current_num_threads())
319 .max(MIN_PAR_ROWS);
320 y.par_chunks_mut(chunk).enumerate().for_each(|(ci, part)| {
321 self.matvec_rows_serial(xh, row_start + ci * chunk, part);
322 });
323 }
324
325 fn matvec_rows_serial(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
326 debug_assert_eq!(xh.len(), self.in_padded);
327 debug_assert!(row_start + y.len() <= self.out_feat);
328 match self.per_byte {
329 4 => self.matvec_lut::<4>(xh, row_start, y),
330 2 => self.matvec_lut::<2>(xh, row_start, y),
331 _ => self.matvec_generic(xh, row_start, y),
332 }
333 }
334
335 #[cfg(feature = "bench-internals")]
342 pub fn matvec_prepared_single_acc(&self, xh: &[f32], y: &mut [f32]) {
343 debug_assert_eq!(xh.len(), self.in_padded);
344 debug_assert_eq!(y.len(), self.out_feat);
345 match self.per_byte {
349 4 => self.single_acc::<4>(xh, y),
350 2 => self.single_acc::<2>(xh, y),
351 _ => self.matvec_prepared_reference(xh, y),
352 }
353 }
354
355 #[cfg(feature = "bench-internals")]
356 fn single_acc<const P: usize>(&self, xh: &[f32], y: &mut [f32]) {
357 let bytes_per_group = self.group / P;
358 let lut = &self.lut[..256 * P];
359 for o in 0..self.out_feat {
360 let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
361 let row_norms = &self.norms[o * self.num_groups..(o + 1) * self.num_groups];
362 let mut total = 0.0f32;
363 for (g, &norm) in row_norms.iter().enumerate() {
364 let gbytes = &row[g * bytes_per_group..(g + 1) * bytes_per_group];
365 let gx = &xh[g * self.group..(g + 1) * self.group];
366 let mut acc = 0.0f32;
367 for (bi, &byte) in gbytes.iter().enumerate() {
368 let base = byte as usize * P;
369 let vals: &[f32; P] = lut[base..base + P].try_into().unwrap();
370 let xs: &[f32; P] = gx[bi * P..bi * P + P].try_into().unwrap();
371 for k in 0..P {
372 acc += vals[k] * xs[k];
373 }
374 }
375 total += norm * acc;
376 }
377 y[o] = total;
378 }
379 }
380
381 pub fn matvec_prepared_reference(&self, xh: &[f32], y: &mut [f32]) {
394 debug_assert_eq!(xh.len(), self.in_padded);
395 debug_assert_eq!(y.len(), self.out_feat);
396 let bits = if self.bits == TERNARY_RECORD_BITS {
397 2
398 } else {
399 self.bits as usize
400 };
401 let mask = (1u32 << bits) - 1;
402 for (o, slot) in y.iter_mut().enumerate() {
403 let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
404 let mut total = 0.0f32;
405 for g in 0..self.num_groups {
406 let norm = self.norms[o * self.num_groups + g];
407 let mut acc = 0.0f32;
408 for k in 0..self.group {
409 let code = read_bits(row, (g * self.group + k) * bits, bits, mask);
410 let idx = if self.bits == TERNARY_RECORD_BITS {
411 match code {
412 3 => 0,
413 0 => 1,
414 1 => 2,
415 _ => 1,
416 }
417 } else {
418 code as usize
419 };
420 acc += self.levels[idx] * xh[g * self.group + k];
421 }
422 total += norm * acc;
423 }
424 *slot = total;
425 }
426 }
427
428 pub fn matmul_rows_prepared(
447 &self,
448 xh: &[f32],
449 batch: usize,
450 row_start: usize,
451 rows: usize,
452 y: &mut [f32],
453 acc: &mut [f32],
454 ) {
455 #[cfg(feature = "parallel")]
456 if self.matmul_work(rows, batch) >= PAR_MIN_MACS {
457 if rows >= 2 * MIN_PAR_ROWS {
465 return self.matmul_rows_par(xh, batch, row_start, rows, y);
466 }
467 if batch >= 2 * MIN_PAR_BATCH {
468 return self.matmul_batch_par(xh, batch, row_start, rows, y);
469 }
470 }
471 self.matmul_rows_serial(xh, batch, row_start, rows, y, acc)
472 }
473
474 #[cfg(feature = "parallel")]
481 #[inline]
482 fn should_parallelise(&self, rows: usize) -> bool {
483 rows >= 2 * MIN_PAR_ROWS && rows.saturating_mul(self.in_padded) >= PAR_MIN_MACS
484 }
485
486 #[cfg(feature = "parallel")]
494 fn matmul_rows_par(
495 &self,
496 xh: &[f32],
497 batch: usize,
498 row_start: usize,
499 rows: usize,
500 y: &mut [f32],
501 ) {
502 use rayon::prelude::*;
503
504 struct Out(*mut f32);
507 unsafe impl Send for Out {}
510 unsafe impl Sync for Out {}
511 impl Out {
512 #[inline]
517 fn ptr(&self) -> *mut f32 {
518 self.0
519 }
520 }
521
522 let out = Out(y.as_mut_ptr());
523 let chunk = rows
524 .div_ceil(rayon::current_num_threads())
525 .max(MIN_PAR_ROWS);
526 let bands: Vec<usize> = (0..rows).step_by(chunk).collect();
527
528 bands.into_par_iter().for_each(|r0| {
529 let band = chunk.min(rows - r0);
530 let mut local = vec![0.0f32; batch * band];
531 let mut acc = vec![0.0f32; batch];
532 self.matmul_rows_serial(xh, batch, row_start + r0, band, &mut local, &mut acc);
533 for b in 0..batch {
534 unsafe {
536 core::ptr::copy_nonoverlapping(
537 local.as_ptr().add(b * band),
538 out.ptr().add(b * rows + r0),
539 band,
540 );
541 }
542 }
543 });
544 }
545
546 #[cfg(feature = "parallel")]
552 fn matmul_batch_par(
553 &self,
554 xh: &[f32],
555 batch: usize,
556 row_start: usize,
557 rows: usize,
558 y: &mut [f32],
559 ) {
560 use rayon::prelude::*;
561 let stride = self.in_padded;
562 let band = batch
563 .div_ceil(rayon::current_num_threads())
564 .max(MIN_PAR_BATCH);
565 y[..batch * rows]
566 .par_chunks_mut(band * rows)
567 .enumerate()
568 .for_each(|(ci, part)| {
569 let b0 = ci * band;
570 let n = part.len() / rows;
571 let mut acc = vec![0.0f32; n];
572 self.matmul_rows_serial(
573 &xh[b0 * stride..(b0 + n) * stride],
574 n,
575 row_start,
576 rows,
577 part,
578 &mut acc,
579 );
580 });
581 }
582
583 #[cfg(feature = "parallel")]
585 #[inline]
586 fn matmul_work(&self, rows: usize, batch: usize) -> usize {
587 rows.saturating_mul(batch).saturating_mul(self.in_padded)
588 }
589
590 #[allow(clippy::too_many_arguments)]
591 fn matmul_rows_serial(
592 &self,
593 xh: &[f32],
594 batch: usize,
595 row_start: usize,
596 rows: usize,
597 y: &mut [f32],
598 acc: &mut [f32],
599 ) {
600 #[cfg(all(target_arch = "x86_64", feature = "simd"))]
606 if crate::quant::has_avx2() {
607 return unsafe { self.matmul_rows_prepared_avx2(xh, batch, row_start, rows, y, acc) };
609 }
610 self.matmul_rows_prepared_impl(xh, batch, row_start, rows, y, acc)
611 }
612
613 #[cfg(all(target_arch = "x86_64", feature = "simd"))]
618 #[target_feature(enable = "avx2,fma")]
619 unsafe fn matmul_rows_prepared_avx2(
620 &self,
621 xh: &[f32],
622 batch: usize,
623 row_start: usize,
624 rows: usize,
625 y: &mut [f32],
626 acc: &mut [f32],
627 ) {
628 self.matmul_rows_prepared_impl(xh, batch, row_start, rows, y, acc)
629 }
630
631 #[inline(always)]
632 fn matmul_rows_prepared_impl(
633 &self,
634 xh: &[f32],
635 batch: usize,
636 row_start: usize,
637 rows: usize,
638 y: &mut [f32],
639 acc: &mut [f32],
640 ) {
641 debug_assert_eq!(xh.len(), batch * self.in_padded);
642 debug_assert_eq!(y.len(), batch * rows);
643 debug_assert!(acc.len() >= batch);
644 debug_assert!(row_start + rows <= self.out_feat);
645 debug_assert!(self.group <= MAX_GROUP);
646
647 let stride = self.in_padded;
648 let mut buf = [0.0f32; MAX_GROUP];
650 let ug = &mut buf[..self.group];
651 let lanewise = self.per_byte != 0;
652
653 for r in 0..rows {
654 let o = row_start + r;
655 let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
656 let row_norms = &self.norms[o * self.num_groups..(o + 1) * self.num_groups];
657 acc[..batch].fill(0.0);
658
659 for (g, &norm) in row_norms.iter().enumerate() {
660 self.decode_group(row, g, 1.0, ug);
662 let base = g * self.group;
663 for b in 0..batch {
664 let gx = &xh[b * stride + base..b * stride + base + self.group];
665 let s = if lanewise {
668 group_dot_lanes(ug, gx)
669 } else {
670 group_dot_serial(ug, gx)
671 };
672 acc[b] += norm * s;
673 }
674 }
675 for b in 0..batch {
676 y[b * rows + r] = acc[b];
677 }
678 }
679 }
680
681 fn matvec_lut<const P: usize>(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
694 #[cfg(all(target_arch = "x86_64", feature = "simd"))]
703 if crate::quant::has_avx2() {
704 return unsafe { self.matvec_lut_avx2::<P>(xh, row_start, y) };
706 }
707 self.matvec_lut_impl::<P>(xh, row_start, y)
708 }
709
710 #[cfg(all(target_arch = "x86_64", feature = "simd"))]
715 #[target_feature(enable = "avx2,fma")]
716 unsafe fn matvec_lut_avx2<const P: usize>(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
717 self.matvec_lut_impl::<P>(xh, row_start, y)
718 }
719
720 #[inline(always)]
721 fn matvec_lut_impl<const P: usize>(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
722 let bytes_per_group = self.group / P;
723 let lut = &self.lut[..256 * P];
724 for (yi, o) in (row_start..row_start + y.len()).enumerate() {
725 let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
726 let row_norms = &self.norms[o * self.num_groups..(o + 1) * self.num_groups];
727 let mut total = 0.0f32;
728 for (g, &norm) in row_norms.iter().enumerate() {
729 let gbytes = &row[g * bytes_per_group..(g + 1) * bytes_per_group];
730 let gx = &xh[g * self.group..(g + 1) * self.group];
731 total += norm * dot_group::<P>(lut, gbytes, gx);
732 }
733 y[yi] = total;
734 }
735 }
736
737 fn matvec_generic(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
739 let bits = self.bits as usize;
740 let mask = (1u32 << bits) - 1;
741 for (yi, o) in (row_start..row_start + y.len()).enumerate() {
742 let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
743 let row_norms = &self.norms[o * self.num_groups..(o + 1) * self.num_groups];
744 let mut total = 0.0f32;
745 for (g, &norm) in row_norms.iter().enumerate() {
746 let gx = &xh[g * self.group..(g + 1) * self.group];
747 let mut acc = 0.0f32;
748 for (k, &x) in gx.iter().enumerate() {
749 let bit = (g * self.group + k) * bits;
750 let idx = read_bits(row, bit, bits, mask);
751 acc += self.levels[idx as usize] * x;
752 }
753 total += norm * acc;
754 }
755 y[yi] = total;
756 }
757 }
758
759 pub fn dequantize_row(&self, o: usize, out: &mut [f32]) {
764 debug_assert!(o < self.out_feat);
765 debug_assert_eq!(out.len(), self.in_feat);
766 let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
767 let row_norms = &self.norms[o * self.num_groups..(o + 1) * self.num_groups];
768 let mut buf = vec![0.0f32; self.group];
769 for (g, &norm) in row_norms.iter().enumerate() {
770 self.decode_group(row, g, norm, &mut buf);
771 fwht_normalized(&mut buf);
772 let base = g * self.group;
773 for k in 0..self.group {
774 if base + k < self.in_feat {
775 out[base + k] = buf[k];
776 }
777 }
778 }
779 }
780
781 pub fn dequantize_to(&self, out: &mut [f32]) {
783 debug_assert_eq!(out.len(), self.out_feat * self.in_feat);
784 for o in 0..self.out_feat {
785 self.dequantize_row(o, &mut out[o * self.in_feat..(o + 1) * self.in_feat]);
786 }
787 }
788
789 fn decode_group(&self, row: &[u8], g: usize, norm: f32, buf: &mut [f32]) {
792 if self.per_byte != 0 {
793 let p = self.per_byte;
794 let bytes_per_group = self.group / p;
795 let gbytes = &row[g * bytes_per_group..(g + 1) * bytes_per_group];
796 for (bi, &byte) in gbytes.iter().enumerate() {
797 for k in 0..p {
798 buf[bi * p + k] = self.lut[byte as usize * p + k] * norm;
799 }
800 }
801 } else {
802 let bits = self.bits as usize;
803 let mask = (1u32 << bits) - 1;
804 for (k, slot) in buf.iter_mut().enumerate() {
805 let idx = read_bits(row, (g * self.group + k) * bits, bits, mask);
806 *slot = self.levels[idx as usize] * norm;
807 }
808 }
809 }
810}
811
812#[cfg(feature = "parallel")]
815const MIN_PAR_ROWS: usize = 64;
816
817#[cfg(feature = "parallel")]
819const MIN_PAR_BATCH: usize = 8;
820
821#[cfg(feature = "parallel")]
823const PAR_MIN_MACS: usize = 1 << 20;
824
825pub const ACC_LANES: usize = 8;
831
832#[inline(always)]
838fn dot_group<const P: usize>(lut: &[f32], gbytes: &[u8], gx: &[f32]) -> f32 {
839 debug_assert_eq!(ACC_LANES % P, 0);
840 let per_iter = ACC_LANES / P;
841 let mut lanes = [0.0f32; ACC_LANES];
842
843 let full = gbytes.len() - gbytes.len() % per_iter;
844 let mut bi = 0;
845 while bi < full {
846 let mut vals = [0.0f32; ACC_LANES];
852 for t in 0..per_iter {
853 let base = gbytes[bi + t] as usize * P;
854 let entry: &[f32; P] = lut[base..base + P].try_into().unwrap();
855 vals[t * P..(t + 1) * P].copy_from_slice(entry);
856 }
857 let xs: &[f32; ACC_LANES] = gx[bi * P..bi * P + ACC_LANES].try_into().unwrap();
858 for k in 0..ACC_LANES {
859 lanes[k] += vals[k] * xs[k];
860 }
861 bi += per_iter;
862 }
863
864 let mut acc: f32 = lanes.iter().sum();
865 for b in bi..gbytes.len() {
867 let base = gbytes[b] as usize * P;
868 for k in 0..P {
869 acc += lut[base + k] * gx[b * P + k];
870 }
871 }
872 acc
873}
874
875#[inline(always)]
879fn group_dot_lanes(u: &[f32], x: &[f32]) -> f32 {
880 debug_assert_eq!(u.len(), x.len());
881 let chunks = u.len() / ACC_LANES;
882 let mut lanes = [0.0f32; ACC_LANES];
883 for c in 0..chunks {
884 let uu: &[f32; ACC_LANES] = u[c * ACC_LANES..(c + 1) * ACC_LANES].try_into().unwrap();
885 let xx: &[f32; ACC_LANES] = x[c * ACC_LANES..(c + 1) * ACC_LANES].try_into().unwrap();
886 for k in 0..ACC_LANES {
887 lanes[k] += uu[k] * xx[k];
888 }
889 }
890 let mut s: f32 = lanes.iter().sum();
891 for k in chunks * ACC_LANES..u.len() {
892 s += u[k] * x[k];
893 }
894 s
895}
896
897#[inline(always)]
900fn group_dot_serial(u: &[f32], x: &[f32]) -> f32 {
901 let mut s = 0.0f32;
902 for (a, b) in u.iter().zip(x.iter()) {
903 s += a * b;
904 }
905 s
906}
907
908#[inline]
914fn read_bits(row: &[u8], bit: usize, bits: usize, mask: u32) -> u32 {
915 let byte = bit / 8;
916 let shift = bit % 8;
917 let lo = row[byte] as u32;
919 let hi = if shift + bits > 8 {
920 row[byte + 1] as u32
921 } else {
922 0
923 };
924 ((lo | (hi << 8)) >> shift) & mask
925}
926
927fn build_lut(bits: u8, levels: &[f32]) -> (usize, Vec<f32>) {
933 let ternary = bits == TERNARY_RECORD_BITS;
936 let width = if ternary { 2 } else { bits as usize };
937 let per_byte = match width {
938 2 => 4,
939 4 => 2,
940 _ => return (0, Vec::new()), };
942 let mask = (1u32 << width) - 1;
943
944 let mut lut = vec![0.0f32; 256 * per_byte];
945 for byte in 0..256usize {
946 for k in 0..per_byte {
947 let code = ((byte as u32) >> (k * width)) & mask;
948 let idx = if ternary {
949 match code {
952 3 => 0,
953 0 => 1,
954 1 => 2,
955 _ => 1,
956 }
957 } else {
958 code as usize
959 };
960 lut[byte * per_byte + k] = levels[idx];
961 }
962 }
963 (per_byte, lut)
964}
965
966#[cfg(test)]
967mod tests {
968 use super::*;
969 use crate::hadamard::fwht_normalized;
970
971 fn fake_codebook(group: usize) -> Vec<f32> {
974 let s = 1.0 / math::sqrt(group as f32);
975 let mut cb = Vec::with_capacity(CODEBOOK_LEN);
976 for &bits in &CODEBOOK_WIDTHS {
977 let levels = 1usize << bits;
978 for i in 0..levels {
979 let v = (i as f32 - (levels as f32 - 1.0) / 2.0) / (levels as f32 / 2.0);
981 cb.push(v * s);
982 }
983 }
984 assert_eq!(cb.len(), CODEBOOK_LEN);
985 cb
986 }
987
988 fn pack_lsb(idx: &[u8], out: usize, in_pad: usize, bits: usize) -> Vec<u8> {
990 let row_bytes = in_pad * bits / 8;
991 let mut packed = vec![0u8; out * row_bytes];
992 for o in 0..out {
993 for k in 0..in_pad {
994 let v = idx[o * in_pad + k] as u32;
995 let bit = k * bits;
996 for b in 0..bits {
997 if (v >> b) & 1 == 1 {
998 let abs = bit + b;
999 packed[o * row_bytes + abs / 8] |= 1 << (abs % 8);
1000 }
1001 }
1002 }
1003 }
1004 packed
1005 }
1006
1007 fn f32_to_f16(v: f32) -> u16 {
1008 let x = v.to_bits();
1009 let sign = ((x >> 31) as u16) << 15;
1010 let exp = ((x >> 23) & 0xFF) as i32 - 127;
1011 let mant = x & 0x7F_FFFF;
1012 if exp < -14 {
1013 return sign;
1014 }
1015 if exp > 15 {
1016 return sign | 0x7C00;
1017 }
1018 sign | (((exp + 15) as u16) << 10) | ((mant >> 13) as u16)
1019 }
1020
1021 struct Case {
1022 w: CqWeight,
1023 dense: Vec<f32>,
1026 }
1027
1028 fn build_case(out: usize, in_feat: usize, group: usize, bits: u8) -> Case {
1029 let cb = fake_codebook(group);
1030 let levels: Vec<f32> = if bits == TERNARY_RECORD_BITS {
1031 let s = TERNARY_CENTROID / math::sqrt(group as f32);
1032 vec![-s, 0.0, s]
1033 } else {
1034 codebook_slice(&cb, bits).unwrap().to_vec()
1035 };
1036 let n_levels = levels.len();
1037
1038 let in_pad = in_feat.div_ceil(group) * group;
1039 let num_groups = in_pad / group;
1040
1041 let idx: Vec<u8> = (0..out * in_pad)
1043 .map(|i| ((i * 7 + i / 13) % n_levels) as u8)
1044 .collect();
1045 let norms: Vec<f32> = (0..out * num_groups)
1046 .map(|i| 0.25 + (i % 11) as f32 * 0.125)
1047 .collect();
1048
1049 let packed = if bits == TERNARY_RECORD_BITS {
1050 let crumbs: Vec<u8> = idx
1052 .iter()
1053 .map(|&t| if t == 0 { 3 } else { t - 1 })
1054 .collect();
1055 pack_lsb(&crumbs, out, in_pad, 2)
1056 } else {
1057 pack_lsb(&idx, out, in_pad, bits as usize)
1058 };
1059
1060 let mut blob = packed;
1061 for &n in &norms {
1062 blob.extend_from_slice(&f32_to_f16(n).to_le_bytes());
1063 }
1064
1065 let mut dense = vec![0.0f32; out * in_feat];
1067 for o in 0..out {
1068 for g in 0..num_groups {
1069 let mut u: Vec<f32> = (0..group)
1070 .map(|k| levels[idx[o * in_pad + g * group + k] as usize])
1071 .collect();
1072 let norm = math::f16_to_f32(f32_to_f16(norms[o * num_groups + g]));
1073 for v in u.iter_mut() {
1074 *v *= norm;
1075 }
1076 fwht_normalized(&mut u);
1077 for (k, &val) in u.iter().enumerate() {
1078 let col = g * group + k;
1079 if col < in_feat {
1080 dense[o * in_feat + col] = val;
1081 }
1082 }
1083 }
1084 }
1085
1086 let w = CqWeight::from_blob(&blob, out, in_feat, group, bits, &cb).unwrap();
1087 Case { w, dense }
1088 }
1089
1090 #[test]
1095 fn selecting_rows_commutes_with_dequantisation() {
1096 for bits in WIDTHS {
1097 let (out, in_feat, group) = (12usize, 256usize, 128usize);
1098 let case = build_case(out, in_feat, group, bits);
1099
1100 let rows = [0usize, 3, 4, 9, 11];
1101 let cut = case.w.select_rows(&rows).expect("rows are in range");
1102 assert_eq!(cut.out_feat, rows.len());
1103 assert_eq!(cut.in_feat, in_feat);
1104 assert_eq!(cut.bits, case.w.bits);
1105
1106 let mut dense_cut = vec![0.0f32; rows.len() * in_feat];
1107 cut.dequantize_to(&mut dense_cut);
1108
1109 for (n, &r) in rows.iter().enumerate() {
1110 let want = &case.dense[r * in_feat..(r + 1) * in_feat];
1111 let got = &dense_cut[n * in_feat..(n + 1) * in_feat];
1112 assert_eq!(
1113 want, got,
1114 "bits {bits}: row {r} changed when sliced into position {n}"
1115 );
1116 }
1117 }
1118 }
1119
1120 #[test]
1122 fn selection_follows_the_order_given() {
1123 let (out, in_feat, group) = (6usize, 128usize, 128usize);
1124 let case = build_case(out, in_feat, group, 4);
1125 let rows = [5usize, 0, 5];
1126 let cut = case.w.select_rows(&rows).unwrap();
1127 let mut dense_cut = vec![0.0f32; rows.len() * in_feat];
1128 cut.dequantize_to(&mut dense_cut);
1129 for (n, &r) in rows.iter().enumerate() {
1130 assert_eq!(
1131 &case.dense[r * in_feat..(r + 1) * in_feat],
1132 &dense_cut[n * in_feat..(n + 1) * in_feat]
1133 );
1134 }
1135 }
1136
1137 #[test]
1138 fn an_out_of_range_row_is_refused() {
1139 let case = build_case(4, 128, 128, 4);
1140 assert!(case.w.select_rows(&[0, 3]).is_some());
1141 assert!(case.w.select_rows(&[0, 4]).is_none());
1142 }
1143
1144 const WIDTHS: [u8; 4] = [2, 3, 4, TERNARY_RECORD_BITS];
1145
1146 #[test]
1147 fn dequantize_matches_reference_reconstruction() {
1148 for &bits in &WIDTHS {
1149 for &(out, in_feat, group) in &[(5usize, 16usize, 16usize), (3, 40, 8), (7, 128, 128)] {
1150 let c = build_case(out, in_feat, group, bits);
1151 let mut got = vec![0.0f32; out * in_feat];
1152 c.w.dequantize_to(&mut got);
1153 for (i, (&g_i, &want)) in got.iter().zip(c.dense.iter()).enumerate() {
1154 assert!(
1155 (g_i - want).abs() < 1e-5,
1156 "bits={bits} shape={out}x{in_feat} g={group} i={i}: {g_i} vs {want}"
1157 );
1158 }
1159 }
1160 }
1161 }
1162
1163 #[test]
1166 fn matvec_matches_dense_multiply() {
1167 for &bits in &WIDTHS {
1168 for &(out, in_feat, group) in &[
1169 (5usize, 16usize, 16usize),
1170 (3, 40, 8),
1171 (9, 128, 128),
1172 (4, 200, 64),
1173 ] {
1174 let c = build_case(out, in_feat, group, bits);
1175 let x: Vec<f32> = (0..in_feat)
1176 .map(|i| (i as f32 * 0.31).sin() * 1.7 + 0.05 * i as f32)
1177 .collect();
1178
1179 let mut got = vec![0.0f32; out];
1180 c.w.matvec(&x, &mut got);
1181
1182 for (o, &g_o) in got.iter().enumerate() {
1183 let want: f32 = (0..in_feat).map(|j| c.dense[o * in_feat + j] * x[j]).sum();
1184 let tol = 1e-4 * want.abs().max(1.0);
1185 assert!(
1186 (g_o - want).abs() < tol,
1187 "bits={bits} shape={out}x{in_feat} g={group} o={o}: {g_o} vs {want}"
1188 );
1189 }
1190 }
1191 }
1192 }
1193
1194 #[test]
1196 fn padding_does_not_leak_into_matvec() {
1197 let c = build_case(6, 40, 32, 2);
1199 let x: Vec<f32> = (0..40).map(|i| 1.0 + i as f32).collect();
1200 let mut got = vec![0.0f32; 6];
1201 c.w.matvec(&x, &mut got);
1202 for (o, &g_o) in got.iter().enumerate() {
1203 let want: f32 = (0..40).map(|j| c.dense[o * 40 + j] * x[j]).sum();
1204 assert!((g_o - want).abs() < 1e-3 * want.abs().max(1.0));
1205 }
1206 }
1207
1208 #[test]
1210 fn row_range_matches_full_matvec() {
1211 for &bits in &WIDTHS {
1212 let c = build_case(24, 128, 128, bits);
1213 let x: Vec<f32> = (0..128).map(|i| (i as f32 * 0.19).sin()).collect();
1214 let mut full = vec![0.0f32; 24];
1215 c.w.matvec(&x, &mut full);
1216
1217 let mut xh = vec![0.0f32; c.w.prepared_len()];
1218 c.w.prepare_input(&x, &mut xh);
1219 for &(start, len) in &[(0usize, 4usize), (4, 4), (8, 16), (20, 4), (23, 1)] {
1220 let mut band = vec![0.0f32; len];
1221 c.w.matvec_rows_prepared(&xh, start, &mut band);
1222 assert_eq!(
1223 &band[..],
1224 &full[start..start + len],
1225 "bits={bits} band {start}+{len}"
1226 );
1227 }
1228 }
1229 }
1230
1231 #[test]
1237 fn matvec_matches_simple_reference() {
1238 for &bits in &WIDTHS {
1239 for &(out, in_feat, group) in &[
1240 (16usize, 128usize, 128usize),
1241 (5, 40, 8),
1242 (7, 200, 64),
1243 (9, 512, 128),
1244 ] {
1245 let c = build_case(out, in_feat, group, bits);
1246 let x: Vec<f32> = (0..in_feat)
1247 .map(|i| (i as f32 * 0.29).sin() * 2.0)
1248 .collect();
1249 let mut xh = vec![0.0f32; c.w.prepared_len()];
1250 c.w.prepare_input(&x, &mut xh);
1251
1252 let mut fast = vec![0.0f32; out];
1253 let mut slow = vec![0.0f32; out];
1254 c.w.matvec_prepared(&xh, &mut fast);
1255 c.w.matvec_prepared_reference(&xh, &mut slow);
1256
1257 let l1: f32 = xh.iter().map(|v| v.abs()).sum();
1258 for o in 0..out {
1259 assert!(
1260 (fast[o] - slow[o]).abs() <= 1e-5 * l1.max(1.0),
1261 "bits={bits} {out}x{in_feat} g={group} o={o}: {} vs {}",
1262 fast[o],
1263 slow[o]
1264 );
1265 }
1266 }
1267 }
1268 }
1269
1270 #[test]
1271 fn matmul_matches_repeated_matvec() {
1272 for &bits in &WIDTHS {
1273 for &(out, in_feat, group) in &[
1274 (24usize, 128usize, 128usize),
1275 (9, 40, 8),
1276 (5, 200, 64),
1277 (16, 512, 128),
1278 ] {
1279 let c = build_case(out, in_feat, group, bits);
1280 for &batch in &[1usize, 2, 7, 16] {
1281 let mut xh = vec![0.0f32; batch * c.w.prepared_len()];
1283 let mut xs = Vec::new();
1284 for b in 0..batch {
1285 let x: Vec<f32> = (0..in_feat)
1286 .map(|i| ((i + b * 13) as f32 * 0.23).sin() * 1.3)
1287 .collect();
1288 c.w.prepare_input(
1289 &x,
1290 &mut xh[b * c.w.prepared_len()..(b + 1) * c.w.prepared_len()],
1291 );
1292 xs.push(x);
1293 }
1294
1295 for &(row_start, rows) in &[(0usize, out), (0, 1), (1, 3.min(out - 1))] {
1296 if row_start + rows > out || rows == 0 {
1297 continue;
1298 }
1299 let mut got = vec![0.0f32; batch * rows];
1300 let mut acc = vec![0.0f32; batch];
1301 c.w.matmul_rows_prepared(&xh, batch, row_start, rows, &mut got, &mut acc);
1302
1303 for b in 0..batch {
1304 let mut want = vec![0.0f32; rows];
1305 c.w.matvec_rows_prepared(
1306 &xh[b * c.w.prepared_len()..(b + 1) * c.w.prepared_len()],
1307 row_start,
1308 &mut want,
1309 );
1310 for r in 0..rows {
1311 assert_eq!(
1312 got[b * rows + r],
1313 want[r],
1314 "bits={bits} {out}x{in_feat} g={group} batch={batch} \
1315 rows={row_start}+{rows} b={b} r={r}"
1316 );
1317 }
1318 }
1319 }
1320 }
1321 }
1322 }
1323 }
1324
1325 #[test]
1326 fn prepared_input_is_reusable_across_weights() {
1327 let a = build_case(4, 128, 128, 2);
1328 let b = build_case(6, 128, 128, 4);
1329 let x: Vec<f32> = (0..128).map(|i| (i as f32 * 0.07).cos()).collect();
1330
1331 let mut xh = vec![0.0f32; a.w.prepared_len()];
1332 a.w.prepare_input(&x, &mut xh);
1333
1334 let mut ya = vec![0.0f32; 4];
1335 let mut yb = vec![0.0f32; 6];
1336 a.w.matvec_prepared(&xh, &mut ya);
1337 b.w.matvec_prepared(&xh, &mut yb);
1338
1339 let mut ya_ref = vec![0.0f32; 4];
1340 let mut yb_ref = vec![0.0f32; 6];
1341 a.w.matvec(&x, &mut ya_ref);
1342 b.w.matvec(&x, &mut yb_ref);
1343 assert_eq!(ya, ya_ref);
1344 assert_eq!(yb, yb_ref);
1345 }
1346
1347 #[test]
1348 fn row_bytes_match_shipped_tensor_sizes() {
1349 for &(out, in_f, bits, group, want) in &[
1352 (8192usize, 512usize, 4u8, 128usize, 2_162_688usize), (512, 512, 2, 128, 69_632), (256, 512, 2, 128, 34_816), (108, 2048, 4, 128, 114_048), (432, 2048, 4, 128, 456_192), (32768, 128, 2, 128, 1_114_112), ] {
1359 let in_pad = in_f.div_ceil(group) * group;
1360 let got = out * packed_row_bytes(in_pad, bits) + out * (in_pad / group) * 2;
1361 assert_eq!(got, want, "{out}x{in_f} @ {bits}b/{group}");
1362 }
1363 }
1364
1365 #[test]
1366 fn rejects_bad_geometry() {
1367 let cb = fake_codebook(128);
1368 let blob = vec![0u8; 1 << 16];
1369 let err = |b: &[u8], out, in_f, group, bits, cb: &[f32]| {
1372 CqWeight::from_blob(b, out, in_f, group, bits, cb).err()
1373 };
1374 assert_eq!(
1375 err(&blob, 4, 128, 128, 7, &cb),
1376 Some(CqError::UnsupportedBits(7))
1377 );
1378 assert_eq!(err(&blob, 4, 128, 96, 2, &cb), Some(CqError::BadGroup(96)));
1379 assert_eq!(
1380 err(&blob, 4, 128, 100, 2, &cb),
1381 Some(CqError::BadGroup(100))
1382 );
1383 assert_eq!(err(&blob, 0, 128, 128, 2, &cb), Some(CqError::EmptyShape));
1384 assert_eq!(err(&blob, 4, 0, 128, 2, &cb), Some(CqError::EmptyShape));
1385 assert!(matches!(
1386 err(&[0u8; 4], 512, 512, 128, 2, &cb),
1387 Some(CqError::ShortBlob { .. })
1388 ));
1389 assert_eq!(
1390 err(&blob, 4, 128, 128, 2, &cb[..10]),
1391 Some(CqError::BadCodebookLen(10))
1392 );
1393 }
1394}
1395
1396#[cfg(test)]
1397mod dispatch_tests {
1398 extern crate std;
1401
1402 #[test]
1410 #[cfg(all(target_arch = "x86_64", feature = "simd"))]
1411 fn avx2_path_is_actually_selected_when_supported() {
1412 let cpuid_says = self::std::is_x86_feature_detected!("avx2");
1413 let we_say = crate::quant::has_avx2();
1414 assert_eq!(
1415 we_say, cpuid_says,
1416 "has_avx2() disagrees with std's detection; the dispatch would pick the wrong kernel"
1417 );
1418 if cpuid_says {
1419 assert!(
1420 we_say,
1421 "AVX2 is present but the wide kernel would not be selected"
1422 );
1423 self::std::eprintln!("AVX2 dispatch active: the wide packed-dot kernel is in use");
1424 } else {
1425 self::std::eprintln!("no AVX2 on this host; the portable kernel is in use");
1426 }
1427 }
1428 }