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 from_blob(
147 blob: &[u8],
148 out_feat: usize,
149 in_feat: usize,
150 group: usize,
151 bits: u8,
152 codebook: &[f32],
153 ) -> Result<Self, CqError> {
154 if out_feat == 0 || in_feat == 0 {
155 return Err(CqError::EmptyShape);
156 }
157 if group == 0 || !group.is_multiple_of(8) || !group.is_power_of_two() {
158 return Err(CqError::BadGroup(group));
161 }
162 let levels: Vec<f32> = if bits == TERNARY_RECORD_BITS {
163 let s = TERNARY_CENTROID / math::sqrt(group as f32);
164 vec![-s, 0.0, s]
165 } else {
166 codebook_slice(codebook, bits)?.to_vec()
167 };
168
169 let in_padded = in_feat.div_ceil(group) * group;
170 let num_groups = in_padded / group;
171 let row_bytes = packed_row_bytes(in_padded, bits);
172 let n_packed = out_feat * row_bytes;
173 let n_norm_bytes = out_feat * num_groups * 2;
174 if blob.len() < n_packed + n_norm_bytes {
175 return Err(CqError::ShortBlob {
176 need: n_packed + n_norm_bytes,
177 got: blob.len(),
178 });
179 }
180
181 let packed = blob[..n_packed].to_vec();
182 let mut norms = Vec::with_capacity(out_feat * num_groups);
183 for c in blob[n_packed..n_packed + n_norm_bytes].chunks_exact(2) {
184 norms.push(math::f16_to_f32(u16::from_le_bytes([c[0], c[1]])));
185 }
186
187 let (per_byte, lut) = build_lut(bits, &levels);
188
189 Ok(Self {
190 packed,
191 norms,
192 lut,
193 per_byte,
194 row_bytes,
195 num_groups,
196 out_feat,
197 in_feat,
198 in_padded,
199 group,
200 bits,
201 levels,
202 })
203 }
204
205 pub fn packed_bytes(&self) -> usize {
207 self.packed.len()
208 }
209
210 pub fn effective_bits(&self) -> f32 {
212 let total_bits = (self.packed.len() + self.norms.len() * 2) * 8;
213 total_bits as f32 / (self.out_feat * self.in_padded) as f32
214 }
215
216 #[inline]
218 pub fn prepared_len(&self) -> usize {
219 self.in_padded
220 }
221
222 pub fn prepare_input(&self, x: &[f32], xh: &mut [f32]) {
230 debug_assert_eq!(x.len(), self.in_feat);
231 debug_assert_eq!(xh.len(), self.in_padded);
232 xh[..self.in_feat].copy_from_slice(x);
233 xh[self.in_feat..].fill(0.0);
236 for g in xh.chunks_exact_mut(self.group) {
237 fwht_normalized(g);
238 }
239 }
240
241 pub fn matvec(&self, x: &[f32], y: &mut [f32]) {
249 let mut xh = vec![0.0f32; self.in_padded];
250 self.prepare_input(x, &mut xh);
251 self.matvec_prepared(&xh, y);
252 }
253
254 pub fn matvec_prepared(&self, xh: &[f32], y: &mut [f32]) {
258 self.matvec_rows_prepared(xh, 0, y)
259 }
260
261 pub fn matvec_rows_prepared(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
267 #[cfg(feature = "parallel")]
268 if self.should_parallelise(y.len()) {
269 return self.matvec_rows_par(xh, row_start, y);
270 }
271 self.matvec_rows_serial(xh, row_start, y)
272 }
273
274 #[cfg(feature = "parallel")]
279 fn matvec_rows_par(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
280 use rayon::prelude::*;
281 let rows = y.len();
282 let chunk = rows
283 .div_ceil(rayon::current_num_threads())
284 .max(MIN_PAR_ROWS);
285 y.par_chunks_mut(chunk).enumerate().for_each(|(ci, part)| {
286 self.matvec_rows_serial(xh, row_start + ci * chunk, part);
287 });
288 }
289
290 fn matvec_rows_serial(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
291 debug_assert_eq!(xh.len(), self.in_padded);
292 debug_assert!(row_start + y.len() <= self.out_feat);
293 match self.per_byte {
294 4 => self.matvec_lut::<4>(xh, row_start, y),
295 2 => self.matvec_lut::<2>(xh, row_start, y),
296 _ => self.matvec_generic(xh, row_start, y),
297 }
298 }
299
300 #[cfg(feature = "bench-internals")]
307 pub fn matvec_prepared_single_acc(&self, xh: &[f32], y: &mut [f32]) {
308 debug_assert_eq!(xh.len(), self.in_padded);
309 debug_assert_eq!(y.len(), self.out_feat);
310 match self.per_byte {
314 4 => self.single_acc::<4>(xh, y),
315 2 => self.single_acc::<2>(xh, y),
316 _ => self.matvec_prepared_reference(xh, y),
317 }
318 }
319
320 #[cfg(feature = "bench-internals")]
321 fn single_acc<const P: usize>(&self, xh: &[f32], y: &mut [f32]) {
322 let bytes_per_group = self.group / P;
323 let lut = &self.lut[..256 * P];
324 for o in 0..self.out_feat {
325 let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
326 let row_norms = &self.norms[o * self.num_groups..(o + 1) * self.num_groups];
327 let mut total = 0.0f32;
328 for (g, &norm) in row_norms.iter().enumerate() {
329 let gbytes = &row[g * bytes_per_group..(g + 1) * bytes_per_group];
330 let gx = &xh[g * self.group..(g + 1) * self.group];
331 let mut acc = 0.0f32;
332 for (bi, &byte) in gbytes.iter().enumerate() {
333 let base = byte as usize * P;
334 let vals: &[f32; P] = lut[base..base + P].try_into().unwrap();
335 let xs: &[f32; P] = gx[bi * P..bi * P + P].try_into().unwrap();
336 for k in 0..P {
337 acc += vals[k] * xs[k];
338 }
339 }
340 total += norm * acc;
341 }
342 y[o] = total;
343 }
344 }
345
346 pub fn matvec_prepared_reference(&self, xh: &[f32], y: &mut [f32]) {
359 debug_assert_eq!(xh.len(), self.in_padded);
360 debug_assert_eq!(y.len(), self.out_feat);
361 let bits = if self.bits == TERNARY_RECORD_BITS {
362 2
363 } else {
364 self.bits as usize
365 };
366 let mask = (1u32 << bits) - 1;
367 for (o, slot) in y.iter_mut().enumerate() {
368 let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
369 let mut total = 0.0f32;
370 for g in 0..self.num_groups {
371 let norm = self.norms[o * self.num_groups + g];
372 let mut acc = 0.0f32;
373 for k in 0..self.group {
374 let code = read_bits(row, (g * self.group + k) * bits, bits, mask);
375 let idx = if self.bits == TERNARY_RECORD_BITS {
376 match code {
377 3 => 0,
378 0 => 1,
379 1 => 2,
380 _ => 1,
381 }
382 } else {
383 code as usize
384 };
385 acc += self.levels[idx] * xh[g * self.group + k];
386 }
387 total += norm * acc;
388 }
389 *slot = total;
390 }
391 }
392
393 pub fn matmul_rows_prepared(
412 &self,
413 xh: &[f32],
414 batch: usize,
415 row_start: usize,
416 rows: usize,
417 y: &mut [f32],
418 acc: &mut [f32],
419 ) {
420 #[cfg(feature = "parallel")]
421 if self.matmul_work(rows, batch) >= PAR_MIN_MACS {
422 if rows >= 2 * MIN_PAR_ROWS {
430 return self.matmul_rows_par(xh, batch, row_start, rows, y);
431 }
432 if batch >= 2 * MIN_PAR_BATCH {
433 return self.matmul_batch_par(xh, batch, row_start, rows, y);
434 }
435 }
436 self.matmul_rows_serial(xh, batch, row_start, rows, y, acc)
437 }
438
439 #[cfg(feature = "parallel")]
446 #[inline]
447 fn should_parallelise(&self, rows: usize) -> bool {
448 rows >= 2 * MIN_PAR_ROWS && rows.saturating_mul(self.in_padded) >= PAR_MIN_MACS
449 }
450
451 #[cfg(feature = "parallel")]
459 fn matmul_rows_par(
460 &self,
461 xh: &[f32],
462 batch: usize,
463 row_start: usize,
464 rows: usize,
465 y: &mut [f32],
466 ) {
467 use rayon::prelude::*;
468
469 struct Out(*mut f32);
472 unsafe impl Send for Out {}
475 unsafe impl Sync for Out {}
476 impl Out {
477 #[inline]
482 fn ptr(&self) -> *mut f32 {
483 self.0
484 }
485 }
486
487 let out = Out(y.as_mut_ptr());
488 let chunk = rows
489 .div_ceil(rayon::current_num_threads())
490 .max(MIN_PAR_ROWS);
491 let bands: Vec<usize> = (0..rows).step_by(chunk).collect();
492
493 bands.into_par_iter().for_each(|r0| {
494 let band = chunk.min(rows - r0);
495 let mut local = vec![0.0f32; batch * band];
496 let mut acc = vec![0.0f32; batch];
497 self.matmul_rows_serial(xh, batch, row_start + r0, band, &mut local, &mut acc);
498 for b in 0..batch {
499 unsafe {
501 core::ptr::copy_nonoverlapping(
502 local.as_ptr().add(b * band),
503 out.ptr().add(b * rows + r0),
504 band,
505 );
506 }
507 }
508 });
509 }
510
511 #[cfg(feature = "parallel")]
517 fn matmul_batch_par(
518 &self,
519 xh: &[f32],
520 batch: usize,
521 row_start: usize,
522 rows: usize,
523 y: &mut [f32],
524 ) {
525 use rayon::prelude::*;
526 let stride = self.in_padded;
527 let band = batch
528 .div_ceil(rayon::current_num_threads())
529 .max(MIN_PAR_BATCH);
530 y[..batch * rows]
531 .par_chunks_mut(band * rows)
532 .enumerate()
533 .for_each(|(ci, part)| {
534 let b0 = ci * band;
535 let n = part.len() / rows;
536 let mut acc = vec![0.0f32; n];
537 self.matmul_rows_serial(
538 &xh[b0 * stride..(b0 + n) * stride],
539 n,
540 row_start,
541 rows,
542 part,
543 &mut acc,
544 );
545 });
546 }
547
548 #[cfg(feature = "parallel")]
550 #[inline]
551 fn matmul_work(&self, rows: usize, batch: usize) -> usize {
552 rows.saturating_mul(batch).saturating_mul(self.in_padded)
553 }
554
555 #[allow(clippy::too_many_arguments)]
556 fn matmul_rows_serial(
557 &self,
558 xh: &[f32],
559 batch: usize,
560 row_start: usize,
561 rows: usize,
562 y: &mut [f32],
563 acc: &mut [f32],
564 ) {
565 #[cfg(all(target_arch = "x86_64", feature = "simd"))]
571 if crate::quant::has_avx2() {
572 return unsafe { self.matmul_rows_prepared_avx2(xh, batch, row_start, rows, y, acc) };
574 }
575 self.matmul_rows_prepared_impl(xh, batch, row_start, rows, y, acc)
576 }
577
578 #[cfg(all(target_arch = "x86_64", feature = "simd"))]
583 #[target_feature(enable = "avx2,fma")]
584 unsafe fn matmul_rows_prepared_avx2(
585 &self,
586 xh: &[f32],
587 batch: usize,
588 row_start: usize,
589 rows: usize,
590 y: &mut [f32],
591 acc: &mut [f32],
592 ) {
593 self.matmul_rows_prepared_impl(xh, batch, row_start, rows, y, acc)
594 }
595
596 #[inline(always)]
597 fn matmul_rows_prepared_impl(
598 &self,
599 xh: &[f32],
600 batch: usize,
601 row_start: usize,
602 rows: usize,
603 y: &mut [f32],
604 acc: &mut [f32],
605 ) {
606 debug_assert_eq!(xh.len(), batch * self.in_padded);
607 debug_assert_eq!(y.len(), batch * rows);
608 debug_assert!(acc.len() >= batch);
609 debug_assert!(row_start + rows <= self.out_feat);
610 debug_assert!(self.group <= MAX_GROUP);
611
612 let stride = self.in_padded;
613 let mut buf = [0.0f32; MAX_GROUP];
615 let ug = &mut buf[..self.group];
616 let lanewise = self.per_byte != 0;
617
618 for r in 0..rows {
619 let o = row_start + r;
620 let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
621 let row_norms = &self.norms[o * self.num_groups..(o + 1) * self.num_groups];
622 acc[..batch].fill(0.0);
623
624 for (g, &norm) in row_norms.iter().enumerate() {
625 self.decode_group(row, g, 1.0, ug);
627 let base = g * self.group;
628 for b in 0..batch {
629 let gx = &xh[b * stride + base..b * stride + base + self.group];
630 let s = if lanewise {
633 group_dot_lanes(ug, gx)
634 } else {
635 group_dot_serial(ug, gx)
636 };
637 acc[b] += norm * s;
638 }
639 }
640 for b in 0..batch {
641 y[b * rows + r] = acc[b];
642 }
643 }
644 }
645
646 fn matvec_lut<const P: usize>(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
659 #[cfg(all(target_arch = "x86_64", feature = "simd"))]
668 if crate::quant::has_avx2() {
669 return unsafe { self.matvec_lut_avx2::<P>(xh, row_start, y) };
671 }
672 self.matvec_lut_impl::<P>(xh, row_start, y)
673 }
674
675 #[cfg(all(target_arch = "x86_64", feature = "simd"))]
680 #[target_feature(enable = "avx2,fma")]
681 unsafe fn matvec_lut_avx2<const P: usize>(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
682 self.matvec_lut_impl::<P>(xh, row_start, y)
683 }
684
685 #[inline(always)]
686 fn matvec_lut_impl<const P: usize>(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
687 let bytes_per_group = self.group / P;
688 let lut = &self.lut[..256 * P];
689 for (yi, o) in (row_start..row_start + y.len()).enumerate() {
690 let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
691 let row_norms = &self.norms[o * self.num_groups..(o + 1) * self.num_groups];
692 let mut total = 0.0f32;
693 for (g, &norm) in row_norms.iter().enumerate() {
694 let gbytes = &row[g * bytes_per_group..(g + 1) * bytes_per_group];
695 let gx = &xh[g * self.group..(g + 1) * self.group];
696 total += norm * dot_group::<P>(lut, gbytes, gx);
697 }
698 y[yi] = total;
699 }
700 }
701
702 fn matvec_generic(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
704 let bits = self.bits as usize;
705 let mask = (1u32 << bits) - 1;
706 for (yi, o) in (row_start..row_start + y.len()).enumerate() {
707 let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
708 let row_norms = &self.norms[o * self.num_groups..(o + 1) * self.num_groups];
709 let mut total = 0.0f32;
710 for (g, &norm) in row_norms.iter().enumerate() {
711 let gx = &xh[g * self.group..(g + 1) * self.group];
712 let mut acc = 0.0f32;
713 for (k, &x) in gx.iter().enumerate() {
714 let bit = (g * self.group + k) * bits;
715 let idx = read_bits(row, bit, bits, mask);
716 acc += self.levels[idx as usize] * x;
717 }
718 total += norm * acc;
719 }
720 y[yi] = total;
721 }
722 }
723
724 pub fn dequantize_row(&self, o: usize, out: &mut [f32]) {
729 debug_assert!(o < self.out_feat);
730 debug_assert_eq!(out.len(), self.in_feat);
731 let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
732 let row_norms = &self.norms[o * self.num_groups..(o + 1) * self.num_groups];
733 let mut buf = vec![0.0f32; self.group];
734 for (g, &norm) in row_norms.iter().enumerate() {
735 self.decode_group(row, g, norm, &mut buf);
736 fwht_normalized(&mut buf);
737 let base = g * self.group;
738 for k in 0..self.group {
739 if base + k < self.in_feat {
740 out[base + k] = buf[k];
741 }
742 }
743 }
744 }
745
746 pub fn dequantize_to(&self, out: &mut [f32]) {
748 debug_assert_eq!(out.len(), self.out_feat * self.in_feat);
749 for o in 0..self.out_feat {
750 self.dequantize_row(o, &mut out[o * self.in_feat..(o + 1) * self.in_feat]);
751 }
752 }
753
754 fn decode_group(&self, row: &[u8], g: usize, norm: f32, buf: &mut [f32]) {
757 if self.per_byte != 0 {
758 let p = self.per_byte;
759 let bytes_per_group = self.group / p;
760 let gbytes = &row[g * bytes_per_group..(g + 1) * bytes_per_group];
761 for (bi, &byte) in gbytes.iter().enumerate() {
762 for k in 0..p {
763 buf[bi * p + k] = self.lut[byte as usize * p + k] * norm;
764 }
765 }
766 } else {
767 let bits = self.bits as usize;
768 let mask = (1u32 << bits) - 1;
769 for (k, slot) in buf.iter_mut().enumerate() {
770 let idx = read_bits(row, (g * self.group + k) * bits, bits, mask);
771 *slot = self.levels[idx as usize] * norm;
772 }
773 }
774 }
775}
776
777#[cfg(feature = "parallel")]
780const MIN_PAR_ROWS: usize = 64;
781
782#[cfg(feature = "parallel")]
784const MIN_PAR_BATCH: usize = 8;
785
786#[cfg(feature = "parallel")]
788const PAR_MIN_MACS: usize = 1 << 20;
789
790pub const ACC_LANES: usize = 8;
796
797#[inline(always)]
803fn dot_group<const P: usize>(lut: &[f32], gbytes: &[u8], gx: &[f32]) -> f32 {
804 debug_assert_eq!(ACC_LANES % P, 0);
805 let per_iter = ACC_LANES / P;
806 let mut lanes = [0.0f32; ACC_LANES];
807
808 let full = gbytes.len() - gbytes.len() % per_iter;
809 let mut bi = 0;
810 while bi < full {
811 let mut vals = [0.0f32; ACC_LANES];
817 for t in 0..per_iter {
818 let base = gbytes[bi + t] as usize * P;
819 let entry: &[f32; P] = lut[base..base + P].try_into().unwrap();
820 vals[t * P..(t + 1) * P].copy_from_slice(entry);
821 }
822 let xs: &[f32; ACC_LANES] = gx[bi * P..bi * P + ACC_LANES].try_into().unwrap();
823 for k in 0..ACC_LANES {
824 lanes[k] += vals[k] * xs[k];
825 }
826 bi += per_iter;
827 }
828
829 let mut acc: f32 = lanes.iter().sum();
830 for b in bi..gbytes.len() {
832 let base = gbytes[b] as usize * P;
833 for k in 0..P {
834 acc += lut[base + k] * gx[b * P + k];
835 }
836 }
837 acc
838}
839
840#[inline(always)]
844fn group_dot_lanes(u: &[f32], x: &[f32]) -> f32 {
845 debug_assert_eq!(u.len(), x.len());
846 let chunks = u.len() / ACC_LANES;
847 let mut lanes = [0.0f32; ACC_LANES];
848 for c in 0..chunks {
849 let uu: &[f32; ACC_LANES] = u[c * ACC_LANES..(c + 1) * ACC_LANES].try_into().unwrap();
850 let xx: &[f32; ACC_LANES] = x[c * ACC_LANES..(c + 1) * ACC_LANES].try_into().unwrap();
851 for k in 0..ACC_LANES {
852 lanes[k] += uu[k] * xx[k];
853 }
854 }
855 let mut s: f32 = lanes.iter().sum();
856 for k in chunks * ACC_LANES..u.len() {
857 s += u[k] * x[k];
858 }
859 s
860}
861
862#[inline(always)]
865fn group_dot_serial(u: &[f32], x: &[f32]) -> f32 {
866 let mut s = 0.0f32;
867 for (a, b) in u.iter().zip(x.iter()) {
868 s += a * b;
869 }
870 s
871}
872
873#[inline]
879fn read_bits(row: &[u8], bit: usize, bits: usize, mask: u32) -> u32 {
880 let byte = bit / 8;
881 let shift = bit % 8;
882 let lo = row[byte] as u32;
884 let hi = if shift + bits > 8 {
885 row[byte + 1] as u32
886 } else {
887 0
888 };
889 ((lo | (hi << 8)) >> shift) & mask
890}
891
892fn build_lut(bits: u8, levels: &[f32]) -> (usize, Vec<f32>) {
898 let ternary = bits == TERNARY_RECORD_BITS;
901 let width = if ternary { 2 } else { bits as usize };
902 let per_byte = match width {
903 2 => 4,
904 4 => 2,
905 _ => return (0, Vec::new()), };
907 let mask = (1u32 << width) - 1;
908
909 let mut lut = vec![0.0f32; 256 * per_byte];
910 for byte in 0..256usize {
911 for k in 0..per_byte {
912 let code = ((byte as u32) >> (k * width)) & mask;
913 let idx = if ternary {
914 match code {
917 3 => 0,
918 0 => 1,
919 1 => 2,
920 _ => 1,
921 }
922 } else {
923 code as usize
924 };
925 lut[byte * per_byte + k] = levels[idx];
926 }
927 }
928 (per_byte, lut)
929}
930
931#[cfg(test)]
932mod tests {
933 use super::*;
934 use crate::hadamard::fwht_normalized;
935
936 fn fake_codebook(group: usize) -> Vec<f32> {
939 let s = 1.0 / math::sqrt(group as f32);
940 let mut cb = Vec::with_capacity(CODEBOOK_LEN);
941 for &bits in &CODEBOOK_WIDTHS {
942 let levels = 1usize << bits;
943 for i in 0..levels {
944 let v = (i as f32 - (levels as f32 - 1.0) / 2.0) / (levels as f32 / 2.0);
946 cb.push(v * s);
947 }
948 }
949 assert_eq!(cb.len(), CODEBOOK_LEN);
950 cb
951 }
952
953 fn pack_lsb(idx: &[u8], out: usize, in_pad: usize, bits: usize) -> Vec<u8> {
955 let row_bytes = in_pad * bits / 8;
956 let mut packed = vec![0u8; out * row_bytes];
957 for o in 0..out {
958 for k in 0..in_pad {
959 let v = idx[o * in_pad + k] as u32;
960 let bit = k * bits;
961 for b in 0..bits {
962 if (v >> b) & 1 == 1 {
963 let abs = bit + b;
964 packed[o * row_bytes + abs / 8] |= 1 << (abs % 8);
965 }
966 }
967 }
968 }
969 packed
970 }
971
972 fn f32_to_f16(v: f32) -> u16 {
973 let x = v.to_bits();
974 let sign = ((x >> 31) as u16) << 15;
975 let exp = ((x >> 23) & 0xFF) as i32 - 127;
976 let mant = x & 0x7F_FFFF;
977 if exp < -14 {
978 return sign;
979 }
980 if exp > 15 {
981 return sign | 0x7C00;
982 }
983 sign | (((exp + 15) as u16) << 10) | ((mant >> 13) as u16)
984 }
985
986 struct Case {
987 w: CqWeight,
988 dense: Vec<f32>,
991 }
992
993 fn build_case(out: usize, in_feat: usize, group: usize, bits: u8) -> Case {
994 let cb = fake_codebook(group);
995 let levels: Vec<f32> = if bits == TERNARY_RECORD_BITS {
996 let s = TERNARY_CENTROID / math::sqrt(group as f32);
997 vec![-s, 0.0, s]
998 } else {
999 codebook_slice(&cb, bits).unwrap().to_vec()
1000 };
1001 let n_levels = levels.len();
1002
1003 let in_pad = in_feat.div_ceil(group) * group;
1004 let num_groups = in_pad / group;
1005
1006 let idx: Vec<u8> = (0..out * in_pad)
1008 .map(|i| ((i * 7 + i / 13) % n_levels) as u8)
1009 .collect();
1010 let norms: Vec<f32> = (0..out * num_groups)
1011 .map(|i| 0.25 + (i % 11) as f32 * 0.125)
1012 .collect();
1013
1014 let packed = if bits == TERNARY_RECORD_BITS {
1015 let crumbs: Vec<u8> = idx
1017 .iter()
1018 .map(|&t| if t == 0 { 3 } else { t - 1 })
1019 .collect();
1020 pack_lsb(&crumbs, out, in_pad, 2)
1021 } else {
1022 pack_lsb(&idx, out, in_pad, bits as usize)
1023 };
1024
1025 let mut blob = packed;
1026 for &n in &norms {
1027 blob.extend_from_slice(&f32_to_f16(n).to_le_bytes());
1028 }
1029
1030 let mut dense = vec![0.0f32; out * in_feat];
1032 for o in 0..out {
1033 for g in 0..num_groups {
1034 let mut u: Vec<f32> = (0..group)
1035 .map(|k| levels[idx[o * in_pad + g * group + k] as usize])
1036 .collect();
1037 let norm = math::f16_to_f32(f32_to_f16(norms[o * num_groups + g]));
1038 for v in u.iter_mut() {
1039 *v *= norm;
1040 }
1041 fwht_normalized(&mut u);
1042 for (k, &val) in u.iter().enumerate() {
1043 let col = g * group + k;
1044 if col < in_feat {
1045 dense[o * in_feat + col] = val;
1046 }
1047 }
1048 }
1049 }
1050
1051 let w = CqWeight::from_blob(&blob, out, in_feat, group, bits, &cb).unwrap();
1052 Case { w, dense }
1053 }
1054
1055 const WIDTHS: [u8; 4] = [2, 3, 4, TERNARY_RECORD_BITS];
1056
1057 #[test]
1058 fn dequantize_matches_reference_reconstruction() {
1059 for &bits in &WIDTHS {
1060 for &(out, in_feat, group) in &[(5usize, 16usize, 16usize), (3, 40, 8), (7, 128, 128)] {
1061 let c = build_case(out, in_feat, group, bits);
1062 let mut got = vec![0.0f32; out * in_feat];
1063 c.w.dequantize_to(&mut got);
1064 for (i, (&g_i, &want)) in got.iter().zip(c.dense.iter()).enumerate() {
1065 assert!(
1066 (g_i - want).abs() < 1e-5,
1067 "bits={bits} shape={out}x{in_feat} g={group} i={i}: {g_i} vs {want}"
1068 );
1069 }
1070 }
1071 }
1072 }
1073
1074 #[test]
1077 fn matvec_matches_dense_multiply() {
1078 for &bits in &WIDTHS {
1079 for &(out, in_feat, group) in &[
1080 (5usize, 16usize, 16usize),
1081 (3, 40, 8),
1082 (9, 128, 128),
1083 (4, 200, 64),
1084 ] {
1085 let c = build_case(out, in_feat, group, bits);
1086 let x: Vec<f32> = (0..in_feat)
1087 .map(|i| (i as f32 * 0.31).sin() * 1.7 + 0.05 * i as f32)
1088 .collect();
1089
1090 let mut got = vec![0.0f32; out];
1091 c.w.matvec(&x, &mut got);
1092
1093 for (o, &g_o) in got.iter().enumerate() {
1094 let want: f32 = (0..in_feat).map(|j| c.dense[o * in_feat + j] * x[j]).sum();
1095 let tol = 1e-4 * want.abs().max(1.0);
1096 assert!(
1097 (g_o - want).abs() < tol,
1098 "bits={bits} shape={out}x{in_feat} g={group} o={o}: {g_o} vs {want}"
1099 );
1100 }
1101 }
1102 }
1103 }
1104
1105 #[test]
1107 fn padding_does_not_leak_into_matvec() {
1108 let c = build_case(6, 40, 32, 2);
1110 let x: Vec<f32> = (0..40).map(|i| 1.0 + i as f32).collect();
1111 let mut got = vec![0.0f32; 6];
1112 c.w.matvec(&x, &mut got);
1113 for (o, &g_o) in got.iter().enumerate() {
1114 let want: f32 = (0..40).map(|j| c.dense[o * 40 + j] * x[j]).sum();
1115 assert!((g_o - want).abs() < 1e-3 * want.abs().max(1.0));
1116 }
1117 }
1118
1119 #[test]
1121 fn row_range_matches_full_matvec() {
1122 for &bits in &WIDTHS {
1123 let c = build_case(24, 128, 128, bits);
1124 let x: Vec<f32> = (0..128).map(|i| (i as f32 * 0.19).sin()).collect();
1125 let mut full = vec![0.0f32; 24];
1126 c.w.matvec(&x, &mut full);
1127
1128 let mut xh = vec![0.0f32; c.w.prepared_len()];
1129 c.w.prepare_input(&x, &mut xh);
1130 for &(start, len) in &[(0usize, 4usize), (4, 4), (8, 16), (20, 4), (23, 1)] {
1131 let mut band = vec![0.0f32; len];
1132 c.w.matvec_rows_prepared(&xh, start, &mut band);
1133 assert_eq!(
1134 &band[..],
1135 &full[start..start + len],
1136 "bits={bits} band {start}+{len}"
1137 );
1138 }
1139 }
1140 }
1141
1142 #[test]
1148 fn matvec_matches_simple_reference() {
1149 for &bits in &WIDTHS {
1150 for &(out, in_feat, group) in &[
1151 (16usize, 128usize, 128usize),
1152 (5, 40, 8),
1153 (7, 200, 64),
1154 (9, 512, 128),
1155 ] {
1156 let c = build_case(out, in_feat, group, bits);
1157 let x: Vec<f32> = (0..in_feat)
1158 .map(|i| (i as f32 * 0.29).sin() * 2.0)
1159 .collect();
1160 let mut xh = vec![0.0f32; c.w.prepared_len()];
1161 c.w.prepare_input(&x, &mut xh);
1162
1163 let mut fast = vec![0.0f32; out];
1164 let mut slow = vec![0.0f32; out];
1165 c.w.matvec_prepared(&xh, &mut fast);
1166 c.w.matvec_prepared_reference(&xh, &mut slow);
1167
1168 let l1: f32 = xh.iter().map(|v| v.abs()).sum();
1169 for o in 0..out {
1170 assert!(
1171 (fast[o] - slow[o]).abs() <= 1e-5 * l1.max(1.0),
1172 "bits={bits} {out}x{in_feat} g={group} o={o}: {} vs {}",
1173 fast[o],
1174 slow[o]
1175 );
1176 }
1177 }
1178 }
1179 }
1180
1181 #[test]
1182 fn matmul_matches_repeated_matvec() {
1183 for &bits in &WIDTHS {
1184 for &(out, in_feat, group) in &[
1185 (24usize, 128usize, 128usize),
1186 (9, 40, 8),
1187 (5, 200, 64),
1188 (16, 512, 128),
1189 ] {
1190 let c = build_case(out, in_feat, group, bits);
1191 for &batch in &[1usize, 2, 7, 16] {
1192 let mut xh = vec![0.0f32; batch * c.w.prepared_len()];
1194 let mut xs = Vec::new();
1195 for b in 0..batch {
1196 let x: Vec<f32> = (0..in_feat)
1197 .map(|i| ((i + b * 13) as f32 * 0.23).sin() * 1.3)
1198 .collect();
1199 c.w.prepare_input(
1200 &x,
1201 &mut xh[b * c.w.prepared_len()..(b + 1) * c.w.prepared_len()],
1202 );
1203 xs.push(x);
1204 }
1205
1206 for &(row_start, rows) in &[(0usize, out), (0, 1), (1, 3.min(out - 1))] {
1207 if row_start + rows > out || rows == 0 {
1208 continue;
1209 }
1210 let mut got = vec![0.0f32; batch * rows];
1211 let mut acc = vec![0.0f32; batch];
1212 c.w.matmul_rows_prepared(&xh, batch, row_start, rows, &mut got, &mut acc);
1213
1214 for b in 0..batch {
1215 let mut want = vec![0.0f32; rows];
1216 c.w.matvec_rows_prepared(
1217 &xh[b * c.w.prepared_len()..(b + 1) * c.w.prepared_len()],
1218 row_start,
1219 &mut want,
1220 );
1221 for r in 0..rows {
1222 assert_eq!(
1223 got[b * rows + r],
1224 want[r],
1225 "bits={bits} {out}x{in_feat} g={group} batch={batch} \
1226 rows={row_start}+{rows} b={b} r={r}"
1227 );
1228 }
1229 }
1230 }
1231 }
1232 }
1233 }
1234 }
1235
1236 #[test]
1237 fn prepared_input_is_reusable_across_weights() {
1238 let a = build_case(4, 128, 128, 2);
1239 let b = build_case(6, 128, 128, 4);
1240 let x: Vec<f32> = (0..128).map(|i| (i as f32 * 0.07).cos()).collect();
1241
1242 let mut xh = vec![0.0f32; a.w.prepared_len()];
1243 a.w.prepare_input(&x, &mut xh);
1244
1245 let mut ya = vec![0.0f32; 4];
1246 let mut yb = vec![0.0f32; 6];
1247 a.w.matvec_prepared(&xh, &mut ya);
1248 b.w.matvec_prepared(&xh, &mut yb);
1249
1250 let mut ya_ref = vec![0.0f32; 4];
1251 let mut yb_ref = vec![0.0f32; 6];
1252 a.w.matvec(&x, &mut ya_ref);
1253 b.w.matvec(&x, &mut yb_ref);
1254 assert_eq!(ya, ya_ref);
1255 assert_eq!(yb, yb_ref);
1256 }
1257
1258 #[test]
1259 fn row_bytes_match_shipped_tensor_sizes() {
1260 for &(out, in_f, bits, group, want) in &[
1263 (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), ] {
1270 let in_pad = in_f.div_ceil(group) * group;
1271 let got = out * packed_row_bytes(in_pad, bits) + out * (in_pad / group) * 2;
1272 assert_eq!(got, want, "{out}x{in_f} @ {bits}b/{group}");
1273 }
1274 }
1275
1276 #[test]
1277 fn rejects_bad_geometry() {
1278 let cb = fake_codebook(128);
1279 let blob = vec![0u8; 1 << 16];
1280 let err = |b: &[u8], out, in_f, group, bits, cb: &[f32]| {
1283 CqWeight::from_blob(b, out, in_f, group, bits, cb).err()
1284 };
1285 assert_eq!(
1286 err(&blob, 4, 128, 128, 7, &cb),
1287 Some(CqError::UnsupportedBits(7))
1288 );
1289 assert_eq!(err(&blob, 4, 128, 96, 2, &cb), Some(CqError::BadGroup(96)));
1290 assert_eq!(
1291 err(&blob, 4, 128, 100, 2, &cb),
1292 Some(CqError::BadGroup(100))
1293 );
1294 assert_eq!(err(&blob, 0, 128, 128, 2, &cb), Some(CqError::EmptyShape));
1295 assert_eq!(err(&blob, 4, 0, 128, 2, &cb), Some(CqError::EmptyShape));
1296 assert!(matches!(
1297 err(&[0u8; 4], 512, 512, 128, 2, &cb),
1298 Some(CqError::ShortBlob { .. })
1299 ));
1300 assert_eq!(
1301 err(&blob, 4, 128, 128, 2, &cb[..10]),
1302 Some(CqError::BadCodebookLen(10))
1303 );
1304 }
1305}
1306
1307#[cfg(test)]
1308mod dispatch_tests {
1309 extern crate std;
1312
1313 #[test]
1321 #[cfg(all(target_arch = "x86_64", feature = "simd"))]
1322 fn avx2_path_is_actually_selected_when_supported() {
1323 let cpuid_says = self::std::is_x86_feature_detected!("avx2");
1324 let we_say = crate::quant::has_avx2();
1325 assert_eq!(
1326 we_say, cpuid_says,
1327 "has_avx2() disagrees with std's detection; the dispatch would pick the wrong kernel"
1328 );
1329 if cpuid_says {
1330 assert!(
1331 we_say,
1332 "AVX2 is present but the wide kernel would not be selected"
1333 );
1334 self::std::eprintln!("AVX2 dispatch active: the wide packed-dot kernel is in use");
1335 } else {
1336 self::std::eprintln!("no AVX2 on this host; the portable kernel is in use");
1337 }
1338 }
1339 }