1use crate::pool::{Pool, matvec_rows, matvec_rows2};
15use cortiq_core::quant::{
16 GROUP_SIZE, Q1_TILE, Q4_TILE, Q4TP_NIB, f16_to_f32, q4tp_code, q4tp_ladder, q4tp_sections,
17};
18use cortiq_core::{CmfModel, TensorDtype};
19use std::sync::Arc;
20
21pub enum QTensor {
22 F32 {
23 data: Vec<f32>,
24 rows: usize,
25 cols: usize,
26 },
27 Mapped {
28 model: Arc<CmfModel>,
29 idx: usize,
31 dtype: TensorDtype,
32 rows: usize,
33 cols: usize,
34 row_scale: Vec<f32>,
36 col_field: Vec<f32>,
38 vbit_offsets: Vec<usize>,
42 repack: Vec<u8>,
50 },
51}
52
53fn repack_enabled() -> bool {
60 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
61 *ON.get_or_init(|| {
62 std::env::var("CMF_REPACK")
63 .map(|v| v == "1")
64 .unwrap_or(cfg!(target_os = "android"))
65 })
66}
67
68fn q8_repack(bytes: &[u8], rows: usize, cols: usize) -> Vec<u8> {
72 #[cfg(target_arch = "aarch64")]
73 let arch_ok = sdot_enabled();
74 #[cfg(not(target_arch = "aarch64"))]
75 let arch_ok = false;
76 if !arch_ok || !repack_enabled() || rows < 256 || cols % 16 != 0 {
77 return Vec::new();
78 }
79 q8_repack_layout(bytes, rows, cols)
80}
81
82fn q8_repack_layout(bytes: &[u8], rows: usize, cols: usize) -> Vec<u8> {
85 let groups = rows / 4;
86 let mut rep = vec![0u8; groups * 4 * cols];
87 for g in 0..groups {
88 let dst = &mut rep[g * 4 * cols..(g + 1) * 4 * cols];
89 for c in 0..cols / 16 {
90 for lane in 0..4 {
91 let src = (g * 4 + lane) * cols + c * 16;
92 dst[c * 64 + lane * 16..c * 64 + lane * 16 + 16]
93 .copy_from_slice(&bytes[src..src + 16]);
94 }
95 }
96 }
97 rep
98}
99
100fn vbit_row_offsets(bytes: &[u8], rows: usize, cols: usize) -> Vec<usize> {
103 let ng = cols / GROUP_SIZE;
104 let bits = &bytes[..rows];
105 let mut offsets = Vec::with_capacity(rows + 1);
106 let mut off = rows + rows * ng * 2;
107 for r in 0..rows {
108 offsets.push(off);
109 off += (cols * bits[r] as usize).div_ceil(8);
110 }
111 offsets.push(off);
112 offsets
113}
114
115impl QTensor {
116 pub fn from_f32(data: Vec<f32>, rows: usize, cols: usize) -> Self {
117 debug_assert_eq!(data.len(), rows * cols);
118 Self::F32 { data, rows, cols }
119 }
120
121 pub fn from_model(model: &Arc<CmfModel>, name: &str) -> Result<Self, String> {
124 let idx = model
127 .tensor_index(name)
128 .ok_or_else(|| format!("tensor '{name}' not found in CMF directory"))?;
129 let entry = &model.tensors[idx];
130 if entry.shape.len() != 2 {
131 return Err(format!("QTensor::from_model needs 2-D, got '{name}'"));
132 }
133 let (rows, cols) = (entry.shape[0], entry.shape[1]);
134 let bytes = model.entry_bytes(entry);
135
136 match entry.dtype {
137 TensorDtype::Q8Row | TensorDtype::Q8_2f => {
138 let n = rows * cols;
139 let scales_off = n;
140 let row_scale: Vec<f32> = (0..rows)
141 .map(|o| {
142 f16_to_f32(u16::from_le_bytes([
143 bytes[scales_off + o * 2],
144 bytes[scales_off + o * 2 + 1],
145 ]))
146 })
147 .collect();
148 let col_field: Vec<f32> = if entry.dtype == TensorDtype::Q8_2f {
149 let col_off = n + rows * 2;
150 (0..cols)
151 .map(|i| {
152 f16_to_f32(u16::from_le_bytes([
153 bytes[col_off + i * 2],
154 bytes[col_off + i * 2 + 1],
155 ]))
156 })
157 .collect()
158 } else {
159 Vec::new()
160 };
161 Ok(Self::Mapped {
162 model: model.clone(),
163 idx,
164 dtype: entry.dtype,
165 rows,
166 cols,
167 row_scale,
168 col_field,
169 vbit_offsets: Vec::new(),
170 repack: q8_repack(bytes, rows, cols),
171 })
172 }
173 TensorDtype::Vbit if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
175 model: model.clone(),
176 idx,
177 dtype: entry.dtype,
178 rows,
179 cols,
180 row_scale: Vec::new(),
181 col_field: Vec::new(),
182 vbit_offsets: vbit_row_offsets(bytes, rows, cols),
183 repack: Vec::new(),
184 }),
185 TensorDtype::VbitRo if cols % GROUP_SIZE == 0 => {
189 let (_, off_off, packed_off) = cortiq_core::quant::vbit_ro_sections(rows, cols);
190 let offsets: Vec<usize> = (0..=rows)
191 .map(|r| packed_off + cortiq_core::quant::vbit_ro_offset(bytes, off_off, r))
192 .collect();
193 Ok(Self::Mapped {
194 model: model.clone(),
195 idx,
196 dtype: entry.dtype,
197 rows,
198 cols,
199 row_scale: Vec::new(),
200 col_field: Vec::new(),
201 vbit_offsets: offsets,
202 repack: Vec::new(),
203 })
204 }
205 TensorDtype::Q4Tiled if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
211 model: model.clone(),
212 idx,
213 dtype: entry.dtype,
214 rows,
215 cols,
216 row_scale: Vec::new(),
217 col_field: Vec::new(),
218 vbit_offsets: Vec::new(),
219 repack: Vec::new(),
220 }),
221 TensorDtype::Q4TiledP if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
224 model: model.clone(),
225 idx,
226 dtype: entry.dtype,
227 rows,
228 cols,
229 row_scale: Vec::new(),
230 col_field: Vec::new(),
231 vbit_offsets: Vec::new(),
232 repack: Vec::new(),
233 }),
234 TensorDtype::Q4Block if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
235 model: model.clone(),
236 idx,
237 dtype: entry.dtype,
238 rows,
239 cols,
240 row_scale: Vec::new(),
241 col_field: Vec::new(),
242 vbit_offsets: Vec::new(),
243 repack: Vec::new(),
244 }),
245 TensorDtype::Q1 if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
247 model: model.clone(),
248 idx,
249 dtype: entry.dtype,
250 rows,
251 cols,
252 row_scale: Vec::new(),
253 col_field: Vec::new(),
254 vbit_offsets: Vec::new(),
255 repack: Vec::new(),
256 }),
257 TensorDtype::Q1T if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
261 model: model.clone(),
262 idx,
263 dtype: entry.dtype,
264 rows,
265 cols,
266 row_scale: Vec::new(),
267 col_field: Vec::new(),
268 vbit_offsets: Vec::new(),
269 repack: Vec::new(),
270 }),
271 _ => {
273 let mut data = vec![0.0f32; rows * cols];
274 cortiq_core::quant::dequant_tensor(entry, bytes, &mut data)?;
275 Ok(Self::from_f32(data, rows, cols))
276 }
277 }
278 }
279
280 pub(crate) fn is_q1(&self) -> bool {
283 matches!(
284 self,
285 Self::Mapped {
286 dtype: TensorDtype::Q1,
287 ..
288 }
289 )
290 }
291
292 pub(crate) fn f32_parts(&self) -> Option<(&[f32], usize, usize)> {
295 match self {
296 Self::F32 { data, rows, cols } => Some((data, *rows, *cols)),
297 _ => None,
298 }
299 }
300
301 pub(crate) fn q1_parts(&self) -> Option<(usize, usize, usize)> {
308 match self {
309 #[cfg(target_os = "macos")]
310 Self::Mapped {
311 dtype: TensorDtype::Q1T,
312 ..
313 } if !crate::gpu::metal_q1t_enabled() => None,
314 Self::Mapped {
315 idx,
316 dtype:
317 TensorDtype::Q1
318 | TensorDtype::Q1T
319 | TensorDtype::Q4Block
320 | TensorDtype::Q4Tiled
321 | TensorDtype::Q4TiledP
322 | TensorDtype::Q8Row
323 | TensorDtype::Q8_2f,
324 rows,
325 cols,
326 ..
327 } => Some((*idx, *rows, *cols)),
328 _ => None,
329 }
330 }
331
332 pub(crate) fn q4t_parts(&self) -> Option<(usize, usize, usize)> {
338 match self {
339 Self::Mapped {
340 idx,
341 dtype: TensorDtype::Q4Tiled,
342 rows,
343 cols,
344 ..
345 } => Some((*idx, *rows, *cols)),
346 _ => None,
347 }
348 }
349
350 pub(crate) fn q4tp_parts(&self) -> Option<(usize, usize, usize)> {
354 match self {
355 Self::Mapped {
356 idx,
357 dtype: TensorDtype::Q4TiledP,
358 rows,
359 cols,
360 ..
361 } => Some((*idx, *rows, *cols)),
362 _ => None,
363 }
364 }
365
366 pub(crate) fn q8_row_parts(&self) -> Option<(usize, usize, usize, &[f32])> {
371 match self {
372 Self::Mapped {
373 idx,
374 dtype: TensorDtype::Q8Row,
375 rows,
376 cols,
377 row_scale,
378 col_field,
379 ..
380 } if col_field.is_empty() => Some((*idx, *rows, *cols, row_scale)),
381 _ => None,
382 }
383 }
384
385 pub fn rows(&self) -> usize {
386 match self {
387 Self::F32 { rows, .. } | Self::Mapped { rows, .. } => *rows,
388 }
389 }
390
391 pub(crate) fn mapped_q4t(&self) -> Option<(&Arc<CmfModel>, usize)> {
394 match self {
395 Self::Mapped {
396 model,
397 idx,
398 dtype: TensorDtype::Q4Tiled,
399 ..
400 } => Some((model, *idx)),
401 _ => None,
402 }
403 }
404
405 pub fn mapped_q4tp(&self) -> Option<(&Arc<CmfModel>, usize)> {
408 match self {
409 Self::Mapped {
410 model,
411 idx,
412 dtype: TensorDtype::Q4TiledP,
413 ..
414 } => Some((model, *idx)),
415 _ => None,
416 }
417 }
418
419 pub fn cols(&self) -> usize {
420 match self {
421 Self::F32 { cols, .. } | Self::Mapped { cols, .. } => *cols,
422 }
423 }
424
425 pub fn mapped_q1(&self) -> Option<(&std::sync::Arc<CmfModel>, usize)> {
428 match self {
429 Self::Mapped {
430 model,
431 idx,
432 dtype: TensorDtype::Q1,
433 ..
434 } => Some((model, *idx)),
435 _ => None,
436 }
437 }
438
439 pub fn graph_weight(&self) -> Option<(&std::sync::Arc<CmfModel>, usize, u8, &[f32])> {
443 match self {
444 Self::Mapped {
445 model,
446 idx,
447 dtype: TensorDtype::Q8Row,
448 row_scale,
449 ..
450 } => Some((model, *idx, 0, row_scale.as_slice())),
451 Self::Mapped {
452 model,
453 idx,
454 dtype: TensorDtype::Q1,
455 ..
456 } => Some((model, *idx, 1, &[])),
457 Self::Mapped {
462 model,
463 idx,
464 dtype: TensorDtype::Q4Tiled,
465 ..
466 } => Some((model, *idx, 5, &[])),
467 Self::Mapped {
471 model,
472 idx,
473 dtype: TensorDtype::Q4TiledP,
474 ..
475 } => Some((model, *idx, 6, &[])),
476 Self::Mapped {
477 model,
478 idx,
479 dtype: TensorDtype::Q4Block,
480 ..
481 } => Some((model, *idx, 2, &[])),
482 Self::Mapped {
483 model,
484 idx,
485 dtype: TensorDtype::Q1T,
486 ..
487 } => Some((model, *idx, 3, &[])),
488 _ => None,
489 }
490 }
491
492 pub fn as_f32(&self) -> Option<&[f32]> {
495 match self {
496 Self::F32 { data, .. } => Some(data),
497 Self::Mapped { .. } => None,
498 }
499 }
500
501 fn quant_bytes(&self) -> &[u8] {
502 match self {
503 Self::Mapped { model, idx, .. } => model.entry_bytes(&model.tensors[*idx]),
504 Self::F32 { .. } => unreachable!("quant_bytes on F32"),
505 }
506 }
507
508 pub fn row_f32(&self, r: usize, dst: &mut [f32]) {
510 let cols = self.cols();
511 debug_assert_eq!(dst.len(), cols);
512 match self {
513 Self::F32 { data, .. } => dst.copy_from_slice(&data[r * cols..(r + 1) * cols]),
514 Self::Mapped {
515 dtype,
516 row_scale,
517 col_field,
518 vbit_offsets,
519 ..
520 } => {
521 if *dtype == TensorDtype::Q4Tiled {
522 let bytes = self.quant_bytes();
523 let gpr = cols / GROUP_SIZE;
524 for gi in 0..gpr {
525 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
526 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
527 for (k, &b) in tile[2..].iter().enumerate() {
528 dst[gi * GROUP_SIZE + k * 2] = ((b & 0x0F) as f32 - 8.0) * s;
529 dst[gi * GROUP_SIZE + k * 2 + 1] = (((b >> 4) & 0x0F) as f32 - 8.0) * s;
530 }
531 }
532 return;
533 }
534 if *dtype == TensorDtype::Q4TiledP {
535 let bytes = self.quant_bytes();
536 let gpr = cols / GROUP_SIZE;
537 let v = Q4tpView::new(bytes, self.rows(), cols);
538 let mut sc = vec![0f32; gpr];
539 v.scales_into(r, gpr, &mut sc);
540 for gi in 0..gpr {
541 let tile = &v.nib[(r * gpr + gi) * Q4TP_NIB..(r * gpr + gi + 1) * Q4TP_NIB];
542 let s = sc[gi];
543 for (k, &b) in tile.iter().enumerate() {
544 dst[gi * GROUP_SIZE + k * 2] = ((b & 0x0F) as f32 - 8.0) * s;
545 dst[gi * GROUP_SIZE + k * 2 + 1] = (((b >> 4) & 0x0F) as f32 - 8.0) * s;
546 }
547 }
548 return;
549 }
550 if *dtype == TensorDtype::Q4Block {
551 let (packed, scales) = q4_split(self.quant_bytes(), self.rows(), cols);
552 let gpr = cols / GROUP_SIZE;
553 for gi in 0..gpr {
554 let g = r * gpr + gi;
555 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
556 for (k, &b) in packed[g * 16..(g + 1) * 16].iter().enumerate() {
557 dst[gi * GROUP_SIZE + k * 2] = ((b & 0x0F) as f32 - 8.0) * s;
558 dst[gi * GROUP_SIZE + k * 2 + 1] = (((b >> 4) & 0x0F) as f32 - 8.0) * s;
559 }
560 }
561 return;
562 }
563 if *dtype == TensorDtype::Q1 {
564 let bytes = self.quant_bytes();
565 let gpr = cols / GROUP_SIZE;
566 for gi in 0..gpr {
567 let tile = &bytes[(r * gpr + gi) * Q1_TILE..(r * gpr + gi + 1) * Q1_TILE];
568 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
569 for (j, &b) in tile[2..].iter().enumerate() {
570 for k in 0..8 {
571 dst[gi * GROUP_SIZE + j * 8 + k] =
572 (((b >> k) & 1) as f32 * 2.0 - 1.0) * s;
573 }
574 }
575 }
576 return;
577 }
578 if *dtype == TensorDtype::Q1T {
579 let bytes = self.quant_bytes();
580 let gpr = cols / GROUP_SIZE;
581 let base_len = self.rows() * gpr * cortiq_core::quant::Q1T_TILE;
582 for gi in 0..gpr {
583 let off = (r * gpr + gi) * cortiq_core::quant::Q1T_TILE;
584 let s = cortiq_core::quant::f16_to_f32(u16::from_le_bytes([
585 bytes[off],
586 bytes[off + 1],
587 ]));
588 let codes = &bytes[off + 2..off + cortiq_core::quant::Q1T_TILE];
589 for k in 0..GROUP_SIZE {
590 dst[gi * GROUP_SIZE + k] = match cortiq_core::quant::q1t_code(codes, k)
591 {
592 1 => s,
593 2 => -s,
594 _ => 0.0,
595 };
596 }
597 }
598 let rows = self.rows();
600 let entries = base_len + (rows + 1) * 4;
601 if entries <= bytes.len() {
602 let ptrs = &bytes[base_len..base_len + (rows + 1) * 4];
603 let r0 = u32::from_le_bytes([
604 ptrs[r * 4],
605 ptrs[r * 4 + 1],
606 ptrs[r * 4 + 2],
607 ptrs[r * 4 + 3],
608 ]) as usize;
609 let r1 = u32::from_le_bytes([
610 ptrs[(r + 1) * 4],
611 ptrs[(r + 1) * 4 + 1],
612 ptrs[(r + 1) * 4 + 2],
613 ptrs[(r + 1) * 4 + 3],
614 ]) as usize;
615 let off = entries + r0 * 4;
616 for i in 0..r1 - r0 {
617 let item = &bytes[off + i * 4..off + i * 4 + 4];
618 let c = u16::from_le_bytes([item[0], item[1]]) as usize;
619 let v = cortiq_core::quant::f16_to_f32(u16::from_le_bytes([
620 item[2], item[3],
621 ]));
622 if c < cols {
623 dst[c] = v;
624 }
625 }
626 }
627 return;
628 }
629 if matches!(dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
630 let bytes = self.quant_bytes();
631 let rows = self.rows();
632 let ng = cols / GROUP_SIZE;
633 let bits = &bytes[..rows];
634 let sc_off = rows;
635 let off = vbit_offsets[r];
638 let b = bits[r] as usize;
639 let l = ((1usize << (b - 1)) - 1) as f32;
640 let data = &bytes[off..];
641 let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
642 for (i, d) in dst.iter_mut().enumerate() {
643 while nbits < b {
644 acc = (acc << 8) | data[idx] as u64;
645 idx += 1;
646 nbits += 8;
647 }
648 let u = ((acc >> (nbits - b)) & ((1u64 << b) - 1)) as f32;
649 nbits -= b;
650 let so = (r * ng + i / GROUP_SIZE) * 2;
651 let sv = f16_to_f32(u16::from_le_bytes([
652 bytes[sc_off + so],
653 bytes[sc_off + so + 1],
654 ]));
655 *d = (u - l) * sv;
656 }
657 return;
658 }
659 let q = &self.quant_bytes()[r * cols..(r + 1) * cols];
660 let s = row_scale[r];
661 match dtype {
662 TensorDtype::Q8Row => {
663 for (d, &b) in dst.iter_mut().zip(q) {
664 *d = (b as i8) as f32 * s;
665 }
666 }
667 TensorDtype::Q8_2f => {
668 for (i, (d, &b)) in dst.iter_mut().zip(q).enumerate() {
669 *d = (b as i8) as f32 * s * col_field[i];
670 }
671 }
672 _ => unreachable!(),
673 }
674 }
675 }
676 }
677
678 pub fn sparse_col_ok(&self) -> bool {
683 match self {
684 Self::F32 { .. } => true,
685 Self::Mapped { dtype, .. } => {
686 matches!(dtype, TensorDtype::Q8Row | TensorDtype::Q8_2f)
687 }
688 }
689 }
690
691 pub fn add_col_scaled(&self, c: usize, w: f32, out: &mut [f32]) {
695 let inter = self.cols();
696 let hidden = self.rows();
697 debug_assert_eq!(out.len(), hidden);
698 match self {
699 Self::F32 { data, .. } => {
700 for (k, o) in out.iter_mut().enumerate() {
701 *o += w * data[k * inter + c];
702 }
703 }
704 Self::Mapped {
705 dtype,
706 row_scale,
707 col_field,
708 ..
709 } => {
710 let q = self.quant_bytes();
711 let colf = if *dtype == TensorDtype::Q8_2f {
712 col_field[c]
713 } else {
714 1.0
715 };
716 let wc = w * colf;
717 for (k, o) in out.iter_mut().enumerate() {
718 let b = q[k * inter + c] as i8 as f32;
719 *o += wc * b * row_scale[k];
720 }
721 }
722 }
723 }
724
725 pub fn row_dot(&self, r: usize, x: &[f32], scratch: &mut [f32]) -> f32 {
729 let cols = self.cols();
730 match self {
731 Self::F32 { data, .. } => {
732 let row = &data[r * cols..(r + 1) * cols];
733 row.iter().zip(x).map(|(w, v)| w * v).sum()
734 }
735 Self::Mapped {
736 dtype,
737 row_scale,
738 col_field,
739 ..
740 } => match dtype {
741 TensorDtype::Q8Row => {
742 let q = &self.quant_bytes()[r * cols..(r + 1) * cols];
743 dot_i8_f32(q, x) * row_scale[r]
744 }
745 TensorDtype::Q8_2f => {
746 let q = &self.quant_bytes()[r * cols..(r + 1) * cols];
747 dot_i8_col_f32(q, x, col_field) * row_scale[r]
748 }
749 _ => {
750 self.row_f32(r, scratch);
751 scratch.iter().zip(x).map(|(w, v)| w * v).sum()
752 }
753 },
754 }
755 }
756
757 pub fn matvec(&self, x: &[f32], out: &mut [f32], pool: Option<&Pool>) {
760 match self {
761 Self::F32 { data, .. } => matvec_rows(pool, data, x, out),
762 Self::Mapped {
763 model,
764 idx,
765 dtype,
766 rows,
767 cols,
768 row_scale,
769 col_field,
770 vbit_offsets,
771 repack,
772 } => {
773 let _ = (model, idx);
774 if *dtype == TensorDtype::Q4Block {
775 if *rows * *cols >= 8_388_608 && crate::gpu::enabled_here() {
779 let t0 = std::time::Instant::now();
780 match crate::gpu::probe_arm(crate::gpu::OpClass::Matvec) {
781 crate::gpu::ProbeArm::Gpu => {
782 if crate::gpu::q4b_matvec(model, *idx, x, *rows, *cols, out) {
783 crate::gpu::probe_record(
784 crate::gpu::OpClass::Matvec,
785 true,
786 t0.elapsed(),
787 );
788 return;
789 }
790 }
791 crate::gpu::ProbeArm::CpuTimed => {
792 q4matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
793 crate::gpu::probe_record(
794 crate::gpu::OpClass::Matvec,
795 false,
796 t0.elapsed(),
797 );
798 return;
799 }
800 crate::gpu::ProbeArm::Cpu => {}
801 }
802 }
803 q4matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
804 return;
805 }
806 if *dtype == TensorDtype::Q4Tiled {
807 q4t_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
808 return;
809 }
810 if *dtype == TensorDtype::Q4TiledP {
811 q4tp_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
812 return;
813 }
814 if *dtype == TensorDtype::Q1 {
815 if *rows * *cols >= 8_388_608 && crate::gpu::enabled_here() {
820 let t0 = std::time::Instant::now();
821 let arm = if crate::gpu::q1_force() {
822 crate::gpu::ProbeArm::Gpu
823 } else {
824 crate::gpu::probe_arm(crate::gpu::OpClass::Matvec)
825 };
826 match arm {
827 crate::gpu::ProbeArm::Gpu => {
828 if crate::gpu::q1_matvec(model, *idx, x, *rows, *cols, out) {
829 crate::gpu::probe_record(
830 crate::gpu::OpClass::Matvec,
831 true,
832 t0.elapsed(),
833 );
834 return;
835 }
836 }
837 crate::gpu::ProbeArm::CpuTimed => {
838 q1_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
839 crate::gpu::probe_record(
840 crate::gpu::OpClass::Matvec,
841 false,
842 t0.elapsed(),
843 );
844 return;
845 }
846 crate::gpu::ProbeArm::Cpu => {}
847 }
848 }
849 q1_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
850 return;
851 }
852 if *dtype == TensorDtype::Q1T {
853 if *rows * *cols >= 8_388_608 && crate::gpu::enabled_here() {
857 let t0 = std::time::Instant::now();
858 match crate::gpu::probe_arm(crate::gpu::OpClass::Matvec) {
859 crate::gpu::ProbeArm::Gpu => {
860 if crate::gpu::q1t_matvec(model, *idx, x, *rows, *cols, out) {
861 q1t_add_overlay(self.quant_bytes(), x, *rows, *cols, out, pool);
862 crate::gpu::probe_record(
863 crate::gpu::OpClass::Matvec,
864 true,
865 t0.elapsed(),
866 );
867 return;
868 }
869 }
870 crate::gpu::ProbeArm::CpuTimed => {
871 q1t_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
872 crate::gpu::probe_record(
873 crate::gpu::OpClass::Matvec,
874 false,
875 t0.elapsed(),
876 );
877 return;
878 }
879 crate::gpu::ProbeArm::Cpu => {}
880 }
881 }
882 q1t_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
883 return;
884 }
885 if matches!(dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
886 vbitmatvec(self.quant_bytes(), vbit_offsets, x, *rows, *cols, out, pool);
887 return;
888 }
889 let xs = prescale(x, col_field, *dtype);
890 if *rows >= crate::gpu::min_rows()
895 && matches!(dtype, TensorDtype::Q8Row | TensorDtype::Q8_2f)
896 && std::env::var("CMF_GPU_LMHEAD")
897 .map(|v| v != "0")
898 .unwrap_or(true)
899 && crate::gpu::enabled_here()
900 {
901 let t0 = std::time::Instant::now();
904 match crate::gpu::probe_arm(crate::gpu::OpClass::Matvec) {
905 crate::gpu::ProbeArm::Gpu => {}
906 crate::gpu::ProbeArm::CpuTimed => {
907 qmatvec(
908 self.quant_bytes(),
909 repack,
910 row_scale,
911 x,
912 col_field,
913 *dtype,
914 *rows,
915 *cols,
916 out,
917 pool,
918 );
919 crate::gpu::probe_record(
920 crate::gpu::OpClass::Matvec,
921 false,
922 t0.elapsed(),
923 );
924 return;
925 }
926 crate::gpu::ProbeArm::Cpu => {
927 qmatvec(
928 self.quant_bytes(),
929 repack,
930 row_scale,
931 x,
932 col_field,
933 *dtype,
934 *rows,
935 *cols,
936 out,
937 pool,
938 );
939 return;
940 }
941 }
942 let frac = std::env::var("CMF_GPU_SPLIT")
943 .ok()
944 .and_then(|v| v.parse::<f32>().ok())
945 .unwrap_or(0.5)
946 .clamp(0.0, 1.0);
947 let cpu_rows = ((*rows as f32) * (1.0 - frac)) as usize;
948 let (out_cpu, out_gpu) = out.split_at_mut(cpu_rows);
949 let bytes = self.quant_bytes();
950 let ok = std::thread::scope(|sc| {
951 let g = sc.spawn(|| {
952 crate::gpu::q8_matvec_range(
953 model,
954 *idx,
955 cpu_rows,
956 &row_scale[cpu_rows..],
957 &xs,
958 *rows - cpu_rows,
959 *cols,
960 out_gpu,
961 )
962 });
963 if cpu_rows > 0 {
964 let rep_cpu = if repack.is_empty() {
967 &[][..]
968 } else {
969 &repack[..(cpu_rows / 4) * 4 * *cols]
970 };
971 qmatvec(
972 &bytes[..cpu_rows * *cols],
973 rep_cpu,
974 &row_scale[..cpu_rows],
975 x,
976 col_field,
977 *dtype,
978 cpu_rows,
979 *cols,
980 out_cpu,
981 pool,
982 );
983 }
984 g.join().unwrap_or(false)
985 });
986 if ok {
987 crate::gpu::probe_record(crate::gpu::OpClass::Matvec, true, t0.elapsed());
988 return;
989 }
990 qmatvec(
993 &bytes[cpu_rows * *cols..(*rows) * *cols],
994 &[],
995 &row_scale[cpu_rows..],
996 x,
997 col_field,
998 *dtype,
999 *rows - cpu_rows,
1000 *cols,
1001 out_gpu,
1002 pool,
1003 );
1004 return;
1005 }
1006 qmatvec(
1007 self.quant_bytes(),
1008 repack,
1009 row_scale,
1010 x,
1011 col_field,
1012 *dtype,
1013 *rows,
1014 *cols,
1015 out,
1016 pool,
1017 );
1018 }
1019 }
1020 }
1021
1022 pub fn matvec2(
1024 &self,
1025 x1: &[f32],
1026 x2: &[f32],
1027 o1: &mut [f32],
1028 o2: &mut [f32],
1029 pool: Option<&Pool>,
1030 ) {
1031 match self {
1032 Self::F32 { data, .. } => matvec_rows2(pool, data, x1, x2, o1, o2),
1033 Self::Mapped {
1034 dtype,
1035 rows,
1036 cols,
1037 row_scale,
1038 col_field,
1039 vbit_offsets,
1040 ..
1041 } => {
1042 if *dtype == TensorDtype::Q4Block {
1043 q4matvec2(self.quant_bytes(), x1, x2, *rows, *cols, o1, o2, pool);
1044 return;
1045 }
1046 if *dtype == TensorDtype::Q4Tiled {
1047 q4t_matvec2(self.quant_bytes(), x1, x2, *rows, *cols, o1, o2, pool);
1048 return;
1049 }
1050 if *dtype == TensorDtype::Q4TiledP {
1051 q4tp_matvec2(self.quant_bytes(), x1, x2, *rows, *cols, o1, o2, pool);
1052 return;
1053 }
1054 if *dtype == TensorDtype::Q1 {
1055 q1_matvec2(self.quant_bytes(), x1, x2, *rows, *cols, o1, o2, pool);
1056 return;
1057 }
1058 if *dtype == TensorDtype::Q1T {
1059 q1t_matvec2(self.quant_bytes(), x1, x2, *rows, *cols, o1, o2, pool);
1065 return;
1066 }
1067 if matches!(dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
1068 vbitmatvec2(
1069 self.quant_bytes(),
1070 vbit_offsets,
1071 x1,
1072 x2,
1073 *rows,
1074 *cols,
1075 o1,
1076 o2,
1077 pool,
1078 );
1079 return;
1080 }
1081 qmatvec2(
1082 self.quant_bytes(),
1083 row_scale,
1084 x1,
1085 x2,
1086 col_field,
1087 *dtype,
1088 *rows,
1089 *cols,
1090 o1,
1091 o2,
1092 pool,
1093 );
1094 }
1095 }
1096 }
1097}
1098
1099impl QTensor {
1100 pub fn matmat(&self, xs_all: &[f32], b: usize, out: &mut [f32], pool: Option<&Pool>) {
1105 let cols = self.cols();
1106 let rows = self.rows();
1107 debug_assert_eq!(xs_all.len(), b * cols);
1108 debug_assert_eq!(out.len(), b * rows);
1109 if crate::gptq_capture::capturing() {
1113 if let Self::Mapped { model, idx, .. } = self {
1114 crate::gptq_capture::accumulate(&model.tensors[*idx].name, xs_all, b, cols);
1115 }
1116 }
1117 match self {
1118 Self::F32 { data, .. } => {
1119 let out_addr = SendMut(out.as_mut_ptr());
1120 let run = |start: usize, end: usize| {
1121 for o in start..end {
1122 let row = &data[o * cols..(o + 1) * cols];
1123 for bi in 0..b {
1124 let x = &xs_all[bi * cols..(bi + 1) * cols];
1125 let mut acc = 0f32;
1126 for j in 0..cols {
1127 acc += row[j] * x[j];
1128 }
1129 unsafe { *out_addr.at(bi * rows + o) = acc };
1130 }
1131 }
1132 };
1133 dispatch_rows(pool, rows, &run);
1134 }
1135 Self::Mapped {
1136 dtype,
1137 row_scale,
1138 col_field,
1139 vbit_offsets,
1140 ..
1141 } => {
1142 if *dtype == TensorDtype::Q4Block {
1143 q4matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
1144 return;
1145 }
1146 if *dtype == TensorDtype::Q4TiledP {
1147 if b >= 32
1159 && b * rows * cols >= 128_000_000
1160 && cols % 32 == 0
1161 && !crate::gpu::mm_killed()
1162 && crate::gpu::enabled_here()
1163 {
1164 let class = if b >= 128 {
1165 crate::gpu::OpClass::MatmatWide
1166 } else {
1167 crate::gpu::OpClass::Matmat
1168 };
1169 if let Self::Mapped { model, idx, .. } = self {
1170 let t0 = std::time::Instant::now();
1171 match crate::gpu::probe_arm(class) {
1172 crate::gpu::ProbeArm::Gpu => {
1173 if crate::gpu::q4tp_matmat(
1174 model, *idx, xs_all, b, rows, cols, out,
1175 ) {
1176 let el = t0.elapsed();
1177 let flops = 2.0 * b as f64 * rows as f64 * cols as f64;
1187 let budget = std::time::Duration::from_secs_f64(
1188 flops / 1.5e12 * 8.0 + 0.020,
1189 );
1190 if el > budget && !crate::gpu::probe_was_cold() {
1191 tracing::warn!(
1192 "gpu q4tp matmat took {el:?} (budget {budget:?}) — \
1193 device contended, CPU for the rest of the process"
1194 );
1195 crate::gpu::mm_kill();
1196 }
1197 crate::gpu::probe_record(class, true, el);
1198 return;
1199 }
1200 }
1201 crate::gpu::ProbeArm::CpuTimed => {
1202 q4tp_matmat(
1203 self.quant_bytes(),
1204 xs_all,
1205 b,
1206 rows,
1207 cols,
1208 out,
1209 pool,
1210 );
1211 crate::gpu::probe_record(class, false, t0.elapsed());
1212 return;
1213 }
1214 crate::gpu::ProbeArm::Cpu => {}
1215 }
1216 }
1217 }
1218 q4tp_matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
1219 return;
1220 }
1221 if *dtype == TensorDtype::Q4Tiled {
1222 if b >= 32
1234 && b * rows * cols >= 128_000_000
1235 && cols % 32 == 0
1236 && !crate::gpu::mm_killed()
1237 && crate::gpu::enabled_here()
1238 {
1239 let class = if b >= 128 {
1240 crate::gpu::OpClass::MatmatWide
1241 } else {
1242 crate::gpu::OpClass::Matmat
1243 };
1244 if let Self::Mapped { model, idx, .. } = self {
1245 let t0 = std::time::Instant::now();
1246 match crate::gpu::probe_arm(class) {
1247 crate::gpu::ProbeArm::Gpu => {
1248 if crate::gpu::q4t_matmat(
1249 model, *idx, xs_all, b, rows, cols, out,
1250 ) {
1251 let el = t0.elapsed();
1252 let flops = 2.0 * b as f64 * rows as f64 * cols as f64;
1262 let budget = std::time::Duration::from_secs_f64(
1263 flops / 1.5e12 * 8.0 + 0.020,
1264 );
1265 if el > budget && !crate::gpu::probe_was_cold() {
1266 tracing::warn!(
1267 "gpu q4t matmat took {el:?} (budget {budget:?}) — \
1268 device contended, CPU for the rest of the process"
1269 );
1270 crate::gpu::mm_kill();
1271 }
1272 crate::gpu::probe_record(class, true, el);
1273 return;
1274 }
1275 }
1276 crate::gpu::ProbeArm::CpuTimed => {
1277 q4t_matmat(
1278 self.quant_bytes(),
1279 xs_all,
1280 b,
1281 rows,
1282 cols,
1283 out,
1284 pool,
1285 );
1286 crate::gpu::probe_record(class, false, t0.elapsed());
1287 return;
1288 }
1289 crate::gpu::ProbeArm::Cpu => {}
1290 }
1291 }
1292 }
1293 q4t_matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
1294 return;
1295 }
1296 if *dtype == TensorDtype::Q1 {
1297 if b >= 32
1300 && b * rows * cols >= 128_000_000
1301 && cols % 64 == 0
1302 && crate::gpu::enabled_here()
1303 {
1304 if let Self::Mapped { model, idx, .. } = self {
1305 let t0 = std::time::Instant::now();
1306 match crate::gpu::probe_arm(crate::gpu::OpClass::Matmat) {
1307 crate::gpu::ProbeArm::Gpu => {
1308 if crate::gpu::q1_matmat(
1309 model, *idx, xs_all, b, rows, cols, out,
1310 ) {
1311 crate::gpu::probe_record(
1312 crate::gpu::OpClass::Matmat,
1313 true,
1314 t0.elapsed(),
1315 );
1316 return;
1317 }
1318 }
1319 crate::gpu::ProbeArm::CpuTimed => {
1320 q1_matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
1321 crate::gpu::probe_record(
1322 crate::gpu::OpClass::Matmat,
1323 false,
1324 t0.elapsed(),
1325 );
1326 return;
1327 }
1328 crate::gpu::ProbeArm::Cpu => {}
1329 }
1330 }
1331 }
1332 q1_matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
1333 return;
1334 }
1335 if *dtype == TensorDtype::Q1T {
1336 if b >= 32 && b * rows * cols >= 128_000_000 && crate::gpu::enabled_here() {
1339 if let Self::Mapped { model, idx, .. } = self {
1340 let t0 = std::time::Instant::now();
1341 match crate::gpu::probe_arm(crate::gpu::OpClass::Matmat) {
1342 crate::gpu::ProbeArm::Gpu => {
1343 if crate::gpu::q1t_matmat(
1344 model, *idx, xs_all, b, rows, cols, out,
1345 ) {
1346 crate::gpu::probe_record(
1347 crate::gpu::OpClass::Matmat,
1348 true,
1349 t0.elapsed(),
1350 );
1351 return;
1352 }
1353 }
1354 crate::gpu::ProbeArm::CpuTimed => {
1355 q1t_matmat(
1356 self.quant_bytes(),
1357 xs_all,
1358 b,
1359 rows,
1360 cols,
1361 out,
1362 pool,
1363 );
1364 crate::gpu::probe_record(
1365 crate::gpu::OpClass::Matmat,
1366 false,
1367 t0.elapsed(),
1368 );
1369 return;
1370 }
1371 crate::gpu::ProbeArm::Cpu => {}
1372 }
1373 }
1374 }
1375 q1t_matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
1376 return;
1377 }
1378 if matches!(dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
1379 vbitmatmat(
1380 self.quant_bytes(),
1381 vbit_offsets,
1382 xs_all,
1383 b,
1384 rows,
1385 cols,
1386 out,
1387 pool,
1388 );
1389 return;
1390 }
1391 let pre: Vec<std::borrow::Cow<'_, [f32]>> = (0..b)
1392 .map(|bi| prescale(&xs_all[bi * cols..(bi + 1) * cols], col_field, *dtype))
1393 .collect();
1394 if b >= 8 && b * rows * cols >= 128_000_000 && crate::gpu::enabled_here() {
1400 if let Self::Mapped { model, idx, .. } = self {
1401 let t0 = std::time::Instant::now();
1402 match crate::gpu::probe_arm(crate::gpu::OpClass::Matmat) {
1403 crate::gpu::ProbeArm::Gpu
1404 if crate::gpu::probe_deciding(crate::gpu::OpClass::Matmat)
1405 && !crate::gpu::q8_resident_or_upload(model, *idx) =>
1406 {
1407 let q = self.quant_bytes();
1411 qmatmat(q, row_scale, &pre, rows, cols, out, pool);
1412 return;
1413 }
1414 crate::gpu::ProbeArm::Gpu => {
1415 let flat: Vec<f32> =
1416 pre.iter().flat_map(|v| v.iter().copied()).collect();
1417 if crate::gpu::q8_matmat(
1418 model, *idx, row_scale, &flat, b, rows, cols, out,
1419 ) {
1420 crate::gpu::probe_record(
1421 crate::gpu::OpClass::Matmat,
1422 true,
1423 t0.elapsed(),
1424 );
1425 return;
1426 }
1427 }
1428 crate::gpu::ProbeArm::CpuTimed => {
1429 let q = self.quant_bytes();
1430 qmatmat(q, row_scale, &pre, rows, cols, out, pool);
1431 crate::gpu::probe_record(
1432 crate::gpu::OpClass::Matmat,
1433 false,
1434 t0.elapsed(),
1435 );
1436 return;
1437 }
1438 crate::gpu::ProbeArm::Cpu => {}
1439 }
1440 }
1441 }
1442 let q = self.quant_bytes();
1443 qmatmat(q, row_scale, &pre, rows, cols, out, pool);
1444 }
1445 }
1446 }
1447}
1448
1449impl QTensor {
1450 pub fn matvec_many<const N: usize>(
1457 ts: [&QTensor; N],
1458 x: &[f32],
1459 mut outs: [&mut [f32]; N],
1460 pool: Option<&Pool>,
1461 ) {
1462 let total_rows: usize = ts.iter().map(|t| t.rows()).sum();
1463 let uniform_q8 = ts.iter().all(|t| {
1464 matches!(
1465 t,
1466 Self::Mapped {
1467 dtype: TensorDtype::Q8Row | TensorDtype::Q8_2f,
1468 ..
1469 }
1470 )
1471 });
1472 let uniform_f32 = ts.iter().all(|t| matches!(t, Self::F32 { .. }));
1473 let uniform_q4 = ts.iter().all(|t| {
1474 matches!(
1475 t,
1476 Self::Mapped {
1477 dtype: TensorDtype::Q4Block,
1478 ..
1479 }
1480 )
1481 });
1482 let uniform_vbit = ts.iter().all(|t| {
1483 matches!(
1484 t,
1485 Self::Mapped {
1486 dtype: TensorDtype::Vbit | TensorDtype::VbitRo,
1487 ..
1488 }
1489 )
1490 });
1491 let uniform_q1 = ts.iter().all(|t| {
1492 matches!(
1493 t,
1494 Self::Mapped {
1495 dtype: TensorDtype::Q1,
1496 ..
1497 }
1498 )
1499 });
1500 let uniform_q1t = ts.iter().all(|t| {
1501 matches!(
1502 t,
1503 Self::Mapped {
1504 dtype: TensorDtype::Q1T,
1505 ..
1506 }
1507 )
1508 });
1509 let Some(pool) = pool else {
1510 for (t, o) in ts.iter().zip(outs.iter_mut()) {
1511 t.matvec(x, o, None);
1512 }
1513 return;
1514 };
1515 if total_rows < 256
1516 || !(uniform_q8
1517 || uniform_f32
1518 || uniform_q4
1519 || uniform_vbit
1520 || uniform_q1
1521 || uniform_q1t)
1522 {
1523 for (t, o) in ts.iter().zip(outs.iter_mut()) {
1524 t.matvec(x, o, Some(pool));
1525 }
1526 return;
1527 }
1528
1529 if uniform_q1 {
1530 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
1533 if a8w8_enabled() {
1534 let act = split_act(x);
1535 let gsum = q1_group_sums(&act.xq, ts[0].cols() / GROUP_SIZE);
1536 let (act, gsum) = (&act, &gsum);
1537 let closures: [_; N] = std::array::from_fn(|i| {
1538 let (bytes, gpr, out) =
1539 (ts[i].quant_bytes(), ts[i].cols() / GROUP_SIZE, outs_addr[i]);
1540 move |s: usize, e: usize| q1_range_a8w8(bytes, gpr, act, gsum, out, s, e)
1541 });
1542 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1543 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1544 pool.run_many(&parts);
1545 } else {
1546 let closures: [_; N] = std::array::from_fn(|i| {
1547 let (bytes, gpr, out) =
1548 (ts[i].quant_bytes(), ts[i].cols() / GROUP_SIZE, outs_addr[i]);
1549 move |s: usize, e: usize| q1_range_f32(bytes, gpr, x, out, s, e)
1550 });
1551 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1552 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1553 pool.run_many(&parts);
1554 }
1555 return;
1556 }
1557
1558 if uniform_q1t {
1559 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
1563 const TILE: usize = cortiq_core::quant::Q1T_TILE;
1564 if a8w8_enabled() {
1565 let act = split_act(x);
1566 let act = &act;
1567 let x_ref = x;
1568 let closures: [_; N] = std::array::from_fn(|i| {
1569 let bytes = ts[i].quant_bytes();
1570 let (rows, cols) = (ts[i].rows(), ts[i].cols());
1571 let gpr = cols / GROUP_SIZE;
1572 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
1573 let out = outs_addr[i];
1574 move |s: usize, e: usize| {
1575 q1t_range_a8w8(bytes, gpr, rp_off, ent_off, has_ov, act, x_ref, out, s, e)
1576 }
1577 });
1578 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1579 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1580 pool.run_many(&parts);
1581 } else {
1582 let x_ref = x;
1583 let closures: [_; N] = std::array::from_fn(|i| {
1584 let bytes = ts[i].quant_bytes();
1585 let (rows, cols) = (ts[i].rows(), ts[i].cols());
1586 let gpr = cols / GROUP_SIZE;
1587 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
1588 let out = outs_addr[i];
1589 move |s: usize, e: usize| {
1590 q1t_range_f32_batch(bytes, gpr, rp_off, ent_off, has_ov, x_ref, out, s, e)
1591 }
1592 });
1593 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1594 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1595 pool.run_many(&parts);
1596 }
1597 return;
1598 }
1599
1600 if uniform_q4 || uniform_vbit {
1601 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
1602 if a8w8_enabled() {
1604 let act = split_act(x);
1605 let act = &act;
1606 if uniform_q4 {
1607 let closures: [_; N] = std::array::from_fn(|i| {
1608 let (packed, scales) =
1609 q4_split(ts[i].quant_bytes(), ts[i].rows(), ts[i].cols());
1610 let (gpr, cols, out) =
1611 (ts[i].cols() / GROUP_SIZE, ts[i].cols(), outs_addr[i]);
1612 move |s: usize, e: usize| {
1613 q4_range_a8w8(packed, scales, gpr, cols, act, out, s, e)
1614 }
1615 });
1616 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1617 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1618 pool.run_many(&parts);
1619 } else {
1620 let closures: [_; N] = std::array::from_fn(|i| {
1621 let Self::Mapped { vbit_offsets, .. } = ts[i] else {
1622 unreachable!()
1623 };
1624 let (bytes, rows, cols, out) = (
1625 ts[i].quant_bytes(),
1626 ts[i].rows(),
1627 ts[i].cols(),
1628 outs_addr[i],
1629 );
1630 move |s: usize, e: usize| {
1631 vbit_range_a8w8(bytes, vbit_offsets, x, act, rows, cols, out, s, e)
1632 }
1633 });
1634 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1635 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1636 pool.run_many(&parts);
1637 }
1638 return;
1639 }
1640 if uniform_q4 {
1641 let closures: [_; N] = std::array::from_fn(|i| {
1642 let (packed, scales) =
1643 q4_split(ts[i].quant_bytes(), ts[i].rows(), ts[i].cols());
1644 let (gpr, out) = (ts[i].cols() / GROUP_SIZE, outs_addr[i]);
1645 move |s: usize, e: usize| q4_range_f32(packed, scales, gpr, x, out, s, e)
1646 });
1647 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1648 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1649 pool.run_many(&parts);
1650 } else {
1651 let closures: [_; N] = std::array::from_fn(|i| {
1652 let Self::Mapped { vbit_offsets, .. } = ts[i] else {
1653 unreachable!()
1654 };
1655 let (bytes, rows, cols, out) = (
1656 ts[i].quant_bytes(),
1657 ts[i].rows(),
1658 ts[i].cols(),
1659 outs_addr[i],
1660 );
1661 move |s: usize, e: usize| {
1662 vbit_range_f32(bytes, vbit_offsets, x, rows, cols, out, s, e)
1663 }
1664 });
1665 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1666 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1667 pool.run_many(&parts);
1668 }
1669 return;
1670 }
1671
1672 if uniform_f32 {
1673 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
1674 let closures: [_; N] = std::array::from_fn(|i| {
1675 let Self::F32 { data, cols, .. } = ts[i] else {
1676 unreachable!()
1677 };
1678 let out = outs_addr[i];
1679 move |start: usize, end: usize| {
1680 for o in start..end {
1681 let row = &data[o * cols..(o + 1) * cols];
1682 let mut sum = 0.0f32;
1683 for j in 0..*cols {
1684 sum += row[j] * x[j];
1685 }
1686 unsafe { *out.at(o) = sum };
1688 }
1689 }
1690 });
1691 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1692 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1693 pool.run_many(&parts);
1694 return;
1695 }
1696
1697 struct Ctx<'a> {
1700 bytes: &'a [u8],
1701 #[cfg_attr(not(target_arch = "aarch64"), allow(dead_code))]
1702 rep: &'a [u8],
1703 row_scale: &'a [f32],
1704 cols: usize,
1705 xs: std::borrow::Cow<'a, [f32]>,
1706 }
1707 let ctxs: [Ctx<'_>; N] = std::array::from_fn(|i| {
1708 let Self::Mapped {
1709 dtype,
1710 cols,
1711 row_scale,
1712 col_field,
1713 repack,
1714 ..
1715 } = ts[i]
1716 else {
1717 unreachable!()
1718 };
1719 Ctx {
1720 bytes: ts[i].quant_bytes(),
1721 rep: repack,
1722 row_scale,
1723 cols: *cols,
1724 xs: prescale(x, col_field, *dtype),
1725 }
1726 });
1727 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
1728 #[cfg(target_arch = "aarch64")]
1729 if sdot_enabled() {
1730 let acts: [SplitAct; N] = std::array::from_fn(|i| split_act(&ctxs[i].xs));
1731 let closures: [_; N] = std::array::from_fn(|i| {
1732 let (c, act, out) = (&ctxs[i], &acts[i], outs_addr[i]);
1733 move |start: usize, end: usize| {
1734 q8_range_sdot(c.bytes, c.rep, c.row_scale, act, c.cols, out, start, end)
1735 }
1736 });
1737 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1738 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1739 pool.run_many(&parts);
1740 return;
1741 }
1742 #[cfg(target_arch = "x86_64")]
1743 if avx2_a8w8_enabled() {
1744 let acts: [SplitAct; N] = std::array::from_fn(|i| split_act(&ctxs[i].xs));
1745 let closures: [_; N] = std::array::from_fn(|i| {
1746 let (c, act, out) = (&ctxs[i], &acts[i], outs_addr[i]);
1747 move |start: usize, end: usize| {
1748 q8_range_avx2(c.bytes, c.row_scale, act, c.cols, out, start, end)
1749 }
1750 });
1751 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1752 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1753 pool.run_many(&parts);
1754 return;
1755 }
1756 let closures: [_; N] = std::array::from_fn(|i| {
1757 let (c, out) = (&ctxs[i], outs_addr[i]);
1758 move |start: usize, end: usize| {
1759 q8_range_f32(c.bytes, c.row_scale, &c.xs, c.cols, out, start, end)
1760 }
1761 });
1762 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1763 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1764 pool.run_many(&parts);
1765 }
1766}
1767
1768impl QTensor {
1769 #[allow(clippy::needless_range_loop)]
1774 pub fn matvec2_many<const N: usize>(
1775 ts: [&QTensor; N],
1776 x1: &[f32],
1777 x2: &[f32],
1778 mut o1s: [&mut [f32]; N],
1779 mut o2s: [&mut [f32]; N],
1780 pool: Option<&Pool>,
1781 ) {
1782 let total_rows: usize = ts.iter().map(|t| t.rows()).sum();
1783 let uniform_q8 = ts.iter().all(|t| {
1784 matches!(
1785 t,
1786 Self::Mapped {
1787 dtype: TensorDtype::Q8Row | TensorDtype::Q8_2f,
1788 ..
1789 }
1790 )
1791 });
1792 let uniform_f32 = ts.iter().all(|t| matches!(t, Self::F32 { .. }));
1793 let uniform_q4 = ts.iter().all(|t| {
1794 matches!(
1795 t,
1796 Self::Mapped {
1797 dtype: TensorDtype::Q4Block,
1798 ..
1799 }
1800 )
1801 });
1802 let uniform_vbit = ts.iter().all(|t| {
1803 matches!(
1804 t,
1805 Self::Mapped {
1806 dtype: TensorDtype::Vbit | TensorDtype::VbitRo,
1807 ..
1808 }
1809 )
1810 });
1811 let fusable = pool.is_some()
1812 && total_rows >= 256
1813 && (uniform_q8 || uniform_f32 || uniform_q4 || uniform_vbit);
1814 if !fusable {
1815 for i in 0..N {
1816 ts[i].matvec2(x1, x2, o1s[i], o2s[i], pool);
1817 }
1818 return;
1819 }
1820 let pool = pool.unwrap();
1821
1822 if uniform_q4 || uniform_vbit {
1823 let p1: [SendMut; N] = std::array::from_fn(|i| SendMut(o1s[i].as_mut_ptr()));
1824 let p2: [SendMut; N] = std::array::from_fn(|i| SendMut(o2s[i].as_mut_ptr()));
1825 if a8w8_enabled() {
1827 let a1 = split_act(x1);
1828 let a2 = split_act(x2);
1829 let (a1, a2) = (&a1, &a2);
1830 if uniform_q4 {
1831 let closures: [_; N] = std::array::from_fn(|i| {
1832 let (packed, scales) =
1833 q4_split(ts[i].quant_bytes(), ts[i].rows(), ts[i].cols());
1834 let (gpr, cols, o1, o2) =
1835 (ts[i].cols() / GROUP_SIZE, ts[i].cols(), p1[i], p2[i]);
1836 move |s: usize, e: usize| {
1837 q4_range2_a8w8(packed, scales, gpr, cols, a1, a2, o1, o2, s, e)
1838 }
1839 });
1840 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1841 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1842 pool.run_many(&parts);
1843 } else {
1844 let closures: [_; N] = std::array::from_fn(|i| {
1845 let Self::Mapped { vbit_offsets, .. } = ts[i] else {
1846 unreachable!()
1847 };
1848 let (bytes, rows, cols, o1, o2) = (
1849 ts[i].quant_bytes(),
1850 ts[i].rows(),
1851 ts[i].cols(),
1852 p1[i],
1853 p2[i],
1854 );
1855 move |s: usize, e: usize| {
1856 vbit_range2_a8w8(
1857 bytes,
1858 vbit_offsets,
1859 x1,
1860 x2,
1861 a1,
1862 a2,
1863 rows,
1864 cols,
1865 o1,
1866 o2,
1867 s,
1868 e,
1869 )
1870 }
1871 });
1872 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1873 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1874 pool.run_many(&parts);
1875 }
1876 return;
1877 }
1878 if uniform_q4 {
1879 let closures: [_; N] = std::array::from_fn(|i| {
1880 let (packed, scales) =
1881 q4_split(ts[i].quant_bytes(), ts[i].rows(), ts[i].cols());
1882 let (gpr, o1, o2) = (ts[i].cols() / GROUP_SIZE, p1[i], p2[i]);
1883 move |s: usize, e: usize| {
1884 q4_range2_f32(packed, scales, gpr, x1, x2, o1, o2, s, e)
1885 }
1886 });
1887 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1888 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1889 pool.run_many(&parts);
1890 } else {
1891 let closures: [_; N] = std::array::from_fn(|i| {
1892 let Self::Mapped { vbit_offsets, .. } = ts[i] else {
1893 unreachable!()
1894 };
1895 let (bytes, rows, cols, o1, o2) = (
1896 ts[i].quant_bytes(),
1897 ts[i].rows(),
1898 ts[i].cols(),
1899 p1[i],
1900 p2[i],
1901 );
1902 move |s: usize, e: usize| {
1903 vbit_range2_f32(bytes, vbit_offsets, x1, x2, rows, cols, o1, o2, s, e)
1904 }
1905 });
1906 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1907 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1908 pool.run_many(&parts);
1909 }
1910 return;
1911 }
1912
1913 if uniform_f32 {
1914 let p1: [SendMut; N] = std::array::from_fn(|i| SendMut(o1s[i].as_mut_ptr()));
1915 let p2: [SendMut; N] = std::array::from_fn(|i| SendMut(o2s[i].as_mut_ptr()));
1916 let closures: [_; N] = std::array::from_fn(|i| {
1917 let Self::F32 { data, cols, .. } = ts[i] else {
1918 unreachable!()
1919 };
1920 let (o1, o2) = (p1[i], p2[i]);
1921 move |start: usize, end: usize| {
1922 for o in start..end {
1923 let row = &data[o * cols..(o + 1) * cols];
1924 let (mut s1, mut s2) = (0.0f32, 0.0f32);
1925 for j in 0..*cols {
1926 s1 += row[j] * x1[j];
1927 s2 += row[j] * x2[j];
1928 }
1929 unsafe {
1931 *o1.at(o) = s1;
1932 *o2.at(o) = s2;
1933 }
1934 }
1935 }
1936 });
1937 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1938 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1939 pool.run_many(&parts);
1940 return;
1941 }
1942
1943 struct Ctx<'a> {
1944 bytes: &'a [u8],
1945 row_scale: &'a [f32],
1946 cols: usize,
1947 xs1: std::borrow::Cow<'a, [f32]>,
1948 xs2: std::borrow::Cow<'a, [f32]>,
1949 }
1950 let ctxs: [Ctx<'_>; N] = std::array::from_fn(|i| {
1951 let Self::Mapped {
1952 dtype,
1953 cols,
1954 row_scale,
1955 col_field,
1956 ..
1957 } = ts[i]
1958 else {
1959 unreachable!()
1960 };
1961 Ctx {
1962 bytes: ts[i].quant_bytes(),
1963 row_scale,
1964 cols: *cols,
1965 xs1: prescale(x1, col_field, *dtype),
1966 xs2: prescale(x2, col_field, *dtype),
1967 }
1968 });
1969 let p1: [SendMut; N] = std::array::from_fn(|i| SendMut(o1s[i].as_mut_ptr()));
1970 let p2: [SendMut; N] = std::array::from_fn(|i| SendMut(o2s[i].as_mut_ptr()));
1971 #[cfg(target_arch = "aarch64")]
1972 if sdot_enabled() {
1973 let acts: [(SplitAct, SplitAct); N] =
1974 std::array::from_fn(|i| (split_act(&ctxs[i].xs1), split_act(&ctxs[i].xs2)));
1975 let closures: [_; N] = std::array::from_fn(|i| {
1976 let (c, a, o1, o2) = (&ctxs[i], &acts[i], p1[i], p2[i]);
1977 move |start: usize, end: usize| {
1978 q8_range2_sdot(c.bytes, c.row_scale, &a.0, &a.1, c.cols, o1, o2, start, end)
1979 }
1980 });
1981 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1982 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1983 pool.run_many(&parts);
1984 return;
1985 }
1986 #[cfg(target_arch = "x86_64")]
1987 if avx2_a8w8_enabled() {
1988 let acts: [(SplitAct, SplitAct); N] =
1989 std::array::from_fn(|i| (split_act(&ctxs[i].xs1), split_act(&ctxs[i].xs2)));
1990 let closures: [_; N] = std::array::from_fn(|i| {
1991 let (c, a, o1, o2) = (&ctxs[i], &acts[i], p1[i], p2[i]);
1992 move |start: usize, end: usize| {
1993 q8_range2_avx2(c.bytes, c.row_scale, &a.0, &a.1, c.cols, o1, o2, start, end)
1994 }
1995 });
1996 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1997 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1998 pool.run_many(&parts);
1999 return;
2000 }
2001 let closures: [_; N] = std::array::from_fn(|i| {
2002 let (c, o1, o2) = (&ctxs[i], p1[i], p2[i]);
2003 move |start: usize, end: usize| {
2004 q8_range2_f32(
2005 c.bytes,
2006 c.row_scale,
2007 &c.xs1,
2008 &c.xs2,
2009 c.cols,
2010 o1,
2011 o2,
2012 start,
2013 end,
2014 )
2015 }
2016 });
2017 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
2018 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
2019 pool.run_many(&parts);
2020 }
2021
2022 pub fn matvec_silu_mul(
2027 gate: &QTensor,
2028 up: &QTensor,
2029 x: &[f32],
2030 out: &mut [f32],
2031 pool: Option<&Pool>,
2032 ) -> bool {
2033 let inter = gate.rows();
2034 debug_assert_eq!(up.rows(), inter);
2035 debug_assert_eq!(out.len(), inter);
2036 debug_assert_eq!(gate.cols(), up.cols());
2037 if !a8w8_enabled() {
2038 return false;
2039 }
2040 let act = split_act(x);
2041 let act = &act;
2042 let x_ref = x;
2043 let out_addr = SendMut(out.as_mut_ptr());
2044
2045 match (gate, up) {
2046 (
2048 Self::Mapped {
2049 dtype: TensorDtype::Q4Block,
2050 ..
2051 },
2052 Self::Mapped {
2053 dtype: TensorDtype::Q4Block,
2054 ..
2055 },
2056 ) => {
2057 let (gp, gs) = q4_split(gate.quant_bytes(), gate.rows(), gate.cols());
2058 let (up_p, up_s) = q4_split(up.quant_bytes(), up.rows(), up.cols());
2059 let gpr = gate.cols() / GROUP_SIZE;
2060 let cols = gate.cols();
2061 let run = move |start: usize, end: usize| {
2062 for r in start..end {
2063 let mut gv = dot_q4_row_i8(gp, gs, r * gpr, gpr, &act.xq) * act.sx;
2064 let mut uv = dot_q4_row_i8(up_p, up_s, r * gpr, gpr, &act.xq) * act.sx;
2065 for &(j, xv) in &act.outliers {
2066 let flat = r * cols + j;
2067 let gb = gp[flat / 2];
2068 let gn = if flat & 1 == 0 { gb & 0x0F } else { gb >> 4 };
2069 let gsc = f16_to_f32(u16::from_le_bytes([
2070 gs[(flat / GROUP_SIZE) * 2],
2071 gs[(flat / GROUP_SIZE) * 2 + 1],
2072 ]));
2073 gv += ((gn as i32 - 8) as f32) * gsc * xv;
2074 let ub = up_p[flat / 2];
2075 let un = if flat & 1 == 0 { ub & 0x0F } else { ub >> 4 };
2076 let usc = f16_to_f32(u16::from_le_bytes([
2077 up_s[(flat / GROUP_SIZE) * 2],
2078 up_s[(flat / GROUP_SIZE) * 2 + 1],
2079 ]));
2080 uv += ((un as i32 - 8) as f32) * usc * xv;
2081 }
2082 let silu_g = gv / (1.0 + (-gv).exp());
2083 unsafe { *out_addr.at(r) = silu_g * uv };
2085 }
2086 };
2087 dispatch_rows(pool, inter, &run);
2088 true
2089 }
2090 (
2094 Self::Mapped {
2095 dtype: TensorDtype::Q4Tiled,
2096 ..
2097 },
2098 Self::Mapped {
2099 dtype: TensorDtype::Q4Tiled,
2100 ..
2101 },
2102 ) => {
2103 let g_bytes = gate.quant_bytes();
2104 let u_bytes = up.quant_bytes();
2105 let gpr = gate.cols() / GROUP_SIZE;
2106 let run = move |start: usize, end: usize| {
2107 for r in start..end {
2108 let mut gv = dot_q4t_row_i8(g_bytes, r, gpr, &act.xq) * act.sx;
2109 let mut uv = dot_q4t_row_i8(u_bytes, r, gpr, &act.xq) * act.sx;
2110 for &(j, xv) in &act.outliers {
2111 let (w, s) = q4t_outlier(g_bytes, r, gpr, j);
2112 gv += w * s * xv;
2113 let (w, s) = q4t_outlier(u_bytes, r, gpr, j);
2114 uv += w * s * xv;
2115 }
2116 let silu_g = gv / (1.0 + (-gv).exp());
2117 unsafe { *out_addr.at(r) = silu_g * uv };
2119 }
2120 };
2121 dispatch_rows(pool, inter, &run);
2122 true
2123 }
2124 (
2127 Self::Mapped {
2128 dtype: TensorDtype::Q4TiledP,
2129 ..
2130 },
2131 Self::Mapped {
2132 dtype: TensorDtype::Q4TiledP,
2133 ..
2134 },
2135 ) => {
2136 let cols = gate.cols();
2137 let gpr = cols / GROUP_SIZE;
2138 let gv_view = Q4tpView::new(gate.quant_bytes(), inter, cols);
2139 let uv_view = Q4tpView::new(up.quant_bytes(), inter, cols);
2140 let run = |start: usize, end: usize| {
2141 let (mut gsc, mut usc) = (vec![0f32; gpr], vec![0f32; gpr]);
2142 for r in start..end {
2143 gv_view.scales_into(r, gpr, &mut gsc);
2144 uv_view.scales_into(r, gpr, &mut usc);
2145 let mut gv =
2146 dot_q4tp_row_i8(gv_view.nib, r, gpr, &act.xq, &gsc) * act.sx;
2147 let mut uv =
2148 dot_q4tp_row_i8(uv_view.nib, r, gpr, &act.xq, &usc) * act.sx;
2149 for &(j, xv) in &act.outliers {
2150 let (w, s) = q4tp_outlier(gv_view.nib, r, gpr, j, &gsc);
2151 gv += w * s * xv;
2152 let (w, s) = q4tp_outlier(uv_view.nib, r, gpr, j, &usc);
2153 uv += w * s * xv;
2154 }
2155 let silu_g = gv / (1.0 + (-gv).exp());
2156 unsafe { *out_addr.at(r) = silu_g * uv };
2158 }
2159 };
2160 dispatch_rows(pool, inter, &run);
2161 true
2162 }
2163 (
2165 Self::Mapped {
2166 dtype: TensorDtype::Q1T,
2167 ..
2168 },
2169 Self::Mapped {
2170 dtype: TensorDtype::Q1T,
2171 ..
2172 },
2173 ) => {
2174 const TILE: usize = cortiq_core::quant::Q1T_TILE;
2175 let g_bytes = gate.quant_bytes();
2176 let u_bytes = up.quant_bytes();
2177 let gpr = gate.cols() / GROUP_SIZE;
2178 let (g_rp, g_ent, g_ov) = q1t_overlay(g_bytes, inter * gpr * TILE, inter);
2179 let (u_rp, u_ent, u_ov) = q1t_overlay(u_bytes, inter * gpr * TILE, inter);
2180 let run = move |start: usize, end: usize| {
2181 for r in start..end {
2182 let mut gv = q1t_dot_row_i8(g_bytes, r, gpr, &act.xq) * act.sx;
2183 let mut uv = q1t_dot_row_i8(u_bytes, r, gpr, &act.xq) * act.sx;
2184 for &(j, xv) in &act.outliers {
2185 gv += q1t_base_weight(g_bytes, r, gpr, j) * xv;
2186 uv += q1t_base_weight(u_bytes, r, gpr, j) * xv;
2187 }
2188 gv += q1t_row_outlier_correction(g_bytes, r, g_rp, g_ent, g_ov, x_ref);
2189 uv += q1t_row_outlier_correction(u_bytes, r, u_rp, u_ent, u_ov, x_ref);
2190 let silu_g = gv / (1.0 + (-gv).exp());
2191 unsafe { *out_addr.at(r) = silu_g * uv };
2193 }
2194 };
2195 dispatch_rows(pool, inter, &run);
2196 true
2197 }
2198 _ => false,
2199 }
2200 }
2201
2202 pub fn moe_gate_up_many(
2216 pairs: &[(&QTensor, &QTensor)],
2217 x: &[f32],
2218 outs: &mut [Vec<f32>],
2219 pool: Option<&Pool>,
2220 ) -> bool {
2221 if pairs.is_empty() || pairs.len() != outs.len() || !a8w8_enabled() {
2222 return false;
2223 }
2224 let inter = pairs[0].0.rows();
2225 let cols = pairs[0].0.cols();
2226 if cols % GROUP_SIZE != 0 {
2227 return false;
2228 }
2229 let gpr = cols / GROUP_SIZE;
2230 let mut views = Vec::with_capacity(pairs.len() * 2);
2231 for ((g, u), o) in pairs.iter().zip(outs.iter()) {
2232 let both_q4tp = matches!(
2233 g,
2234 Self::Mapped {
2235 dtype: TensorDtype::Q4TiledP,
2236 ..
2237 }
2238 ) && matches!(
2239 u,
2240 Self::Mapped {
2241 dtype: TensorDtype::Q4TiledP,
2242 ..
2243 }
2244 );
2245 if !both_q4tp
2246 || g.rows() != inter
2247 || u.rows() != inter
2248 || g.cols() != cols
2249 || u.cols() != cols
2250 || o.len() != inter
2251 {
2252 return false;
2253 }
2254 views.push(Q4tpView::new(g.quant_bytes(), inter, cols));
2255 views.push(Q4tpView::new(u.quant_bytes(), inter, cols));
2256 }
2257 let act = split_act(x);
2258 let act = &act;
2259 let ptrs: Vec<SendMut> = outs.iter_mut().map(|o| SendMut(o.as_mut_ptr())).collect();
2260 let (views, ptrs) = (&views, &ptrs);
2261 let run = |start: usize, end: usize| {
2262 let (mut gsc, mut usc) = (vec![0f32; gpr], vec![0f32; gpr]);
2263 for flat in start..end {
2264 let (e, r) = (flat / inter, flat % inter);
2265 let gv_view = &views[e * 2];
2266 let uv_view = &views[e * 2 + 1];
2267 gv_view.scales_into(r, gpr, &mut gsc);
2268 uv_view.scales_into(r, gpr, &mut usc);
2269 let mut gv = dot_q4tp_row_i8(gv_view.nib, r, gpr, &act.xq, &gsc) * act.sx;
2270 let mut uv = dot_q4tp_row_i8(uv_view.nib, r, gpr, &act.xq, &usc) * act.sx;
2271 for &(j, xv) in &act.outliers {
2272 let (w, s) = q4tp_outlier(gv_view.nib, r, gpr, j, &gsc);
2273 gv += w * s * xv;
2274 let (w, s) = q4tp_outlier(uv_view.nib, r, gpr, j, &usc);
2275 uv += w * s * xv;
2276 }
2277 let silu_g = gv / (1.0 + (-gv).exp());
2278 unsafe { *ptrs[e].at(r) = silu_g * uv };
2280 }
2281 };
2282 dispatch_rows(pool, pairs.len() * inter, &run);
2283 true
2284 }
2285
2286 pub fn moe_down_many(
2295 downs: &[&QTensor],
2296 gs: &[Vec<f32>],
2297 weights: &[f32],
2298 out: &mut [f32],
2299 pool: Option<&Pool>,
2300 ) -> bool {
2301 if downs.is_empty()
2302 || downs.len() != gs.len()
2303 || downs.len() != weights.len()
2304 || !a8w8_enabled()
2305 {
2306 return false;
2307 }
2308 let rows = out.len();
2309 let cols = downs[0].cols();
2310 if cols % GROUP_SIZE != 0 {
2311 return false;
2312 }
2313 let gpr = cols / GROUP_SIZE;
2314 let mut views = Vec::with_capacity(downs.len());
2315 for (d, g) in downs.iter().zip(gs.iter()) {
2316 if !matches!(
2317 d,
2318 Self::Mapped {
2319 dtype: TensorDtype::Q4TiledP,
2320 ..
2321 }
2322 ) || d.rows() != rows
2323 || d.cols() != cols
2324 || g.len() != cols
2325 {
2326 return false;
2327 }
2328 views.push(Q4tpView::new(d.quant_bytes(), rows, cols));
2329 }
2330 let acts: Vec<SplitAct> = gs.iter().map(|g| split_act(g)).collect();
2332 let out_addr = SendMut(out.as_mut_ptr());
2340 let (views, acts, weights) = (&views, &acts, &weights);
2341 let run = |start: usize, end: usize| {
2342 let mut sc = vec![0f32; gpr];
2343 for r in start..end {
2344 let mut acc = 0f32;
2345 for (e, v) in views.iter().enumerate() {
2346 v.scales_into(r, gpr, &mut sc);
2347 let a = &acts[e];
2348 let mut d = dot_q4tp_row_i8(v.nib, r, gpr, &a.xq, &sc) * a.sx;
2349 for &(j, xv) in &a.outliers {
2350 let (w, s) = q4tp_outlier(v.nib, r, gpr, j, &sc);
2351 d += w * s * xv;
2352 }
2353 acc += weights[e] * d;
2354 }
2355 unsafe { *out_addr.at(r) = acc };
2357 }
2358 };
2359 dispatch_rows(pool, rows, &run);
2360 true
2361 }
2362}
2363
2364#[cfg(target_os = "macos")]
2369mod accel_blas {
2370 #[link(name = "Accelerate", kind = "framework")]
2371 unsafe extern "C" {
2372 pub fn cblas_sgemm(
2373 order: i32,
2374 trans_a: i32,
2375 trans_b: i32,
2376 m: i32,
2377 n: i32,
2378 k: i32,
2379 alpha: f32,
2380 a: *const f32,
2381 lda: i32,
2382 b: *const f32,
2383 ldb: i32,
2384 beta: f32,
2385 c: *mut f32,
2386 ldc: i32,
2387 );
2388 }
2389}
2390
2391#[cfg(target_os = "macos")]
2392pub(crate) fn accel_gemm_enabled() -> bool {
2393 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2394 *ON.get_or_init(|| std::env::var("CMF_ACCEL").map(|v| v != "0").unwrap_or(true))
2395}
2396
2397#[cfg(all(target_arch = "aarch64", not(target_os = "macos")))]
2400pub(crate) fn accel_gemm_enabled() -> bool {
2401 true
2402}
2403
2404#[cfg(target_arch = "aarch64")]
2411#[allow(clippy::too_many_arguments)]
2412pub(crate) fn neon_gemm_rm(
2413 m: usize,
2414 n: usize,
2415 k: usize,
2416 alpha: f32,
2417 a: &[f32],
2418 lda: usize,
2419 b_mat: &[f32],
2420 ldb: usize,
2421 b_rows_are_n: bool,
2422 c: &mut [f32],
2423 ldc: usize,
2424) {
2425 debug_assert!(a.len() >= (m - 1) * lda + k);
2426 debug_assert!(c.len() >= (m - 1) * ldc + n);
2427 unsafe {
2429 use core::arch::aarch64::*;
2430 let mut i = 0usize;
2431 while i < m {
2432 let mi = (m - i).min(4);
2433 let mut j = 0usize;
2434 while j < n {
2435 let nj = (n - j).min(8);
2436 if mi == 4 && nj == 8 {
2437 let (mut c0a, mut c0b) = (vdupq_n_f32(0.0), vdupq_n_f32(0.0));
2438 let (mut c1a, mut c1b) = (vdupq_n_f32(0.0), vdupq_n_f32(0.0));
2439 let (mut c2a, mut c2b) = (vdupq_n_f32(0.0), vdupq_n_f32(0.0));
2440 let (mut c3a, mut c3b) = (vdupq_n_f32(0.0), vdupq_n_f32(0.0));
2441 for p in 0..k {
2442 let (b0, b1) = if b_rows_are_n {
2443 let base = b_mat.as_ptr().add(j * ldb + p);
2446 let g = |o: usize| *base.add(o * ldb);
2447 ([g(0), g(1), g(2), g(3)], [g(4), g(5), g(6), g(7)])
2448 } else {
2449 let base = b_mat.as_ptr().add(p * ldb + j);
2450 (
2451 [*base, *base.add(1), *base.add(2), *base.add(3)],
2452 [*base.add(4), *base.add(5), *base.add(6), *base.add(7)],
2453 )
2454 };
2455 let bv0 = vld1q_f32(b0.as_ptr());
2456 let bv1 = vld1q_f32(b1.as_ptr());
2457 let a0 = vdupq_n_f32(*a.as_ptr().add(i * lda + p));
2458 let a1 = vdupq_n_f32(*a.as_ptr().add((i + 1) * lda + p));
2459 let a2 = vdupq_n_f32(*a.as_ptr().add((i + 2) * lda + p));
2460 let a3 = vdupq_n_f32(*a.as_ptr().add((i + 3) * lda + p));
2461 c0a = vfmaq_f32(c0a, a0, bv0);
2462 c0b = vfmaq_f32(c0b, a0, bv1);
2463 c1a = vfmaq_f32(c1a, a1, bv0);
2464 c1b = vfmaq_f32(c1b, a1, bv1);
2465 c2a = vfmaq_f32(c2a, a2, bv0);
2466 c2b = vfmaq_f32(c2b, a2, bv1);
2467 c3a = vfmaq_f32(c3a, a3, bv0);
2468 c3b = vfmaq_f32(c3b, a3, bv1);
2469 }
2470 let al = vdupq_n_f32(alpha);
2471 for (r, (ca, cb)) in [(c0a, c0b), (c1a, c1b), (c2a, c2b), (c3a, c3b)]
2472 .iter()
2473 .enumerate()
2474 {
2475 let dst = c.as_mut_ptr().add((i + r) * ldc + j);
2476 vst1q_f32(dst, vmulq_f32(*ca, al));
2477 vst1q_f32(dst.add(4), vmulq_f32(*cb, al));
2478 }
2479 } else {
2480 for r in 0..mi {
2481 for q in 0..nj {
2482 let mut acc = 0f32;
2483 for p in 0..k {
2484 let bv = if b_rows_are_n {
2485 b_mat[(j + q) * ldb + p]
2486 } else {
2487 b_mat[p * ldb + j + q]
2488 };
2489 acc += a[(i + r) * lda + p] * bv;
2490 }
2491 c[(i + r) * ldc + j + q] = acc * alpha;
2492 }
2493 }
2494 }
2495 j += nj;
2496 }
2497 i += mi;
2498 }
2499 }
2500}
2501
2502#[cfg(all(target_arch = "aarch64", not(target_os = "macos")))]
2504#[allow(clippy::too_many_arguments)]
2505pub(crate) fn sgemm_rm(
2506 m: usize,
2507 n: usize,
2508 k: usize,
2509 alpha: f32,
2510 a: &[f32],
2511 lda: usize,
2512 b_mat: &[f32],
2513 ldb: usize,
2514 b_rows_are_n: bool,
2515 c: &mut [f32],
2516 ldc: usize,
2517) {
2518 neon_gemm_rm(m, n, k, alpha, a, lda, b_mat, ldb, b_rows_are_n, c, ldc);
2519}
2520
2521#[allow(clippy::too_many_arguments)]
2525pub fn sgemm_public(
2526 m: usize,
2527 n: usize,
2528 k: usize,
2529 alpha: f32,
2530 a: &[f32],
2531 lda: usize,
2532 b_mat: &[f32],
2533 ldb: usize,
2534 b_rows_are_n: bool,
2535 c: &mut [f32],
2536 ldc: usize,
2537) {
2538 #[cfg(any(target_os = "macos", target_arch = "aarch64"))]
2539 {
2540 sgemm_rm(m, n, k, alpha, a, lda, b_mat, ldb, b_rows_are_n, c, ldc);
2541 }
2542 #[cfg(not(any(target_os = "macos", target_arch = "aarch64")))]
2547 {
2548 for i in 0..m {
2549 for j in 0..n {
2550 let mut acc = 0f32;
2551 for p in 0..k {
2552 let bv = if b_rows_are_n {
2553 b_mat[j * ldb + p]
2554 } else {
2555 b_mat[p * ldb + j]
2556 };
2557 acc += a[i * lda + p] * bv;
2558 }
2559 c[i * ldc + j] = alpha * acc;
2560 }
2561 }
2562 }
2563}
2564
2565#[cfg(target_os = "macos")]
2568#[allow(clippy::too_many_arguments)]
2569pub(crate) fn sgemm_rm(
2570 m: usize,
2571 n: usize,
2572 k: usize,
2573 alpha: f32,
2574 a: &[f32],
2575 lda: usize,
2576 b_mat: &[f32],
2577 ldb: usize,
2578 b_rows_are_n: bool,
2579 c: &mut [f32],
2580 ldc: usize,
2581) {
2582 debug_assert!(a.len() >= (m - 1) * lda + k);
2583 debug_assert!(c.len() >= (m - 1) * ldc + n);
2584 #[cfg(target_arch = "aarch64")]
2589 if std::env::var("CMF_FORCE_NEON_GEMM")
2590 .map(|v| v == "1")
2591 .unwrap_or(false)
2592 {
2593 return neon_gemm_rm(m, n, k, alpha, a, lda, b_mat, ldb, b_rows_are_n, c, ldc);
2594 }
2595 unsafe {
2596 accel_blas::cblas_sgemm(
2597 101, 111, if b_rows_are_n { 112 } else { 111 },
2600 m as i32,
2601 n as i32,
2602 k as i32,
2603 alpha,
2604 a.as_ptr(),
2605 lda as i32,
2606 b_mat.as_ptr(),
2607 ldb as i32,
2608 0.0,
2609 c.as_mut_ptr(),
2610 ldc as i32,
2611 );
2612 }
2613}
2614
2615#[cfg(target_os = "macos")]
2622fn qmatmat_accel(
2623 q: &[u8],
2624 row_scale: &[f32],
2625 pre: &[std::borrow::Cow<'_, [f32]>],
2626 rows: usize,
2627 cols: usize,
2628 out: &mut [f32],
2629 pool: Option<&Pool>,
2630) {
2631 const TR: usize = 2048;
2636 let b = pre.len();
2637 thread_local! {
2638 static XPANEL: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
2639 static WTILE: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
2640 }
2641 XPANEL.with(|xp| {
2642 WTILE.with(|wt| {
2643 let mut xpanel = xp.borrow_mut();
2644 xpanel.clear();
2645 for x in pre {
2646 xpanel.extend_from_slice(x);
2647 }
2648 let mut wtile = wt.borrow_mut();
2649 wtile.resize(TR * cols, 0.0);
2650 let mut r0 = 0usize;
2651 while r0 < rows {
2652 let tr = TR.min(rows - r0);
2653 let wt_addr = SendMut(wtile.as_mut_ptr());
2655 let run = |start: usize, end: usize| {
2656 for r in start..end {
2657 let row = &q[(r0 + r) * cols..(r0 + r + 1) * cols];
2658 let s = row_scale[r0 + r];
2659 let dst =
2661 unsafe { std::slice::from_raw_parts_mut(wt_addr.at(r * cols), cols) };
2662 for (d, &v) in dst.iter_mut().zip(row) {
2663 *d = (v as i8) as f32 * s;
2664 }
2665 }
2666 };
2667 dispatch_rows(pool, tr, &run);
2668 unsafe {
2670 accel_blas::cblas_sgemm(
2671 101, 111, 112, b as i32,
2675 tr as i32,
2676 cols as i32,
2677 1.0,
2678 xpanel.as_ptr(),
2679 cols as i32,
2680 wtile.as_ptr(),
2681 cols as i32,
2682 0.0,
2683 out.as_mut_ptr().add(r0),
2684 rows as i32,
2685 );
2686 }
2687 r0 += tr;
2688 }
2689 })
2690 });
2691}
2692
2693fn qmatmat(
2694 q: &[u8],
2695 row_scale: &[f32],
2696 pre: &[std::borrow::Cow<'_, [f32]>],
2697 rows: usize,
2698 cols: usize,
2699 out: &mut [f32],
2700 pool: Option<&Pool>,
2701) {
2702 let b = pre.len();
2703 debug_assert_eq!(out.len(), b * rows);
2704 #[cfg(target_os = "macos")]
2709 if b >= 8 && rows * cols >= 500_000 && accel_gemm_enabled() {
2710 qmatmat_accel(q, row_scale, pre, rows, cols, out, pool);
2711 return;
2712 }
2713 #[cfg(target_arch = "aarch64")]
2714 if sdot_enabled() {
2715 let acts: Vec<SplitAct> = pre.iter().map(|x| split_act(x)).collect();
2716 let out_addr = SendMut(out.as_mut_ptr());
2717 let blocked_ok = std::env::var("CMF_X86_BLOCKED")
2720 .map(|v| v != "0")
2721 .unwrap_or(true);
2722 let use_i8mm = i8mm_enabled();
2723 if blocked_ok {
2724 let run = |start: usize, end: usize| {
2725 let mut o = start;
2726 while o < end {
2727 if o + 2 <= end {
2728 let r0 = &q[o * cols..(o + 1) * cols];
2729 let r1 = &q[(o + 1) * cols..(o + 2) * cols];
2730 let mut bi = 0usize;
2731 while bi + 4 <= acts.len() {
2732 let xs = [
2733 acts[bi].xq.as_slice(),
2734 acts[bi + 1].xq.as_slice(),
2735 acts[bi + 2].xq.as_slice(),
2736 acts[bi + 3].xq.as_slice(),
2737 ];
2738 let d = if use_i8mm {
2739 unsafe { dot_i8_smmla_2x4(r0, r1, xs) }
2740 } else {
2741 unsafe { dot_i8_sdot_2x4(r0, r1, xs) }
2742 };
2743 for (r, row) in [r0, r1].into_iter().enumerate() {
2744 for k in 0..4 {
2745 let act = &acts[bi + k];
2746 let mut v = d[r][k] as f32 * act.sx;
2747 for &(j, xv) in &act.outliers {
2748 v += (row[j] as i8) as f32 * xv;
2749 }
2750 unsafe {
2751 *out_addr.at((bi + k) * rows + o + r) = v * row_scale[o + r]
2752 };
2753 }
2754 }
2755 bi += 4;
2756 }
2757 while bi < acts.len() {
2758 for (r, row) in [r0, r1].into_iter().enumerate() {
2759 let v = row_dot_sdot(row, &acts[bi]) * row_scale[o + r];
2760 unsafe { *out_addr.at(bi * rows + o + r) = v };
2761 }
2762 bi += 1;
2763 }
2764 o += 2;
2765 } else {
2766 let row = &q[o * cols..(o + 1) * cols];
2767 for (bi, act) in acts.iter().enumerate() {
2768 let v = row_dot_sdot(row, act) * row_scale[o];
2769 unsafe { *out_addr.at(bi * rows + o) = v };
2770 }
2771 o += 1;
2772 }
2773 }
2774 };
2775 dispatch_rows(pool, rows, &run);
2776 return;
2777 }
2778 let run = |start: usize, end: usize| {
2779 for o in start..end {
2780 let row = &q[o * cols..(o + 1) * cols];
2781 for (bi, act) in acts.iter().enumerate() {
2782 let v = row_dot_sdot(row, act) * row_scale[o];
2783 unsafe { *out_addr.at(bi * rows + o) = v };
2784 }
2785 }
2786 };
2787 dispatch_rows(pool, rows, &run);
2788 return;
2789 }
2790 #[cfg(target_arch = "x86_64")]
2795 if avx2_a8w8_enabled() {
2796 let acts: Vec<SplitAct> = pre.iter().map(|x| split_act(x)).collect();
2797 let out_addr = SendMut(out.as_mut_ptr());
2798 let blocked_ok = std::env::var("CMF_X86_BLOCKED")
2801 .map(|v| v != "0")
2802 .unwrap_or(true);
2803 if !avx512vnni_enabled() && blocked_ok {
2804 let run = |start: usize, end: usize| {
2805 let mut o = start;
2806 while o < end {
2807 if o + 2 <= end {
2808 let r0 = &q[o * cols..(o + 1) * cols];
2809 let r1 = &q[(o + 1) * cols..(o + 2) * cols];
2810 let mut bi = 0usize;
2811 while bi + 4 <= acts.len() {
2812 let xs = [
2813 acts[bi].xq.as_slice(),
2814 acts[bi + 1].xq.as_slice(),
2815 acts[bi + 2].xq.as_slice(),
2816 acts[bi + 3].xq.as_slice(),
2817 ];
2818 let d = unsafe { dot_i8_i8_avx2_2x4(r0, r1, xs) };
2819 for (r, row) in [r0, r1].into_iter().enumerate() {
2820 for k in 0..4 {
2821 let act = &acts[bi + k];
2822 let mut v = d[r][k] as f32 * act.sx;
2823 for &(j, xv) in &act.outliers {
2824 v += (row[j] as i8) as f32 * xv;
2825 }
2826 unsafe {
2827 *out_addr.at((bi + k) * rows + o + r) = v * row_scale[o + r]
2828 };
2829 }
2830 }
2831 bi += 4;
2832 }
2833 while bi < acts.len() {
2834 for (r, row) in [r0, r1].into_iter().enumerate() {
2835 let v = row_dot_avx2(row, &acts[bi]) * row_scale[o + r];
2836 unsafe { *out_addr.at(bi * rows + o + r) = v };
2837 }
2838 bi += 1;
2839 }
2840 o += 2;
2841 } else {
2842 let row = &q[o * cols..(o + 1) * cols];
2843 for (bi, act) in acts.iter().enumerate() {
2844 let v = row_dot_avx2(row, act) * row_scale[o];
2845 unsafe { *out_addr.at(bi * rows + o) = v };
2846 }
2847 o += 1;
2848 }
2849 }
2850 };
2851 dispatch_rows(pool, rows, &run);
2852 return;
2853 }
2854 let run = |start: usize, end: usize| {
2855 for o in start..end {
2856 let row = &q[o * cols..(o + 1) * cols];
2857 for (bi, act) in acts.iter().enumerate() {
2858 let v = row_dot_avx2(row, act) * row_scale[o];
2859 unsafe { *out_addr.at(bi * rows + o) = v };
2860 }
2861 }
2862 };
2863 dispatch_rows(pool, rows, &run);
2864 return;
2865 }
2866 let out_addr = SendMut(out.as_mut_ptr());
2867 let run = |start: usize, end: usize| {
2868 for o in start..end {
2869 let row = &q[o * cols..(o + 1) * cols];
2870 for (bi, x) in pre.iter().enumerate() {
2871 let mut acc = 0f32;
2872 for j in 0..cols {
2873 acc += (row[j] as i8) as f32 * x[j];
2874 }
2875 unsafe { *out_addr.at(bi * rows + o) = acc * row_scale[o] };
2876 }
2877 }
2878 };
2879 dispatch_rows(pool, rows, &run);
2880}
2881
2882fn dispatch_rows(pool: Option<&Pool>, rows: usize, run: &(dyn Fn(usize, usize) + Sync)) {
2885 match pool {
2886 Some(pool) if rows >= 256 => pool.run_rows(rows, run),
2887 _ => run(0, rows),
2888 }
2889}
2890
2891fn q4_split(bytes: &[u8], rows: usize, cols: usize) -> (&[u8], &[u8]) {
2893 let groups = rows * cols / GROUP_SIZE;
2894 bytes.split_at(groups * 16)
2895}
2896
2897#[inline]
2902fn vbit_fill4(data: &[u8], buf: &mut [u8]) {
2903 #[cfg(target_arch = "aarch64")]
2904 unsafe {
2905 return vbit_fill4_neon(data, buf);
2906 }
2907 #[cfg(target_arch = "x86_64")]
2908 if avx2_enabled() {
2909 return unsafe { vbit_fill4_avx2(data, buf) };
2910 }
2911 #[allow(unreachable_code)]
2912 for (blk, chunk) in buf.chunks_exact_mut(8).enumerate() {
2913 let u = unpack8::<4>(&data[blk * 4..]);
2914 for k in 0..8 {
2915 chunk[k] = (u[k] - 7) as i8 as u8;
2916 }
2917 }
2918}
2919
2920#[cfg(target_arch = "aarch64")]
2921#[target_feature(enable = "neon")]
2922unsafe fn vbit_fill4_neon(data: &[u8], buf: &mut [u8]) {
2923 unsafe {
2926 use core::arch::aarch64::*;
2927 let n = buf.len();
2928 let mask = vdupq_n_u8(0x0F);
2929 let seven = vdupq_n_s8(7);
2930 let mut g = 0usize;
2931 while g * 32 + 32 <= n {
2932 let b = vld1q_u8(data.as_ptr().add(g * 16));
2933 let hi = vshrq_n_u8::<4>(b);
2934 let lo = vandq_u8(b, mask);
2935 let z0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(hi, lo)), seven);
2936 let z1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(hi, lo)), seven);
2937 vst1q_u8(buf.as_mut_ptr().add(g * 32), vreinterpretq_u8_s8(z0));
2938 vst1q_u8(buf.as_mut_ptr().add(g * 32 + 16), vreinterpretq_u8_s8(z1));
2939 g += 1;
2940 }
2941 }
2942}
2943
2944#[cfg(target_arch = "x86_64")]
2945#[target_feature(enable = "avx2")]
2946unsafe fn vbit_fill4_avx2(data: &[u8], buf: &mut [u8]) {
2947 unsafe {
2949 use core::arch::x86_64::*;
2950 let n = buf.len();
2951 let mask = _mm_set1_epi8(0x0F);
2952 let seven = _mm256_set1_epi8(7);
2953 let mut g = 0usize;
2954 while g * 32 + 32 <= n {
2955 let b = _mm_loadu_si128(data.as_ptr().add(g * 16) as *const __m128i);
2956 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), mask);
2957 let lo = _mm_and_si128(b, mask);
2958 let z = _mm256_sub_epi8(
2959 _mm256_set_m128i(_mm_unpackhi_epi8(hi, lo), _mm_unpacklo_epi8(hi, lo)),
2960 seven,
2961 );
2962 _mm256_storeu_si256(buf.as_mut_ptr().add(g * 32) as *mut __m256i, z);
2963 g += 1;
2964 }
2965 }
2966}
2967
2968#[inline(always)]
2973fn unpack8<const B: usize>(data: &[u8]) -> [i32; 8] {
2974 let mut acc = 0u64;
2975 for i in 0..B {
2976 acc = (acc << 8) | data[i] as u64;
2977 }
2978 let mask = (1u64 << B) - 1;
2979 let mut out = [0i32; 8];
2980 for (k, o) in out.iter_mut().enumerate() {
2981 *o = ((acc >> ((7 - k) * B)) & mask) as i32;
2982 }
2983 out
2984}
2985
2986#[allow(clippy::too_many_arguments)]
2992fn vbitmatvec(
2993 bytes: &[u8],
2994 offsets: &[usize],
2995 x: &[f32],
2996 rows: usize,
2997 cols: usize,
2998 out: &mut [f32],
2999 pool: Option<&Pool>,
3000) {
3001 debug_assert_eq!(out.len(), rows);
3002 debug_assert_eq!(offsets.len(), rows + 1);
3003
3004 if a8w8_enabled() {
3008 let act = split_act(x);
3009 let out_addr = SendMut(out.as_mut_ptr());
3010 let run = move |start: usize, end: usize| {
3011 vbit_range_a8w8(bytes, offsets, x, &act, rows, cols, out_addr, start, end)
3012 };
3013 dispatch_rows(pool, rows, &run);
3014 return;
3015 }
3016
3017 let out_addr = SendMut(out.as_mut_ptr());
3018 let run = move |start: usize, end: usize| {
3019 vbit_range_f32(bytes, offsets, x, rows, cols, out_addr, start, end)
3020 };
3021 dispatch_rows(pool, rows, &run);
3022}
3023
3024#[allow(clippy::too_many_arguments)]
3028fn vbit_range_a8w8(
3029 bytes: &[u8],
3030 offsets: &[usize],
3031 x: &[f32],
3032 act: &SplitAct,
3033 rows: usize,
3034 cols: usize,
3035 out: SendMut,
3036 start: usize,
3037 end: usize,
3038) {
3039 let ng = cols / GROUP_SIZE;
3040 let bits = &bytes[..rows];
3041 let sc_off = rows;
3042 let row_dot = |r: usize| -> f32 {
3043 let b = bits[r] as usize;
3044 let l = (1i32 << (b - 1)) - 1;
3045 let mask = (1u64 << b) - 1;
3046 let data = &bytes[offsets[r]..offsets[r + 1]];
3047 if b == 8 {
3048 let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
3050 let mut dot = 0f32;
3051 for g in 0..ng {
3052 let so = (r * ng + g) * 2;
3053 let sgf = f16_to_f32(u16::from_le_bytes([
3054 bytes[sc_off + so],
3055 bytes[sc_off + so + 1],
3056 ]));
3057 let xg = &x[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
3058 let mut gd = 0f32;
3059 for &xv in xg.iter() {
3060 if nbits < 8 {
3061 acc = (acc << 8) | data[idx] as u64;
3062 idx += 1;
3063 nbits += 8;
3064 }
3065 let u = ((acc >> (nbits - 8)) & 0xFF) as i32;
3066 nbits -= 8;
3067 gd += (u - l) as f32 * xv;
3068 }
3069 dot += gd * sgf;
3070 }
3071 return dot;
3072 }
3073 thread_local! {
3077 static VBIT_SCRATCH: std::cell::RefCell<Vec<u8>> =
3078 const { std::cell::RefCell::new(Vec::new()) };
3079 }
3080 #[inline(always)]
3081 fn fill<const B: usize>(data: &[u8], l: i32, buf: &mut [u8]) {
3082 for (blk, chunk) in buf.chunks_exact_mut(8).enumerate() {
3083 let u = unpack8::<B>(&data[blk * B..]);
3084 for k in 0..8 {
3085 chunk[k] = (u[k] - l) as i8 as u8;
3086 }
3087 }
3088 }
3089 let _ = mask;
3090 VBIT_SCRATCH.with(|scratch| {
3091 let mut buf = scratch.borrow_mut();
3092 buf.resize(cols, 0);
3093 match b {
3094 3 => fill::<3>(data, l, &mut buf),
3095 4 => vbit_fill4(data, &mut buf),
3096 5 => fill::<5>(data, l, &mut buf),
3097 6 => fill::<6>(data, l, &mut buf),
3098 _ => unreachable!(),
3099 }
3100 let mut dot = 0f32;
3101 for g in 0..ng {
3102 let so = (r * ng + g) * 2;
3103 let s = f16_to_f32(u16::from_le_bytes([
3104 bytes[sc_off + so],
3105 bytes[sc_off + so + 1],
3106 ]));
3107 let d = dot_i8_i8(
3108 &buf[g * GROUP_SIZE..(g + 1) * GROUP_SIZE],
3109 &act.xq[g * GROUP_SIZE..(g + 1) * GROUP_SIZE],
3110 ) as f32
3111 * act.sx;
3112 dot += d * s;
3113 }
3114 for &(j, xv) in &act.outliers {
3115 let so = (r * ng + j / GROUP_SIZE) * 2;
3116 let s = f16_to_f32(u16::from_le_bytes([
3117 bytes[sc_off + so],
3118 bytes[sc_off + so + 1],
3119 ]));
3120 dot += (buf[j] as i8) as f32 * s * xv;
3122 }
3123 dot
3124 })
3125 };
3126 for r in start..end {
3127 unsafe { *out.at(r) = row_dot(r) };
3129 }
3130}
3131
3132#[allow(clippy::too_many_arguments)]
3134fn vbit_range_f32(
3135 bytes: &[u8],
3136 offsets: &[usize],
3137 x: &[f32],
3138 rows: usize,
3139 cols: usize,
3140 out: SendMut,
3141 start: usize,
3142 end: usize,
3143) {
3144 let ng = cols / GROUP_SIZE;
3145 let bits = &bytes[..rows];
3146 let sc_off = rows;
3147 #[inline(always)]
3151 fn dot_row<const B: usize>(
3152 data: &[u8],
3153 bytes: &[u8],
3154 sc_off: usize,
3155 r: usize,
3156 ng: usize,
3157 x: &[f32],
3158 ) -> f32 {
3159 let l = ((1i32 << (B - 1)) - 1) as f32;
3160 let gbytes = GROUP_SIZE * B / 8;
3161 let mut dot = 0f32;
3162 for g in 0..ng {
3163 let so = (r * ng + g) * 2;
3164 let s = f16_to_f32(u16::from_le_bytes([
3165 bytes[sc_off + so],
3166 bytes[sc_off + so + 1],
3167 ]));
3168 let xg = &x[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
3169 let gd0 = &data[g * gbytes..(g + 1) * gbytes];
3170 let mut gd = 0f32;
3171 for blk in 0..GROUP_SIZE / 8 {
3172 let u = unpack8::<B>(&gd0[blk * B..]);
3173 let xb = &xg[blk * 8..blk * 8 + 8];
3174 for k in 0..8 {
3175 gd += (u[k] as f32 - l) * xb[k];
3176 }
3177 }
3178 dot += gd * s;
3179 }
3180 dot
3181 }
3182 for r in start..end {
3183 let data = &bytes[offsets[r]..offsets[r + 1]];
3184 let v = match bits[r] {
3185 3 => dot_row::<3>(data, bytes, sc_off, r, ng, x),
3186 4 => dot_row::<4>(data, bytes, sc_off, r, ng, x),
3187 5 => dot_row::<5>(data, bytes, sc_off, r, ng, x),
3188 6 => dot_row::<6>(data, bytes, sc_off, r, ng, x),
3189 8 => dot_row::<8>(data, bytes, sc_off, r, ng, x),
3190 b => unreachable!("vbit bit-width {b} (validated at load)"),
3191 };
3192 unsafe { *out.at(r) = v };
3194 }
3195}
3196
3197#[allow(clippy::too_many_arguments)]
3202fn vbitmatvec2(
3203 bytes: &[u8],
3204 offsets: &[usize],
3205 x1: &[f32],
3206 x2: &[f32],
3207 rows: usize,
3208 cols: usize,
3209 o1: &mut [f32],
3210 o2: &mut [f32],
3211 pool: Option<&Pool>,
3212) {
3213 debug_assert_eq!(o1.len(), rows);
3214 debug_assert_eq!(o2.len(), rows);
3215
3216 if a8w8_enabled() {
3217 let a1 = split_act(x1);
3218 let a2 = split_act(x2);
3219 let p1 = SendMut(o1.as_mut_ptr());
3220 let p2 = SendMut(o2.as_mut_ptr());
3221 let run = move |start: usize, end: usize| {
3222 vbit_range2_a8w8(
3223 bytes, offsets, x1, x2, &a1, &a2, rows, cols, p1, p2, start, end,
3224 )
3225 };
3226 dispatch_rows(pool, rows, &run);
3227 return;
3228 }
3229
3230 let p1 = SendMut(o1.as_mut_ptr());
3231 let p2 = SendMut(o2.as_mut_ptr());
3232 let run = move |start: usize, end: usize| {
3233 vbit_range2_f32(bytes, offsets, x1, x2, rows, cols, p1, p2, start, end)
3234 };
3235 dispatch_rows(pool, rows, &run);
3236}
3237
3238#[allow(clippy::too_many_arguments)]
3242fn vbit_range2_a8w8(
3243 bytes: &[u8],
3244 offsets: &[usize],
3245 x1: &[f32],
3246 x2: &[f32],
3247 a1: &SplitAct,
3248 a2: &SplitAct,
3249 rows: usize,
3250 cols: usize,
3251 p1: SendMut,
3252 p2: SendMut,
3253 start: usize,
3254 end: usize,
3255) {
3256 let ng = cols / GROUP_SIZE;
3257 let bits = &bytes[..rows];
3258 let sc_off = rows;
3259 let row_dots = |r: usize| -> (f32, f32) {
3260 let b = bits[r] as usize;
3261 let l = (1i32 << (b - 1)) - 1;
3262 let data = &bytes[offsets[r]..offsets[r + 1]];
3263 if b == 8 {
3264 let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
3267 let (mut d1, mut d2) = (0f32, 0f32);
3268 for g in 0..ng {
3269 let so = (r * ng + g) * 2;
3270 let sgf = f16_to_f32(u16::from_le_bytes([
3271 bytes[sc_off + so],
3272 bytes[sc_off + so + 1],
3273 ]));
3274 let (mut g1, mut g2) = (0f32, 0f32);
3275 for k in 0..GROUP_SIZE {
3276 if nbits < 8 {
3277 acc = (acc << 8) | data[idx] as u64;
3278 idx += 1;
3279 nbits += 8;
3280 }
3281 let u = ((acc >> (nbits - 8)) & 0xFF) as i32;
3282 nbits -= 8;
3283 let w = (u - l) as f32;
3284 g1 += w * x1[g * GROUP_SIZE + k];
3285 g2 += w * x2[g * GROUP_SIZE + k];
3286 }
3287 d1 += g1 * sgf;
3288 d2 += g2 * sgf;
3289 }
3290 return (d1, d2);
3291 }
3292 thread_local! {
3293 static VBIT_SCRATCH2: std::cell::RefCell<Vec<u8>> =
3294 const { std::cell::RefCell::new(Vec::new()) };
3295 }
3296 #[inline(always)]
3297 fn fill<const B: usize>(data: &[u8], l: i32, buf: &mut [u8]) {
3298 for (blk, chunk) in buf.chunks_exact_mut(8).enumerate() {
3299 let u = unpack8::<B>(&data[blk * B..]);
3300 for k in 0..8 {
3301 chunk[k] = (u[k] - l) as i8 as u8;
3302 }
3303 }
3304 }
3305 VBIT_SCRATCH2.with(|scratch| {
3306 let mut buf = scratch.borrow_mut();
3307 buf.resize(cols, 0);
3308 match b {
3309 3 => fill::<3>(data, l, &mut buf),
3310 4 => vbit_fill4(data, &mut buf),
3311 5 => fill::<5>(data, l, &mut buf),
3312 6 => fill::<6>(data, l, &mut buf),
3313 _ => unreachable!(),
3314 }
3315 let (mut d1, mut d2) = (0f32, 0f32);
3316 for g in 0..ng {
3317 let so = (r * ng + g) * 2;
3318 let s = f16_to_f32(u16::from_le_bytes([
3319 bytes[sc_off + so],
3320 bytes[sc_off + so + 1],
3321 ]));
3322 let wg = &buf[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
3323 let v1 = dot_i8_i8(wg, &a1.xq[g * GROUP_SIZE..(g + 1) * GROUP_SIZE]) as f32 * a1.sx;
3324 let v2 = dot_i8_i8(wg, &a2.xq[g * GROUP_SIZE..(g + 1) * GROUP_SIZE]) as f32 * a2.sx;
3325 d1 += v1 * s;
3326 d2 += v2 * s;
3327 }
3328 for &(j, xv) in &a1.outliers {
3329 let so = (r * ng + j / GROUP_SIZE) * 2;
3330 let s = f16_to_f32(u16::from_le_bytes([
3331 bytes[sc_off + so],
3332 bytes[sc_off + so + 1],
3333 ]));
3334 d1 += (buf[j] as i8) as f32 * s * xv;
3335 }
3336 for &(j, xv) in &a2.outliers {
3337 let so = (r * ng + j / GROUP_SIZE) * 2;
3338 let s = f16_to_f32(u16::from_le_bytes([
3339 bytes[sc_off + so],
3340 bytes[sc_off + so + 1],
3341 ]));
3342 d2 += (buf[j] as i8) as f32 * s * xv;
3343 }
3344 (d1, d2)
3345 })
3346 };
3347 for r in start..end {
3348 let (v1, v2) = row_dots(r);
3349 unsafe {
3351 *p1.at(r) = v1;
3352 *p2.at(r) = v2;
3353 }
3354 }
3355}
3356
3357#[allow(clippy::too_many_arguments)]
3361fn vbit_range2_f32(
3362 bytes: &[u8],
3363 offsets: &[usize],
3364 x1: &[f32],
3365 x2: &[f32],
3366 rows: usize,
3367 cols: usize,
3368 p1: SendMut,
3369 p2: SendMut,
3370 start: usize,
3371 end: usize,
3372) {
3373 let ng = cols / GROUP_SIZE;
3374 let bits = &bytes[..rows];
3375 let sc_off = rows;
3376 #[inline(always)]
3377 #[allow(clippy::too_many_arguments)]
3378 fn dot_row2<const B: usize>(
3379 data: &[u8],
3380 bytes: &[u8],
3381 sc_off: usize,
3382 r: usize,
3383 ng: usize,
3384 x1: &[f32],
3385 x2: &[f32],
3386 ) -> (f32, f32) {
3387 let l = ((1i32 << (B - 1)) - 1) as f32;
3388 let gbytes = GROUP_SIZE * B / 8;
3389 let (mut d1, mut d2) = (0f32, 0f32);
3390 for g in 0..ng {
3391 let so = (r * ng + g) * 2;
3392 let s = f16_to_f32(u16::from_le_bytes([
3393 bytes[sc_off + so],
3394 bytes[sc_off + so + 1],
3395 ]));
3396 let x1g = &x1[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
3397 let x2g = &x2[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
3398 let gd0 = &data[g * gbytes..(g + 1) * gbytes];
3399 let (mut g1, mut g2) = (0f32, 0f32);
3400 for blk in 0..GROUP_SIZE / 8 {
3401 let u = unpack8::<B>(&gd0[blk * B..]);
3402 for k in 0..8 {
3403 let w = u[k] as f32 - l;
3404 g1 += w * x1g[blk * 8 + k];
3405 g2 += w * x2g[blk * 8 + k];
3406 }
3407 }
3408 d1 += g1 * s;
3409 d2 += g2 * s;
3410 }
3411 (d1, d2)
3412 }
3413 for r in start..end {
3414 let data = &bytes[offsets[r]..offsets[r + 1]];
3415 let (v1, v2) = match bits[r] {
3416 3 => dot_row2::<3>(data, bytes, sc_off, r, ng, x1, x2),
3417 4 => dot_row2::<4>(data, bytes, sc_off, r, ng, x1, x2),
3418 5 => dot_row2::<5>(data, bytes, sc_off, r, ng, x1, x2),
3419 6 => dot_row2::<6>(data, bytes, sc_off, r, ng, x1, x2),
3420 8 => dot_row2::<8>(data, bytes, sc_off, r, ng, x1, x2),
3421 b => unreachable!("vbit bit-width {b} (validated at load)"),
3422 };
3423 unsafe {
3425 *p1.at(r) = v1;
3426 *p2.at(r) = v2;
3427 }
3428 }
3429}
3430
3431#[inline]
3438#[allow(unreachable_code)]
3439fn dot_q4t_row_i8(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
3440 #[cfg(target_arch = "aarch64")]
3441 unsafe {
3442 return dot_q4t_row_sdot(bytes, r, gpr, xq);
3443 }
3444 #[cfg(target_arch = "x86_64")]
3445 unsafe {
3446 if vnni_tiles_enabled() {
3447 return dot_q4t_row_vnni(bytes, r, gpr, xq);
3448 }
3449 return dot_q4t_row_avx2(bytes, r, gpr, xq);
3450 }
3451 let mut acc = 0f32;
3452 for gi in 0..gpr {
3453 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
3454 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
3455 let mut d = 0i32;
3456 for (k, &b) in tile[2..].iter().enumerate() {
3457 d += ((b & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2] as i32
3458 + (((b >> 4) & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2 + 1] as i32;
3459 }
3460 acc += d as f32 * s;
3461 }
3462 acc
3463}
3464
3465#[cfg(target_arch = "aarch64")]
3466#[target_feature(enable = "neon,dotprod")]
3467unsafe fn dot_q4t_row_sdot(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
3468 unsafe {
3471 use core::arch::aarch64::*;
3472 use core::arch::asm;
3473 let lomask = vdupq_n_u8(0x0F);
3474 let eight = vdupq_n_s8(8);
3475 let mut acc = 0f32;
3476 for gi in 0..gpr {
3477 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
3478 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3479 let b = vld1q_u8(t.add(2));
3480 let lo = vandq_u8(b, lomask);
3481 let hi = vshrq_n_u8::<4>(b);
3482 let e0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(lo, hi)), eight);
3483 let e1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(lo, hi)), eight);
3484 let x0 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE));
3485 let x1 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE + 16));
3486 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
3487 asm!(
3488 "sdot {a0:v}.4s, {e0:v}.16b, {x0:v}.16b",
3489 "sdot {a1:v}.4s, {e1:v}.16b, {x1:v}.16b",
3490 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
3491 e0 = in(vreg) e0, x0 = in(vreg) x0, e1 = in(vreg) e1, x1 = in(vreg) x1,
3492 options(pure, nomem, nostack),
3493 );
3494 acc += vaddvq_s32(vaddq_s32(a0, a1)) as f32 * s;
3495 }
3496 acc
3497 }
3498}
3499
3500#[cfg(target_arch = "x86_64")]
3501#[target_feature(enable = "avx2")]
3502unsafe fn dot_q4t_row_avx2(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
3503 unsafe {
3505 use core::arch::x86_64::*;
3506 let lomask = _mm_set1_epi8(0x0F);
3507 let eight = _mm256_set1_epi8(8);
3508 let ones = _mm256_set1_epi16(1);
3509 let mut acc = 0f32;
3510 for gi in 0..gpr {
3511 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
3512 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3513 let b = _mm_loadu_si128(t.add(2) as *const __m128i);
3514 let lo = _mm_and_si128(b, lomask);
3515 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), lomask);
3516 let w = _mm256_sub_epi8(
3517 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
3518 eight,
3519 );
3520 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
3521 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(w), _mm256_sign_epi8(x, w));
3522 let d = _mm256_madd_epi16(p16, ones);
3523 let hi128 = _mm256_extracti128_si256::<1>(d);
3524 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
3525 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
3526 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
3527 acc += _mm_cvtsi128_si32(s32) as f32 * s;
3528 }
3529 acc
3530 }
3531}
3532
3533#[cfg(target_arch = "x86_64")]
3537#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
3538unsafe fn dot_q4t_row_vnni(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
3539 unsafe {
3541 use core::arch::x86_64::*;
3542 let lomask = _mm_set1_epi8(0x0F);
3543 let eight = _mm256_set1_epi8(8);
3544 let mut acc = 0f32;
3545 for gi in 0..gpr {
3546 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
3547 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3548 let b = _mm_loadu_si128(t.add(2) as *const __m128i);
3549 let lo = _mm_and_si128(b, lomask);
3550 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), lomask);
3551 let w = _mm256_sub_epi8(
3552 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
3553 eight,
3554 );
3555 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
3556 let d = dpbusd_hsum(_mm256_abs_epi8(w), _mm256_sign_epi8(x, w));
3557 acc += d as f32 * s;
3558 }
3559 acc
3560 }
3561}
3562
3563#[cfg(target_arch = "x86_64")]
3568#[target_feature(enable = "avx2,fma")]
3573unsafe fn dot_q4t_row_1x4_avx2(bytes: &[u8], r: usize, gpr: usize, xs: [&[i8]; 4]) -> [f32; 4] {
3574 unsafe {
3576 use core::arch::x86_64::*;
3577 let lomask = _mm_set1_epi8(0x0F);
3578 let eight = _mm256_set1_epi8(8);
3579 let ones = _mm256_set1_epi16(1);
3580 let mut f0 = _mm256_setzero_ps();
3593 let mut f1 = _mm256_setzero_ps();
3594 let mut f2 = _mm256_setzero_ps();
3595 let mut f3 = _mm256_setzero_ps();
3596 for gi in 0..gpr {
3597 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
3598 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3599 let sv = _mm256_set1_ps(s);
3600 let bb = _mm_loadu_si128(t.add(2) as *const __m128i);
3601 let lo = _mm_and_si128(bb, lomask);
3602 let hi = _mm_and_si128(_mm_srli_epi16::<4>(bb), lomask);
3603 let w = _mm256_sub_epi8(
3604 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
3605 eight,
3606 );
3607 let aw = _mm256_abs_epi8(w);
3608 let off = gi * GROUP_SIZE;
3609 let dot = |xq: &[i8]| {
3610 let x = _mm256_loadu_si256(xq.as_ptr().add(off) as *const __m256i);
3611 let p16 = _mm256_maddubs_epi16(aw, _mm256_sign_epi8(x, w));
3612 _mm256_cvtepi32_ps(_mm256_madd_epi16(p16, ones))
3613 };
3614 f0 = _mm256_fmadd_ps(dot(xs[0]), sv, f0);
3615 f1 = _mm256_fmadd_ps(dot(xs[1]), sv, f1);
3616 f2 = _mm256_fmadd_ps(dot(xs[2]), sv, f2);
3617 f3 = _mm256_fmadd_ps(dot(xs[3]), sv, f3);
3618 }
3619 [
3620 hsum256_ps(f0),
3621 hsum256_ps(f1),
3622 hsum256_ps(f2),
3623 hsum256_ps(f3),
3624 ]
3625 }
3626}
3627
3628#[cfg(target_arch = "x86_64")]
3631#[target_feature(enable = "avx2")]
3632#[inline]
3633unsafe fn hsum256_ps(v: core::arch::x86_64::__m256) -> f32 {
3634 unsafe {
3636 use core::arch::x86_64::*;
3637 let hi = _mm256_extractf128_ps::<1>(v);
3638 let s = _mm_add_ps(_mm256_castps256_ps128(v), hi);
3639 let s = _mm_add_ps(s, _mm_movehl_ps(s, s));
3640 let s = _mm_add_ss(s, _mm_shuffle_ps::<0x55>(s, s));
3641 _mm_cvtss_f32(s)
3642 }
3643}
3644
3645#[cfg(target_arch = "x86_64")]
3647#[target_feature(enable = "avx2,fma,avx512f,avx512bw,avx512vl,avx512vnni")]
3648unsafe fn dot_q4t_row_1x4_vnni(bytes: &[u8], r: usize, gpr: usize, xs: [&[i8]; 4]) -> [f32; 4] {
3649 unsafe {
3651 use core::arch::x86_64::*;
3652 let lomask = _mm_set1_epi8(0x0F);
3653 let eight = _mm256_set1_epi8(8);
3654 let mut f0 = _mm256_setzero_ps();
3657 let mut f1 = _mm256_setzero_ps();
3658 let mut f2 = _mm256_setzero_ps();
3659 let mut f3 = _mm256_setzero_ps();
3660 for gi in 0..gpr {
3661 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
3662 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3663 let sv = _mm256_set1_ps(s);
3664 let bb = _mm_loadu_si128(t.add(2) as *const __m128i);
3665 let lo = _mm_and_si128(bb, lomask);
3666 let hi = _mm_and_si128(_mm_srli_epi16::<4>(bb), lomask);
3667 let w = _mm256_sub_epi8(
3668 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
3669 eight,
3670 );
3671 let aw = _mm256_abs_epi8(w);
3672 let off = gi * GROUP_SIZE;
3673 let dot = |xq: &[i8]| {
3674 let x = _mm256_loadu_si256(xq.as_ptr().add(off) as *const __m256i);
3675 _mm256_cvtepi32_ps(_mm256_dpbusd_epi32(
3676 _mm256_setzero_si256(),
3677 aw,
3678 _mm256_sign_epi8(x, w),
3679 ))
3680 };
3681 f0 = _mm256_fmadd_ps(dot(xs[0]), sv, f0);
3682 f1 = _mm256_fmadd_ps(dot(xs[1]), sv, f1);
3683 f2 = _mm256_fmadd_ps(dot(xs[2]), sv, f2);
3684 f3 = _mm256_fmadd_ps(dot(xs[3]), sv, f3);
3685 }
3686 let acc = [
3687 hsum256_ps(f0),
3688 hsum256_ps(f1),
3689 hsum256_ps(f2),
3690 hsum256_ps(f3),
3691 ];
3692 acc
3693 }
3694}
3695
3696#[cfg(target_arch = "aarch64")]
3701#[target_feature(enable = "neon,dotprod")]
3702unsafe fn dot_q4t_row_1x4_sdot(bytes: &[u8], r: usize, gpr: usize, xs: [&[i8]; 4]) -> [f32; 4] {
3703 unsafe {
3705 use core::arch::aarch64::*;
3706 use core::arch::asm;
3707 let lomask = vdupq_n_u8(0x0F);
3708 let eight = vdupq_n_s8(8);
3709 let mut acc = [0f32; 4];
3710 for gi in 0..gpr {
3711 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
3712 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3713 let b = vld1q_u8(t.add(2));
3714 let lo = vandq_u8(b, lomask);
3715 let hi = vshrq_n_u8::<4>(b);
3716 let e0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(lo, hi)), eight);
3717 let e1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(lo, hi)), eight);
3718 for (k, xq) in xs.iter().enumerate() {
3719 let x0 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE));
3720 let x1 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE + 16));
3721 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
3722 asm!(
3723 "sdot {a0:v}.4s, {e0:v}.16b, {x0:v}.16b",
3724 "sdot {a1:v}.4s, {e1:v}.16b, {x1:v}.16b",
3725 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
3726 e0 = in(vreg) e0, x0 = in(vreg) x0, e1 = in(vreg) e1, x1 = in(vreg) x1,
3727 options(pure, nomem, nostack),
3728 );
3729 acc[k] += vaddvq_s32(vaddq_s32(a0, a1)) as f32 * s;
3730 }
3731 }
3732 acc
3733 }
3734}
3735
3736#[inline]
3738fn q4t_outlier(bytes: &[u8], r: usize, gpr: usize, j: usize) -> (f32, f32) {
3739 let gi = j / GROUP_SIZE;
3740 let k = j % GROUP_SIZE;
3741 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
3742 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
3743 let byte = tile[2 + k / 2];
3744 let nib = if k & 1 == 0 { byte & 0x0F } else { byte >> 4 };
3745 ((nib as i32 - 8) as f32, s)
3746}
3747
3748#[inline]
3751fn q4t_row_exact(bytes: &[u8], r: usize, gpr: usize, x: &[f32]) -> f32 {
3752 let mut acc = 0f32;
3753 for gi in 0..gpr {
3754 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
3755 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
3756 let xg = &x[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
3757 let mut ga = 0f32;
3758 for (k, &b) in tile[2..].iter().enumerate() {
3759 ga += ((b & 0x0F) as f32 - 8.0) * xg[k * 2]
3760 + (((b >> 4) & 0x0F) as f32 - 8.0) * xg[k * 2 + 1];
3761 }
3762 acc += ga * s;
3763 }
3764 acc
3765}
3766
3767struct Q4tpView<'a> {
3771 nib: &'a [u8],
3772 params: &'a [u8],
3773 codes: &'a [u8],
3774 stride: usize,
3775}
3776
3777impl<'a> Q4tpView<'a> {
3778 fn new(bytes: &'a [u8], rows: usize, cols: usize) -> Self {
3779 let (params_off, codes_off, stride) = q4tp_sections(rows, cols);
3780 Self {
3781 nib: &bytes[..params_off],
3782 params: &bytes[params_off..codes_off],
3783 codes: &bytes[codes_off..],
3784 stride,
3785 }
3786 }
3787
3788 #[inline]
3804 fn scales_into(&self, r: usize, gpr: usize, out: &mut [f32]) {
3805 let tab = q4tp_ladder(self.params, r);
3806 let codes = &self.codes[r * self.stride..(r + 1) * self.stride];
3807 let out = &mut out[..gpr];
3808 let mut chunks = out.chunks_exact_mut(8);
3809 let mut ci = 0usize;
3810 for c in &mut chunks {
3811 let w = u64::from(codes[ci])
3812 | u64::from(codes[ci + 1]) << 8
3813 | u64::from(codes[ci + 2]) << 16
3814 | u64::from(codes[ci + 3]) << 24
3815 | u64::from(codes[ci + 4]) << 32;
3816 for (k, o) in c.iter_mut().enumerate() {
3817 *o = tab[((w >> (5 * k)) & 31) as usize];
3818 }
3819 ci += 5;
3820 }
3821 let tail = &codes[ci..];
3824 for (k, o) in chunks.into_remainder().iter_mut().enumerate() {
3825 *o = tab[q4tp_code(tail, k)];
3826 }
3827 }
3828}
3829
3830#[inline]
3831fn dot_q4tp_row_i8(nib: &[u8], r: usize, gpr: usize, xq: &[i8], scales: &[f32]) -> f32 {
3832 #[cfg(target_arch = "aarch64")]
3833 unsafe {
3834 return dot_q4tp_row_sdot(nib, r, gpr, xq, scales);
3835 }
3836 #[cfg(target_arch = "x86_64")]
3837 unsafe {
3838 if vnni_tiles_enabled() {
3839 return dot_q4tp_row_vnni(nib, r, gpr, xq, scales);
3840 }
3841 return dot_q4tp_row_avx2(nib, r, gpr, xq, scales);
3842 }
3843 #[allow(unreachable_code)]
3844 {
3845 let mut acc = 0f32;
3846 for gi in 0..gpr {
3847 let tile = &nib[(r * gpr + gi) * Q4TP_NIB..(r * gpr + gi + 1) * Q4TP_NIB];
3848 let s = scales[gi];
3849 let mut d = 0i32;
3850 for (k, &b) in tile.iter().enumerate() {
3851 d += ((b & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2] as i32
3852 + (((b >> 4) & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2 + 1] as i32;
3853 }
3854 acc += d as f32 * s;
3855 }
3856 acc
3857 }
3858}
3859
3860#[cfg(target_arch = "aarch64")]
3863#[target_feature(enable = "neon,dotprod")]
3864unsafe fn dot_q4tp_row_sdot(
3865 nib: &[u8],
3866 r: usize,
3867 gpr: usize,
3868 xq: &[i8],
3869 scales: &[f32],
3870) -> f32 {
3871 unsafe {
3874 use core::arch::aarch64::*;
3875 use core::arch::asm;
3876 let lomask = vdupq_n_u8(0x0F);
3877 let eight = vdupq_n_s8(8);
3878 let mut acc = 0f32;
3879 for gi in 0..gpr {
3880 let t = nib.as_ptr().add((r * gpr + gi) * Q4TP_NIB);
3881 let s = *scales.get_unchecked(gi);
3882 let b = vld1q_u8(t);
3883 let lo = vandq_u8(b, lomask);
3884 let hi = vshrq_n_u8::<4>(b);
3885 let e0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(lo, hi)), eight);
3886 let e1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(lo, hi)), eight);
3887 let x0 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE));
3888 let x1 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE + 16));
3889 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
3890 asm!(
3891 "sdot {a0:v}.4s, {e0:v}.16b, {x0:v}.16b",
3892 "sdot {a1:v}.4s, {e1:v}.16b, {x1:v}.16b",
3893 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
3894 e0 = in(vreg) e0, x0 = in(vreg) x0, e1 = in(vreg) e1, x1 = in(vreg) x1,
3895 options(pure, nomem, nostack),
3896 );
3897 acc += vaddvq_s32(vaddq_s32(a0, a1)) as f32 * s;
3898 }
3899 acc
3900 }
3901}
3902
3903#[cfg(target_arch = "x86_64")]
3904#[target_feature(enable = "avx2")]
3905unsafe fn dot_q4tp_row_avx2(
3906 nib: &[u8],
3907 r: usize,
3908 gpr: usize,
3909 xq: &[i8],
3910 scales: &[f32],
3911) -> f32 {
3912 unsafe {
3914 use core::arch::x86_64::*;
3915 let lomask = _mm_set1_epi8(0x0F);
3916 let eight = _mm256_set1_epi8(8);
3917 let ones = _mm256_set1_epi16(1);
3918 let mut acc = 0f32;
3919 for gi in 0..gpr {
3920 let t = nib.as_ptr().add((r * gpr + gi) * Q4TP_NIB);
3921 let s = *scales.get_unchecked(gi);
3922 let b = _mm_loadu_si128(t as *const __m128i);
3923 let lo = _mm_and_si128(b, lomask);
3924 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), lomask);
3925 let w = _mm256_sub_epi8(
3926 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
3927 eight,
3928 );
3929 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
3930 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(w), _mm256_sign_epi8(x, w));
3931 let d = _mm256_madd_epi16(p16, ones);
3932 let hi128 = _mm256_extracti128_si256::<1>(d);
3933 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
3934 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
3935 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
3936 acc += _mm_cvtsi128_si32(s32) as f32 * s;
3937 }
3938 acc
3939 }
3940}
3941
3942#[cfg(target_arch = "x86_64")]
3945#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
3946unsafe fn dot_q4tp_row_vnni(
3947 nib: &[u8],
3948 r: usize,
3949 gpr: usize,
3950 xq: &[i8],
3951 scales: &[f32],
3952) -> f32 {
3953 unsafe {
3955 use core::arch::x86_64::*;
3956 let lomask = _mm_set1_epi8(0x0F);
3957 let eight = _mm256_set1_epi8(8);
3958 let mut acc = 0f32;
3959 for gi in 0..gpr {
3960 let t = nib.as_ptr().add((r * gpr + gi) * Q4TP_NIB);
3961 let s = *scales.get_unchecked(gi);
3962 let b = _mm_loadu_si128(t as *const __m128i);
3963 let lo = _mm_and_si128(b, lomask);
3964 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), lomask);
3965 let w = _mm256_sub_epi8(
3966 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
3967 eight,
3968 );
3969 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
3970 acc += dpbusd_hsum(_mm256_abs_epi8(w), _mm256_sign_epi8(x, w)) as f32 * s;
3971 }
3972 acc
3973 }
3974}
3975
3976#[inline]
3979fn q4tp_row_exact(nib: &[u8], r: usize, gpr: usize, x: &[f32], scales: &[f32]) -> f32 {
3980 let mut acc = 0f32;
3981 for gi in 0..gpr {
3982 let tile = &nib[(r * gpr + gi) * Q4TP_NIB..(r * gpr + gi + 1) * Q4TP_NIB];
3983 let s = scales[gi];
3984 let xg = &x[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
3985 let mut ga = 0f32;
3986 for (k, &b) in tile.iter().enumerate() {
3987 ga += ((b & 0x0F) as f32 - 8.0) * xg[k * 2]
3988 + (((b >> 4) & 0x0F) as f32 - 8.0) * xg[k * 2 + 1];
3989 }
3990 acc += ga * s;
3991 }
3992 acc
3993}
3994
3995#[inline]
3998fn q4tp_outlier(nib: &[u8], r: usize, gpr: usize, j: usize, scales: &[f32]) -> (f32, f32) {
3999 let (gi, k) = (j / GROUP_SIZE, j % GROUP_SIZE);
4000 let byte = nib[(r * gpr + gi) * Q4TP_NIB + k / 2];
4001 let n = if k & 1 == 0 { byte & 0x0F } else { byte >> 4 };
4002 ((n as i32 - 8) as f32, scales[gi])
4003}
4004
4005fn q4tp_matvec(
4007 bytes: &[u8],
4008 x: &[f32],
4009 rows: usize,
4010 cols: usize,
4011 out: &mut [f32],
4012 pool: Option<&Pool>,
4013) {
4014 debug_assert_eq!(out.len(), rows);
4015 let gpr = cols / GROUP_SIZE;
4016 let v = Q4tpView::new(bytes, rows, cols);
4017 let out_addr = SendMut(out.as_mut_ptr());
4018 if a8w8_enabled() {
4019 let act = split_act(x);
4020 let run = |start: usize, end: usize| {
4021 let mut sc = vec![0f32; gpr];
4023 for r in start..end {
4024 v.scales_into(r, gpr, &mut sc);
4025 let mut acc = dot_q4tp_row_i8(v.nib, r, gpr, &act.xq, &sc) * act.sx;
4026 for &(j, xv) in &act.outliers {
4027 let (w, s) = q4tp_outlier(v.nib, r, gpr, j, &sc);
4028 acc += w * s * xv;
4029 }
4030 unsafe { *out_addr.at(r) = acc };
4032 }
4033 };
4034 dispatch_rows(pool, rows, &run);
4035 return;
4036 }
4037 let run = |start: usize, end: usize| {
4038 let mut sc = vec![0f32; gpr];
4039 for r in start..end {
4040 v.scales_into(r, gpr, &mut sc);
4041 unsafe { *out_addr.at(r) = q4tp_row_exact(v.nib, r, gpr, x, &sc) };
4043 }
4044 };
4045 dispatch_rows(pool, rows, &run);
4046}
4047
4048#[allow(clippy::too_many_arguments)]
4051fn q4tp_matvec2(
4052 bytes: &[u8],
4053 x1: &[f32],
4054 x2: &[f32],
4055 rows: usize,
4056 cols: usize,
4057 o1: &mut [f32],
4058 o2: &mut [f32],
4059 pool: Option<&Pool>,
4060) {
4061 let gpr = cols / GROUP_SIZE;
4062 let v = Q4tpView::new(bytes, rows, cols);
4063 let (p1, p2) = (SendMut(o1.as_mut_ptr()), SendMut(o2.as_mut_ptr()));
4064 let run = |start: usize, end: usize| {
4065 let mut sc = vec![0f32; gpr];
4066 for r in start..end {
4067 v.scales_into(r, gpr, &mut sc);
4068 unsafe {
4070 *p1.at(r) = q4tp_row_exact(v.nib, r, gpr, x1, &sc);
4071 *p2.at(r) = q4tp_row_exact(v.nib, r, gpr, x2, &sc);
4072 }
4073 }
4074 };
4075 dispatch_rows(pool, rows, &run);
4076}
4077
4078#[cfg(target_arch = "aarch64")]
4082#[target_feature(enable = "neon,dotprod")]
4083unsafe fn dot_q4tp_row_1x4_sdot(
4084 nib: &[u8],
4085 r: usize,
4086 gpr: usize,
4087 xs: [&[i8]; 4],
4088 scales: &[f32],
4089) -> [f32; 4] {
4090 unsafe {
4092 use core::arch::aarch64::*;
4093 use core::arch::asm;
4094 let lomask = vdupq_n_u8(0x0F);
4095 let eight = vdupq_n_s8(8);
4096 let (mut f0, mut f1, mut f2, mut f3) = (0f32, 0f32, 0f32, 0f32);
4100 for gi in 0..gpr {
4101 let t = nib.as_ptr().add((r * gpr + gi) * Q4TP_NIB);
4102 let s = *scales.get_unchecked(gi);
4103 let bb = vld1q_u8(t);
4104 let lo = vandq_u8(bb, lomask);
4105 let hi = vshrq_n_u8::<4>(bb);
4106 let e0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(lo, hi)), eight);
4107 let e1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(lo, hi)), eight);
4108 let mut d = [0f32; 4];
4109 for (k, dk) in d.iter_mut().enumerate() {
4110 let x0 = vld1q_s8(xs[k].as_ptr().add(gi * GROUP_SIZE));
4111 let x1 = vld1q_s8(xs[k].as_ptr().add(gi * GROUP_SIZE + 16));
4112 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
4113 asm!(
4114 "sdot {a0:v}.4s, {e0:v}.16b, {x0:v}.16b",
4115 "sdot {a1:v}.4s, {e1:v}.16b, {x1:v}.16b",
4116 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
4117 e0 = in(vreg) e0, x0 = in(vreg) x0, e1 = in(vreg) e1, x1 = in(vreg) x1,
4118 options(pure, nomem, nostack),
4119 );
4120 *dk = vaddvq_s32(vaddq_s32(a0, a1)) as f32 * s;
4121 }
4122 f0 += d[0];
4123 f1 += d[1];
4124 f2 += d[2];
4125 f3 += d[3];
4126 }
4127 [f0, f1, f2, f3]
4128 }
4129}
4130
4131fn q4tp_matmat(
4135 bytes: &[u8],
4136 xs_all: &[f32],
4137 b: usize,
4138 rows: usize,
4139 cols: usize,
4140 out: &mut [f32],
4141 pool: Option<&Pool>,
4142) {
4143 debug_assert_eq!(out.len(), b * rows);
4144 let gpr = cols / GROUP_SIZE;
4145 let v = Q4tpView::new(bytes, rows, cols);
4146
4147 #[cfg(target_os = "macos")]
4149 if b >= 8 && rows * cols >= 500_000 && accel_gemm_enabled() {
4150 dequant_matmat_accel(
4151 &|r, dst| {
4152 let mut sc = [0f32; 32];
4153 let mut scv;
4154 let s: &[f32] = if gpr <= 32 {
4155 v.scales_into(r, gpr, &mut sc);
4156 &sc[..gpr]
4157 } else {
4158 scv = vec![0f32; gpr];
4159 v.scales_into(r, gpr, &mut scv);
4160 &scv
4161 };
4162 for gi in 0..gpr {
4163 let tile = &v.nib[(r * gpr + gi) * Q4TP_NIB..(r * gpr + gi + 1) * Q4TP_NIB];
4164 for (k, &bb) in tile.iter().enumerate() {
4165 dst[gi * GROUP_SIZE + k * 2] = ((bb & 0x0F) as f32 - 8.0) * s[gi];
4166 dst[gi * GROUP_SIZE + k * 2 + 1] = (((bb >> 4) & 0x0F) as f32 - 8.0) * s[gi];
4167 }
4168 }
4169 },
4170 xs_all,
4171 b,
4172 rows,
4173 cols,
4174 out,
4175 pool,
4176 );
4177 return;
4178 }
4179
4180 let out_addr = SendMut(out.as_mut_ptr());
4181 if a8w8_enabled() {
4182 let acts: Vec<SplitAct> = (0..b)
4183 .map(|bi| split_act(&xs_all[bi * cols..(bi + 1) * cols]))
4184 .collect();
4185 let acts = &acts;
4186 #[cfg(target_arch = "aarch64")]
4187 let blocked_ok = sdot_enabled()
4188 && std::env::var("CMF_X86_BLOCKED")
4189 .map(|val| val != "0")
4190 .unwrap_or(true);
4191 #[cfg(not(target_arch = "aarch64"))]
4192 let blocked_ok = false;
4193 let run = |start: usize, end: usize| {
4194 let mut sc = vec![0f32; gpr];
4195 for r in start..end {
4196 v.scales_into(r, gpr, &mut sc);
4197 let mut bi = 0usize;
4198 #[cfg(target_arch = "aarch64")]
4199 if blocked_ok {
4200 while bi + 4 <= acts.len() {
4201 let xs = [
4202 acts[bi].xq.as_slice(),
4203 acts[bi + 1].xq.as_slice(),
4204 acts[bi + 2].xq.as_slice(),
4205 acts[bi + 3].xq.as_slice(),
4206 ];
4207 let d = unsafe { dot_q4tp_row_1x4_sdot(v.nib, r, gpr, xs, &sc) };
4208 for k in 0..4 {
4209 let act = &acts[bi + k];
4210 let mut acc = d[k] * act.sx;
4211 for &(j, xv) in &act.outliers {
4212 let (w, s) = q4tp_outlier(v.nib, r, gpr, j, &sc);
4213 acc += w * s * xv;
4214 }
4215 unsafe { *out_addr.at((bi + k) * rows + r) = acc };
4217 }
4218 bi += 4;
4219 }
4220 }
4221 let _ = blocked_ok;
4222 while bi < acts.len() {
4223 let act = &acts[bi];
4224 let mut acc = dot_q4tp_row_i8(v.nib, r, gpr, &act.xq, &sc) * act.sx;
4225 for &(j, xv) in &act.outliers {
4226 let (w, s) = q4tp_outlier(v.nib, r, gpr, j, &sc);
4227 acc += w * s * xv;
4228 }
4229 unsafe { *out_addr.at(bi * rows + r) = acc };
4231 bi += 1;
4232 }
4233 }
4234 };
4235 dispatch_rows(pool, rows, &run);
4236 return;
4237 }
4238
4239 let run = |start: usize, end: usize| {
4240 let mut sc = vec![0f32; gpr];
4241 for r in start..end {
4242 v.scales_into(r, gpr, &mut sc);
4243 for bi in 0..b {
4244 let x = &xs_all[bi * cols..(bi + 1) * cols];
4245 unsafe { *out_addr.at(bi * rows + r) = q4tp_row_exact(v.nib, r, gpr, x, &sc) };
4247 }
4248 }
4249 };
4250 dispatch_rows(pool, rows, &run);
4251}
4252
4253fn q4t_matvec(
4255 bytes: &[u8],
4256 x: &[f32],
4257 rows: usize,
4258 cols: usize,
4259 out: &mut [f32],
4260 pool: Option<&Pool>,
4261) {
4262 debug_assert_eq!(out.len(), rows);
4263 let gpr = cols / GROUP_SIZE;
4264 let out_addr = SendMut(out.as_mut_ptr());
4265 if a8w8_enabled() {
4266 let act = split_act(x);
4267 let run = move |start: usize, end: usize| {
4268 for r in start..end {
4269 let mut acc = dot_q4t_row_i8(bytes, r, gpr, &act.xq) * act.sx;
4270 for &(j, xv) in &act.outliers {
4271 let (w, s) = q4t_outlier(bytes, r, gpr, j);
4272 acc += w * s * xv;
4273 }
4274 unsafe { *out_addr.at(r) = acc };
4276 }
4277 };
4278 dispatch_rows(pool, rows, &run);
4279 return;
4280 }
4281 let run = move |start: usize, end: usize| {
4282 for r in start..end {
4283 unsafe { *out_addr.at(r) = q4t_row_exact(bytes, r, gpr, x) };
4285 }
4286 };
4287 dispatch_rows(pool, rows, &run);
4288}
4289
4290#[allow(clippy::too_many_arguments)]
4292fn q4t_matvec2(
4293 bytes: &[u8],
4294 x1: &[f32],
4295 x2: &[f32],
4296 rows: usize,
4297 cols: usize,
4298 o1: &mut [f32],
4299 o2: &mut [f32],
4300 pool: Option<&Pool>,
4301) {
4302 let gpr = cols / GROUP_SIZE;
4303 let p1 = SendMut(o1.as_mut_ptr());
4304 let p2 = SendMut(o2.as_mut_ptr());
4305 if a8w8_enabled() {
4306 let a1 = split_act(x1);
4307 let a2 = split_act(x2);
4308 let run = move |start: usize, end: usize| {
4309 for r in start..end {
4310 let mut v1 = dot_q4t_row_i8(bytes, r, gpr, &a1.xq) * a1.sx;
4311 let mut v2 = dot_q4t_row_i8(bytes, r, gpr, &a2.xq) * a2.sx;
4312 for &(j, xv) in &a1.outliers {
4313 let (w, s) = q4t_outlier(bytes, r, gpr, j);
4314 v1 += w * s * xv;
4315 }
4316 for &(j, xv) in &a2.outliers {
4317 let (w, s) = q4t_outlier(bytes, r, gpr, j);
4318 v2 += w * s * xv;
4319 }
4320 unsafe {
4322 *p1.at(r) = v1;
4323 *p2.at(r) = v2;
4324 }
4325 }
4326 };
4327 dispatch_rows(pool, rows, &run);
4328 return;
4329 }
4330 let run = move |start: usize, end: usize| {
4331 for r in start..end {
4332 unsafe {
4334 *p1.at(r) = q4t_row_exact(bytes, r, gpr, x1);
4335 *p2.at(r) = q4t_row_exact(bytes, r, gpr, x2);
4336 }
4337 }
4338 };
4339 dispatch_rows(pool, rows, &run);
4340}
4341
4342#[allow(clippy::too_many_arguments)]
4344#[cfg(target_os = "macos")]
4350fn dequant_matmat_accel(
4351 dequant_row: &(dyn Fn(usize, &mut [f32]) + Sync),
4352 xs_all: &[f32],
4353 b: usize,
4354 rows: usize,
4355 cols: usize,
4356 out: &mut [f32],
4357 pool: Option<&Pool>,
4358) {
4359 const TR: usize = 2048;
4360 thread_local! {
4361 static WTILE: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
4362 }
4363 WTILE.with(|wt| {
4364 let mut wtile = wt.borrow_mut();
4365 wtile.resize(TR * cols, 0.0);
4366 let mut r0 = 0usize;
4367 while r0 < rows {
4368 let tr = TR.min(rows - r0);
4369 let wt_addr = SendMut(wtile.as_mut_ptr());
4370 let run = |start: usize, end: usize| {
4371 for r in start..end {
4372 let dst = unsafe { std::slice::from_raw_parts_mut(wt_addr.at(r * cols), cols) };
4374 dequant_row(r0 + r, dst);
4375 }
4376 };
4377 dispatch_rows(pool, tr, &run);
4378 unsafe {
4379 accel_blas::cblas_sgemm(
4380 101, 111, 112, b as i32,
4384 tr as i32,
4385 cols as i32,
4386 1.0,
4387 xs_all.as_ptr(),
4388 cols as i32,
4389 wtile.as_ptr(),
4390 cols as i32,
4391 0.0,
4392 out.as_mut_ptr().add(r0),
4393 rows as i32,
4394 );
4395 }
4396 r0 += tr;
4397 }
4398 });
4399}
4400
4401fn q4t_matmat(
4402 bytes: &[u8],
4403 xs_all: &[f32],
4404 b: usize,
4405 rows: usize,
4406 cols: usize,
4407 out: &mut [f32],
4408 pool: Option<&Pool>,
4409) {
4410 debug_assert_eq!(out.len(), b * rows);
4411 let gpr = cols / GROUP_SIZE;
4412 #[cfg(target_os = "macos")]
4416 if b >= 8 && rows * cols >= 500_000 && accel_gemm_enabled() {
4417 dequant_matmat_accel(
4418 &|r, dst| {
4419 for gi in 0..gpr {
4420 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
4421 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
4422 for (k, &bb) in tile[2..].iter().enumerate() {
4423 dst[gi * GROUP_SIZE + k * 2] = ((bb & 0x0F) as f32 - 8.0) * s;
4424 dst[gi * GROUP_SIZE + k * 2 + 1] = (((bb >> 4) & 0x0F) as f32 - 8.0) * s;
4425 }
4426 }
4427 },
4428 xs_all,
4429 b,
4430 rows,
4431 cols,
4432 out,
4433 pool,
4434 );
4435 return;
4436 }
4437 let out_addr = SendMut(out.as_mut_ptr());
4438 if a8w8_enabled() {
4439 let acts: Vec<SplitAct> = (0..b)
4440 .map(|bi| split_act(&xs_all[bi * cols..(bi + 1) * cols]))
4441 .collect();
4442 let acts = &acts;
4443 #[cfg(target_arch = "x86_64")]
4444 let blocked_ok = avx2_enabled()
4445 && std::env::var("CMF_X86_BLOCKED")
4446 .map(|v| v != "0")
4447 .unwrap_or(true);
4448 #[cfg(target_arch = "aarch64")]
4449 let blocked_ok = sdot_enabled()
4450 && std::env::var("CMF_X86_BLOCKED")
4451 .map(|v| v != "0")
4452 .unwrap_or(true);
4453 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
4454 let blocked_ok = false;
4455 let run = move |start: usize, end: usize| {
4456 for r in start..end {
4457 let mut bi = 0usize;
4458 #[cfg(target_arch = "aarch64")]
4459 if blocked_ok {
4460 while bi + 4 <= acts.len() {
4461 let xs = [
4462 acts[bi].xq.as_slice(),
4463 acts[bi + 1].xq.as_slice(),
4464 acts[bi + 2].xq.as_slice(),
4465 acts[bi + 3].xq.as_slice(),
4466 ];
4467 let d = unsafe { dot_q4t_row_1x4_sdot(bytes, r, gpr, xs) };
4468 for k in 0..4 {
4469 let act = &acts[bi + k];
4470 let mut acc = d[k] * act.sx;
4471 for &(j, xv) in &act.outliers {
4472 let (w, sc) = q4t_outlier(bytes, r, gpr, j);
4473 acc += w * sc * xv;
4474 }
4475 unsafe { *out_addr.at((bi + k) * rows + r) = acc };
4477 }
4478 bi += 4;
4479 }
4480 }
4481 #[cfg(target_arch = "x86_64")]
4482 if blocked_ok {
4483 while bi + 4 <= acts.len() {
4484 let xs = [
4485 acts[bi].xq.as_slice(),
4486 acts[bi + 1].xq.as_slice(),
4487 acts[bi + 2].xq.as_slice(),
4488 acts[bi + 3].xq.as_slice(),
4489 ];
4490 let d = unsafe {
4491 if vnni_tiles_enabled() {
4492 dot_q4t_row_1x4_vnni(bytes, r, gpr, xs)
4493 } else {
4494 dot_q4t_row_1x4_avx2(bytes, r, gpr, xs)
4495 }
4496 };
4497 for k in 0..4 {
4498 let act = &acts[bi + k];
4499 let mut acc = d[k] * act.sx;
4500 for &(j, xv) in &act.outliers {
4501 let (w, sc) = q4t_outlier(bytes, r, gpr, j);
4502 acc += w * sc * xv;
4503 }
4504 unsafe { *out_addr.at((bi + k) * rows + r) = acc };
4506 }
4507 bi += 4;
4508 }
4509 }
4510 let _ = blocked_ok;
4511 while bi < acts.len() {
4512 let act = &acts[bi];
4513 let mut acc = dot_q4t_row_i8(bytes, r, gpr, &act.xq) * act.sx;
4514 for &(j, xv) in &act.outliers {
4515 let (w, s) = q4t_outlier(bytes, r, gpr, j);
4516 acc += w * s * xv;
4517 }
4518 unsafe { *out_addr.at(bi * rows + r) = acc };
4520 bi += 1;
4521 }
4522 }
4523 };
4524 dispatch_rows(pool, rows, &run);
4525 return;
4526 }
4527 let run = move |start: usize, end: usize| {
4528 for r in start..end {
4529 for bi in 0..b {
4530 let x = &xs_all[bi * cols..(bi + 1) * cols];
4531 unsafe { *out_addr.at(bi * rows + r) = q4t_row_exact(bytes, r, gpr, x) };
4533 }
4534 }
4535 };
4536 dispatch_rows(pool, rows, &run);
4537}
4538
4539fn q1_group_sums(xq: &[i8], gpr: usize) -> Vec<i32> {
4548 (0..gpr)
4549 .map(|gi| {
4550 xq[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE]
4551 .iter()
4552 .map(|&v| v as i32)
4553 .sum()
4554 })
4555 .collect()
4556}
4557
4558#[inline]
4562#[allow(unreachable_code)]
4563#[cfg(target_arch = "x86_64")]
4568#[target_feature(enable = "avx2")]
4569unsafe fn dot_q1_row_avx2(bytes: &[u8], r: usize, gpr: usize, xq: &[i8], gsum: &[i32]) -> f32 {
4570 unsafe {
4572 use core::arch::x86_64::*;
4573 let expand = _mm256_setr_epi8(
4575 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,
4576 3, 3, 3,
4577 );
4578 let bitsel = _mm256_setr_epi8(
4579 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64,
4580 -128, 1, 2, 4, 8, 16, 32, 64, -128,
4581 );
4582 let ones8 = _mm256_set1_epi8(1);
4583 let ones16 = _mm256_set1_epi16(1);
4584 let mut acc = 0f32;
4585 for gi in 0..gpr {
4586 let t = bytes.as_ptr().add((r * gpr + gi) * Q1_TILE);
4587 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
4588 let bits = u32::from_le_bytes([*t.add(2), *t.add(3), *t.add(4), *t.add(5)]);
4589 let bc = _mm256_shuffle_epi8(_mm256_set1_epi32(bits as i32), expand);
4590 let mask = _mm256_cmpeq_epi8(_mm256_and_si256(bc, bitsel), bitsel);
4591 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
4592 let sel = _mm256_and_si256(x, mask);
4593 let p16 = _mm256_maddubs_epi16(ones8, sel);
4595 let d32 = _mm256_madd_epi16(p16, ones16);
4596 let hi128 = _mm256_extracti128_si256::<1>(d32);
4597 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d32), hi128);
4598 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
4599 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
4600 let msum = _mm_cvtsi128_si32(s32);
4601 let d = 2 * msum - gsum[gi];
4604 acc += d as f32 * s;
4605 }
4606 acc
4607 }
4608}
4609
4610#[cfg(target_arch = "x86_64")]
4613#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
4614unsafe fn dot_q1_row_vnni(bytes: &[u8], r: usize, gpr: usize, xq: &[i8], gsum: &[i32]) -> f32 {
4615 unsafe {
4617 use core::arch::x86_64::*;
4618 let expand = _mm256_setr_epi8(
4619 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,
4620 3, 3, 3,
4621 );
4622 let bitsel = _mm256_setr_epi8(
4623 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64,
4624 -128, 1, 2, 4, 8, 16, 32, 64, -128,
4625 );
4626 let ones8 = _mm256_set1_epi8(1);
4627 let mut acc = 0f32;
4628 for gi in 0..gpr {
4629 let t = bytes.as_ptr().add((r * gpr + gi) * Q1_TILE);
4630 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
4631 let bits = u32::from_le_bytes([*t.add(2), *t.add(3), *t.add(4), *t.add(5)]);
4632 let bc = _mm256_shuffle_epi8(_mm256_set1_epi32(bits as i32), expand);
4633 let mask = _mm256_cmpeq_epi8(_mm256_and_si256(bc, bitsel), bitsel);
4634 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
4635 let msum = dpbusd_hsum(ones8, _mm256_and_si256(x, mask));
4636 let d = 2 * msum - gsum[gi];
4637 acc += d as f32 * s;
4638 }
4639 acc
4640 }
4641}
4642
4643#[cfg(target_arch = "x86_64")]
4645#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
4646unsafe fn dot_q1_row_1x4_vnni(
4647 bytes: &[u8],
4648 r: usize,
4649 gpr: usize,
4650 xs: [&[i8]; 4],
4651 gsums: [&[i32]; 4],
4652) -> [f32; 4] {
4653 unsafe {
4655 use core::arch::x86_64::*;
4656 let expand = _mm256_setr_epi8(
4657 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,
4658 3, 3, 3,
4659 );
4660 let bitsel = _mm256_setr_epi8(
4661 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64,
4662 -128, 1, 2, 4, 8, 16, 32, 64, -128,
4663 );
4664 let ones8 = _mm256_set1_epi8(1);
4665 let mut acc = [0f32; 4];
4666 for gi in 0..gpr {
4667 let t = bytes.as_ptr().add((r * gpr + gi) * Q1_TILE);
4668 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
4669 let bits = u32::from_le_bytes([*t.add(2), *t.add(3), *t.add(4), *t.add(5)]);
4670 let bc = _mm256_shuffle_epi8(_mm256_set1_epi32(bits as i32), expand);
4671 let mask = _mm256_cmpeq_epi8(_mm256_and_si256(bc, bitsel), bitsel);
4672 for (k, xq) in xs.iter().enumerate() {
4673 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
4674 let msum = dpbusd_hsum(ones8, _mm256_and_si256(x, mask));
4675 let d = 2 * msum - gsums[k][gi];
4676 acc[k] += d as f32 * s;
4677 }
4678 }
4679 acc
4680 }
4681}
4682
4683#[cfg(target_arch = "x86_64")]
4686#[target_feature(enable = "avx2")]
4687unsafe fn dot_q1_row_1x4_avx2(
4688 bytes: &[u8],
4689 r: usize,
4690 gpr: usize,
4691 xs: [&[i8]; 4],
4692 gsums: [&[i32]; 4],
4693) -> [f32; 4] {
4694 unsafe {
4696 use core::arch::x86_64::*;
4697 let expand = _mm256_setr_epi8(
4698 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,
4699 3, 3, 3,
4700 );
4701 let bitsel = _mm256_setr_epi8(
4702 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64,
4703 -128, 1, 2, 4, 8, 16, 32, 64, -128,
4704 );
4705 let ones8 = _mm256_set1_epi8(1);
4706 let ones16 = _mm256_set1_epi16(1);
4707 let mut acc = [0f32; 4];
4708 for gi in 0..gpr {
4709 let t = bytes.as_ptr().add((r * gpr + gi) * Q1_TILE);
4710 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
4711 let bits = u32::from_le_bytes([*t.add(2), *t.add(3), *t.add(4), *t.add(5)]);
4712 let bc = _mm256_shuffle_epi8(_mm256_set1_epi32(bits as i32), expand);
4713 let mask = _mm256_cmpeq_epi8(_mm256_and_si256(bc, bitsel), bitsel);
4714 for (k, xq) in xs.iter().enumerate() {
4715 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
4716 let sel = _mm256_and_si256(x, mask);
4717 let p16 = _mm256_maddubs_epi16(ones8, sel);
4718 let d32 = _mm256_madd_epi16(p16, ones16);
4719 let hi128 = _mm256_extracti128_si256::<1>(d32);
4720 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d32), hi128);
4721 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
4722 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
4723 let msum = _mm_cvtsi128_si32(s32);
4724 let d = 2 * msum - gsums[k][gi];
4725 acc[k] += d as f32 * s;
4726 }
4727 }
4728 acc
4729 }
4730}
4731
4732#[allow(unreachable_code)]
4733fn dot_q1_row_i8(bytes: &[u8], r: usize, gpr: usize, xq: &[i8], gsum: &[i32]) -> f32 {
4734 #[cfg(target_arch = "aarch64")]
4735 unsafe {
4736 return dot_q1_row_sdot(bytes, r, gpr, xq, gsum);
4737 }
4738 #[cfg(target_arch = "x86_64")]
4739 if avx2_enabled() {
4740 unsafe {
4741 if vnni_tiles_enabled() {
4742 return dot_q1_row_vnni(bytes, r, gpr, xq, gsum);
4743 }
4744 return dot_q1_row_avx2(bytes, r, gpr, xq, gsum);
4745 }
4746 }
4747 let _ = gsum;
4748 let mut acc = 0f32;
4749 for gi in 0..gpr {
4750 let tile = &bytes[(r * gpr + gi) * Q1_TILE..(r * gpr + gi + 1) * Q1_TILE];
4751 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
4752 let mut d = 0i32;
4753 for (j, &b) in tile[2..].iter().enumerate() {
4754 for k in 0..8 {
4755 let w = ((b >> k) & 1) as i32 * 2 - 1;
4756 d += w * xq[gi * GROUP_SIZE + j * 8 + k] as i32;
4757 }
4758 }
4759 acc += d as f32 * s;
4760 }
4761 acc
4762}
4763
4764#[cfg(target_arch = "aarch64")]
4773#[target_feature(enable = "neon,dotprod")]
4774unsafe fn dot_q1_row_sdot(bytes: &[u8], r: usize, gpr: usize, xq: &[i8], gsum: &[i32]) -> f32 {
4775 unsafe {
4778 use core::arch::aarch64::*;
4779 use core::arch::asm;
4780 const MASKS: [u8; 16] = [1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128];
4781 let m = vld1q_u8(MASKS.as_ptr());
4782 macro_rules! tile_dot {
4784 ($t:expr, $x:expr) => {{
4785 let v0 = vcombine_u8(vdup_n_u8(*$t.add(2)), vdup_n_u8(*$t.add(3)));
4786 let v1 = vcombine_u8(vdup_n_u8(*$t.add(4)), vdup_n_u8(*$t.add(5)));
4787 let w0 = vreinterpretq_s8_u8(vtstq_u8(v0, m));
4788 let w1 = vreinterpretq_s8_u8(vtstq_u8(v1, m));
4789 let x0 = vld1q_s8($x);
4790 let x1 = vld1q_s8($x.add(16));
4791 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
4792 asm!(
4793 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
4794 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
4795 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
4796 w0 = in(vreg) w0, x0 = in(vreg) x0, w1 = in(vreg) w1, x1 = in(vreg) x1,
4797 options(pure, nomem, nostack),
4798 );
4799 vaddq_s32(a0, a1)
4800 }};
4801 }
4802 const IW00: [u8; 16] = [2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3];
4811 const IW01: [u8; 16] = [4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5];
4812 const IW10: [u8; 16] = [8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9];
4813 const IW11: [u8; 16] = [
4814 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11,
4815 ];
4816 const ISC: [u8; 8] = [0, 1, 6, 7, 16, 17, 22, 23];
4817 let (iw00, iw01) = (vld1q_u8(IW00.as_ptr()), vld1q_u8(IW01.as_ptr()));
4818 let (iw10, iw11) = (vld1q_u8(IW10.as_ptr()), vld1q_u8(IW11.as_ptr()));
4819 let isc = vld1_u8(ISC.as_ptr());
4820 macro_rules! tile_dot_tbl {
4822 ($ld:expr, $i0:expr, $i1:expr, $x:expr) => {{
4823 let w0 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8($ld, $i0), m));
4824 let w1 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8($ld, $i1), m));
4825 let x0 = vld1q_s8($x);
4826 let x1 = vld1q_s8($x.add(16));
4827 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
4828 asm!(
4829 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
4830 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
4831 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
4832 w0 = in(vreg) w0, x0 = in(vreg) x0, w1 = in(vreg) w1, x1 = in(vreg) x1,
4833 options(pure, nomem, nostack),
4834 );
4835 vaddq_s32(a0, a1)
4836 }};
4837 }
4838 let base = bytes.as_ptr().add(r * gpr * Q1_TILE);
4839 let row_base = r * gpr * Q1_TILE;
4840 let abs_end = bytes.len();
4841 let xp = xq.as_ptr();
4842 let gp = gsum.as_ptr();
4843 let mut accv = vdupq_n_f32(0.0);
4844 let mut gi = 0;
4845 while gi + 4 <= gpr && row_base + (gi + 4) * Q1_TILE + 4 <= abs_end {
4848 let t0 = base.add(gi * Q1_TILE);
4849 let ld_a = vld1q_u8(t0);
4850 let ld_b = vld1q_u8(t0.add(2 * Q1_TILE));
4851 let d0 = tile_dot_tbl!(ld_a, iw00, iw01, xp.add(gi * GROUP_SIZE));
4852 let d1 = tile_dot_tbl!(ld_a, iw10, iw11, xp.add((gi + 1) * GROUP_SIZE));
4853 let d2 = tile_dot_tbl!(ld_b, iw00, iw01, xp.add((gi + 2) * GROUP_SIZE));
4854 let d3 = tile_dot_tbl!(ld_b, iw10, iw11, xp.add((gi + 3) * GROUP_SIZE));
4855 let neg = vpaddq_s32(vpaddq_s32(d0, d1), vpaddq_s32(d2, d3));
4857 let g = vld1q_s32(gp.add(gi));
4858 let dots = vnegq_s32(vaddq_s32(vshlq_n_s32::<1>(neg), g));
4859 let sc16 = vqtbl2_u8(uint8x16x2_t(ld_a, ld_b), isc);
4860 let scf: float32x4_t;
4861 asm!(
4862 "fcvtl {o:v}.4s, {i:v}.4h",
4863 o = out(vreg) scf, i = in(vreg) sc16,
4864 options(pure, nomem, nostack),
4865 );
4866 accv = vfmaq_f32(accv, vcvtq_f32_s32(dots), scf);
4867 gi += 4;
4868 }
4869 let mut acc = vaddvq_f32(accv);
4870 while gi < gpr {
4871 let t = base.add(gi * Q1_TILE);
4872 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
4873 let d = vaddvq_s32(tile_dot!(t, xp.add(gi * GROUP_SIZE)));
4874 acc += (-(2 * d + *gp.add(gi))) as f32 * s;
4875 gi += 1;
4876 }
4877 acc
4878 }
4879}
4880
4881#[cfg(target_arch = "aarch64")]
4886#[target_feature(enable = "neon,dotprod")]
4887unsafe fn dot_q1_row_1x4_sdot(
4888 bytes: &[u8],
4889 r: usize,
4890 gpr: usize,
4891 xs: [&[i8]; 4],
4892 gs: [&[i32]; 4],
4893) -> [f32; 4] {
4894 unsafe {
4896 use core::arch::aarch64::*;
4897 use core::arch::asm;
4898 const MASKS: [u8; 16] = [1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128];
4899 const IW00: [u8; 16] = [2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3];
4900 const IW01: [u8; 16] = [4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5];
4901 const IW10: [u8; 16] = [8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9];
4902 const IW11: [u8; 16] = [
4903 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11,
4904 ];
4905 const ISC: [u8; 8] = [0, 1, 6, 7, 16, 17, 22, 23];
4906 let m = vld1q_u8(MASKS.as_ptr());
4907 let (iw00, iw01) = (vld1q_u8(IW00.as_ptr()), vld1q_u8(IW01.as_ptr()));
4908 let (iw10, iw11) = (vld1q_u8(IW10.as_ptr()), vld1q_u8(IW11.as_ptr()));
4909 let isc = vld1_u8(ISC.as_ptr());
4910 macro_rules! sdot2 {
4911 ($w0:expr, $w1:expr, $x:expr) => {{
4912 let x0 = vld1q_s8($x);
4913 let x1 = vld1q_s8($x.add(16));
4914 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
4915 asm!(
4916 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
4917 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
4918 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
4919 w0 = in(vreg) $w0, x0 = in(vreg) x0, w1 = in(vreg) $w1, x1 = in(vreg) x1,
4920 options(pure, nomem, nostack),
4921 );
4922 vaddq_s32(a0, a1)
4923 }};
4924 }
4925 let base = bytes.as_ptr().add(r * gpr * Q1_TILE);
4926 let row_base = r * gpr * Q1_TILE;
4927 let abs_end = bytes.len();
4928 let mut accv = [vdupq_n_f32(0.0); 4];
4929 let mut gi = 0;
4930 while gi + 4 <= gpr && row_base + (gi + 4) * Q1_TILE + 4 <= abs_end {
4931 let t0 = base.add(gi * Q1_TILE);
4932 let ld_a = vld1q_u8(t0);
4933 let ld_b = vld1q_u8(t0.add(2 * Q1_TILE));
4934 let w00 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_a, iw00), m));
4936 let w01 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_a, iw01), m));
4937 let w10 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_a, iw10), m));
4938 let w11 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_a, iw11), m));
4939 let w20 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_b, iw00), m));
4940 let w21 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_b, iw01), m));
4941 let w30 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_b, iw10), m));
4942 let w31 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_b, iw11), m));
4943 let sc16 = vqtbl2_u8(uint8x16x2_t(ld_a, ld_b), isc);
4944 let scf: float32x4_t;
4945 asm!(
4946 "fcvtl {o:v}.4s, {i:v}.4h",
4947 o = out(vreg) scf, i = in(vreg) sc16,
4948 options(pure, nomem, nostack),
4949 );
4950 for k in 0..4 {
4951 let xp = xs[k].as_ptr();
4952 let d0 = sdot2!(w00, w01, xp.add(gi * GROUP_SIZE));
4953 let d1 = sdot2!(w10, w11, xp.add((gi + 1) * GROUP_SIZE));
4954 let d2 = sdot2!(w20, w21, xp.add((gi + 2) * GROUP_SIZE));
4955 let d3 = sdot2!(w30, w31, xp.add((gi + 3) * GROUP_SIZE));
4956 let neg = vpaddq_s32(vpaddq_s32(d0, d1), vpaddq_s32(d2, d3));
4957 let g = vld1q_s32(gs[k].as_ptr().add(gi));
4958 let dots = vnegq_s32(vaddq_s32(vshlq_n_s32::<1>(neg), g));
4959 accv[k] = vfmaq_f32(accv[k], vcvtq_f32_s32(dots), scf);
4960 }
4961 gi += 4;
4962 }
4963 let mut acc = [
4964 vaddvq_f32(accv[0]),
4965 vaddvq_f32(accv[1]),
4966 vaddvq_f32(accv[2]),
4967 vaddvq_f32(accv[3]),
4968 ];
4969 while gi < gpr {
4970 let t = base.add(gi * Q1_TILE);
4971 let sc = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
4972 let v0 = vcombine_u8(vdup_n_u8(*t.add(2)), vdup_n_u8(*t.add(3)));
4973 let v1 = vcombine_u8(vdup_n_u8(*t.add(4)), vdup_n_u8(*t.add(5)));
4974 let w0 = vreinterpretq_s8_u8(vtstq_u8(v0, m));
4975 let w1 = vreinterpretq_s8_u8(vtstq_u8(v1, m));
4976 for k in 0..4 {
4977 let d = vaddvq_s32(sdot2!(w0, w1, xs[k].as_ptr().add(gi * GROUP_SIZE)));
4978 acc[k] += (-(2 * d + *gs[k].as_ptr().add(gi))) as f32 * sc;
4979 }
4980 gi += 1;
4981 }
4982 acc
4983 }
4984}
4985
4986#[inline]
4988fn q1_outlier(bytes: &[u8], r: usize, gpr: usize, j: usize) -> (f32, f32) {
4989 let gi = j / GROUP_SIZE;
4990 let k = j % GROUP_SIZE;
4991 let tile = &bytes[(r * gpr + gi) * Q1_TILE..(r * gpr + gi + 1) * Q1_TILE];
4992 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
4993 let bit = (tile[2 + k / 8] >> (k % 8)) & 1;
4994 ((bit as i32 * 2 - 1) as f32, s)
4995}
4996
4997#[inline]
4999fn q1_row_exact(bytes: &[u8], r: usize, gpr: usize, x: &[f32]) -> f32 {
5000 let mut acc = 0f32;
5001 for gi in 0..gpr {
5002 let tile = &bytes[(r * gpr + gi) * Q1_TILE..(r * gpr + gi + 1) * Q1_TILE];
5003 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
5004 let xg = &x[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
5005 let mut ga = 0f32;
5006 for (j, &b) in tile[2..].iter().enumerate() {
5007 for k in 0..8 {
5008 ga += (((b >> k) & 1) as f32 * 2.0 - 1.0) * xg[j * 8 + k];
5009 }
5010 }
5011 acc += ga * s;
5012 }
5013 acc
5014}
5015
5016#[allow(clippy::too_many_arguments)]
5019fn q1_range_a8w8(
5020 bytes: &[u8],
5021 gpr: usize,
5022 act: &SplitAct,
5023 gsum: &[i32],
5024 out: SendMut,
5025 start: usize,
5026 end: usize,
5027) {
5028 for r in start..end {
5029 let mut acc = dot_q1_row_i8(bytes, r, gpr, &act.xq, gsum) * act.sx;
5030 for &(j, xv) in &act.outliers {
5031 let (w, s) = q1_outlier(bytes, r, gpr, j);
5032 acc += w * s * xv;
5033 }
5034 unsafe { *out.at(r) = acc };
5036 }
5037}
5038
5039fn q1_range_f32(bytes: &[u8], gpr: usize, x: &[f32], out: SendMut, start: usize, end: usize) {
5041 for r in start..end {
5042 unsafe { *out.at(r) = q1_row_exact(bytes, r, gpr, x) };
5044 }
5045}
5046
5047fn q1t_overlay(bytes: &[u8], base_len: usize, rows: usize) -> (usize, usize, bool) {
5052 let entries = base_len + (rows + 1) * 4;
5053 (base_len, entries, entries <= bytes.len())
5054}
5055
5056#[inline]
5058fn q1t_rowptr(bytes: &[u8], rp_off: usize, r: usize) -> usize {
5059 let o = rp_off + r * 4;
5060 u32::from_le_bytes([bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]]) as usize
5061}
5062
5063const SIGN5: [[f32; 5]; 256] = {
5067 let mut lut = [[0.0f32; 5]; 256];
5068 let pow3 = [1u16, 3, 9, 27, 81];
5069 let mut byte = 0usize;
5070 while byte < 256 {
5071 let mut i = 0usize;
5072 while i < 5 {
5073 let code = (byte as u16 / pow3[i]) % 3;
5074 lut[byte][i] = if code == 1 {
5075 1.0
5076 } else if code == 2 {
5077 -1.0
5078 } else {
5079 0.0
5080 };
5081 i += 1;
5082 }
5083 byte += 1;
5084 }
5085 lut
5086};
5087
5088const SIGN5_I8: [[i8; 5]; 256] = {
5090 let mut lut = [[0i8; 5]; 256];
5091 let pow3 = [1u16, 3, 9, 27, 81];
5092 let mut byte = 0usize;
5093 while byte < 256 {
5094 let mut i = 0usize;
5095 while i < 5 {
5096 let code = (byte as u16 / pow3[i]) % 3;
5097 lut[byte][i] = if code == 1 {
5098 1
5099 } else if code == 2 {
5100 -1
5101 } else {
5102 0
5103 };
5104 i += 1;
5105 }
5106 byte += 1;
5107 }
5108 lut
5109};
5110
5111const SIGN5_U64: [u64; 256] = {
5117 let mut lut = [0u64; 256];
5118 let pow3 = [1u16, 3, 9, 27, 81];
5119 let mut byte = 0usize;
5120 while byte < 256 {
5121 let mut v = 0u64;
5122 let mut i = 0usize;
5123 while i < 5 {
5124 let code = (byte as u16 / pow3[i]) % 3;
5125 let s: u8 = if code == 1 {
5126 1
5127 } else if code == 2 {
5128 0xFF
5129 } else {
5130 0
5131 };
5132 v |= (s as u64) << (i * 8);
5133 i += 1;
5134 }
5135 lut[byte] = v;
5136 byte += 1;
5137 }
5138 lut
5139};
5140
5141#[inline]
5146fn q1t_base_weight(bytes: &[u8], r: usize, gpr: usize, j: usize) -> f32 {
5147 const TILE: usize = cortiq_core::quant::Q1T_TILE;
5148 let off = (r * gpr + j / GROUP_SIZE) * TILE;
5149 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
5150 let within = j % GROUP_SIZE;
5151 SIGN5[bytes[off + 2 + within / 5] as usize][within % 5] * s
5152}
5153
5154#[cfg(target_arch = "aarch64")]
5157#[target_feature(enable = "neon,dotprod")]
5158#[inline]
5159unsafe fn sdot32_i8(w: *const i8, x: *const i8) -> i32 {
5160 unsafe {
5162 use core::arch::aarch64::*;
5163 use core::arch::asm;
5164 let w0 = vld1q_s8(w);
5165 let w1 = vld1q_s8(w.add(16));
5166 let x0 = vld1q_s8(x);
5167 let x1 = vld1q_s8(x.add(16));
5168 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
5169 asm!(
5170 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
5171 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
5172 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
5173 w0 = in(vreg) w0, x0 = in(vreg) x0, w1 = in(vreg) w1, x1 = in(vreg) x1,
5174 options(pure, nomem, nostack),
5175 );
5176 vaddvq_s32(vaddq_s32(a0, a1))
5177 }
5178}
5179
5180#[cfg(target_arch = "x86_64")]
5183#[target_feature(enable = "avx2")]
5184#[inline]
5185unsafe fn i8dot32_avx2(w: *const i8, x: *const i8) -> i32 {
5186 unsafe {
5188 use core::arch::x86_64::*;
5189 let wv = _mm256_loadu_si256(w as *const __m256i);
5190 let xv = _mm256_loadu_si256(x as *const __m256i);
5191 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(wv), _mm256_sign_epi8(xv, wv));
5192 let d = _mm256_madd_epi16(p16, _mm256_set1_epi16(1));
5193 let hi128 = _mm256_extracti128_si256::<1>(d);
5194 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
5195 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
5196 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
5197 _mm_cvtsi128_si32(s32)
5198 }
5199}
5200
5201#[inline]
5206fn q1t_unpack_group_i8(codes: *const u8, dst: &mut [i8]) {
5207 debug_assert!(dst.len() >= 40);
5208 unsafe {
5211 let p = dst.as_mut_ptr();
5212 for bi in 0..7 {
5213 core::ptr::write_unaligned(
5214 p.add(bi * 5) as *mut u64,
5215 SIGN5_U64[*codes.add(bi) as usize],
5216 );
5217 }
5218 }
5219}
5220
5221#[inline]
5226fn q1t_i8dot32(w: *const i8, x: *const i8) -> i32 {
5227 #[cfg(target_arch = "aarch64")]
5228 unsafe {
5229 return sdot32_i8(w, x);
5230 }
5231 #[cfg(target_arch = "x86_64")]
5232 unsafe {
5233 return i8dot32_avx2(w, x);
5234 }
5235 #[allow(unreachable_code)]
5236 unsafe {
5237 let mut s = 0i32;
5238 for k in 0..GROUP_SIZE {
5239 s += *w.add(k) as i32 * *x.add(k) as i32;
5240 }
5241 s
5242 }
5243}
5244
5245#[inline]
5246unsafe fn q1t_unpack_reg_u64s(codes: *const u8) -> (u64, u64, u64, u64) {
5247 let (s0, s1, s2, s3, s4, s5, s6) = unsafe {
5248 (
5249 SIGN5_U64[*codes as usize],
5250 SIGN5_U64[*codes.add(1) as usize],
5251 SIGN5_U64[*codes.add(2) as usize],
5252 SIGN5_U64[*codes.add(3) as usize],
5253 SIGN5_U64[*codes.add(4) as usize],
5254 SIGN5_U64[*codes.add(5) as usize],
5255 SIGN5_U64[*codes.add(6) as usize],
5256 )
5257 };
5258
5259 let u0 = s0 | (s1 << 40);
5260 let u1 = (s1 >> 24) | (s2 << 16) | (s3 << 56);
5261 let u2 = (s3 >> 8) | (s4 << 32);
5262 let u3 = (s4 >> 32) | (s5 << 8) | (s6 << 48);
5263
5264 (u0, u1, u2, u3)
5265}
5266
5267#[cfg(target_arch = "aarch64")]
5271#[target_feature(enable = "neon,dotprod")]
5272unsafe fn q1t_dot_row_sdot(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
5273 use core::arch::aarch64::*;
5274 use core::arch::asm;
5275 unsafe {
5276 const TILE: usize = cortiq_core::quant::Q1T_TILE;
5277 let mut acc = 0f32;
5278 let bytes_ptr = bytes.as_ptr();
5279 let xq_ptr = xq.as_ptr();
5280 let row_off = r * gpr * TILE;
5281
5282 let gpr2 = gpr & !1;
5283 let mut gi = 0;
5284 while gi < gpr2 {
5285 let off0 = row_off + gi * TILE;
5286 let off1 = off0 + TILE;
5287 let s0 = f16_to_f32(u16::from_le_bytes([
5288 *bytes_ptr.add(off0),
5289 *bytes_ptr.add(off0 + 1),
5290 ]));
5291 let s1 = f16_to_f32(u16::from_le_bytes([
5292 *bytes_ptr.add(off1),
5293 *bytes_ptr.add(off1 + 1),
5294 ]));
5295
5296 let (u0_0, u1_0, u2_0, u3_0) = q1t_unpack_reg_u64s(bytes_ptr.add(off0 + 2));
5297 let (u0_1, u1_1, u2_1, u3_1) = q1t_unpack_reg_u64s(bytes_ptr.add(off1 + 2));
5298
5299 let w0_0 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u0_0), vcreate_u64(u1_0)));
5300 let w1_0 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u2_0), vcreate_u64(u3_0)));
5301 let w0_1 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u0_1), vcreate_u64(u1_1)));
5302 let w1_1 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u2_1), vcreate_u64(u3_1)));
5303
5304 let x0_0 = vld1q_s8(xq_ptr.add(gi * GROUP_SIZE));
5305 let x1_0 = vld1q_s8(xq_ptr.add(gi * GROUP_SIZE + 16));
5306 let x0_1 = vld1q_s8(xq_ptr.add((gi + 1) * GROUP_SIZE));
5307 let x1_1 = vld1q_s8(xq_ptr.add((gi + 1) * GROUP_SIZE + 16));
5308
5309 let (mut a0_0, mut a1_0) = (vdupq_n_s32(0), vdupq_n_s32(0));
5310 let (mut a0_1, mut a1_1) = (vdupq_n_s32(0), vdupq_n_s32(0));
5311 asm!(
5312 "sdot {a0_0:v}.4s, {w0_0:v}.16b, {x0_0:v}.16b",
5313 "sdot {a1_0:v}.4s, {w1_0:v}.16b, {x1_0:v}.16b",
5314 "sdot {a0_1:v}.4s, {w0_1:v}.16b, {x0_1:v}.16b",
5315 "sdot {a1_1:v}.4s, {w1_1:v}.16b, {x1_1:v}.16b",
5316 a0_0 = inout(vreg) a0_0, a1_0 = inout(vreg) a1_0,
5317 a0_1 = inout(vreg) a0_1, a1_1 = inout(vreg) a1_1,
5318 w0_0 = in(vreg) w0_0, x0_0 = in(vreg) x0_0, w1_0 = in(vreg) w1_0, x1_0 = in(vreg) x1_0,
5319 w0_1 = in(vreg) w0_1, x0_1 = in(vreg) x0_1, w1_1 = in(vreg) w1_1, x1_1 = in(vreg) x1_1,
5320 options(pure, nomem, nostack),
5321 );
5322 let d0 = vaddvq_s32(vaddq_s32(a0_0, a1_0));
5323 let d1 = vaddvq_s32(vaddq_s32(a0_1, a1_1));
5324 acc += d0 as f32 * s0 + d1 as f32 * s1;
5325 gi += 2;
5326 }
5327
5328 if gi < gpr {
5329 let off = row_off + gi * TILE;
5330 let s = f16_to_f32(u16::from_le_bytes([
5331 *bytes_ptr.add(off),
5332 *bytes_ptr.add(off + 1),
5333 ]));
5334 let (u0, u1, u2, u3) = q1t_unpack_reg_u64s(bytes_ptr.add(off + 2));
5335 let w0 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u0), vcreate_u64(u1)));
5336 let w1 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u2), vcreate_u64(u3)));
5337 let x0 = vld1q_s8(xq_ptr.add(gi * GROUP_SIZE));
5338 let x1 = vld1q_s8(xq_ptr.add(gi * GROUP_SIZE + 16));
5339 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
5340 asm!(
5341 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
5342 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
5343 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
5344 w0 = in(vreg) w0, x0 = in(vreg) x0, w1 = in(vreg) w1, x1 = in(vreg) x1,
5345 options(pure, nomem, nostack),
5346 );
5347 let d = vaddvq_s32(vaddq_s32(a0, a1));
5348 acc += d as f32 * s;
5349 }
5350 acc
5351 }
5352}
5353
5354#[cfg(target_arch = "x86_64")]
5356#[target_feature(enable = "avx2")]
5357unsafe fn q1t_dot_row_avx2(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
5358 use core::arch::x86_64::*;
5359 unsafe {
5360 const TILE: usize = cortiq_core::quant::Q1T_TILE;
5361 let mut acc = 0f32;
5362 let bytes_ptr = bytes.as_ptr();
5363 let xq_ptr = xq.as_ptr();
5364 let row_off = r * gpr * TILE;
5365
5366 let ones = _mm256_set1_epi16(1);
5367 for gi in 0..gpr {
5368 let off = row_off + gi * TILE;
5369 let s = f16_to_f32(u16::from_le_bytes([
5370 *bytes_ptr.add(off),
5371 *bytes_ptr.add(off + 1),
5372 ]));
5373 let (u0, u1, u2, u3) = q1t_unpack_reg_u64s(bytes_ptr.add(off + 2));
5374 let wv = _mm256_set_epi64x(u3 as i64, u2 as i64, u1 as i64, u0 as i64);
5375 let xv = _mm256_loadu_si256(xq_ptr.add(gi * GROUP_SIZE) as *const __m256i);
5376 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(wv), _mm256_sign_epi8(xv, wv));
5377 let d256 = _mm256_madd_epi16(p16, ones);
5378 let d128 = _mm_add_epi32(
5379 _mm256_castsi256_si128(d256),
5380 _mm256_extracti128_si256(d256, 1),
5381 );
5382 let d64 = _mm_add_epi32(d128, _mm_shuffle_epi32(d128, 0xee));
5383 let d32 = _mm_cvtsi128_si32(_mm_add_epi32(d64, _mm_shuffle_epi32(d64, 0x55)));
5384 acc += d32 as f32 * s;
5385 }
5386 acc
5387 }
5388}
5389
5390#[cfg(target_arch = "x86_64")]
5392#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
5393unsafe fn q1t_dot_row_vnni(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
5394 use core::arch::x86_64::*;
5395 unsafe {
5397 const TILE: usize = cortiq_core::quant::Q1T_TILE;
5398 let mut acc = 0f32;
5399 let bytes_ptr = bytes.as_ptr();
5400 let xq_ptr = xq.as_ptr();
5401 let row_off = r * gpr * TILE;
5402 for gi in 0..gpr {
5403 let off = row_off + gi * TILE;
5404 let s = f16_to_f32(u16::from_le_bytes([
5405 *bytes_ptr.add(off),
5406 *bytes_ptr.add(off + 1),
5407 ]));
5408 let (u0, u1, u2, u3) = q1t_unpack_reg_u64s(bytes_ptr.add(off + 2));
5409 let wv = _mm256_set_epi64x(u3 as i64, u2 as i64, u1 as i64, u0 as i64);
5410 let xv = _mm256_loadu_si256(xq_ptr.add(gi * GROUP_SIZE) as *const __m256i);
5411 let d = dpbusd_hsum(_mm256_abs_epi8(wv), _mm256_sign_epi8(xv, wv));
5412 acc += d as f32 * s;
5413 }
5414 acc
5415 }
5416}
5417
5418#[inline]
5422fn q1t_dot_row_i8(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
5423 #[cfg(target_arch = "aarch64")]
5424 unsafe {
5425 return q1t_dot_row_sdot(bytes, r, gpr, xq);
5426 }
5427 #[cfg(target_arch = "x86_64")]
5428 unsafe {
5429 if vnni_tiles_enabled() {
5430 return q1t_dot_row_vnni(bytes, r, gpr, xq);
5431 }
5432 return q1t_dot_row_avx2(bytes, r, gpr, xq);
5433 }
5434 #[allow(unreachable_code)]
5435 {
5436 const TILE: usize = cortiq_core::quant::Q1T_TILE;
5437 let mut acc = 0f32;
5438 let mut sg = [0i8; GROUP_SIZE + 8]; for gi in 0..gpr {
5440 let off = (r * gpr + gi) * TILE;
5441 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
5442 q1t_unpack_group_i8(bytes.as_ptr().wrapping_add(off + 2), &mut sg);
5443 let mut d = 0i32;
5444 for k in 0..GROUP_SIZE {
5445 d += sg[k] as i32 * xq[gi * GROUP_SIZE + k] as i32;
5446 }
5447 acc += d as f32 * s;
5448 }
5449 acc
5450 }
5451}
5452
5453fn q1t_row_outlier_correction(
5460 bytes: &[u8],
5461 r: usize,
5462 rp_off: usize,
5463 entries_off: usize,
5464 has_ov: bool,
5465 x: &[f32],
5466) -> f32 {
5467 if !has_ov {
5468 return 0.0;
5469 }
5470 let (c0, c1) = (
5471 q1t_rowptr(bytes, rp_off, r),
5472 q1t_rowptr(bytes, rp_off, r + 1),
5473 );
5474 let mut corr = 0f32;
5475 for p in c0..c1 {
5476 let e = entries_off + p * 4;
5477 let col = u16::from_le_bytes([bytes[e], bytes[e + 1]]) as usize;
5478 let val = f16_to_f32(u16::from_le_bytes([bytes[e + 2], bytes[e + 3]]));
5479 corr += val * x[col];
5480 }
5481 corr
5482}
5483
5484fn q1t_dequant_row(
5488 bytes: &[u8],
5489 r: usize,
5490 gpr: usize,
5491 rp_off: usize,
5492 entries_off: usize,
5493 has_ov: bool,
5494 buf: &mut [f32],
5495) {
5496 const TILE: usize = cortiq_core::quant::Q1T_TILE;
5497 for g in 0..gpr {
5498 let off = (r * gpr + g) * TILE;
5499 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
5500 let codes = &bytes[off + 2..off + TILE];
5501 let bc = g * GROUP_SIZE;
5502 for bi in 0..6 {
5504 let lut = &SIGN5[codes[bi] as usize];
5505 let d = &mut buf[bc + bi * 5..bc + bi * 5 + 5];
5506 for i in 0..5 {
5507 d[i] = lut[i] * s;
5508 }
5509 }
5510 let lut = &SIGN5[codes[6] as usize];
5511 buf[bc + 30] = lut[0] * s;
5512 buf[bc + 31] = lut[1] * s;
5513 }
5514 if !has_ov {
5515 return;
5516 }
5517 let (c0, c1) = (
5518 q1t_rowptr(bytes, rp_off, r),
5519 q1t_rowptr(bytes, rp_off, r + 1),
5520 );
5521 for p in c0..c1 {
5522 let e = entries_off + p * 4;
5523 let col = u16::from_le_bytes([bytes[e], bytes[e + 1]]) as usize;
5524 buf[col] = f16_to_f32(u16::from_le_bytes([bytes[e + 2], bytes[e + 3]]));
5525 }
5526}
5527
5528fn q1t_add_overlay(
5532 bytes: &[u8],
5533 x: &[f32],
5534 rows: usize,
5535 cols: usize,
5536 out: &mut [f32],
5537 pool: Option<&Pool>,
5538) {
5539 const TILE: usize = cortiq_core::quant::Q1T_TILE;
5540 let gpr = cols / GROUP_SIZE;
5541 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
5542 if !has_ov {
5543 return;
5544 }
5545 let out_addr = SendMut(out.as_mut_ptr());
5546 let run = move |start: usize, end: usize| {
5547 for r in start..end {
5548 let corr = q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x);
5549 unsafe { *out_addr.at(r) += corr };
5551 }
5552 };
5553 dispatch_rows(pool, rows, &run);
5554}
5555
5556#[allow(clippy::too_many_arguments)]
5559fn q1t_range_a8w8(
5560 bytes: &[u8],
5561 gpr: usize,
5562 rp_off: usize,
5563 ent_off: usize,
5564 has_ov: bool,
5565 act: &SplitAct,
5566 x: &[f32],
5567 out: SendMut,
5568 start: usize,
5569 end: usize,
5570) {
5571 for r in start..end {
5572 let mut acc = q1t_dot_row_i8(bytes, r, gpr, &act.xq) * act.sx;
5573 for &(j, xv) in &act.outliers {
5574 acc += q1t_base_weight(bytes, r, gpr, j) * xv;
5575 }
5576 acc += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x);
5577 unsafe { *out.at(r) = acc };
5579 }
5580}
5581
5582#[allow(clippy::too_many_arguments)]
5585fn q1t_range_f32_batch(
5586 bytes: &[u8],
5587 gpr: usize,
5588 rp_off: usize,
5589 ent_off: usize,
5590 has_ov: bool,
5591 x: &[f32],
5592 out: SendMut,
5593 start: usize,
5594 end: usize,
5595) {
5596 const TILE: usize = cortiq_core::quant::Q1T_TILE;
5597 let mut sg = [0f32; GROUP_SIZE];
5598 for r in start..end {
5599 let mut acc = 0f32;
5600 for g in 0..gpr {
5601 let off = (r * gpr + g) * TILE;
5602 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
5603 let codes = &bytes[off + 2..off + TILE];
5604 let xg = &x[g * GROUP_SIZE..g * GROUP_SIZE + GROUP_SIZE];
5605 for bi in 0..6 {
5606 sg[bi * 5..bi * 5 + 5].copy_from_slice(&SIGN5[codes[bi] as usize]);
5607 }
5608 let lut = &SIGN5[codes[6] as usize];
5609 sg[30] = lut[0];
5610 sg[31] = lut[1];
5611 let mut gsum = 0f32;
5612 for k in 0..GROUP_SIZE {
5613 gsum += sg[k] * xg[k];
5614 }
5615 acc += s * gsum;
5616 }
5617 acc += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x);
5618 unsafe { *out.at(r) = acc };
5620 }
5621}
5622
5623fn q1t_matvec(
5627 bytes: &[u8],
5628 x: &[f32],
5629 rows: usize,
5630 cols: usize,
5631 out: &mut [f32],
5632 pool: Option<&Pool>,
5633) {
5634 debug_assert_eq!(out.len(), rows);
5635 const TILE: usize = cortiq_core::quant::Q1T_TILE;
5636 let gpr = cols / GROUP_SIZE;
5637 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
5638 let out_addr = SendMut(out.as_mut_ptr());
5639 if a8w8_enabled() {
5643 let act = split_act(x);
5644 let act = &act;
5645 let run = move |start: usize, end: usize| {
5646 for r in start..end {
5647 let mut acc = q1t_dot_row_i8(bytes, r, gpr, &act.xq) * act.sx;
5648 for &(j, xv) in &act.outliers {
5649 acc += q1t_base_weight(bytes, r, gpr, j) * xv;
5650 }
5651 acc += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x);
5652 unsafe { *out_addr.at(r) = acc };
5654 }
5655 };
5656 dispatch_rows(pool, rows, &run);
5657 return;
5658 }
5659 let run = move |start: usize, end: usize| {
5660 let mut sg = [0f32; GROUP_SIZE];
5664 for r in start..end {
5665 let mut acc = 0f32;
5666 for g in 0..gpr {
5667 let off = (r * gpr + g) * TILE;
5668 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
5669 let codes = &bytes[off + 2..off + TILE];
5670 let xg = &x[g * GROUP_SIZE..g * GROUP_SIZE + GROUP_SIZE];
5671 for bi in 0..6 {
5672 sg[bi * 5..bi * 5 + 5].copy_from_slice(&SIGN5[codes[bi] as usize]);
5673 }
5674 let lut = &SIGN5[codes[6] as usize];
5675 sg[30] = lut[0];
5676 sg[31] = lut[1];
5677 let mut gsum = 0f32;
5678 for k in 0..GROUP_SIZE {
5679 gsum += sg[k] * xg[k];
5680 }
5681 acc += s * gsum;
5682 }
5683 acc += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x);
5684 unsafe { *out_addr.at(r) = acc };
5685 }
5686 };
5687 dispatch_rows(pool, rows, &run);
5688}
5689
5690#[cfg(target_arch = "aarch64")]
5696#[target_feature(enable = "neon,dotprod")]
5697unsafe fn q1t_dot_row_sdot2(bytes: &[u8], r: usize, gpr: usize, xa: &[i8], xb: &[i8]) -> [f32; 2] {
5698 use core::arch::aarch64::*;
5699 use core::arch::asm;
5700 unsafe {
5702 const TILE: usize = cortiq_core::quant::Q1T_TILE;
5703 let bytes_ptr = bytes.as_ptr();
5704 let row_off = r * gpr * TILE;
5705 let xp = [xa.as_ptr(), xb.as_ptr()];
5706 let mut acc = [0f32; 2];
5707 macro_rules! sdot2 {
5708 ($w0:expr, $w1:expr, $x:expr) => {{
5709 let x0 = vld1q_s8($x);
5710 let x1 = vld1q_s8($x.add(16));
5711 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
5712 asm!(
5713 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
5714 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
5715 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
5716 w0 = in(vreg) $w0, x0 = in(vreg) x0, w1 = in(vreg) $w1, x1 = in(vreg) x1,
5717 options(pure, nomem, nostack),
5718 );
5719 vaddvq_s32(vaddq_s32(a0, a1))
5720 }};
5721 }
5722 let gpr2 = gpr & !1;
5723 let mut gi = 0;
5724 while gi < gpr2 {
5725 let off0 = row_off + gi * TILE;
5726 let off1 = off0 + TILE;
5727 let s0 = f16_to_f32(u16::from_le_bytes([
5728 *bytes_ptr.add(off0),
5729 *bytes_ptr.add(off0 + 1),
5730 ]));
5731 let s1 = f16_to_f32(u16::from_le_bytes([
5732 *bytes_ptr.add(off1),
5733 *bytes_ptr.add(off1 + 1),
5734 ]));
5735 let (u0_0, u1_0, u2_0, u3_0) = q1t_unpack_reg_u64s(bytes_ptr.add(off0 + 2));
5736 let (u0_1, u1_1, u2_1, u3_1) = q1t_unpack_reg_u64s(bytes_ptr.add(off1 + 2));
5737 let w0_0 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u0_0), vcreate_u64(u1_0)));
5738 let w1_0 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u2_0), vcreate_u64(u3_0)));
5739 let w0_1 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u0_1), vcreate_u64(u1_1)));
5740 let w1_1 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u2_1), vcreate_u64(u3_1)));
5741 for k in 0..2 {
5742 let d0 = sdot2!(w0_0, w1_0, xp[k].add(gi * GROUP_SIZE));
5743 let d1 = sdot2!(w0_1, w1_1, xp[k].add((gi + 1) * GROUP_SIZE));
5744 acc[k] += d0 as f32 * s0 + d1 as f32 * s1;
5745 }
5746 gi += 2;
5747 }
5748 if gi < gpr {
5749 let off = row_off + gi * TILE;
5750 let s = f16_to_f32(u16::from_le_bytes([
5751 *bytes_ptr.add(off),
5752 *bytes_ptr.add(off + 1),
5753 ]));
5754 let (u0, u1, u2, u3) = q1t_unpack_reg_u64s(bytes_ptr.add(off + 2));
5755 let w0 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u0), vcreate_u64(u1)));
5756 let w1 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u2), vcreate_u64(u3)));
5757 for k in 0..2 {
5758 let d = sdot2!(w0, w1, xp[k].add(gi * GROUP_SIZE));
5759 acc[k] += d as f32 * s;
5760 }
5761 }
5762 acc
5763 }
5764}
5765
5766fn q1t_matvec2(
5772 bytes: &[u8],
5773 x1: &[f32],
5774 x2: &[f32],
5775 rows: usize,
5776 cols: usize,
5777 o1: &mut [f32],
5778 o2: &mut [f32],
5779 pool: Option<&Pool>,
5780) {
5781 debug_assert_eq!(o1.len(), rows);
5782 debug_assert_eq!(o2.len(), rows);
5783 const TILE: usize = cortiq_core::quant::Q1T_TILE;
5784 let gpr = cols / GROUP_SIZE;
5785 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
5786 let out1 = SendMut(o1.as_mut_ptr());
5787 let out2 = SendMut(o2.as_mut_ptr());
5788 if a8w8_enabled() {
5789 let a1 = split_act(x1);
5790 let a2 = split_act(x2);
5791 let (a1, a2) = (&a1, &a2);
5792 let run = move |start: usize, end: usize| {
5793 for r in start..end {
5794 #[cfg(target_arch = "aarch64")]
5795 let ds = unsafe { q1t_dot_row_sdot2(bytes, r, gpr, &a1.xq, &a2.xq) };
5798 #[cfg(not(target_arch = "aarch64"))]
5799 let ds = [
5800 q1t_dot_row_i8(bytes, r, gpr, &a1.xq),
5801 q1t_dot_row_i8(bytes, r, gpr, &a2.xq),
5802 ];
5803 let mut acc1 = ds[0] * a1.sx;
5804 for &(j, xv) in &a1.outliers {
5805 acc1 += q1t_base_weight(bytes, r, gpr, j) * xv;
5806 }
5807 acc1 += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x1);
5808 let mut acc2 = ds[1] * a2.sx;
5809 for &(j, xv) in &a2.outliers {
5810 acc2 += q1t_base_weight(bytes, r, gpr, j) * xv;
5811 }
5812 acc2 += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x2);
5813 unsafe {
5815 *out1.at(r) = acc1;
5816 *out2.at(r) = acc2;
5817 }
5818 }
5819 };
5820 dispatch_rows(pool, rows, &run);
5821 return;
5822 }
5823 let run = move |start: usize, end: usize| {
5824 let mut sg = [0f32; GROUP_SIZE];
5827 for r in start..end {
5828 let mut acc1 = 0f32;
5829 let mut acc2 = 0f32;
5830 for g in 0..gpr {
5831 let off = (r * gpr + g) * TILE;
5832 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
5833 let codes = &bytes[off + 2..off + TILE];
5834 for bi in 0..6 {
5835 sg[bi * 5..bi * 5 + 5].copy_from_slice(&SIGN5[codes[bi] as usize]);
5836 }
5837 let lut = &SIGN5[codes[6] as usize];
5838 sg[30] = lut[0];
5839 sg[31] = lut[1];
5840 let xg1 = &x1[g * GROUP_SIZE..g * GROUP_SIZE + GROUP_SIZE];
5841 let xg2 = &x2[g * GROUP_SIZE..g * GROUP_SIZE + GROUP_SIZE];
5842 let mut gsum1 = 0f32;
5843 for k in 0..GROUP_SIZE {
5844 gsum1 += sg[k] * xg1[k];
5845 }
5846 acc1 += s * gsum1;
5847 let mut gsum2 = 0f32;
5848 for k in 0..GROUP_SIZE {
5849 gsum2 += sg[k] * xg2[k];
5850 }
5851 acc2 += s * gsum2;
5852 }
5853 acc1 += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x1);
5854 acc2 += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x2);
5855 unsafe {
5857 *out1.at(r) = acc1;
5858 *out2.at(r) = acc2;
5859 }
5860 }
5861 };
5862 dispatch_rows(pool, rows, &run);
5863}
5864
5865fn q1t_matmat(
5868 bytes: &[u8],
5869 xs: &[f32],
5870 b: usize,
5871 rows: usize,
5872 cols: usize,
5873 out: &mut [f32],
5874 pool: Option<&Pool>,
5875) {
5876 debug_assert_eq!(out.len(), b * rows);
5877 const TILE: usize = cortiq_core::quant::Q1T_TILE;
5878 let gpr = cols / GROUP_SIZE;
5879 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
5880 let out_addr = SendMut(out.as_mut_ptr());
5881 if a8w8_enabled() {
5885 let acts: Vec<SplitAct> = (0..b)
5886 .map(|bi| split_act(&xs[bi * cols..(bi + 1) * cols]))
5887 .collect();
5888 let acts = &acts;
5889 let run = move |start: usize, end: usize| {
5890 let mut sg = vec![0i8; cols + 8]; let mut sc = vec![0f32; gpr]; let mut accs = vec![0f32; b]; for r in start..end {
5894 for g in 0..gpr {
5895 let off = (r * gpr + g) * TILE;
5896 sc[g] = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
5897 q1t_unpack_group_i8(
5898 bytes.as_ptr().wrapping_add(off + 2),
5899 &mut sg[g * GROUP_SIZE..],
5900 );
5901 }
5902 for bi in 0..b {
5903 let act = &acts[bi];
5904 let mut isum = 0f32;
5905 for g in 0..gpr {
5906 let d = q1t_i8dot32(
5907 sg.as_ptr().wrapping_add(g * GROUP_SIZE),
5908 act.xq.as_ptr().wrapping_add(g * GROUP_SIZE),
5909 );
5910 isum += d as f32 * sc[g];
5911 }
5912 let mut acc = isum * act.sx;
5913 for &(j, xv) in &act.outliers {
5914 acc += q1t_base_weight(bytes, r, gpr, j) * xv;
5915 }
5916 accs[bi] = acc;
5917 }
5918 if has_ov {
5922 let (c0, c1) = (
5923 q1t_rowptr(bytes, rp_off, r),
5924 q1t_rowptr(bytes, rp_off, r + 1),
5925 );
5926 for p in c0..c1 {
5927 let e = ent_off + p * 4;
5928 let col = u16::from_le_bytes([bytes[e], bytes[e + 1]]) as usize;
5929 let val = f16_to_f32(u16::from_le_bytes([bytes[e + 2], bytes[e + 3]]));
5930 for bi in 0..b {
5931 accs[bi] += val * xs[bi * cols + col];
5932 }
5933 }
5934 }
5935 for bi in 0..b {
5936 unsafe { *out_addr.at(bi * rows + r) = accs[bi] };
5937 }
5938 }
5939 };
5940 dispatch_rows(pool, rows, &run);
5941 return;
5942 }
5943 let run = move |start: usize, end: usize| {
5944 let mut buf = vec![0f32; cols];
5945 for r in start..end {
5946 q1t_dequant_row(bytes, r, gpr, rp_off, ent_off, has_ov, &mut buf);
5947 for bi in 0..b {
5948 let xr = &xs[bi * cols..(bi + 1) * cols];
5949 let mut acc = 0f32;
5950 for j in 0..cols {
5951 acc += buf[j] * xr[j];
5952 }
5953 unsafe { *out_addr.at(bi * rows + r) = acc };
5954 }
5955 }
5956 };
5957 dispatch_rows(pool, rows, &run);
5958}
5959
5960fn q1_matvec(
5961 bytes: &[u8],
5962 x: &[f32],
5963 rows: usize,
5964 cols: usize,
5965 out: &mut [f32],
5966 pool: Option<&Pool>,
5967) {
5968 debug_assert_eq!(out.len(), rows);
5969 let gpr = cols / GROUP_SIZE;
5970 let out_addr = SendMut(out.as_mut_ptr());
5971 if a8w8_enabled() {
5972 let act = split_act(x);
5973 let gsum = q1_group_sums(&act.xq, gpr);
5974 let (act, gsum) = (&act, &gsum);
5975 let run = move |start: usize, end: usize| {
5976 q1_range_a8w8(bytes, gpr, act, gsum, out_addr, start, end)
5977 };
5978 dispatch_rows(pool, rows, &run);
5979 return;
5980 }
5981 let run = move |start: usize, end: usize| q1_range_f32(bytes, gpr, x, out_addr, start, end);
5982 dispatch_rows(pool, rows, &run);
5983}
5984
5985#[allow(clippy::too_many_arguments)]
5987fn q1_matvec2(
5988 bytes: &[u8],
5989 x1: &[f32],
5990 x2: &[f32],
5991 rows: usize,
5992 cols: usize,
5993 o1: &mut [f32],
5994 o2: &mut [f32],
5995 pool: Option<&Pool>,
5996) {
5997 let gpr = cols / GROUP_SIZE;
5998 let p1 = SendMut(o1.as_mut_ptr());
5999 let p2 = SendMut(o2.as_mut_ptr());
6000 if a8w8_enabled() {
6001 let a1 = split_act(x1);
6002 let a2 = split_act(x2);
6003 let g1 = q1_group_sums(&a1.xq, gpr);
6004 let g2 = q1_group_sums(&a2.xq, gpr);
6005 let (a1, a2, g1, g2) = (&a1, &a2, &g1, &g2);
6006 let run = move |start: usize, end: usize| {
6007 for r in start..end {
6008 let mut v1 = dot_q1_row_i8(bytes, r, gpr, &a1.xq, g1) * a1.sx;
6009 let mut v2 = dot_q1_row_i8(bytes, r, gpr, &a2.xq, g2) * a2.sx;
6010 for &(j, xv) in &a1.outliers {
6011 let (w, s) = q1_outlier(bytes, r, gpr, j);
6012 v1 += w * s * xv;
6013 }
6014 for &(j, xv) in &a2.outliers {
6015 let (w, s) = q1_outlier(bytes, r, gpr, j);
6016 v2 += w * s * xv;
6017 }
6018 unsafe {
6020 *p1.at(r) = v1;
6021 *p2.at(r) = v2;
6022 }
6023 }
6024 };
6025 dispatch_rows(pool, rows, &run);
6026 return;
6027 }
6028 let run = move |start: usize, end: usize| {
6029 for r in start..end {
6030 unsafe {
6032 *p1.at(r) = q1_row_exact(bytes, r, gpr, x1);
6033 *p2.at(r) = q1_row_exact(bytes, r, gpr, x2);
6034 }
6035 }
6036 };
6037 dispatch_rows(pool, rows, &run);
6038}
6039
6040#[allow(clippy::too_many_arguments)]
6042fn q1_matmat(
6043 bytes: &[u8],
6044 xs_all: &[f32],
6045 b: usize,
6046 rows: usize,
6047 cols: usize,
6048 out: &mut [f32],
6049 pool: Option<&Pool>,
6050) {
6051 debug_assert_eq!(out.len(), b * rows);
6052 let gpr = cols / GROUP_SIZE;
6053 let out_addr = SendMut(out.as_mut_ptr());
6054 if a8w8_enabled() {
6055 let acts: Vec<(SplitAct, Vec<i32>)> = (0..b)
6056 .map(|bi| {
6057 let act = split_act(&xs_all[bi * cols..(bi + 1) * cols]);
6058 let gsum = q1_group_sums(&act.xq, gpr);
6059 (act, gsum)
6060 })
6061 .collect();
6062 let acts = &acts;
6063 #[cfg(target_arch = "x86_64")]
6064 let blocked_ok = avx2_enabled()
6065 && std::env::var("CMF_X86_BLOCKED")
6066 .map(|v| v != "0")
6067 .unwrap_or(true);
6068 #[cfg(target_arch = "aarch64")]
6069 let blocked_ok = sdot_enabled()
6070 && std::env::var("CMF_X86_BLOCKED")
6071 .map(|v| v != "0")
6072 .unwrap_or(true);
6073 let run = move |start: usize, end: usize| {
6074 for r in start..end {
6075 let mut bi = 0usize;
6076 #[cfg(target_arch = "aarch64")]
6079 if blocked_ok {
6080 while bi + 4 <= acts.len() {
6081 let xs = [
6082 acts[bi].0.xq.as_slice(),
6083 acts[bi + 1].0.xq.as_slice(),
6084 acts[bi + 2].0.xq.as_slice(),
6085 acts[bi + 3].0.xq.as_slice(),
6086 ];
6087 let gs = [
6088 acts[bi].1.as_slice(),
6089 acts[bi + 1].1.as_slice(),
6090 acts[bi + 2].1.as_slice(),
6091 acts[bi + 3].1.as_slice(),
6092 ];
6093 let d = unsafe { dot_q1_row_1x4_sdot(bytes, r, gpr, xs, gs) };
6094 for k in 0..4 {
6095 let (act, _) = &acts[bi + k];
6096 let mut acc = d[k] * act.sx;
6097 for &(j, xv) in &act.outliers {
6098 let (w, sc) = q1_outlier(bytes, r, gpr, j);
6099 acc += w * sc * xv;
6100 }
6101 unsafe { *out_addr.at((bi + k) * rows + r) = acc };
6103 }
6104 bi += 4;
6105 }
6106 }
6107 #[cfg(target_arch = "x86_64")]
6108 if blocked_ok {
6109 while bi + 4 <= acts.len() {
6110 let xs = [
6111 acts[bi].0.xq.as_slice(),
6112 acts[bi + 1].0.xq.as_slice(),
6113 acts[bi + 2].0.xq.as_slice(),
6114 acts[bi + 3].0.xq.as_slice(),
6115 ];
6116 let gs = [
6117 acts[bi].1.as_slice(),
6118 acts[bi + 1].1.as_slice(),
6119 acts[bi + 2].1.as_slice(),
6120 acts[bi + 3].1.as_slice(),
6121 ];
6122 let d = unsafe {
6123 if vnni_tiles_enabled() {
6124 dot_q1_row_1x4_vnni(bytes, r, gpr, xs, gs)
6125 } else {
6126 dot_q1_row_1x4_avx2(bytes, r, gpr, xs, gs)
6127 }
6128 };
6129 for k in 0..4 {
6130 let (act, _) = &acts[bi + k];
6131 let mut acc = d[k] * act.sx;
6132 for &(j, xv) in &act.outliers {
6133 let (w, sc) = q1_outlier(bytes, r, gpr, j);
6134 acc += w * sc * xv;
6135 }
6136 unsafe { *out_addr.at((bi + k) * rows + r) = acc };
6138 }
6139 bi += 4;
6140 }
6141 }
6142 while bi < acts.len() {
6143 let (act, gsum) = &acts[bi];
6144 let mut acc = dot_q1_row_i8(bytes, r, gpr, &act.xq, gsum) * act.sx;
6145 for &(j, xv) in &act.outliers {
6146 let (w, s) = q1_outlier(bytes, r, gpr, j);
6147 acc += w * s * xv;
6148 }
6149 unsafe { *out_addr.at(bi * rows + r) = acc };
6151 bi += 1;
6152 }
6153 }
6154 };
6155 dispatch_rows(pool, rows, &run);
6156 return;
6157 }
6158 let run = move |start: usize, end: usize| {
6159 for r in start..end {
6160 for bi in 0..b {
6161 let x = &xs_all[bi * cols..(bi + 1) * cols];
6162 unsafe { *out_addr.at(bi * rows + r) = q1_row_exact(bytes, r, gpr, x) };
6164 }
6165 }
6166 };
6167 dispatch_rows(pool, rows, &run);
6168}
6169
6170fn q4matvec(
6176 bytes: &[u8],
6177 x: &[f32],
6178 rows: usize,
6179 cols: usize,
6180 out: &mut [f32],
6181 pool: Option<&Pool>,
6182) {
6183 debug_assert_eq!(out.len(), rows);
6184 let (packed, scales) = q4_split(bytes, rows, cols);
6185 let gpr = cols / GROUP_SIZE;
6186 let out_addr = SendMut(out.as_mut_ptr());
6187
6188 if a8w8_enabled() {
6189 let act = split_act(x);
6190 let run = move |start: usize, end: usize| {
6191 q4_range_a8w8(packed, scales, gpr, cols, &act, out_addr, start, end)
6192 };
6193 dispatch_rows(pool, rows, &run);
6194 return;
6195 }
6196
6197 let run =
6198 move |start: usize, end: usize| q4_range_f32(packed, scales, gpr, x, out_addr, start, end);
6199 dispatch_rows(pool, rows, &run);
6200}
6201
6202#[inline]
6205#[allow(unreachable_code)]
6206#[cfg(target_arch = "x86_64")]
6211#[target_feature(enable = "avx2")]
6212unsafe fn dot_q4b_row_1x4_avx2(
6213 buf: &[u8],
6214 scales: &[u8],
6215 g0: usize,
6216 gpr: usize,
6217 xs: [&[i8]; 4],
6218) -> [f32; 4] {
6219 unsafe {
6221 use core::arch::x86_64::*;
6222 let ones = _mm256_set1_epi16(1);
6223 let mut acc = [0f32; 4];
6224 for gi in 0..gpr {
6225 let s = f16_to_f32(u16::from_le_bytes([
6226 scales[(g0 + gi) * 2],
6227 scales[(g0 + gi) * 2 + 1],
6228 ]));
6229 let w = _mm256_loadu_si256(buf.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
6230 let aw = _mm256_abs_epi8(w);
6231 for (k, xq) in xs.iter().enumerate() {
6232 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
6233 let p16 = _mm256_maddubs_epi16(aw, _mm256_sign_epi8(x, w));
6234 let d = _mm256_madd_epi16(p16, ones);
6235 let hi128 = _mm256_extracti128_si256::<1>(d);
6236 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
6237 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
6238 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
6239 acc[k] += _mm_cvtsi128_si32(s32) as f32 * s;
6240 }
6241 }
6242 acc
6243 }
6244}
6245
6246#[cfg(target_arch = "x86_64")]
6248#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
6249unsafe fn dot_q4b_row_1x4_vnni(
6250 buf: &[u8],
6251 scales: &[u8],
6252 g0: usize,
6253 gpr: usize,
6254 xs: [&[i8]; 4],
6255) -> [f32; 4] {
6256 unsafe {
6258 use core::arch::x86_64::*;
6259 let mut acc = [0f32; 4];
6260 for gi in 0..gpr {
6261 let s = f16_to_f32(u16::from_le_bytes([
6262 scales[(g0 + gi) * 2],
6263 scales[(g0 + gi) * 2 + 1],
6264 ]));
6265 let w = _mm256_loadu_si256(buf.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
6266 let aw = _mm256_abs_epi8(w);
6267 for (k, xq) in xs.iter().enumerate() {
6268 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
6269 let d = dpbusd_hsum(aw, _mm256_sign_epi8(x, w));
6270 acc[k] += d as f32 * s;
6271 }
6272 }
6273 acc
6274 }
6275}
6276
6277#[cfg(target_arch = "x86_64")]
6283#[target_feature(enable = "avx2")]
6284unsafe fn dot_q4b_row_1x4_sx_avx2(
6285 buf: &[u8],
6286 scales: &[u8],
6287 g0: usize,
6288 gpr: usize,
6289 xs: [&[i8]; 4],
6290 sxs: [f32; 4],
6291) -> [f32; 4] {
6292 unsafe {
6294 use core::arch::x86_64::*;
6295 let ones = _mm256_set1_epi16(1);
6296 let mut acc = [0f32; 4];
6297 for gi in 0..gpr {
6298 let s = f16_to_f32(u16::from_le_bytes([
6299 scales[(g0 + gi) * 2],
6300 scales[(g0 + gi) * 2 + 1],
6301 ]));
6302 let w = _mm256_loadu_si256(buf.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
6303 let aw = _mm256_abs_epi8(w);
6304 for (k, xq) in xs.iter().enumerate() {
6305 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
6306 let p16 = _mm256_maddubs_epi16(aw, _mm256_sign_epi8(x, w));
6307 let d = _mm256_madd_epi16(p16, ones);
6308 let hi128 = _mm256_extracti128_si256::<1>(d);
6309 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
6310 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
6311 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
6312 acc[k] += (_mm_cvtsi128_si32(s32) as f32 * sxs[k]) * s;
6313 }
6314 }
6315 acc
6316 }
6317}
6318
6319#[cfg(target_arch = "x86_64")]
6322#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
6323unsafe fn dot_q4b_row_1x4_sx_vnni(
6324 buf: &[u8],
6325 scales: &[u8],
6326 g0: usize,
6327 gpr: usize,
6328 xs: [&[i8]; 4],
6329 sxs: [f32; 4],
6330) -> [f32; 4] {
6331 unsafe {
6333 use core::arch::x86_64::*;
6334 let mut acc = [0f32; 4];
6335 for gi in 0..gpr {
6336 let s = f16_to_f32(u16::from_le_bytes([
6337 scales[(g0 + gi) * 2],
6338 scales[(g0 + gi) * 2 + 1],
6339 ]));
6340 let w = _mm256_loadu_si256(buf.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
6341 let aw = _mm256_abs_epi8(w);
6342 for (k, xq) in xs.iter().enumerate() {
6343 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
6344 let d = dpbusd_hsum(aw, _mm256_sign_epi8(x, w));
6345 acc[k] += (d as f32 * sxs[k]) * s;
6346 }
6347 }
6348 acc
6349 }
6350}
6351
6352#[allow(unreachable_code)]
6353fn dot_q4_row_i8(packed: &[u8], scales: &[u8], g0: usize, gpr: usize, xq: &[i8]) -> f32 {
6354 #[cfg(target_arch = "aarch64")]
6355 unsafe {
6356 return dot_q4_row_sdot(packed, scales, g0, gpr, xq);
6357 }
6358 #[cfg(target_arch = "x86_64")]
6359 unsafe {
6360 return dot_q4_row_avx2(packed, scales, g0, gpr, xq);
6361 }
6362 let mut acc = 0f32;
6363 for gi in 0..gpr {
6364 let g = g0 + gi;
6365 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
6366 let mut d = 0i32;
6367 for (k, &b) in packed[g * 16..(g + 1) * 16].iter().enumerate() {
6368 d += ((b & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2] as i32
6369 + (((b >> 4) & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2 + 1] as i32;
6370 }
6371 acc += d as f32 * s;
6372 }
6373 acc
6374}
6375
6376#[inline]
6378#[allow(unreachable_code)]
6379fn dot_q4_row_i8_2(
6380 packed: &[u8],
6381 scales: &[u8],
6382 g0: usize,
6383 gpr: usize,
6384 xq1: &[i8],
6385 xq2: &[i8],
6386) -> (f32, f32) {
6387 #[cfg(target_arch = "aarch64")]
6388 unsafe {
6389 return dot_q4_row_sdot2(packed, scales, g0, gpr, xq1, xq2);
6390 }
6391 #[cfg(target_arch = "x86_64")]
6392 unsafe {
6393 return dot_q4_row_avx2_2(packed, scales, g0, gpr, xq1, xq2);
6394 }
6395 (
6396 dot_q4_row_i8(packed, scales, g0, gpr, xq1),
6397 dot_q4_row_i8(packed, scales, g0, gpr, xq2),
6398 )
6399}
6400
6401#[allow(clippy::too_many_arguments)]
6404fn q4_range_a8w8(
6405 packed: &[u8],
6406 scales: &[u8],
6407 gpr: usize,
6408 cols: usize,
6409 act: &SplitAct,
6410 out: SendMut,
6411 start: usize,
6412 end: usize,
6413) {
6414 for r in start..end {
6415 let mut acc = dot_q4_row_i8(packed, scales, r * gpr, gpr, &act.xq) * act.sx;
6416 for &(j, xv) in &act.outliers {
6418 let flat = r * cols + j;
6419 let byte = packed[flat / 2];
6420 let nib = if flat & 1 == 0 {
6421 byte & 0x0F
6422 } else {
6423 byte >> 4
6424 };
6425 let s = f16_to_f32(u16::from_le_bytes([
6426 scales[(flat / GROUP_SIZE) * 2],
6427 scales[(flat / GROUP_SIZE) * 2 + 1],
6428 ]));
6429 acc += ((nib as i32 - 8) as f32) * s * xv;
6430 }
6431 unsafe { *out.at(r) = acc };
6433 }
6434}
6435
6436#[allow(clippy::too_many_arguments)]
6439fn q4_range2_a8w8(
6440 packed: &[u8],
6441 scales: &[u8],
6442 gpr: usize,
6443 cols: usize,
6444 a1: &SplitAct,
6445 a2: &SplitAct,
6446 p1: SendMut,
6447 p2: SendMut,
6448 start: usize,
6449 end: usize,
6450) {
6451 for r in start..end {
6452 let (s1, s2) = dot_q4_row_i8_2(packed, scales, r * gpr, gpr, &a1.xq, &a2.xq);
6453 let mut acc1 = s1 * a1.sx;
6454 let mut acc2 = s2 * a2.sx;
6455 let fix = |outliers: &[(usize, f32)], acc: &mut f32| {
6457 for &(j, xv) in outliers {
6458 let flat = r * cols + j;
6459 let byte = packed[flat / 2];
6460 let nib = if flat & 1 == 0 {
6461 byte & 0x0F
6462 } else {
6463 byte >> 4
6464 };
6465 let s = f16_to_f32(u16::from_le_bytes([
6466 scales[(flat / GROUP_SIZE) * 2],
6467 scales[(flat / GROUP_SIZE) * 2 + 1],
6468 ]));
6469 *acc += ((nib as i32 - 8) as f32) * s * xv;
6470 }
6471 };
6472 fix(&a1.outliers, &mut acc1);
6473 fix(&a2.outliers, &mut acc2);
6474 unsafe {
6476 *p1.at(r) = acc1;
6477 *p2.at(r) = acc2;
6478 }
6479 }
6480}
6481
6482fn q4_range_f32(
6484 packed: &[u8],
6485 scales: &[u8],
6486 gpr: usize,
6487 x: &[f32],
6488 out: SendMut,
6489 start: usize,
6490 end: usize,
6491) {
6492 for r in start..end {
6493 let mut acc = 0f32;
6494 for gi in 0..gpr {
6495 let g = r * gpr + gi;
6496 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
6497 let pk = &packed[g * 16..(g + 1) * 16];
6498 let xg = &x[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
6499 let mut ga = 0f32;
6500 for (k, &b) in pk.iter().enumerate() {
6501 ga += ((b & 0x0F) as f32 - 8.0) * xg[k * 2]
6502 + (((b >> 4) & 0x0F) as f32 - 8.0) * xg[k * 2 + 1];
6503 }
6504 acc += ga * s;
6505 }
6506 unsafe { *out.at(r) = acc };
6508 }
6509}
6510
6511#[allow(clippy::too_many_arguments)]
6515fn q4matvec2(
6516 bytes: &[u8],
6517 x1: &[f32],
6518 x2: &[f32],
6519 rows: usize,
6520 cols: usize,
6521 o1: &mut [f32],
6522 o2: &mut [f32],
6523 pool: Option<&Pool>,
6524) {
6525 debug_assert_eq!(o1.len(), rows);
6526 debug_assert_eq!(o2.len(), rows);
6527 let (packed, scales) = q4_split(bytes, rows, cols);
6528 let gpr = cols / GROUP_SIZE;
6529
6530 if a8w8_enabled() {
6531 let a1 = split_act(x1);
6532 let a2 = split_act(x2);
6533 let p1 = SendMut(o1.as_mut_ptr());
6534 let p2 = SendMut(o2.as_mut_ptr());
6535 let run = move |start: usize, end: usize| {
6536 q4_range2_a8w8(packed, scales, gpr, cols, &a1, &a2, p1, p2, start, end)
6537 };
6538 dispatch_rows(pool, rows, &run);
6539 return;
6540 }
6541
6542 let p1 = SendMut(o1.as_mut_ptr());
6543 let p2 = SendMut(o2.as_mut_ptr());
6544 let run = move |start: usize, end: usize| {
6545 q4_range2_f32(packed, scales, gpr, x1, x2, p1, p2, start, end)
6546 };
6547 dispatch_rows(pool, rows, &run);
6548}
6549
6550#[allow(clippy::too_many_arguments)]
6552fn q4_range2_f32(
6553 packed: &[u8],
6554 scales: &[u8],
6555 gpr: usize,
6556 x1: &[f32],
6557 x2: &[f32],
6558 p1: SendMut,
6559 p2: SendMut,
6560 start: usize,
6561 end: usize,
6562) {
6563 for r in start..end {
6564 let (mut acc1, mut acc2) = (0f32, 0f32);
6565 for gi in 0..gpr {
6566 let g = r * gpr + gi;
6567 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
6568 let pk = &packed[g * 16..(g + 1) * 16];
6569 let x1g = &x1[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
6570 let x2g = &x2[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
6571 let (mut g1, mut g2) = (0f32, 0f32);
6572 for (k, &b) in pk.iter().enumerate() {
6573 let wl = (b & 0x0F) as f32 - 8.0;
6574 let wh = ((b >> 4) & 0x0F) as f32 - 8.0;
6575 g1 += wl * x1g[k * 2] + wh * x1g[k * 2 + 1];
6576 g2 += wl * x2g[k * 2] + wh * x2g[k * 2 + 1];
6577 }
6578 acc1 += g1 * s;
6579 acc2 += g2 * s;
6580 }
6581 unsafe {
6583 *p1.at(r) = acc1;
6584 *p2.at(r) = acc2;
6585 }
6586 }
6587}
6588
6589thread_local! {
6590 static ROW_I8: std::cell::RefCell<Vec<u8>> = const { std::cell::RefCell::new(Vec::new()) };
6593 static ROW_F32: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
6594}
6595
6596#[allow(clippy::too_many_arguments)]
6602fn q4matmat(
6603 bytes: &[u8],
6604 xs_all: &[f32],
6605 b: usize,
6606 rows: usize,
6607 cols: usize,
6608 out: &mut [f32],
6609 pool: Option<&Pool>,
6610) {
6611 debug_assert_eq!(xs_all.len(), b * cols);
6612 debug_assert_eq!(out.len(), b * rows);
6613 let (packed, scales) = q4_split(bytes, rows, cols);
6614 let gpr = cols / GROUP_SIZE;
6615 let gscale = |g: usize| f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
6616
6617 if a8w8_enabled() {
6618 let acts: Vec<SplitAct> = (0..b)
6619 .map(|bi| split_act(&xs_all[bi * cols..(bi + 1) * cols]))
6620 .collect();
6621 let acts = &acts;
6622 let out_addr = SendMut(out.as_mut_ptr());
6623 let run = move |start: usize, end: usize| {
6624 ROW_I8.with(|rb| {
6625 let mut buf = rb.borrow_mut();
6626 buf.resize(cols, 0);
6627 for r in start..end {
6628 for gi in 0..gpr {
6632 let g = r * gpr + gi;
6633 for (k, &bt) in packed[g * 16..(g + 1) * 16].iter().enumerate() {
6634 buf[gi * GROUP_SIZE + k * 2] = ((bt & 0x0F) as i32 - 8) as i8 as u8;
6635 buf[gi * GROUP_SIZE + k * 2 + 1] =
6636 (((bt >> 4) & 0x0F) as i32 - 8) as i8 as u8;
6637 }
6638 }
6639 let mut bi = 0usize;
6640 #[cfg(target_arch = "x86_64")]
6641 if avx2_enabled()
6642 && std::env::var("CMF_X86_BLOCKED")
6643 .map(|v| v != "0")
6644 .unwrap_or(true)
6645 {
6646 while bi + 4 <= acts.len() {
6647 let xs = [
6648 acts[bi].xq.as_slice(),
6649 acts[bi + 1].xq.as_slice(),
6650 acts[bi + 2].xq.as_slice(),
6651 acts[bi + 3].xq.as_slice(),
6652 ];
6653 let d = unsafe {
6654 if vnni_tiles_enabled() {
6655 dot_q4b_row_1x4_vnni(&buf, scales, r * gpr, gpr, xs)
6656 } else {
6657 dot_q4b_row_1x4_avx2(&buf, scales, r * gpr, gpr, xs)
6658 }
6659 };
6660 for k in 0..4 {
6661 let act = &acts[bi + k];
6662 let mut acc = d[k] * act.sx;
6663 for &(j, xv) in &act.outliers {
6664 acc += (buf[j] as i8) as f32
6665 * gscale((r * cols + j) / GROUP_SIZE)
6666 * xv;
6667 }
6668 unsafe { *out_addr.at((bi + k) * rows + r) = acc };
6670 }
6671 bi += 4;
6672 }
6673 }
6674 while bi < acts.len() {
6675 let act = &acts[bi];
6676 let mut acc = 0f32;
6677 for gi in 0..gpr {
6678 let d = dot_i8_i8(
6679 &buf[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE],
6680 &act.xq[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE],
6681 );
6682 acc += d as f32 * gscale(r * gpr + gi);
6683 }
6684 acc *= act.sx;
6685 for &(j, xv) in &act.outliers {
6687 acc += (buf[j] as i8) as f32 * gscale((r * cols + j) / GROUP_SIZE) * xv;
6688 }
6689 unsafe { *out_addr.at(bi * rows + r) = acc };
6691 bi += 1;
6692 }
6693 }
6694 })
6695 };
6696 dispatch_rows(pool, rows, &run);
6697 return;
6698 }
6699
6700 let out_addr = SendMut(out.as_mut_ptr());
6701 let run = move |start: usize, end: usize| {
6702 ROW_F32.with(|rb| {
6703 let mut buf = rb.borrow_mut();
6704 buf.resize(cols, 0.0);
6705 for r in start..end {
6706 for gi in 0..gpr {
6709 let g = r * gpr + gi;
6710 for (k, &bt) in packed[g * 16..(g + 1) * 16].iter().enumerate() {
6711 buf[gi * GROUP_SIZE + k * 2] = (bt & 0x0F) as f32 - 8.0;
6712 buf[gi * GROUP_SIZE + k * 2 + 1] = ((bt >> 4) & 0x0F) as f32 - 8.0;
6713 }
6714 }
6715 for bi in 0..b {
6716 let x = &xs_all[bi * cols..(bi + 1) * cols];
6717 let mut acc = 0f32;
6718 for gi in 0..gpr {
6719 let mut ga = 0f32;
6720 for k in 0..GROUP_SIZE / 2 {
6725 let e = gi * GROUP_SIZE + k * 2;
6726 ga += buf[e] * x[e] + buf[e + 1] * x[e + 1];
6727 }
6728 acc += ga * gscale(r * gpr + gi);
6729 }
6730 unsafe { *out_addr.at(bi * rows + r) = acc };
6732 }
6733 }
6734 })
6735 };
6736 dispatch_rows(pool, rows, &run);
6737}
6738
6739#[allow(clippy::too_many_arguments)]
6744fn vbitmatmat(
6745 bytes: &[u8],
6746 offsets: &[usize],
6747 xs_all: &[f32],
6748 b: usize,
6749 rows: usize,
6750 cols: usize,
6751 out: &mut [f32],
6752 pool: Option<&Pool>,
6753) {
6754 debug_assert_eq!(xs_all.len(), b * cols);
6755 debug_assert_eq!(out.len(), b * rows);
6756 debug_assert_eq!(offsets.len(), rows + 1);
6757 let ng = cols / GROUP_SIZE;
6758 let bits = &bytes[..rows];
6759 let sc_off = rows;
6760 let gscale = |r: usize, g: usize| {
6761 let so = (r * ng + g) * 2;
6762 f16_to_f32(u16::from_le_bytes([
6763 bytes[sc_off + so],
6764 bytes[sc_off + so + 1],
6765 ]))
6766 };
6767
6768 let decode_f32 = |r: usize, dst: &mut [f32]| {
6770 let bw = bits[r] as usize;
6771 let l = ((1i32 << (bw - 1)) - 1) as f32;
6772 let data = &bytes[offsets[r]..offsets[r + 1]];
6773 let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
6774 for d in dst.iter_mut() {
6775 while nbits < bw {
6776 acc = (acc << 8) | data[idx] as u64;
6777 idx += 1;
6778 nbits += 8;
6779 }
6780 let u = ((acc >> (nbits - bw)) & ((1u64 << bw) - 1)) as f32;
6781 nbits -= bw;
6782 *d = u - l;
6783 }
6784 };
6785
6786 if a8w8_enabled() {
6787 let acts: Vec<SplitAct> = (0..b)
6788 .map(|bi| split_act(&xs_all[bi * cols..(bi + 1) * cols]))
6789 .collect();
6790 let acts = &acts;
6791 let out_addr = SendMut(out.as_mut_ptr());
6792 let run = move |start: usize, end: usize| {
6793 for r in start..end {
6794 let bw = bits[r] as usize;
6795 if bw == 8 {
6796 ROW_F32.with(|rb| {
6799 let mut buf = rb.borrow_mut();
6800 buf.resize(cols, 0.0);
6801 decode_f32(r, &mut buf);
6802 for bi in 0..b {
6803 let x = &xs_all[bi * cols..(bi + 1) * cols];
6804 let mut dot = 0f32;
6805 for g in 0..ng {
6806 let mut gd = 0f32;
6807 for k in 0..GROUP_SIZE {
6808 gd += buf[g * GROUP_SIZE + k] * x[g * GROUP_SIZE + k];
6809 }
6810 dot += gd * gscale(r, g);
6811 }
6812 unsafe { *out_addr.at(bi * rows + r) = dot };
6814 }
6815 });
6816 continue;
6817 }
6818 let l = (1i32 << (bw - 1)) - 1;
6819 let data = &bytes[offsets[r]..offsets[r + 1]];
6820 ROW_I8.with(|rb| {
6821 let mut buf = rb.borrow_mut();
6822 buf.resize(cols, 0);
6823 #[inline(always)]
6824 fn fill<const B: usize>(data: &[u8], l: i32, buf: &mut [u8]) {
6825 for (blk, chunk) in buf.chunks_exact_mut(8).enumerate() {
6826 let u = unpack8::<B>(&data[blk * B..]);
6827 for k in 0..8 {
6828 chunk[k] = (u[k] - l) as i8 as u8;
6829 }
6830 }
6831 }
6832 match bw {
6833 3 => fill::<3>(data, l, &mut buf),
6834 4 => vbit_fill4(data, &mut buf),
6835 5 => fill::<5>(data, l, &mut buf),
6836 6 => fill::<6>(data, l, &mut buf),
6837 _ => unreachable!("vbit bit-width {bw} (validated at load)"),
6838 }
6839 let mut bi = 0usize;
6840 #[cfg(target_arch = "x86_64")]
6844 if avx2_enabled()
6845 && std::env::var("CMF_X86_BLOCKED")
6846 .map(|v| v != "0")
6847 .unwrap_or(true)
6848 {
6849 while bi + 4 <= acts.len() {
6850 let xs = [
6851 acts[bi].xq.as_slice(),
6852 acts[bi + 1].xq.as_slice(),
6853 acts[bi + 2].xq.as_slice(),
6854 acts[bi + 3].xq.as_slice(),
6855 ];
6856 let sxs = [
6857 acts[bi].sx,
6858 acts[bi + 1].sx,
6859 acts[bi + 2].sx,
6860 acts[bi + 3].sx,
6861 ];
6862 let d = unsafe {
6863 if vnni_tiles_enabled() {
6864 dot_q4b_row_1x4_sx_vnni(
6865 &buf,
6866 &bytes[sc_off..],
6867 r * ng,
6868 ng,
6869 xs,
6870 sxs,
6871 )
6872 } else {
6873 dot_q4b_row_1x4_sx_avx2(
6874 &buf,
6875 &bytes[sc_off..],
6876 r * ng,
6877 ng,
6878 xs,
6879 sxs,
6880 )
6881 }
6882 };
6883 for k in 0..4 {
6884 let act = &acts[bi + k];
6885 let mut dot = d[k];
6886 for &(j, xv) in &act.outliers {
6887 dot += (buf[j] as i8) as f32 * gscale(r, j / GROUP_SIZE) * xv;
6888 }
6889 unsafe { *out_addr.at((bi + k) * rows + r) = dot };
6891 }
6892 bi += 4;
6893 }
6894 }
6895 while bi < acts.len() {
6896 let act = &acts[bi];
6897 let mut dot = 0f32;
6898 for g in 0..ng {
6899 let d = dot_i8_i8(
6900 &buf[g * GROUP_SIZE..(g + 1) * GROUP_SIZE],
6901 &act.xq[g * GROUP_SIZE..(g + 1) * GROUP_SIZE],
6902 ) as f32
6903 * act.sx;
6904 dot += d * gscale(r, g);
6905 }
6906 for &(j, xv) in &act.outliers {
6907 dot += (buf[j] as i8) as f32 * gscale(r, j / GROUP_SIZE) * xv;
6908 }
6909 unsafe { *out_addr.at(bi * rows + r) = dot };
6911 bi += 1;
6912 }
6913 });
6914 }
6915 };
6916 dispatch_rows(pool, rows, &run);
6917 return;
6918 }
6919
6920 let out_addr = SendMut(out.as_mut_ptr());
6921 let run = move |start: usize, end: usize| {
6922 ROW_F32.with(|rb| {
6923 let mut buf = rb.borrow_mut();
6924 buf.resize(cols, 0.0);
6925 for r in start..end {
6926 decode_f32(r, &mut buf);
6927 for bi in 0..b {
6928 let x = &xs_all[bi * cols..(bi + 1) * cols];
6929 let mut dot = 0f32;
6930 for g in 0..ng {
6931 let mut gd = 0f32;
6932 for k in 0..GROUP_SIZE {
6933 gd += buf[g * GROUP_SIZE + k] * x[g * GROUP_SIZE + k];
6934 }
6935 dot += gd * gscale(r, g);
6936 }
6937 unsafe { *out_addr.at(bi * rows + r) = dot };
6939 }
6940 }
6941 })
6942 };
6943 dispatch_rows(pool, rows, &run);
6944}
6945
6946pub(crate) fn gpu_batch_job<'a>(
6950 t: &'a QTensor,
6951 x: &[f32],
6952) -> Option<(std::sync::Arc<CmfModel>, crate::gpu::BatchJob<'a>)> {
6953 match t {
6954 QTensor::Mapped {
6955 model,
6956 idx,
6957 dtype: dt @ (TensorDtype::Q8Row | TensorDtype::Q8_2f),
6958 rows,
6959 cols,
6960 row_scale,
6961 col_field,
6962 ..
6963 } => Some((
6964 model.clone(),
6965 crate::gpu::BatchJob {
6966 idx: *idx,
6967 rows: *rows,
6968 cols: *cols,
6969 row_scale,
6970 xs: prescale(x, col_field, *dt).into_owned(),
6971 layout: crate::gpu::BatchLayout::Q8,
6972 },
6973 )),
6974 QTensor::Mapped {
6976 model,
6977 idx,
6978 dtype: TensorDtype::Q1,
6979 rows,
6980 cols,
6981 ..
6982 } => Some((
6983 model.clone(),
6984 crate::gpu::BatchJob {
6985 idx: *idx,
6986 rows: *rows,
6987 cols: *cols,
6988 row_scale: &[],
6989 xs: x.to_vec(),
6990 layout: crate::gpu::BatchLayout::Q1,
6991 },
6992 )),
6993 _ => None,
6994 }
6995}
6996
6997thread_local! {
6998 static PRESCALE_BUF1: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
6999 static PRESCALE_BUF2: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
7000}
7001
7002pub(crate) fn prescale<'a>(
7003 x: &'a [f32],
7004 col_field: &[f32],
7005 dtype: TensorDtype,
7006) -> std::borrow::Cow<'a, [f32]> {
7007 if dtype == TensorDtype::Q8_2f {
7008 x.iter().zip(col_field).map(|(a, c)| a * c).collect()
7009 } else {
7010 std::borrow::Cow::Borrowed(x)
7011 }
7012}
7013
7014pub(crate) fn prescale_with<R, F: FnOnce(&[f32]) -> R>(
7017 x: &[f32],
7018 col_field: &[f32],
7019 dtype: TensorDtype,
7020 buf_id: u8,
7021 f: F,
7022) -> R {
7023 if dtype == TensorDtype::Q8_2f {
7024 if buf_id == 1 {
7025 PRESCALE_BUF1.with(|b| {
7026 let mut buf = b.borrow_mut();
7027 buf.clear();
7028 buf.extend(x.iter().zip(col_field).map(|(a, c)| a * c));
7029 f(&buf)
7030 })
7031 } else {
7032 PRESCALE_BUF2.with(|b| {
7033 let mut buf = b.borrow_mut();
7034 buf.clear();
7035 buf.extend(x.iter().zip(col_field).map(|(a, c)| a * c));
7036 f(&buf)
7037 })
7038 }
7039 } else {
7040 f(x)
7041 }
7042}
7043
7044#[cfg(target_arch = "x86_64")]
7049pub(crate) fn avx2_enabled() -> bool {
7050 use std::sync::OnceLock;
7051 static ON: OnceLock<bool> = OnceLock::new();
7052 *ON.get_or_init(|| {
7053 std::env::var("CMF_AVX2").map(|v| v != "0").unwrap_or(true)
7054 && std::arch::is_x86_feature_detected!("avx2")
7055 && std::arch::is_x86_feature_detected!("fma")
7056 })
7057}
7058
7059#[cfg(target_arch = "x86_64")]
7064fn avx2_a8w8_enabled() -> bool {
7065 use std::sync::OnceLock;
7066 static ON: OnceLock<bool> = OnceLock::new();
7067 *ON.get_or_init(|| {
7068 avx2_enabled() && std::env::var("CMF_SDOT").map(|v| v != "0").unwrap_or(true)
7069 })
7070}
7071
7072#[inline]
7076pub(crate) fn a8w8_enabled() -> bool {
7077 #[cfg(target_arch = "aarch64")]
7078 {
7079 sdot_enabled()
7080 }
7081 #[cfg(target_arch = "x86_64")]
7082 {
7083 avx2_a8w8_enabled()
7084 }
7085 #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
7086 {
7087 false
7088 }
7089}
7090
7091#[inline]
7094#[allow(unreachable_code)]
7095fn dot_i8_i8(w: &[u8], xq: &[i8]) -> i32 {
7096 #[cfg(target_arch = "aarch64")]
7097 unsafe {
7098 return dot_i8_sdot(w, xq);
7099 }
7100 #[cfg(target_arch = "x86_64")]
7101 unsafe {
7102 if avx512vnni_enabled() {
7103 return dot_i8_i8_vnni(w, xq);
7104 }
7105 return dot_i8_i8_avx2(w, xq);
7106 }
7107 w.iter()
7108 .zip(xq)
7109 .map(|(&a, &b)| (a as i8) as i32 * b as i32)
7110 .sum()
7111}
7112
7113#[cfg(target_arch = "x86_64")]
7117fn avx512vnni_enabled() -> bool {
7118 use std::sync::OnceLock;
7119 static ON: OnceLock<bool> = OnceLock::new();
7120 *ON.get_or_init(|| {
7121 std::env::var("CMF_AVX512")
7122 .map(|v| v != "0")
7123 .unwrap_or(true)
7124 && std::arch::is_x86_feature_detected!("avx512f")
7125 && std::arch::is_x86_feature_detected!("avx512bw")
7126 && std::arch::is_x86_feature_detected!("avx512vl")
7127 && std::arch::is_x86_feature_detected!("avx512vnni")
7128 })
7129}
7130
7131#[cfg(target_arch = "x86_64")]
7139fn vnni_tiles_enabled() -> bool {
7140 use std::sync::OnceLock;
7141 static ON: OnceLock<bool> = OnceLock::new();
7142 *ON.get_or_init(|| {
7143 std::env::var("CMF_VNNI_TILES")
7144 .map(|v| v != "0")
7145 .unwrap_or(true)
7146 && avx512vnni_enabled()
7147 })
7148}
7149
7150#[cfg(target_arch = "x86_64")]
7155#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
7156#[inline]
7157unsafe fn dpbusd_hsum(aw: core::arch::x86_64::__m256i, xs: core::arch::x86_64::__m256i) -> i32 {
7158 unsafe {
7160 use core::arch::x86_64::*;
7161 let d = _mm256_dpbusd_epi32(_mm256_setzero_si256(), aw, xs);
7162 let hi128 = _mm256_extracti128_si256::<1>(d);
7163 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
7164 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
7165 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
7166 _mm_cvtsi128_si32(s32)
7167 }
7168}
7169
7170#[cfg(target_arch = "x86_64")]
7175#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
7176unsafe fn dot_i8_i8_vnni(w: &[u8], xq: &[i8]) -> i32 {
7177 unsafe {
7179 use core::arch::x86_64::*;
7180 let n = w.len();
7181 let mut j = 0usize;
7182 let mut total: i32;
7183 {
7188 #[inline(always)]
7189 unsafe fn step(
7190 w: *const u8,
7191 x: *const i8,
7192 acc: core::arch::x86_64::__m512i,
7193 ) -> core::arch::x86_64::__m512i {
7194 unsafe {
7195 use core::arch::x86_64::*;
7196 let wv = _mm512_loadu_si512(w as *const _);
7197 let xv = _mm512_loadu_si512(x as *const _);
7198 let aw = _mm512_abs_epi8(wv);
7199 let neg = _mm512_movepi8_mask(wv);
7200 let sx = _mm512_mask_sub_epi8(xv, neg, _mm512_setzero_si512(), xv);
7201 _mm512_dpbusd_epi32(acc, aw, sx)
7202 }
7203 }
7204 let (mut a0, mut a1, mut a2, mut a3) = (
7205 _mm512_setzero_si512(),
7206 _mm512_setzero_si512(),
7207 _mm512_setzero_si512(),
7208 _mm512_setzero_si512(),
7209 );
7210 while j + 256 <= n {
7211 a0 = step(w.as_ptr().add(j), xq.as_ptr().add(j), a0);
7212 a1 = step(w.as_ptr().add(j + 64), xq.as_ptr().add(j + 64), a1);
7213 a2 = step(w.as_ptr().add(j + 128), xq.as_ptr().add(j + 128), a2);
7214 a3 = step(w.as_ptr().add(j + 192), xq.as_ptr().add(j + 192), a3);
7215 j += 256;
7216 }
7217 while j + 64 <= n {
7218 a0 = step(w.as_ptr().add(j), xq.as_ptr().add(j), a0);
7219 j += 64;
7220 }
7221 let s01 = _mm512_add_epi32(a0, a1);
7222 let s23 = _mm512_add_epi32(a2, a3);
7223 total = _mm512_reduce_add_epi32(_mm512_add_epi32(s01, s23));
7224 }
7225 if j + 32 <= n {
7227 let wv = _mm256_loadu_si256(w.as_ptr().add(j) as *const __m256i);
7228 let xv = _mm256_loadu_si256(xq.as_ptr().add(j) as *const __m256i);
7229 let d = _mm256_dpbusd_epi32(
7230 _mm256_setzero_si256(),
7231 _mm256_abs_epi8(wv),
7232 _mm256_sign_epi8(xv, wv),
7233 );
7234 let hi128 = _mm256_extracti128_si256::<1>(d);
7235 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
7236 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
7237 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
7238 total += _mm_cvtsi128_si32(s32);
7239 j += 32;
7240 }
7241 while j < n {
7242 total += (w[j] as i8) as i32 * xq[j] as i32;
7243 j += 1;
7244 }
7245 total
7246 }
7247}
7248
7249#[cfg(target_arch = "x86_64")]
7251#[target_feature(enable = "avx2,fma")]
7252unsafe fn dot_i8_f32_avx2(w: &[u8], x: &[f32]) -> f32 {
7253 unsafe {
7255 use core::arch::x86_64::*;
7256 let n = x.len();
7257 let wp = w.as_ptr();
7258 let xp = x.as_ptr();
7259 let (mut a0, mut a1) = (_mm256_setzero_ps(), _mm256_setzero_ps());
7260 let mut j = 0usize;
7261 while j + 16 <= n {
7262 let wb = _mm_loadu_si128(wp.add(j) as *const __m128i);
7263 let lo = _mm256_cvtepi8_epi32(wb);
7264 let hi = _mm256_cvtepi8_epi32(_mm_srli_si128::<8>(wb));
7265 a0 = _mm256_fmadd_ps(_mm256_cvtepi32_ps(lo), _mm256_loadu_ps(xp.add(j)), a0);
7266 a1 = _mm256_fmadd_ps(_mm256_cvtepi32_ps(hi), _mm256_loadu_ps(xp.add(j + 8)), a1);
7267 j += 16;
7268 }
7269 let acc = _mm256_add_ps(a0, a1);
7270 let hi128 = _mm256_extractf128_ps::<1>(acc);
7271 let s128 = _mm_add_ps(_mm256_castps256_ps128(acc), hi128);
7272 let s64 = _mm_add_ps(s128, _mm_movehl_ps(s128, s128));
7273 let s32 = _mm_add_ss(s64, _mm_shuffle_ps::<1>(s64, s64));
7274 let mut sum = _mm_cvtss_f32(s32);
7275 while j < n {
7276 sum += (*wp.add(j) as i8) as f32 * *xp.add(j);
7277 j += 1;
7278 }
7279 sum
7280 }
7281}
7282
7283#[cfg(target_arch = "x86_64")]
7288#[target_feature(enable = "avx2")]
7289unsafe fn dot_i8_i8_avx2(w: &[u8], xq: &[i8]) -> i32 {
7290 unsafe {
7292 use core::arch::x86_64::*;
7293 let n = w.len();
7294 let ones = _mm256_set1_epi16(1);
7295 let mut acc = _mm256_setzero_si256();
7296 let mut j = 0usize;
7297 while j + 32 <= n {
7298 let wv = _mm256_loadu_si256(w.as_ptr().add(j) as *const __m256i);
7299 let xv = _mm256_loadu_si256(xq.as_ptr().add(j) as *const __m256i);
7300 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(wv), _mm256_sign_epi8(xv, wv));
7301 acc = _mm256_add_epi32(acc, _mm256_madd_epi16(p16, ones));
7302 j += 32;
7303 }
7304 let hi128 = _mm256_extracti128_si256::<1>(acc);
7305 let s128 = _mm_add_epi32(_mm256_castsi256_si128(acc), hi128);
7306 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
7307 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
7308 let mut s = _mm_cvtsi128_si32(s32);
7309 while j < n {
7310 s += (w[j] as i8) as i32 * xq[j] as i32;
7311 j += 1;
7312 }
7313 s
7314 }
7315}
7316
7317#[cfg(target_arch = "aarch64")]
7321#[target_feature(enable = "neon,i8mm")]
7322unsafe fn dot_i8_smmla_2x4(w0: &[u8], w1: &[u8], xs: [&[i8]; 4]) -> [[i32; 4]; 2] {
7323 unsafe {
7325 use core::arch::aarch64::*;
7326 use core::arch::asm;
7327 let n = w0.len();
7328 let w0p = w0.as_ptr() as *const i8;
7329 let w1p = w1.as_ptr() as *const i8;
7330 let mut acc01 = vdupq_n_s32(0);
7333 let mut acc23 = vdupq_n_s32(0);
7334 let mut i = 0usize;
7335 while i + 8 <= n {
7336 let wa = vcombine_s8(vld1_s8(w0p.add(i)), vld1_s8(w1p.add(i)));
7337 let xb01 = vcombine_s8(
7338 vld1_s8(xs[0].as_ptr().add(i)),
7339 vld1_s8(xs[1].as_ptr().add(i)),
7340 );
7341 let xb23 = vcombine_s8(
7342 vld1_s8(xs[2].as_ptr().add(i)),
7343 vld1_s8(xs[3].as_ptr().add(i)),
7344 );
7345 asm!(
7346 "smmla {a01:v}.4s, {w:v}.16b, {x01:v}.16b",
7347 "smmla {a23:v}.4s, {w:v}.16b, {x23:v}.16b",
7348 a01 = inout(vreg) acc01, a23 = inout(vreg) acc23,
7349 w = in(vreg) wa, x01 = in(vreg) xb01, x23 = in(vreg) xb23,
7350 options(pure, nomem, nostack),
7351 );
7352 i += 8;
7353 }
7354 let mut out = [[0i32; 4]; 2];
7355 let a01: [i32; 4] = core::mem::transmute(acc01);
7356 let a23: [i32; 4] = core::mem::transmute(acc23);
7357 out[0][0] = a01[0];
7358 out[0][1] = a01[1];
7359 out[1][0] = a01[2];
7360 out[1][1] = a01[3];
7361 out[0][2] = a23[0];
7362 out[0][3] = a23[1];
7363 out[1][2] = a23[2];
7364 out[1][3] = a23[3];
7365 if i < n {
7366 for (k, x) in xs.iter().enumerate() {
7367 for j in i..n {
7368 out[0][k] += (w0[j] as i8) as i32 * x[j] as i32;
7369 out[1][k] += (w1[j] as i8) as i32 * x[j] as i32;
7370 }
7371 }
7372 }
7373 out
7374 }
7375}
7376
7377#[cfg(target_arch = "aarch64")]
7381#[target_feature(enable = "neon,dotprod")]
7382unsafe fn dot_i8_sdot_2x4(w0: &[u8], w1: &[u8], xs: [&[i8]; 4]) -> [[i32; 4]; 2] {
7383 unsafe {
7385 use core::arch::aarch64::*;
7386 use core::arch::asm;
7387 let n = w0.len();
7388 let w0p = w0.as_ptr() as *const i8;
7389 let w1p = w1.as_ptr() as *const i8;
7390 let mut acc = [[vdupq_n_s32(0); 4]; 2];
7391 let mut i = 0usize;
7392 while i + 16 <= n {
7393 let wv0 = vld1q_s8(w0p.add(i));
7394 let wv1 = vld1q_s8(w1p.add(i));
7395 for (k, x) in xs.iter().enumerate() {
7396 let xv = vld1q_s8(x.as_ptr().add(i));
7397 let (mut a0, mut a1) = (acc[0][k], acc[1][k]);
7398 asm!(
7399 "sdot {a0:v}.4s, {w0:v}.16b, {x:v}.16b",
7400 "sdot {a1:v}.4s, {w1:v}.16b, {x:v}.16b",
7401 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
7402 w0 = in(vreg) wv0, w1 = in(vreg) wv1, x = in(vreg) xv,
7403 options(pure, nomem, nostack),
7404 );
7405 acc[0][k] = a0;
7406 acc[1][k] = a1;
7407 }
7408 i += 16;
7409 }
7410 let mut out = [[0i32; 4]; 2];
7411 for r in 0..2 {
7412 for k in 0..4 {
7413 out[r][k] = vaddvq_s32(acc[r][k]);
7414 }
7415 }
7416 if i < n {
7417 for (k, x) in xs.iter().enumerate() {
7418 for j in i..n {
7419 out[0][k] += (w0[j] as i8) as i32 * x[j] as i32;
7420 out[1][k] += (w1[j] as i8) as i32 * x[j] as i32;
7421 }
7422 }
7423 }
7424 out
7425 }
7426}
7427
7428#[cfg(target_arch = "x86_64")]
7434#[target_feature(enable = "avx2")]
7435unsafe fn dot_i8_i8_avx2_2x4(w0: &[u8], w1: &[u8], xs: [&[i8]; 4]) -> [[i32; 4]; 2] {
7436 unsafe {
7438 use core::arch::x86_64::*;
7439 let n = w0.len();
7440 let ones = _mm256_set1_epi16(1);
7441 let mut acc = [[_mm256_setzero_si256(); 4]; 2];
7442 let mut j = 0usize;
7443 while j + 32 <= n {
7444 let wv0 = _mm256_loadu_si256(w0.as_ptr().add(j) as *const __m256i);
7445 let wv1 = _mm256_loadu_si256(w1.as_ptr().add(j) as *const __m256i);
7446 let aw0 = _mm256_abs_epi8(wv0);
7447 let aw1 = _mm256_abs_epi8(wv1);
7448 for (k, x) in xs.iter().enumerate() {
7449 let xv = _mm256_loadu_si256(x.as_ptr().add(j) as *const __m256i);
7450 let p0 = _mm256_maddubs_epi16(aw0, _mm256_sign_epi8(xv, wv0));
7451 acc[0][k] = _mm256_add_epi32(acc[0][k], _mm256_madd_epi16(p0, ones));
7452 let p1 = _mm256_maddubs_epi16(aw1, _mm256_sign_epi8(xv, wv1));
7453 acc[1][k] = _mm256_add_epi32(acc[1][k], _mm256_madd_epi16(p1, ones));
7454 }
7455 j += 32;
7456 }
7457 let mut out = [[0i32; 4]; 2];
7458 for r in 0..2 {
7459 for k in 0..4 {
7460 let a = acc[r][k];
7461 let hi128 = _mm256_extracti128_si256::<1>(a);
7462 let s128 = _mm_add_epi32(_mm256_castsi256_si128(a), hi128);
7463 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
7464 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
7465 out[r][k] = _mm_cvtsi128_si32(s32);
7466 }
7467 }
7468 if j < n {
7469 for (k, x) in xs.iter().enumerate() {
7470 for i in j..n {
7471 out[0][k] += (w0[i] as i8) as i32 * x[i] as i32;
7472 out[1][k] += (w1[i] as i8) as i32 * x[i] as i32;
7473 }
7474 }
7475 }
7476 out
7477 }
7478}
7479
7480#[cfg(target_arch = "x86_64")]
7485#[inline]
7486fn row_dot_avx2(row: &[u8], act: &SplitAct) -> f32 {
7487 let dot = if avx512vnni_enabled() && row.len() >= 64 {
7488 (unsafe { dot_u8p128_i8_vnni(row, &act.xq) }) - 128 * act.xsum
7489 } else {
7490 unsafe { dot_i8_i8_avx2(row, &act.xq) }
7491 };
7492 let mut acc = dot as f32 * act.sx;
7493 for &(j, xv) in &act.outliers {
7494 acc += (row[j] as i8) as f32 * xv;
7495 }
7496 acc
7497}
7498
7499#[cfg(target_arch = "x86_64")]
7503#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
7504unsafe fn dot_u8p128_i8_vnni(w: &[u8], xq: &[i8]) -> i32 {
7505 unsafe {
7507 use core::arch::x86_64::*;
7508 let n = w.len();
7509 let flip = _mm512_set1_epi8(-128); #[inline(always)]
7511 unsafe fn step(
7512 w: *const u8,
7513 x: *const i8,
7514 flip: core::arch::x86_64::__m512i,
7515 acc: core::arch::x86_64::__m512i,
7516 ) -> core::arch::x86_64::__m512i {
7517 unsafe {
7518 use core::arch::x86_64::*;
7519 let wv = _mm512_xor_si512(_mm512_loadu_si512(w as *const _), flip);
7520 _mm512_dpbusd_epi32(acc, wv, _mm512_loadu_si512(x as *const _))
7521 }
7522 }
7523 let (mut a0, mut a1, mut a2, mut a3) = (
7524 _mm512_setzero_si512(),
7525 _mm512_setzero_si512(),
7526 _mm512_setzero_si512(),
7527 _mm512_setzero_si512(),
7528 );
7529 let mut j = 0usize;
7530 while j + 256 <= n {
7531 a0 = step(w.as_ptr().add(j), xq.as_ptr().add(j), flip, a0);
7532 a1 = step(w.as_ptr().add(j + 64), xq.as_ptr().add(j + 64), flip, a1);
7533 a2 = step(w.as_ptr().add(j + 128), xq.as_ptr().add(j + 128), flip, a2);
7534 a3 = step(w.as_ptr().add(j + 192), xq.as_ptr().add(j + 192), flip, a3);
7535 j += 256;
7536 }
7537 while j + 64 <= n {
7538 a0 = step(w.as_ptr().add(j), xq.as_ptr().add(j), flip, a0);
7539 j += 64;
7540 }
7541 let mut total = _mm512_reduce_add_epi32(_mm512_add_epi32(
7542 _mm512_add_epi32(a0, a1),
7543 _mm512_add_epi32(a2, a3),
7544 ));
7545 while j < n {
7547 total += ((w[j] ^ 0x80) as i32) * xq[j] as i32;
7548 j += 1;
7549 }
7550 total
7551 }
7552}
7553
7554#[cfg(target_arch = "x86_64")]
7560#[target_feature(enable = "avx2")]
7561unsafe fn dot_q4_row_avx2(packed: &[u8], scales: &[u8], g0: usize, gpr: usize, xq: &[i8]) -> f32 {
7562 unsafe {
7565 use core::arch::x86_64::*;
7566 let lomask = _mm_set1_epi8(0x0F);
7567 let eight = _mm256_set1_epi8(8);
7568 let ones = _mm256_set1_epi16(1);
7569 let mut acc = 0f32;
7570 for gi in 0..gpr {
7571 let g = g0 + gi;
7572 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
7573 let b = _mm_loadu_si128(packed.as_ptr().add(g * 16) as *const __m128i);
7574 let lo = _mm_and_si128(b, lomask);
7575 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), lomask);
7576 let w = _mm256_sub_epi8(
7577 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
7578 eight,
7579 );
7580 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
7581 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(w), _mm256_sign_epi8(x, w));
7582 let d = _mm256_madd_epi16(p16, ones);
7583 let hi128 = _mm256_extracti128_si256::<1>(d);
7584 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
7585 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
7586 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
7587 acc += _mm_cvtsi128_si32(s32) as f32 * s;
7588 }
7589 acc
7590 }
7591}
7592
7593#[cfg(target_arch = "x86_64")]
7596#[target_feature(enable = "avx2")]
7597unsafe fn dot_q4_row_avx2_2(
7598 packed: &[u8],
7599 scales: &[u8],
7600 g0: usize,
7601 gpr: usize,
7602 xq1: &[i8],
7603 xq2: &[i8],
7604) -> (f32, f32) {
7605 unsafe {
7607 use core::arch::x86_64::*;
7608 let lomask = _mm_set1_epi8(0x0F);
7609 let eight = _mm256_set1_epi8(8);
7610 let ones = _mm256_set1_epi16(1);
7611 let (mut acc1, mut acc2) = (0f32, 0f32);
7612 #[inline(always)]
7613 unsafe fn hsum(d: core::arch::x86_64::__m256i) -> i32 {
7614 unsafe {
7615 use core::arch::x86_64::*;
7616 let hi128 = _mm256_extracti128_si256::<1>(d);
7617 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
7618 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
7619 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
7620 _mm_cvtsi128_si32(s32)
7621 }
7622 }
7623 for gi in 0..gpr {
7624 let g = g0 + gi;
7625 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
7626 let b = _mm_loadu_si128(packed.as_ptr().add(g * 16) as *const __m128i);
7627 let lo = _mm_and_si128(b, lomask);
7628 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), lomask);
7629 let w = _mm256_sub_epi8(
7630 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
7631 eight,
7632 );
7633 let aw = _mm256_abs_epi8(w);
7634 let x1 = _mm256_loadu_si256(xq1.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
7635 let x2 = _mm256_loadu_si256(xq2.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
7636 let d1 = _mm256_madd_epi16(_mm256_maddubs_epi16(aw, _mm256_sign_epi8(x1, w)), ones);
7637 let d2 = _mm256_madd_epi16(_mm256_maddubs_epi16(aw, _mm256_sign_epi8(x2, w)), ones);
7638 acc1 += hsum(d1) as f32 * s;
7639 acc2 += hsum(d2) as f32 * s;
7640 }
7641 (acc1, acc2)
7642 }
7643}
7644
7645#[cfg(target_arch = "x86_64")]
7647fn q8_range_avx2(
7648 q: &[u8],
7649 row_scale: &[f32],
7650 act: &SplitAct,
7651 cols: usize,
7652 out_addr: SendMut,
7653 start: usize,
7654 end: usize,
7655) {
7656 for o in start..end {
7657 let v = row_dot_avx2(&q[o * cols..(o + 1) * cols], act) * row_scale[o];
7658 unsafe { *out_addr.at(o) = v };
7660 }
7661}
7662
7663#[cfg(target_arch = "x86_64")]
7665#[allow(clippy::too_many_arguments)]
7666fn q8_range2_avx2(
7667 q: &[u8],
7668 row_scale: &[f32],
7669 a1: &SplitAct,
7670 a2: &SplitAct,
7671 cols: usize,
7672 p1: SendMut,
7673 p2: SendMut,
7674 start: usize,
7675 end: usize,
7676) {
7677 for o in start..end {
7678 let row = &q[o * cols..(o + 1) * cols];
7679 unsafe {
7681 *p1.at(o) = row_dot_avx2(row, a1) * row_scale[o];
7682 *p2.at(o) = row_dot_avx2(row, a2) * row_scale[o];
7683 }
7684 }
7685}
7686
7687#[cfg(target_arch = "aarch64")]
7698fn i8mm_enabled() -> bool {
7699 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7700 *ON.get_or_init(|| {
7701 std::env::var("CMF_I8MM").map(|v| v == "1").unwrap_or(false)
7702 && std::arch::is_aarch64_feature_detected!("i8mm")
7703 })
7704}
7705
7706#[cfg_attr(not(target_arch = "aarch64"), allow(dead_code))]
7710fn sdot_enabled() -> bool {
7711 use std::sync::OnceLock;
7712 static ON: OnceLock<bool> = OnceLock::new();
7713 *ON.get_or_init(|| {
7714 let want = std::env::var("CMF_SDOT").map(|v| v != "0").unwrap_or(true);
7715 if !want {
7716 return false;
7717 }
7718
7719 #[cfg(target_arch = "aarch64")]
7720 {
7721 if std::arch::is_aarch64_feature_detected!("dotprod") {
7722 return true;
7723 }
7724 #[cfg(target_os = "android")]
7725 {
7726 if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") {
7727 if cpuinfo.lines().any(|l| {
7728 (l.starts_with("Features") || l.starts_with("features"))
7729 && l.contains("asimddp")
7730 }) {
7731 return true;
7732 }
7733 }
7734 }
7735 false
7736 }
7737 #[cfg(not(target_arch = "aarch64"))]
7738 {
7739 false
7740 }
7741 })
7742}
7743
7744struct SplitAct {
7749 xq: Vec<i8>,
7750 sx: f32,
7751 outliers: Vec<(usize, f32)>,
7752 #[cfg_attr(not(target_arch = "x86_64"), allow(dead_code))]
7755 xsum: i32,
7756}
7757
7758thread_local! {
7759 static XQ_FREE: std::cell::RefCell<Vec<Vec<i8>>> =
7762 const { std::cell::RefCell::new(Vec::new()) };
7763}
7764
7765impl Drop for SplitAct {
7766 fn drop(&mut self) {
7767 let buf = std::mem::take(&mut self.xq);
7768 if buf.capacity() > 0 {
7769 XQ_FREE.with(|f| {
7770 let mut f = f.borrow_mut();
7771 if f.len() < 16 {
7772 f.push(buf);
7773 }
7774 });
7775 }
7776 }
7777}
7778
7779fn split_act(x: &[f32]) -> SplitAct {
7780 let n = x.len();
7781 let rms = (x.iter().map(|&v| (v * v) as f64).sum::<f64>() / n.max(1) as f64).sqrt() as f32;
7782 let thr = 8.0 * rms;
7783 let mut outliers: Vec<(usize, f32)> = Vec::new();
7787 let mut amax = 0f32;
7788 for (j, &v) in x.iter().enumerate() {
7789 let a = v.abs();
7790 if a > thr {
7791 outliers.push((j, v));
7792 } else if a > amax {
7793 amax = a;
7794 }
7795 }
7796 let sx = if amax > 0.0 { amax / 127.0 } else { 1.0 };
7797 let inv = 1.0 / sx;
7798 let mut xq = XQ_FREE.with(|f| f.borrow_mut().pop()).unwrap_or_default();
7799 xq.clear();
7800 xq.reserve(n);
7801 if outliers.is_empty() {
7802 xq.extend(
7803 x.iter()
7804 .map(|&v| (v * inv).round().clamp(-127.0, 127.0) as i8),
7805 );
7806 } else {
7807 xq.extend(x.iter().map(|&v| {
7809 if v.abs() > thr {
7810 0
7811 } else {
7812 (v * inv).round().clamp(-127.0, 127.0) as i8
7813 }
7814 }));
7815 }
7816 let xsum = xq.iter().map(|&v| v as i32).sum();
7817 SplitAct {
7818 xq,
7819 sx,
7820 outliers,
7821 xsum,
7822 }
7823}
7824
7825fn split_act_q8_2f(x: &[f32], col: &[f32]) -> SplitAct {
7826 let n = x.len();
7827 let rms = (x
7828 .iter()
7829 .zip(col)
7830 .map(|(&a, &c)| {
7831 let v = a * c;
7832 (v * v) as f64
7833 })
7834 .sum::<f64>()
7835 / n.max(1) as f64)
7836 .sqrt() as f32;
7837 let thr = 8.0 * rms;
7838
7839 let mut outliers = Vec::new();
7840 let mut amax = 0f32;
7841 for (j, (&a, &c)) in x.iter().zip(col).enumerate() {
7842 let v = a * c;
7843 let s = v.abs();
7844 if s > thr {
7845 outliers.push((j, v));
7846 } else if s > amax {
7847 amax = s;
7848 }
7849 }
7850
7851 let sx = if amax > 0.0 { amax / 127.0 } else { 1.0 };
7852 let inv = 1.0 / sx;
7853 let mut xq = XQ_FREE.with(|f| f.borrow_mut().pop()).unwrap_or_default();
7854 xq.clear();
7855 xq.reserve(n);
7856 if outliers.is_empty() {
7857 xq.extend(
7858 x.iter()
7859 .zip(col)
7860 .map(|(&a, &c)| ((a * c) * inv).round().clamp(-127.0, 127.0) as i8),
7861 );
7862 } else {
7863 xq.extend(x.iter().zip(col).map(|(&a, &c)| {
7864 let v = a * c;
7865 if v.abs() > thr {
7866 0
7867 } else {
7868 (v * inv).round().clamp(-127.0, 127.0) as i8
7869 }
7870 }));
7871 }
7872 let xsum = xq.iter().map(|&v| v as i32).sum();
7873 SplitAct {
7874 xq,
7875 sx,
7876 outliers,
7877 xsum,
7878 }
7879}
7880
7881#[cfg(target_arch = "aarch64")]
7884#[target_feature(enable = "neon,dotprod")]
7885unsafe fn dot_i8_sdot(w: &[u8], xq: &[i8]) -> i32 {
7886 unsafe {
7888 use core::arch::aarch64::*;
7889 use core::arch::asm;
7890 let wp = w.as_ptr() as *const i8;
7891 let n = w.len();
7892 let (mut a0, mut a1, mut a2, mut a3) = (
7893 vdupq_n_s32(0),
7894 vdupq_n_s32(0),
7895 vdupq_n_s32(0),
7896 vdupq_n_s32(0),
7897 );
7898 let mut i = 0;
7899 while i + 64 <= n {
7900 let (w0, x0) = (vld1q_s8(wp.add(i)), vld1q_s8(xq.as_ptr().add(i)));
7901 let (w1, x1) = (vld1q_s8(wp.add(i + 16)), vld1q_s8(xq.as_ptr().add(i + 16)));
7902 let (w2, x2) = (vld1q_s8(wp.add(i + 32)), vld1q_s8(xq.as_ptr().add(i + 32)));
7903 let (w3, x3) = (vld1q_s8(wp.add(i + 48)), vld1q_s8(xq.as_ptr().add(i + 48)));
7904 asm!(
7905 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
7906 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
7907 "sdot {a2:v}.4s, {w2:v}.16b, {x2:v}.16b",
7908 "sdot {a3:v}.4s, {w3:v}.16b, {x3:v}.16b",
7909 a0 = inout(vreg) a0, a1 = inout(vreg) a1, a2 = inout(vreg) a2, a3 = inout(vreg) a3,
7910 w0 = in(vreg) w0, x0 = in(vreg) x0, w1 = in(vreg) w1, x1 = in(vreg) x1,
7911 w2 = in(vreg) w2, x2 = in(vreg) x2, w3 = in(vreg) w3, x3 = in(vreg) x3,
7912 options(pure, nomem, nostack),
7913 );
7914 i += 64;
7915 }
7916 while i + 16 <= n {
7917 let (wv, xv) = (vld1q_s8(wp.add(i)), vld1q_s8(xq.as_ptr().add(i)));
7918 asm!("sdot {a:v}.4s, {w:v}.16b, {x:v}.16b",
7919 a = inout(vreg) a0, w = in(vreg) wv, x = in(vreg) xv, options(pure, nomem, nostack));
7920 i += 16;
7921 }
7922 let mut s = vaddvq_s32(vaddq_s32(vaddq_s32(a0, a1), vaddq_s32(a2, a3)));
7923 while i < n {
7924 s += (*wp.add(i)) as i32 * xq[i] as i32;
7925 i += 1;
7926 }
7927 s
7928 }
7929}
7930
7931#[cfg(target_arch = "aarch64")]
7935#[target_feature(enable = "neon,dotprod")]
7936unsafe fn dot_i8_sdot_4rows(w0: &[u8], w1: &[u8], w2: &[u8], w3: &[u8], xq: &[i8]) -> [i32; 4] {
7937 unsafe {
7939 use core::arch::aarch64::*;
7940 use core::arch::asm;
7941 let n = xq.len();
7942 let px = xq.as_ptr();
7943 let (p0, p1, p2, p3) = (
7944 w0.as_ptr() as *const i8,
7945 w1.as_ptr() as *const i8,
7946 w2.as_ptr() as *const i8,
7947 w3.as_ptr() as *const i8,
7948 );
7949 let (mut a0, mut a1, mut a2, mut a3) = (
7950 vdupq_n_s32(0),
7951 vdupq_n_s32(0),
7952 vdupq_n_s32(0),
7953 vdupq_n_s32(0),
7954 );
7955 let mut i = 0;
7956 while i + 16 <= n {
7957 let x = vld1q_s8(px.add(i));
7958 let v0 = vld1q_s8(p0.add(i));
7959 let v1 = vld1q_s8(p1.add(i));
7960 let v2 = vld1q_s8(p2.add(i));
7961 let v3 = vld1q_s8(p3.add(i));
7962 asm!(
7963 "sdot {a0:v}.4s, {v0:v}.16b, {x:v}.16b",
7964 "sdot {a1:v}.4s, {v1:v}.16b, {x:v}.16b",
7965 "sdot {a2:v}.4s, {v2:v}.16b, {x:v}.16b",
7966 "sdot {a3:v}.4s, {v3:v}.16b, {x:v}.16b",
7967 a0 = inout(vreg) a0, a1 = inout(vreg) a1, a2 = inout(vreg) a2, a3 = inout(vreg) a3,
7968 v0 = in(vreg) v0, v1 = in(vreg) v1, v2 = in(vreg) v2, v3 = in(vreg) v3, x = in(vreg) x,
7969 options(pure, nomem, nostack),
7970 );
7971 i += 16;
7972 }
7973 let mut r = [
7974 vaddvq_s32(a0),
7975 vaddvq_s32(a1),
7976 vaddvq_s32(a2),
7977 vaddvq_s32(a3),
7978 ];
7979 while i < n {
7980 let xi = *px.add(i) as i32;
7981 r[0] += (*p0.add(i)) as i32 * xi;
7982 r[1] += (*p1.add(i)) as i32 * xi;
7983 r[2] += (*p2.add(i)) as i32 * xi;
7984 r[3] += (*p3.add(i)) as i32 * xi;
7985 i += 1;
7986 }
7987 r
7988 }
7989}
7990
7991#[cfg(target_arch = "aarch64")]
7998#[target_feature(enable = "neon,dotprod")]
7999unsafe fn dot_i8_sdot_4rows_il(g: &[u8], xq: &[i8]) -> [i32; 4] {
8000 unsafe {
8003 use core::arch::aarch64::*;
8004 use core::arch::asm;
8005 let n = xq.len();
8006 let px = xq.as_ptr();
8007 let pg = g.as_ptr() as *const i8;
8008 let (mut a0, mut a1, mut a2, mut a3) = (
8009 vdupq_n_s32(0),
8010 vdupq_n_s32(0),
8011 vdupq_n_s32(0),
8012 vdupq_n_s32(0),
8013 );
8014 let mut i = 0;
8015 while i + 16 <= n {
8016 let x = vld1q_s8(px.add(i));
8017 let base = pg.add(4 * i);
8018 let v0 = vld1q_s8(base);
8019 let v1 = vld1q_s8(base.add(16));
8020 let v2 = vld1q_s8(base.add(32));
8021 let v3 = vld1q_s8(base.add(48));
8022 asm!(
8023 "sdot {a0:v}.4s, {v0:v}.16b, {x:v}.16b",
8024 "sdot {a1:v}.4s, {v1:v}.16b, {x:v}.16b",
8025 "sdot {a2:v}.4s, {v2:v}.16b, {x:v}.16b",
8026 "sdot {a3:v}.4s, {v3:v}.16b, {x:v}.16b",
8027 a0 = inout(vreg) a0, a1 = inout(vreg) a1, a2 = inout(vreg) a2, a3 = inout(vreg) a3,
8028 v0 = in(vreg) v0, v1 = in(vreg) v1, v2 = in(vreg) v2, v3 = in(vreg) v3, x = in(vreg) x,
8029 options(pure, nomem, nostack),
8030 );
8031 i += 16;
8032 }
8033 [
8034 vaddvq_s32(a0),
8035 vaddvq_s32(a1),
8036 vaddvq_s32(a2),
8037 vaddvq_s32(a3),
8038 ]
8039 }
8040}
8041
8042#[cfg(target_arch = "aarch64")]
8048fn q8_range_sdot(
8049 q: &[u8],
8050 rep: &[u8],
8051 row_scale: &[f32],
8052 act: &SplitAct,
8053 cols: usize,
8054 out_addr: SendMut,
8055 start: usize,
8056 end: usize,
8057) {
8058 let mut o = start;
8059 if !rep.is_empty() {
8062 while o < end && o % 4 != 0 {
8063 let v = row_dot_sdot(&q[o * cols..(o + 1) * cols], act) * row_scale[o];
8064 unsafe { *out_addr.at(o) = v };
8065 o += 1;
8066 }
8067 }
8068 while o + 4 <= end {
8069 let r = if rep.is_empty() {
8070 unsafe {
8071 dot_i8_sdot_4rows(
8072 &q[o * cols..(o + 1) * cols],
8073 &q[(o + 1) * cols..(o + 2) * cols],
8074 &q[(o + 2) * cols..(o + 3) * cols],
8075 &q[(o + 3) * cols..(o + 4) * cols],
8076 &act.xq,
8077 )
8078 }
8079 } else {
8080 unsafe { dot_i8_sdot_4rows_il(&rep[o * cols..(o + 4) * cols], &act.xq) }
8081 };
8082 for k in 0..4 {
8083 let mut acc = r[k] as f32 * act.sx;
8084 for &(j, xv) in &act.outliers {
8085 acc += (q[(o + k) * cols + j] as i8) as f32 * xv;
8086 }
8087 unsafe { *out_addr.at(o + k) = acc * row_scale[o + k] };
8089 }
8090 o += 4;
8091 }
8092 while o < end {
8093 let v = row_dot_sdot(&q[o * cols..(o + 1) * cols], act) * row_scale[o];
8094 unsafe { *out_addr.at(o) = v };
8095 o += 1;
8096 }
8097}
8098
8099#[cfg(target_arch = "aarch64")]
8102#[allow(clippy::too_many_arguments)]
8103fn q8_range2_sdot(
8104 q: &[u8],
8105 row_scale: &[f32],
8106 a1: &SplitAct,
8107 a2: &SplitAct,
8108 cols: usize,
8109 p1: SendMut,
8110 p2: SendMut,
8111 start: usize,
8112 end: usize,
8113) {
8114 for o in start..end {
8115 let row = &q[o * cols..(o + 1) * cols];
8116 unsafe {
8118 *p1.at(o) = row_dot_sdot(row, a1) * row_scale[o];
8119 *p2.at(o) = row_dot_sdot(row, a2) * row_scale[o];
8120 }
8121 }
8122}
8123
8124#[allow(clippy::too_many_arguments)]
8126fn q8_range2_f32(
8127 q: &[u8],
8128 row_scale: &[f32],
8129 x1: &[f32],
8130 x2: &[f32],
8131 cols: usize,
8132 p1: SendMut,
8133 p2: SendMut,
8134 start: usize,
8135 end: usize,
8136) {
8137 for o in start..end {
8138 let row = &q[o * cols..(o + 1) * cols];
8139 unsafe {
8141 *p1.at(o) = dot_i8_f32(row, x1) * row_scale[o];
8142 *p2.at(o) = dot_i8_f32(row, x2) * row_scale[o];
8143 }
8144 }
8145}
8146
8147fn q8_range_f32(
8149 q: &[u8],
8150 row_scale: &[f32],
8151 xs: &[f32],
8152 cols: usize,
8153 out_addr: SendMut,
8154 start: usize,
8155 end: usize,
8156) {
8157 for o in start..end {
8158 let v = dot_i8_f32(&q[o * cols..(o + 1) * cols], xs) * row_scale[o];
8159 unsafe { *out_addr.at(o) = v };
8161 }
8162}
8163
8164#[cfg(target_arch = "aarch64")]
8167#[inline]
8168fn row_dot_sdot(row: &[u8], act: &SplitAct) -> f32 {
8169 let mut acc = unsafe { dot_i8_sdot(row, &act.xq) } as f32 * act.sx;
8170 for &(j, xv) in &act.outliers {
8171 acc += (row[j] as i8) as f32 * xv;
8172 }
8173 acc
8174}
8175
8176#[cfg(target_arch = "aarch64")]
8184#[target_feature(enable = "neon,dotprod")]
8185unsafe fn dot_q4_row_sdot(packed: &[u8], scales: &[u8], g0: usize, gpr: usize, xq: &[i8]) -> f32 {
8186 unsafe {
8189 use core::arch::aarch64::*;
8190 use core::arch::asm;
8191 let lomask = vdupq_n_u8(0x0F);
8192 let eight = vdupq_n_s8(8);
8193 let mut acc = 0f32;
8194 for gi in 0..gpr {
8195 let g = g0 + gi;
8196 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
8197 let b = vld1q_u8(packed.as_ptr().add(g * 16));
8198 let lo = vandq_u8(b, lomask);
8199 let hi = vshrq_n_u8::<4>(b);
8200 let e0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(lo, hi)), eight);
8201 let e1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(lo, hi)), eight);
8202 let x0 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE));
8203 let x1 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE + 16));
8204 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
8205 asm!(
8206 "sdot {a0:v}.4s, {e0:v}.16b, {x0:v}.16b",
8207 "sdot {a1:v}.4s, {e1:v}.16b, {x1:v}.16b",
8208 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
8209 e0 = in(vreg) e0, x0 = in(vreg) x0, e1 = in(vreg) e1, x1 = in(vreg) x1,
8210 options(pure, nomem, nostack),
8211 );
8212 acc += vaddvq_s32(vaddq_s32(a0, a1)) as f32 * s;
8213 }
8214 acc
8215 }
8216}
8217
8218#[cfg(target_arch = "aarch64")]
8223#[target_feature(enable = "neon,dotprod")]
8224unsafe fn dot_q4_row_sdot2(
8225 packed: &[u8],
8226 scales: &[u8],
8227 g0: usize,
8228 gpr: usize,
8229 xq1: &[i8],
8230 xq2: &[i8],
8231) -> (f32, f32) {
8232 unsafe {
8235 use core::arch::aarch64::*;
8236 use core::arch::asm;
8237 let lomask = vdupq_n_u8(0x0F);
8238 let eight = vdupq_n_s8(8);
8239 let (mut acc1, mut acc2) = (0f32, 0f32);
8240 for gi in 0..gpr {
8241 let g = g0 + gi;
8242 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
8243 let b = vld1q_u8(packed.as_ptr().add(g * 16));
8244 let lo = vandq_u8(b, lomask);
8245 let hi = vshrq_n_u8::<4>(b);
8246 let e0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(lo, hi)), eight);
8247 let e1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(lo, hi)), eight);
8248 let x10 = vld1q_s8(xq1.as_ptr().add(gi * GROUP_SIZE));
8249 let x11 = vld1q_s8(xq1.as_ptr().add(gi * GROUP_SIZE + 16));
8250 let x20 = vld1q_s8(xq2.as_ptr().add(gi * GROUP_SIZE));
8251 let x21 = vld1q_s8(xq2.as_ptr().add(gi * GROUP_SIZE + 16));
8252 let (mut a0, mut a1, mut b0, mut b1) = (
8253 vdupq_n_s32(0),
8254 vdupq_n_s32(0),
8255 vdupq_n_s32(0),
8256 vdupq_n_s32(0),
8257 );
8258 asm!(
8259 "sdot {a0:v}.4s, {e0:v}.16b, {x10:v}.16b",
8260 "sdot {a1:v}.4s, {e1:v}.16b, {x11:v}.16b",
8261 "sdot {b0:v}.4s, {e0:v}.16b, {x20:v}.16b",
8262 "sdot {b1:v}.4s, {e1:v}.16b, {x21:v}.16b",
8263 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
8264 b0 = inout(vreg) b0, b1 = inout(vreg) b1,
8265 e0 = in(vreg) e0, e1 = in(vreg) e1,
8266 x10 = in(vreg) x10, x11 = in(vreg) x11,
8267 x20 = in(vreg) x20, x21 = in(vreg) x21,
8268 options(pure, nomem, nostack),
8269 );
8270 acc1 += vaddvq_s32(vaddq_s32(a0, a1)) as f32 * s;
8271 acc2 += vaddvq_s32(vaddq_s32(b0, b1)) as f32 * s;
8272 }
8273 (acc1, acc2)
8274 }
8275}
8276
8277#[inline]
8282pub(crate) fn axpy_i8_f32(acc: &mut [f32], row: &[i8], w: f32) {
8283 #[cfg(target_arch = "aarch64")]
8284 unsafe {
8285 return axpy_i8_f32_neon(acc, row, w);
8286 }
8287 #[cfg(target_arch = "x86_64")]
8288 if avx2_enabled() {
8289 return unsafe { axpy_i8_f32_avx2(acc, row, w) };
8290 }
8291 #[allow(unreachable_code)]
8292 {
8293 for (a, &b) in acc.iter_mut().zip(row) {
8294 *a += w * b as f32;
8295 }
8296 }
8297}
8298
8299#[cfg(target_arch = "x86_64")]
8301#[target_feature(enable = "avx2,fma")]
8302unsafe fn axpy_i8_f32_avx2(acc: &mut [f32], row: &[i8], w: f32) {
8303 unsafe {
8305 use core::arch::x86_64::*;
8306 let n = acc.len().min(row.len());
8307 let ap = acc.as_mut_ptr();
8308 let rp = row.as_ptr();
8309 let wv = _mm256_set1_ps(w);
8310 let mut j = 0usize;
8311 while j + 16 <= n {
8312 let rb = _mm_loadu_si128(rp.add(j) as *const __m128i);
8313 let lo = _mm256_cvtepi32_ps(_mm256_cvtepi8_epi32(rb));
8314 let hi = _mm256_cvtepi32_ps(_mm256_cvtepi8_epi32(_mm_srli_si128::<8>(rb)));
8315 let v0 = _mm256_fmadd_ps(wv, lo, _mm256_loadu_ps(ap.add(j)));
8316 let v1 = _mm256_fmadd_ps(wv, hi, _mm256_loadu_ps(ap.add(j + 8)));
8317 _mm256_storeu_ps(ap.add(j), v0);
8318 _mm256_storeu_ps(ap.add(j + 8), v1);
8319 j += 16;
8320 }
8321 while j < n {
8322 *ap.add(j) += w * (*rp.add(j)) as f32;
8323 j += 1;
8324 }
8325 }
8326}
8327
8328#[cfg(target_arch = "aarch64")]
8329#[target_feature(enable = "neon")]
8330unsafe fn axpy_i8_f32_neon(acc: &mut [f32], row: &[i8], w: f32) {
8331 unsafe {
8333 use core::arch::aarch64::*;
8334 let n = acc.len().min(row.len());
8335 let ap = acc.as_mut_ptr();
8336 let rp = row.as_ptr();
8337 let wv = vdupq_n_f32(w);
8338 let mut j = 0usize;
8339 while j + 16 <= n {
8340 let rb = vld1q_s8(rp.add(j));
8341 let lo = vmovl_s8(vget_low_s8(rb));
8342 let hi = vmovl_s8(vget_high_s8(rb));
8343 for (off, half) in [(0, lo), (8, hi)] {
8344 let f0 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(half)));
8345 let f1 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(half)));
8346 let o = j + off;
8347 vst1q_f32(ap.add(o), vfmaq_f32(vld1q_f32(ap.add(o)), wv, f0));
8348 vst1q_f32(ap.add(o + 4), vfmaq_f32(vld1q_f32(ap.add(o + 4)), wv, f1));
8349 }
8350 j += 16;
8351 }
8352 while j < n {
8353 *ap.add(j) += w * (*rp.add(j)) as f32;
8354 j += 1;
8355 }
8356 }
8357}
8358
8359#[inline]
8362pub(crate) fn dot_i8_f32(w: &[u8], x: &[f32]) -> f32 {
8363 #[cfg(target_arch = "aarch64")]
8364 unsafe {
8365 return dot_i8_f32_neon(w, x);
8366 }
8367 #[cfg(target_arch = "x86_64")]
8368 if avx2_enabled() {
8369 return unsafe { dot_i8_f32_avx2(w, x) };
8370 }
8371 #[allow(unreachable_code)]
8372 {
8373 let mut sum = 0.0f32;
8374 for (j, &b) in w.iter().enumerate() {
8375 sum += (b as i8) as f32 * x[j];
8376 }
8377 sum
8378 }
8379}
8380
8381#[inline]
8385fn dot_i8_col_f32(w: &[u8], x: &[f32], col: &[f32]) -> f32 {
8386 #[cfg(target_arch = "aarch64")]
8387 unsafe {
8388 return dot_i8_col_f32_neon(w, x, col);
8389 }
8390 #[allow(unreachable_code)]
8391 {
8392 let mut sum = 0.0f32;
8393 for (j, &b) in w.iter().enumerate() {
8394 sum += (b as i8) as f32 * x[j] * col[j];
8395 }
8396 sum
8397 }
8398}
8399
8400#[cfg(target_arch = "aarch64")]
8401#[target_feature(enable = "neon")]
8402unsafe fn dot_i8_col_f32_neon(w: &[u8], x: &[f32], col: &[f32]) -> f32 {
8403 unsafe {
8405 use core::arch::aarch64::*;
8406 let n = x.len();
8407 let wp = w.as_ptr() as *const i8;
8408 let xp = x.as_ptr();
8409 let cp = col.as_ptr();
8410 let (mut a0, mut a1, mut a2, mut a3) = (
8411 vdupq_n_f32(0.0),
8412 vdupq_n_f32(0.0),
8413 vdupq_n_f32(0.0),
8414 vdupq_n_f32(0.0),
8415 );
8416 let mut j = 0usize;
8417 while j + 16 <= n {
8418 let wb = vld1q_s8(wp.add(j));
8419 let lo = vmovl_s8(vget_low_s8(wb));
8420 let hi = vmovl_s8(vget_high_s8(wb));
8421 let w0 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(lo)));
8422 let w1 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(lo)));
8423 let w2 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(hi)));
8424 let w3 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(hi)));
8425 a0 = vfmaq_f32(
8426 a0,
8427 w0,
8428 vmulq_f32(vld1q_f32(xp.add(j)), vld1q_f32(cp.add(j))),
8429 );
8430 a1 = vfmaq_f32(
8431 a1,
8432 w1,
8433 vmulq_f32(vld1q_f32(xp.add(j + 4)), vld1q_f32(cp.add(j + 4))),
8434 );
8435 a2 = vfmaq_f32(
8436 a2,
8437 w2,
8438 vmulq_f32(vld1q_f32(xp.add(j + 8)), vld1q_f32(cp.add(j + 8))),
8439 );
8440 a3 = vfmaq_f32(
8441 a3,
8442 w3,
8443 vmulq_f32(vld1q_f32(xp.add(j + 12)), vld1q_f32(cp.add(j + 12))),
8444 );
8445 j += 16;
8446 }
8447 let mut sum = vaddvq_f32(vaddq_f32(vaddq_f32(a0, a1), vaddq_f32(a2, a3)));
8448 while j < n {
8449 sum += (*wp.add(j)) as f32 * *xp.add(j) * *cp.add(j);
8450 j += 1;
8451 }
8452 sum
8453 }
8454}
8455
8456#[cfg(target_arch = "aarch64")]
8457#[target_feature(enable = "neon")]
8458unsafe fn dot_i8_f32_neon(w: &[u8], x: &[f32]) -> f32 {
8459 unsafe {
8461 use core::arch::aarch64::*;
8462 let n = x.len();
8463 let wp = w.as_ptr() as *const i8;
8464 let xp = x.as_ptr();
8465 let (mut a0, mut a1, mut a2, mut a3) = (
8466 vdupq_n_f32(0.0),
8467 vdupq_n_f32(0.0),
8468 vdupq_n_f32(0.0),
8469 vdupq_n_f32(0.0),
8470 );
8471 let mut j = 0usize;
8472 while j + 16 <= n {
8473 let wb = vld1q_s8(wp.add(j));
8474 let lo = vmovl_s8(vget_low_s8(wb));
8475 let hi = vmovl_s8(vget_high_s8(wb));
8476 let w0 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(lo)));
8477 let w1 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(lo)));
8478 let w2 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(hi)));
8479 let w3 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(hi)));
8480 a0 = vfmaq_f32(a0, w0, vld1q_f32(xp.add(j)));
8481 a1 = vfmaq_f32(a1, w1, vld1q_f32(xp.add(j + 4)));
8482 a2 = vfmaq_f32(a2, w2, vld1q_f32(xp.add(j + 8)));
8483 a3 = vfmaq_f32(a3, w3, vld1q_f32(xp.add(j + 12)));
8484 j += 16;
8485 }
8486 let mut sum = vaddvq_f32(vaddq_f32(vaddq_f32(a0, a1), vaddq_f32(a2, a3)));
8487 while j < n {
8488 sum += (*wp.add(j)) as f32 * *xp.add(j);
8489 j += 1;
8490 }
8491 sum
8492 }
8493}
8494
8495#[allow(clippy::too_many_arguments)]
8496fn qmatvec(
8497 q: &[u8],
8498 rep: &[u8],
8499 row_scale: &[f32],
8500 x: &[f32],
8501 col_field: &[f32],
8502 dtype: TensorDtype,
8503 rows: usize,
8504 cols: usize,
8505 out: &mut [f32],
8506 pool: Option<&Pool>,
8507) {
8508 debug_assert_eq!(out.len(), rows);
8509 #[cfg(not(target_arch = "aarch64"))]
8510 let _ = rep;
8511
8512 #[cfg(target_arch = "aarch64")]
8513 if sdot_enabled() {
8514 let act = if dtype == TensorDtype::Q8_2f {
8515 split_act_q8_2f(x, col_field)
8516 } else {
8517 split_act(x)
8518 };
8519 let out_addr = SendMut(out.as_mut_ptr());
8520 let run_range = |start: usize, end: usize| {
8521 q8_range_sdot(q, rep, row_scale, &act, cols, out_addr, start, end)
8522 };
8523 match pool {
8524 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
8525 _ => run_range(0, rows),
8526 }
8527 return;
8528 }
8529 #[cfg(target_arch = "x86_64")]
8532 if avx2_a8w8_enabled() {
8533 let act = if dtype == TensorDtype::Q8_2f {
8534 split_act_q8_2f(x, col_field)
8535 } else {
8536 split_act(x)
8537 };
8538 let out_addr = SendMut(out.as_mut_ptr());
8539 let run_range = |start: usize, end: usize| {
8540 q8_range_avx2(q, row_scale, &act, cols, out_addr, start, end)
8541 };
8542 match pool {
8543 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
8544 _ => run_range(0, rows),
8545 }
8546 return;
8547 }
8548
8549 prescale_with(x, col_field, dtype, 1, |xs| {
8550 let out_addr = SendMut(out.as_mut_ptr());
8551 let run_range = move |start: usize, end: usize| {
8552 for o in start..end {
8553 let v = dot_i8_f32(&q[o * cols..(o + 1) * cols], xs) * row_scale[o];
8554 unsafe { *out_addr.at(o) = v };
8556 }
8557 };
8558 match pool {
8559 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
8560 _ => run_range(0, rows),
8561 }
8562 });
8563}
8564
8565#[allow(clippy::too_many_arguments)]
8566fn qmatvec2(
8567 q: &[u8],
8568 row_scale: &[f32],
8569 x1: &[f32],
8570 x2: &[f32],
8571 col_field: &[f32],
8572 dtype: TensorDtype,
8573 rows: usize,
8574 cols: usize,
8575 o1: &mut [f32],
8576 o2: &mut [f32],
8577 pool: Option<&Pool>,
8578) {
8579 #[cfg(target_arch = "aarch64")]
8580 if sdot_enabled() {
8581 let a1s = if dtype == TensorDtype::Q8_2f {
8582 split_act_q8_2f(x1, col_field)
8583 } else {
8584 split_act(x1)
8585 };
8586 let a2s = if dtype == TensorDtype::Q8_2f {
8587 split_act_q8_2f(x2, col_field)
8588 } else {
8589 split_act(x2)
8590 };
8591 let p1 = SendMut(o1.as_mut_ptr());
8592 let p2 = SendMut(o2.as_mut_ptr());
8593 let run_range = |start: usize, end: usize| {
8594 q8_range2_sdot(q, row_scale, &a1s, &a2s, cols, p1, p2, start, end)
8595 };
8596 match pool {
8597 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
8598 _ => run_range(0, rows),
8599 }
8600 return;
8601 }
8602 #[cfg(target_arch = "x86_64")]
8603 if avx2_a8w8_enabled() {
8604 let a1s = if dtype == TensorDtype::Q8_2f {
8605 split_act_q8_2f(x1, col_field)
8606 } else {
8607 split_act(x1)
8608 };
8609 let a2s = if dtype == TensorDtype::Q8_2f {
8610 split_act_q8_2f(x2, col_field)
8611 } else {
8612 split_act(x2)
8613 };
8614 let p1 = SendMut(o1.as_mut_ptr());
8615 let p2 = SendMut(o2.as_mut_ptr());
8616 let run_range = |start: usize, end: usize| {
8617 q8_range2_avx2(q, row_scale, &a1s, &a2s, cols, p1, p2, start, end)
8618 };
8619 match pool {
8620 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
8621 _ => run_range(0, rows),
8622 }
8623 return;
8624 }
8625
8626 prescale_with(x1, col_field, dtype, 1, |x1s| {
8627 prescale_with(x2, col_field, dtype, 2, |x2s| {
8628 let p1 = SendMut(o1.as_mut_ptr());
8629 let p2 = SendMut(o2.as_mut_ptr());
8630 let run_range = move |start: usize, end: usize| {
8631 for o in start..end {
8632 let row = &q[o * cols..(o + 1) * cols];
8633 let s1 = dot_i8_f32(row, x1s) * row_scale[o];
8634 let s2 = dot_i8_f32(row, x2s) * row_scale[o];
8635 unsafe {
8637 *p1.at(o) = s1;
8638 *p2.at(o) = s2;
8639 }
8640 }
8641 };
8642 match pool {
8643 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
8644 _ => run_range(0, rows),
8645 }
8646 });
8647 });
8648}
8649
8650#[derive(Clone, Copy)]
8651struct SendMut(*mut f32);
8652unsafe impl Send for SendMut {}
8653unsafe impl Sync for SendMut {}
8654
8655impl SendMut {
8656 #[inline]
8657 fn at(self, i: usize) -> *mut f32 {
8658 unsafe { self.0.add(i) }
8659 }
8660}
8661
8662#[cfg(test)]
8663mod tests {
8664 use super::*;
8665
8666 #[test]
8667 fn f32_matvec_matches_matvec_rows_bitexact() {
8668 let (rows, cols) = (300, 40);
8669 let w: Vec<f32> = (0..rows * cols).map(|i| (i as f32 * 0.017).sin()).collect();
8670 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.05).cos()).collect();
8671 let qt = QTensor::from_f32(w.clone(), rows, cols);
8672
8673 let mut a = vec![0.0f32; rows];
8674 matvec_rows(None, &w, &x, &mut a);
8675 let mut b = vec![0.0f32; rows];
8676 qt.matvec(&x, &mut b, None);
8677 assert_eq!(a, b);
8678 }
8679
8680 #[test]
8681 fn sdot_kernel_exact_on_grid() {
8682 eprintln!("sdot_enabled = {}", sdot_enabled());
8687 let (rows, cols) = (9, 80); let w: Vec<u8> = (0..rows * cols)
8689 .map(|i| (((i * 37) % 251) as i32 - 125) as i8 as u8)
8690 .collect();
8691 let scales: Vec<f32> = (0..rows).map(|o| 0.005 + o as f32 * 0.001).collect();
8692 let x: Vec<f32> = (0..cols)
8693 .map(|i| match i % 3 {
8694 0 => 1.0,
8695 1 => -1.0,
8696 _ => 0.0,
8697 })
8698 .collect();
8699 let mut a = vec![0.0f32; rows];
8700 qmatvec(
8701 &w,
8702 &[],
8703 &scales,
8704 &x,
8705 &[],
8706 TensorDtype::Q8Row,
8707 rows,
8708 cols,
8709 &mut a,
8710 None,
8711 );
8712 for o in 0..rows {
8713 let mut acc = 0.0f32;
8714 for j in 0..cols {
8715 acc += (w[o * cols + j] as i8) as f32 * x[j];
8716 }
8717 let expect = acc * scales[o];
8718 assert!(
8719 (a[o] - expect).abs() < 1e-3 * expect.abs().max(1e-3),
8720 "row {o}: {} vs {expect}",
8721 a[o]
8722 );
8723 }
8724 }
8725
8726 #[test]
8727 fn q1_tbl_fast_path_matches_reference() {
8728 let (rows, cols) = (5, 256);
8733 let gpr = cols / GROUP_SIZE;
8734 let mut bytes = Vec::new();
8735 for t in 0..rows * gpr {
8736 let s = 0.007 + (t % 11) as f32 * 0.004;
8737 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
8738 for j in 0..4 {
8739 bytes.push(((t * 53 + j * 89 + 7) % 249) as u8);
8740 }
8741 }
8742 let x: Vec<f32> = (0..cols)
8743 .map(|i| if (i * 5) % 7 < 3 { 1.0 } else { -1.0 })
8744 .collect();
8745 let mut w = vec![0.0f32; rows * cols];
8746 cortiq_core::quant::dequant_q1(&bytes, &mut w);
8747 let mut got = vec![0.0f32; rows];
8748 q1_matvec(&bytes, &x, rows, cols, &mut got, None);
8749 for o in 0..rows {
8750 let expect: f32 = (0..cols).map(|j| w[o * cols + j] * x[j]).sum();
8751 assert!(
8752 (got[o] - expect).abs() < 1e-3 * expect.abs().max(1e-3),
8753 "row {o}: {} vs {expect}",
8754 got[o]
8755 );
8756 }
8757 let b = 5usize;
8760 let mut xs_all = Vec::new();
8761 for bi in 0..b {
8762 xs_all.extend(x.iter().map(|v| if bi % 2 == 0 { *v } else { -*v }));
8763 }
8764 let mut mm = vec![0.0f32; b * rows];
8765 q1_matmat(&bytes, &xs_all, b, rows, cols, &mut mm, None);
8766 for bi in 0..b {
8767 let mut single = vec![0.0f32; rows];
8768 q1_matvec(
8769 &bytes,
8770 &xs_all[bi * cols..(bi + 1) * cols],
8771 rows,
8772 cols,
8773 &mut single,
8774 None,
8775 );
8776 assert_eq!(&mm[bi * rows..(bi + 1) * rows], &single[..], "stream {bi}");
8777 }
8778 }
8779
8780 #[test]
8781 fn q1_kernels_match_exact_reference() {
8782 let (rows, cols) = (7, 96);
8784 let gpr = cols / GROUP_SIZE;
8785 let mut bytes = Vec::new();
8786 for t in 0..rows * gpr {
8787 let s = 0.01 + (t % 13) as f32 * 0.003;
8788 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
8789 for j in 0..4 {
8790 bytes.push(((t * 31 + j * 97) % 251) as u8);
8791 }
8792 }
8793 let x: Vec<f32> = (0..cols)
8795 .map(|i| if i % 3 == 0 { 1.0 } else { -1.0 })
8796 .collect();
8797 let mut w = vec![0.0f32; rows * cols];
8799 cortiq_core::quant::dequant_q1(&bytes, &mut w);
8800 let mut expect = vec![0.0f32; rows];
8801 for o in 0..rows {
8802 expect[o] = (0..cols).map(|j| w[o * cols + j] * x[j]).sum();
8803 }
8804 let mut got = vec![0.0f32; rows];
8805 q1_matvec(&bytes, &x, rows, cols, &mut got, None);
8806 for o in 0..rows {
8807 assert!(
8808 (got[o] - expect[o]).abs() < 1e-3 * expect[o].abs().max(1e-3),
8809 "row {o}: {} vs {}",
8810 got[o],
8811 expect[o]
8812 );
8813 }
8814 let x2: Vec<f32> = x.iter().map(|v| -v).collect();
8816 let (mut a1, mut a2) = (vec![0.0f32; rows], vec![0.0f32; rows]);
8817 q1_matvec2(&bytes, &x, &x2, rows, cols, &mut a1, &mut a2, None);
8818 assert_eq!(a1, got);
8819 let mut xs = x.clone();
8820 xs.extend_from_slice(&x2);
8821 let mut mm = vec![0.0f32; 2 * rows];
8822 q1_matmat(&bytes, &xs, 2, rows, cols, &mut mm, None);
8823 assert_eq!(&mm[..rows], got.as_slice());
8824 assert_eq!(&mm[rows..], a2.as_slice());
8825 }
8826
8827 #[test]
8828 fn repack_is_bit_identical() {
8829 let (rows, cols) = (267, 96); let w: Vec<u8> = (0..rows * cols)
8835 .map(|i| (((i * 89) % 253) as i32 - 126) as i8 as u8)
8836 .collect();
8837 let scales: Vec<f32> = (0..rows).map(|o| 0.003 + o as f32 * 0.0007).collect();
8838 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.37).sin() * 2.0).collect();
8839 let rep = q8_repack_layout(&w, rows, cols);
8840 for g in 0..rows / 4 {
8842 for c in 0..cols / 16 {
8843 for lane in 0..4 {
8844 assert_eq!(
8845 &rep[g * 4 * cols + c * 64 + lane * 16
8846 ..g * 4 * cols + c * 64 + lane * 16 + 16],
8847 &w[(g * 4 + lane) * cols + c * 16..(g * 4 + lane) * cols + c * 16 + 16],
8848 );
8849 }
8850 }
8851 }
8852 let mut a = vec![0.0f32; rows];
8853 qmatvec(
8854 &w,
8855 &[],
8856 &scales,
8857 &x,
8858 &[],
8859 TensorDtype::Q8Row,
8860 rows,
8861 cols,
8862 &mut a,
8863 None,
8864 );
8865 let mut b = vec![0.0f32; rows];
8866 qmatvec(
8867 &w,
8868 &rep,
8869 &scales,
8870 &x,
8871 &[],
8872 TensorDtype::Q8Row,
8873 rows,
8874 cols,
8875 &mut b,
8876 None,
8877 );
8878 assert_eq!(a, b, "full-range repack output diverged");
8879
8880 #[cfg(target_arch = "aarch64")]
8881 if sdot_enabled() {
8882 let act = split_act(&x);
8884 let mut c1 = vec![0.0f32; rows];
8885 let mut c2 = vec![0.0f32; rows];
8886 q8_range_sdot(
8887 &w,
8888 &[],
8889 &scales,
8890 &act,
8891 cols,
8892 SendMut(c1.as_mut_ptr()),
8893 3,
8894 rows - 2,
8895 );
8896 q8_range_sdot(
8897 &w,
8898 &rep,
8899 &scales,
8900 &act,
8901 cols,
8902 SendMut(c2.as_mut_ptr()),
8903 3,
8904 rows - 2,
8905 );
8906 assert_eq!(c1, c2, "unaligned-range repack output diverged");
8907 }
8908 }
8909
8910 #[test]
8911 fn sdot_a8w8_noise_is_bounded() {
8912 let (rows, cols) = (16, 512);
8916 let w: Vec<u8> = (0..rows * cols)
8917 .map(|i| (((i * 37) % 251) as i32 - 125) as i8 as u8)
8918 .collect();
8919 let scales = vec![0.01f32; rows];
8920 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.21).sin()).collect();
8921 let mut a = vec![0.0f32; rows];
8922 qmatvec(
8923 &w,
8924 &[],
8925 &scales,
8926 &x,
8927 &[],
8928 TensorDtype::Q8Row,
8929 rows,
8930 cols,
8931 &mut a,
8932 None,
8933 );
8934 let (mut num, mut den) = (0f64, 0f64);
8935 for o in 0..rows {
8936 let mut acc = 0.0f32;
8937 for j in 0..cols {
8938 acc += (w[o * cols + j] as i8) as f32 * x[j];
8939 }
8940 let expect = acc * scales[o];
8941 num += ((a[o] - expect) as f64).powi(2);
8942 den += (expect as f64).powi(2);
8943 }
8944 let rel = (num / den.max(1e-12)).sqrt();
8945 assert!(rel < 0.05, "A8W8 relative L2 error too high: {rel}");
8946 }
8947
8948 #[test]
8949 fn i8_dot_neon_matches_scalar() {
8950 let n = 100;
8951 let w: Vec<u8> = (0..n).map(|i| ((i * 37 + 11) % 251) as u8).collect();
8952 let x: Vec<f32> = (0..n).map(|i| (i as f32 * 0.13).sin()).collect();
8953 let mut scalar = 0.0f32;
8954 for j in 0..n {
8955 scalar += (w[j] as i8) as f32 * x[j];
8956 }
8957 let fast = dot_i8_f32(&w, &x);
8958 assert!((scalar - fast).abs() < 1e-3 * scalar.abs().max(1.0));
8959 }
8960
8961 #[test]
8963 fn vbitmatvec_matches_full_dequant() {
8964 let (rows, cols) = (6, 64);
8965 let ng = cols / GROUP_SIZE;
8966 let bits: Vec<u8> = vec![3, 4, 5, 6, 8, 4];
8968 let mut bytes = bits.clone();
8969 for g in 0..rows * ng {
8970 let s = 0.02 + 0.001 * g as f32;
8971 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
8972 }
8973 for r in 0..rows {
8974 let b = bits[r] as usize;
8975 let (mut acc, mut nb) = (0u64, 0usize);
8976 let mut rowbytes = Vec::new();
8977 for i in 0..cols {
8978 let v = ((i * 7 + r * 13) % (1 << b)) as u64;
8979 acc = (acc << b) | v;
8980 nb += b;
8981 while nb >= 8 {
8982 nb -= 8;
8983 rowbytes.push(((acc >> nb) & 0xFF) as u8);
8984 }
8985 }
8986 if nb > 0 {
8987 rowbytes.push(((acc << (8 - nb)) & 0xFF) as u8);
8988 }
8989 bytes.extend_from_slice(&rowbytes);
8990 }
8991 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.19).sin()).collect();
8992
8993 let mut reference = vec![0f32; rows * cols];
8994 cortiq_core::quant::dequant_vbit(&bytes, rows, cols, &mut reference).unwrap();
8995 let mut expect = vec![0f32; rows];
8996 for r in 0..rows {
8997 expect[r] = reference[r * cols..(r + 1) * cols]
8998 .iter()
8999 .zip(&x)
9000 .map(|(w, xv)| w * xv)
9001 .sum();
9002 }
9003 let mut got = vec![0f32; rows];
9004 let offsets = vbit_row_offsets(&bytes, rows, cols);
9005 vbitmatvec(&bytes, &offsets, &x, rows, cols, &mut got, None);
9006 let tol = if a8w8_enabled() { 6e-2 } else { 1e-4 };
9010 let scale = expect.iter().fold(0f32, |m, v| m.max(v.abs())).max(1e-6);
9011 for r in 0..rows {
9012 assert!(
9013 (got[r] - expect[r]).abs() < tol * scale,
9014 "row {r}: {} vs {}",
9015 got[r],
9016 expect[r]
9017 );
9018 }
9019 }
9020
9021 #[test]
9026 #[cfg(target_arch = "x86_64")]
9027 fn vbit_matmat_blocked_matches_per_row() {
9028 let (rows, cols, b) = (64usize, 128usize, 9usize);
9029 let ng = cols / GROUP_SIZE;
9030 let bits: Vec<u8> = (0..rows).map(|r| [3u8, 4, 5, 6][r % 4]).collect();
9031 let mut bytes = bits.clone();
9032 for g in 0..rows * ng {
9033 let sc = 0.02 + 0.0005 * g as f32;
9034 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(sc).to_le_bytes());
9035 }
9036 for r in 0..rows {
9037 let bw = bits[r] as usize;
9038 let (mut acc, mut nb) = (0u64, 0usize);
9039 let mut rowbytes = Vec::new();
9040 for i in 0..cols {
9041 let v = ((i * 7 + r * 13) % (1 << bw)) as u64;
9042 acc = (acc << bw) | v;
9043 nb += bw;
9044 while nb >= 8 {
9045 nb -= 8;
9046 rowbytes.push(((acc >> nb) & 0xFF) as u8);
9047 }
9048 }
9049 if nb > 0 {
9050 rowbytes.push(((acc << (8 - nb)) & 0xFF) as u8);
9051 }
9052 bytes.extend_from_slice(&rowbytes);
9053 }
9054 let x: Vec<f32> = (0..b * cols)
9055 .map(|i| ((i * 13 + 7) % 97) as f32 / 97.0 - 0.5)
9056 .collect();
9057 let offsets = vbit_row_offsets(&bytes, rows, cols);
9058 let mut y_a = vec![0f32; b * rows];
9059 let mut y_b = vec![0f32; b * rows];
9060 unsafe { std::env::set_var("CMF_X86_BLOCKED", "1") };
9061 vbitmatmat(&bytes, &offsets, &x, b, rows, cols, &mut y_a, None);
9062 unsafe { std::env::set_var("CMF_X86_BLOCKED", "0") };
9063 vbitmatmat(&bytes, &offsets, &x, b, rows, cols, &mut y_b, None);
9064 unsafe { std::env::remove_var("CMF_X86_BLOCKED") };
9065 let max_d = y_a
9066 .iter()
9067 .zip(&y_b)
9068 .map(|(p, q)| (p - q).abs())
9069 .fold(0.0f32, f32::max);
9070 assert!(max_d < 1e-4, "vbit blocked ≠ per-row: max|Δ| = {max_d}");
9071 }
9072
9073 #[test]
9081 fn q4t_matmat_blocked_matches_per_row() {
9082 let (rows, cols, b) = (16usize, 64usize, 9usize);
9083 let gpr = cols / GROUP_SIZE;
9084 let mut bytes = vec![0u8; rows * gpr * Q4_TILE];
9085 for r in 0..rows {
9086 for g in 0..gpr {
9087 let t = (r * gpr + g) * Q4_TILE;
9088 let sc = 0.02 + 0.001 * (r * gpr + g) as f32;
9089 bytes[t..t + 2].copy_from_slice(&cortiq_core::quant::f32_to_f16(sc).to_le_bytes());
9090 for k in 0..16 {
9091 bytes[t + 2 + k] = ((r * 31 + g * 7 + k * 13) % 251) as u8;
9092 }
9093 }
9094 }
9095 let x: Vec<f32> = (0..b * cols)
9096 .map(|i| ((i * 13 + 7) % 97) as f32 / 97.0 - 0.5)
9097 .collect();
9098 let mut y_blk = vec![0f32; b * rows];
9099 let mut y_row = vec![0f32; b * rows];
9100 unsafe { std::env::set_var("CMF_X86_BLOCKED", "1") };
9101 q4t_matmat(&bytes, &x, b, rows, cols, &mut y_blk, None);
9102 unsafe { std::env::set_var("CMF_X86_BLOCKED", "0") };
9103 q4t_matmat(&bytes, &x, b, rows, cols, &mut y_row, None);
9104 unsafe { std::env::remove_var("CMF_X86_BLOCKED") };
9105 assert_eq!(y_blk, y_row, "q4t blocked 1x4 ≠ per-row");
9106 }
9107
9108 fn synth_q4tp(rows: usize, cols: usize) -> Vec<u8> {
9115 use cortiq_core::quant::{f32_to_f16, q4tp_code_stride, q4tp_put_code};
9116 let gpr = cols / GROUP_SIZE;
9117 let stride = q4tp_code_stride(gpr);
9118 let (params_off, codes_off, _) = q4tp_sections(rows, cols);
9119 let mut b = vec![0u8; codes_off + rows * stride];
9120 for r in 0..rows {
9121 for g in 0..gpr {
9122 let t = (r * gpr + g) * Q4TP_NIB;
9123 for k in 0..16 {
9124 b[t + k] = ((r * 31 + g * 7 + k * 13) % 251) as u8;
9125 }
9126 }
9127 let lo = -6.0 - 0.03 * (r % 17) as f32;
9128 let step = 0.01 + 0.004 * (r % 11) as f32;
9129 let p = params_off + r * 4;
9130 b[p..p + 2].copy_from_slice(&f32_to_f16(lo).to_le_bytes());
9131 b[p + 2..p + 4].copy_from_slice(&f32_to_f16(step).to_le_bytes());
9132 let crow = &mut b[codes_off + r * stride..codes_off + (r + 1) * stride];
9133 for g in 0..gpr {
9134 q4tp_put_code(crow, g, (r * 5 + g * 3) % 32);
9135 }
9136 }
9137 b
9138 }
9139
9140 fn q4tp_as_q4t(bytes: &[u8], rows: usize, cols: usize) -> Vec<u8> {
9144 let gpr = cols / GROUP_SIZE;
9145 let v = Q4tpView::new(bytes, rows, cols);
9146 let mut out = vec![0u8; rows * gpr * Q4_TILE];
9147 let mut sc = vec![0f32; gpr];
9148 for r in 0..rows {
9149 v.scales_into(r, gpr, &mut sc);
9150 for g in 0..gpr {
9151 let t = (r * gpr + g) * Q4_TILE;
9152 let s = sc[g];
9153 out[t..t + 2].copy_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
9154 let src = (r * gpr + g) * Q4TP_NIB;
9155 out[t + 2..t + Q4_TILE].copy_from_slice(&v.nib[src..src + Q4TP_NIB]);
9156 }
9157 }
9158 out
9159 }
9160
9161 #[test]
9167 fn q4tp_exact_path_matches_dequant_reference() {
9168 let (rows, cols) = (256usize, 512usize);
9169 let gpr = cols / GROUP_SIZE;
9170 let bytes = synth_q4tp(rows, cols);
9171 let mut w = vec![0f32; rows * cols];
9172 cortiq_core::quant::dequant_q4tp(&bytes, rows, cols, &mut w);
9173
9174 let x: Vec<f32> = (0..cols)
9175 .map(|i| ((i * 13 + 7) % 97) as f32 / 97.0 - 0.5)
9176 .collect();
9177 let v = Q4tpView::new(&bytes, rows, cols);
9178 let mut sc = vec![0f32; gpr];
9179 for r in 0..rows {
9180 v.scales_into(r, gpr, &mut sc);
9181 let got = q4tp_row_exact(v.nib, r, gpr, &x, &sc);
9182 let want: f32 = (0..cols).map(|c| w[r * cols + c] * x[c]).sum();
9183 let mag: f32 = (0..cols).map(|c| (w[r * cols + c] * x[c]).abs()).sum();
9187 assert!(
9188 (got - want).abs() <= 1e-5 * mag,
9189 "row {r}: kernel {got} vs dequant {want}"
9190 );
9191 }
9192 }
9193
9194 #[test]
9200 fn q4tp_matvec_matches_the_q4t_kernel_it_was_ported_from() {
9201 let (rows, cols) = (256usize, 512usize);
9202 let bytes = synth_q4tp(rows, cols);
9203 let twin = q4tp_as_q4t(&bytes, rows, cols);
9204 let x: Vec<f32> = (0..cols)
9205 .map(|i| ((i * 13 + 7) % 97) as f32 / 97.0 - 0.5)
9206 .collect();
9207
9208 let mut got = vec![0f32; rows];
9209 q4tp_matvec(&bytes, &x, rows, cols, &mut got, None);
9210 let mut want = vec![0f32; rows];
9211 q4t_matvec(&twin, &x, rows, cols, &mut want, None);
9212
9213 let mut w = vec![0f32; rows * cols];
9216 cortiq_core::quant::dequant_q4tp(&bytes, rows, cols, &mut w);
9217 for r in 0..rows {
9218 let mag: f32 = (0..cols).map(|c| (w[r * cols + c] * x[c]).abs()).sum();
9219 assert!(
9220 (got[r] - want[r]).abs() <= 1e-3 * mag,
9221 "row {r}: q4tp {} vs q4t {}",
9222 got[r],
9223 want[r]
9224 );
9225 }
9226 }
9227
9228 #[test]
9233 fn q4tp_matmat_matches_the_q4t_kernel_it_was_ported_from() {
9234 let (rows, cols, b) = (256usize, 512usize, 5usize);
9235 let bytes = synth_q4tp(rows, cols);
9236 let twin = q4tp_as_q4t(&bytes, rows, cols);
9237 let xs: Vec<f32> = (0..b * cols)
9238 .map(|i| ((i * 29 + 11) % 89) as f32 / 89.0 - 0.5)
9239 .collect();
9240
9241 let mut got = vec![0f32; b * rows];
9242 q4tp_matmat(&bytes, &xs, b, rows, cols, &mut got, None);
9243 let mut want = vec![0f32; b * rows];
9244 q4t_matmat(&twin, &xs, b, rows, cols, &mut want, None);
9245
9246 let mut w = vec![0f32; rows * cols];
9247 cortiq_core::quant::dequant_q4tp(&bytes, rows, cols, &mut w);
9248 for t in 0..b {
9249 for r in 0..rows {
9250 let mag: f32 = (0..cols)
9251 .map(|c| (w[r * cols + c] * xs[t * cols + c]).abs())
9252 .sum();
9253 let (g, wa) = (got[t * rows + r], want[t * rows + r]);
9254 assert!(
9255 (g - wa).abs() <= 1e-3 * mag,
9256 "batch {t} row {r}: q4tp {g} vs q4t {wa}"
9257 );
9258 }
9259 }
9260 }
9261
9262 #[test]
9263 fn q4tp_matvec2_matches_the_single_stream_kernel() {
9264 let (rows, cols) = (128usize, 256usize);
9265 let gpr = cols / GROUP_SIZE;
9266 let bytes = synth_q4tp(rows, cols);
9267 let xs: Vec<f32> = (0..2 * cols)
9268 .map(|i| ((i * 29 + 11) % 89) as f32 / 89.0 - 0.5)
9269 .collect();
9270
9271 let (mut o1, mut o2) = (vec![0f32; rows], vec![0f32; rows]);
9272 q4tp_matvec2(
9273 &bytes,
9274 &xs[..cols],
9275 &xs[cols..],
9276 rows,
9277 cols,
9278 &mut o1,
9279 &mut o2,
9280 None,
9281 );
9282
9283 let v = Q4tpView::new(&bytes, rows, cols);
9286 let mut sc = vec![0f32; gpr];
9287 for r in 0..rows {
9288 v.scales_into(r, gpr, &mut sc);
9289 assert_eq!(o1[r], q4tp_row_exact(v.nib, r, gpr, &xs[..cols], &sc));
9290 assert_eq!(o2[r], q4tp_row_exact(v.nib, r, gpr, &xs[cols..], &sc));
9291 }
9292 }
9293
9294 #[test]
9301 fn q4tp_matvec_keeps_pace_with_q4t() {
9302 let (rows, cols) = (4096usize, 3072usize);
9303 let bytes = synth_q4tp(rows, cols);
9304 let twin = q4tp_as_q4t(&bytes, rows, cols);
9305 let x: Vec<f32> = (0..cols).map(|i| (i % 97) as f32 / 97.0 - 0.5).collect();
9306 let mut o = vec![0f32; rows];
9307 let n = 12;
9308 let mut best = (f64::MAX, f64::MAX);
9309 for _ in 0..3 {
9312 let t0 = std::time::Instant::now();
9313 for _ in 0..n {
9314 q4t_matvec(&twin, &x, rows, cols, &mut o, None);
9315 }
9316 best.0 = best.0.min(t0.elapsed().as_secs_f64());
9317 let t0 = std::time::Instant::now();
9318 for _ in 0..n {
9319 q4tp_matvec(&bytes, &x, rows, cols, &mut o, None);
9320 }
9321 best.1 = best.1.min(t0.elapsed().as_secs_f64());
9322 }
9323 let ratio = best.1 / best.0;
9324 println!("q4t {:.3} ms | q4tp {:.3} ms | {ratio:.2}x", best.0 * 1e3 / n as f64, best.1 * 1e3 / n as f64);
9325 assert!(ratio < 2.0, "q4tp matvec {ratio:.2}x slower than q4t");
9326 }
9327
9328 #[cfg(target_os = "macos")]
9329 #[test]
9330 fn q4t_matmat_accel_matches_dequant_reference() {
9331 if !accel_gemm_enabled() {
9332 return; }
9334 let (rows, cols, b) = (512usize, 1024usize, 8usize); let gpr = cols / GROUP_SIZE;
9336 let mut bytes = vec![0u8; rows * gpr * Q4_TILE];
9337 for r in 0..rows {
9338 for g in 0..gpr {
9339 let t = (r * gpr + g) * Q4_TILE;
9340 let sc = 0.02 + 0.0005 * ((r * gpr + g) % 64) as f32;
9341 bytes[t..t + 2].copy_from_slice(&cortiq_core::quant::f32_to_f16(sc).to_le_bytes());
9342 for k in 0..16 {
9343 bytes[t + 2 + k] = ((r * 31 + g * 7 + k * 13) % 251) as u8;
9344 }
9345 }
9346 }
9347 let x: Vec<f32> = (0..b * cols)
9348 .map(|i| ((i * 13 + 7) % 97) as f32 / 97.0 - 0.5)
9349 .collect();
9350 let mut got = vec![0f32; b * rows];
9351 q4t_matmat(&bytes, &x, b, rows, cols, &mut got, None);
9352 let mut w = vec![0f32; rows * cols];
9354 for r in 0..rows {
9355 for g in 0..gpr {
9356 let t = (r * gpr + g) * Q4_TILE;
9357 let s = f16_to_f32(u16::from_le_bytes([bytes[t], bytes[t + 1]]));
9358 for (k, &bb) in bytes[t + 2..t + Q4_TILE].iter().enumerate() {
9359 w[r * cols + g * GROUP_SIZE + k * 2] = ((bb & 0x0F) as f32 - 8.0) * s;
9360 w[r * cols + g * GROUP_SIZE + k * 2 + 1] =
9361 (((bb >> 4) & 0x0F) as f32 - 8.0) * s;
9362 }
9363 }
9364 }
9365 for bi in 0..b {
9366 for r in 0..rows {
9367 let want: f32 = (0..cols).map(|j| x[bi * cols + j] * w[r * cols + j]).sum();
9368 let d = (got[bi * rows + r] - want).abs();
9369 assert!(
9370 d <= want.abs().max(1.0) * 1e-4,
9371 "accel q4t GEMM diverged at ({bi},{r}): {} vs {want}",
9372 got[bi * rows + r]
9373 );
9374 }
9375 }
9376 }
9377
9378 #[test]
9379 fn q4matvec_matches_full_dequant() {
9380 let (rows, cols) = (8, 64);
9381 let groups = rows * cols / GROUP_SIZE;
9382 let mut bytes = Vec::with_capacity(groups * 16 + groups * 2);
9384 for i in 0..groups * 16 {
9385 bytes.push((((i * 7 + 3) % 256) & 0xFF) as u8);
9386 }
9387 for g in 0..groups {
9388 let s = 0.01 + 0.003 * g as f32;
9389 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
9390 }
9391 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.17).sin()).collect();
9392
9393 let mut reference = vec![0.0f32; rows * cols];
9394 cortiq_core::quant::dequant_q4_block(&bytes, &mut reference);
9395 let mut expect = vec![0.0f32; rows];
9396 for r in 0..rows {
9397 expect[r] = reference[r * cols..(r + 1) * cols]
9398 .iter()
9399 .zip(&x)
9400 .map(|(w, xv)| w * xv)
9401 .sum();
9402 }
9403
9404 let mut got = vec![0.0f32; rows];
9405 q4matvec(&bytes, &x, rows, cols, &mut got, None);
9406 let tol = if a8w8_enabled() { 6e-2 } else { 1e-4 };
9410 let scale = expect.iter().fold(0f32, |m, v| m.max(v.abs())).max(1.0);
9411 for r in 0..rows {
9412 assert!(
9413 (got[r] - expect[r]).abs() < tol * scale,
9414 "row {r}: {} vs {}",
9415 got[r],
9416 expect[r]
9417 );
9418 }
9419 }
9420
9421 #[test]
9424 fn vbitmatvec2_equals_two_singles() {
9425 let (rows, cols) = (6, 64);
9426 let ng = cols / GROUP_SIZE;
9427 let bits: Vec<u8> = vec![3, 4, 5, 6, 8, 4];
9428 let mut bytes = bits.clone();
9429 for g in 0..rows * ng {
9430 let s = 0.02 + 0.001 * g as f32;
9431 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
9432 }
9433 for r in 0..rows {
9434 let b = bits[r] as usize;
9435 let (mut acc, mut nb) = (0u64, 0usize);
9436 let mut rowbytes = Vec::new();
9437 for i in 0..cols {
9438 let v = ((i * 7 + r * 13) % (1 << b)) as u64;
9439 acc = (acc << b) | v;
9440 nb += b;
9441 while nb >= 8 {
9442 nb -= 8;
9443 rowbytes.push(((acc >> nb) & 0xFF) as u8);
9444 }
9445 }
9446 if nb > 0 {
9447 rowbytes.push(((acc << (8 - nb)) & 0xFF) as u8);
9448 }
9449 bytes.extend_from_slice(&rowbytes);
9450 }
9451 let x1: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.19).sin()).collect();
9452 let x2: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.11).cos()).collect();
9453 let offsets = vbit_row_offsets(&bytes, rows, cols);
9454
9455 let (mut a1, mut a2) = (vec![0f32; rows], vec![0f32; rows]);
9456 vbitmatvec(&bytes, &offsets, &x1, rows, cols, &mut a1, None);
9457 vbitmatvec(&bytes, &offsets, &x2, rows, cols, &mut a2, None);
9458 let (mut b1, mut b2) = (vec![0f32; rows], vec![0f32; rows]);
9459 vbitmatvec2(
9460 &bytes, &offsets, &x1, &x2, rows, cols, &mut b1, &mut b2, None,
9461 );
9462 assert_eq!(a1, b1, "fused vbit lane 1 must be bit-identical");
9463 assert_eq!(a2, b2, "fused vbit lane 2 must be bit-identical");
9464 }
9465
9466 #[test]
9468 fn q4matvec2_equals_two_singles() {
9469 let (rows, cols) = (8, 128);
9470 let groups = rows * cols / GROUP_SIZE;
9471 let mut bytes = Vec::with_capacity(groups * 16 + groups * 2);
9472 for i in 0..groups * 16 {
9473 bytes.push((((i * 7 + 3) % 256) & 0xFF) as u8);
9474 }
9475 for g in 0..groups {
9476 let s = 0.01 + 0.003 * g as f32;
9477 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
9478 }
9479 let mut x1: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.17).sin()).collect();
9482 x1[9] = 250.0;
9483 let x2: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.23).cos()).collect();
9484
9485 let (mut a1, mut a2) = (vec![0f32; rows], vec![0f32; rows]);
9486 q4matvec(&bytes, &x1, rows, cols, &mut a1, None);
9487 q4matvec(&bytes, &x2, rows, cols, &mut a2, None);
9488 let (mut b1, mut b2) = (vec![0f32; rows], vec![0f32; rows]);
9489 q4matvec2(&bytes, &x1, &x2, rows, cols, &mut b1, &mut b2, None);
9490 assert_eq!(a1, b1, "fused q4 lane 1 must be bit-identical");
9491 assert_eq!(a2, b2, "fused q4 lane 2 must be bit-identical");
9492 }
9493
9494 #[test]
9497 fn matvec_many_equals_separate_matvecs() {
9498 use crate::pool::Pool;
9499 let (r1, r2, cols) = (300, 200, 64);
9500 let mk = |salt: usize, rows: usize| {
9501 QTensor::from_f32(
9502 (0..rows * cols)
9503 .map(|i| ((i * 7 + salt) % 97) as f32 / 97.0 - 0.5)
9504 .collect(),
9505 rows,
9506 cols,
9507 )
9508 };
9509 let (a, b) = (mk(1, r1), mk(5, r2));
9510 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.11).sin()).collect();
9511 let pool = Pool::new(3);
9512
9513 let (mut ea, mut eb) = (vec![0f32; r1], vec![0f32; r2]);
9514 a.matvec(&x, &mut ea, Some(&pool));
9515 b.matvec(&x, &mut eb, Some(&pool));
9516 let (mut ga, mut gb) = (vec![0f32; r1], vec![0f32; r2]);
9517 QTensor::matvec_many([&a, &b], &x, [&mut ga, &mut gb], Some(&pool));
9518 assert_eq!(ea, ga, "fused multi-matrix lane 1 must be bit-identical");
9519 assert_eq!(eb, gb, "fused multi-matrix lane 2 must be bit-identical");
9520 }
9521
9522 #[test]
9525 fn batched_matmat_equals_per_position_matvec() {
9526 let (rows, cols, b) = (8, 64, 5);
9527 let groups = rows * cols / GROUP_SIZE;
9529 let mut q4 = Vec::new();
9530 for i in 0..groups * 16 {
9531 q4.push((((i * 7 + 3) % 256) & 0xFF) as u8);
9532 }
9533 for g in 0..groups {
9534 q4.extend_from_slice(
9535 &cortiq_core::quant::f32_to_f16(0.01 + 0.003 * g as f32).to_le_bytes(),
9536 );
9537 }
9538 let ng = cols / GROUP_SIZE;
9540 let bits: Vec<u8> = vec![3, 4, 5, 6, 8, 4, 5, 3];
9541 let mut vb = bits.clone();
9542 for g in 0..rows * ng {
9543 vb.extend_from_slice(
9544 &cortiq_core::quant::f32_to_f16(0.02 + 0.001 * g as f32).to_le_bytes(),
9545 );
9546 }
9547 for r in 0..rows {
9548 let bw = bits[r] as usize;
9549 let (mut acc, mut nb) = (0u64, 0usize);
9550 let mut rowbytes = Vec::new();
9551 for i in 0..cols {
9552 let v = ((i * 7 + r * 13) % (1 << bw)) as u64;
9553 acc = (acc << bw) | v;
9554 nb += bw;
9555 while nb >= 8 {
9556 nb -= 8;
9557 rowbytes.push(((acc >> nb) & 0xFF) as u8);
9558 }
9559 }
9560 if nb > 0 {
9561 rowbytes.push(((acc << (8 - nb)) & 0xFF) as u8);
9562 }
9563 vb.extend_from_slice(&rowbytes);
9564 }
9565 let offsets = vbit_row_offsets(&vb, rows, cols);
9566
9567 let xs: Vec<f32> = (0..b * cols).map(|i| (i as f32 * 0.13).sin()).collect();
9568
9569 let mut got = vec![0f32; b * rows];
9571 q4matmat(&q4, &xs, b, rows, cols, &mut got, None);
9572 for bi in 0..b {
9573 let mut expect = vec![0f32; rows];
9574 q4matvec(
9575 &q4,
9576 &xs[bi * cols..(bi + 1) * cols],
9577 rows,
9578 cols,
9579 &mut expect,
9580 None,
9581 );
9582 assert_eq!(
9583 &got[bi * rows..(bi + 1) * rows],
9584 &expect[..],
9585 "q4 batch pos {bi}"
9586 );
9587 }
9588
9589 let mut got = vec![0f32; b * rows];
9591 vbitmatmat(&vb, &offsets, &xs, b, rows, cols, &mut got, None);
9592 for bi in 0..b {
9593 let mut expect = vec![0f32; rows];
9594 vbitmatvec(
9595 &vb,
9596 &offsets,
9597 &xs[bi * cols..(bi + 1) * cols],
9598 rows,
9599 cols,
9600 &mut expect,
9601 None,
9602 );
9603 assert_eq!(
9604 &got[bi * rows..(bi + 1) * rows],
9605 &expect[..],
9606 "vbit batch pos {bi}"
9607 );
9608 }
9609 }
9610
9611 #[test]
9615 fn q4_tiled_matches_q4_block_bitexact() {
9616 let (rows, cols, b) = (8usize, 128usize, 3usize);
9617 let groups = rows * cols / GROUP_SIZE;
9618 let mut split = Vec::with_capacity(groups * 18);
9619 for i in 0..groups * 16 {
9620 split.push((((i * 7 + 3) % 256) & 0xFF) as u8);
9621 }
9622 for g in 0..groups {
9623 split.extend_from_slice(
9624 &cortiq_core::quant::f32_to_f16(0.01 + 0.003 * g as f32).to_le_bytes(),
9625 );
9626 }
9627 let (packed, scales) = split.split_at(groups * 16);
9629 let mut tiled = Vec::with_capacity(groups * Q4_TILE);
9630 for g in 0..groups {
9631 tiled.extend_from_slice(&scales[g * 2..g * 2 + 2]);
9632 tiled.extend_from_slice(&packed[g * 16..(g + 1) * 16]);
9633 }
9634
9635 let mut x1: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.17).sin()).collect();
9636 x1[9] = 250.0; let x2: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.23).cos()).collect();
9638
9639 let (mut a, mut t) = (vec![0f32; rows], vec![0f32; rows]);
9640 q4matvec(&split, &x1, rows, cols, &mut a, None);
9641 q4t_matvec(&tiled, &x1, rows, cols, &mut t, None);
9642 assert_eq!(a, t, "q4t matvec must match q4 bit-for-bit");
9643
9644 let (mut a1, mut a2) = (vec![0f32; rows], vec![0f32; rows]);
9645 let (mut t1, mut t2) = (vec![0f32; rows], vec![0f32; rows]);
9646 q4matvec2(&split, &x1, &x2, rows, cols, &mut a1, &mut a2, None);
9647 q4t_matvec2(&tiled, &x1, &x2, rows, cols, &mut t1, &mut t2, None);
9648 assert_eq!(a1, t1);
9649 assert_eq!(a2, t2);
9650
9651 let xs: Vec<f32> = (0..b * cols).map(|i| (i as f32 * 0.13).sin()).collect();
9652 let (mut am, mut tm) = (vec![0f32; b * rows], vec![0f32; b * rows]);
9653 q4matmat(&split, &xs, b, rows, cols, &mut am, None);
9654 q4t_matmat(&tiled, &xs, b, rows, cols, &mut tm, None);
9655 assert_eq!(am, tm, "q4t matmat must match q4 bit-for-bit");
9656 }
9657
9658 #[test]
9665 fn q4matvec_sdot_outlier_exact() {
9666 let (rows, cols) = (4, 128);
9667 let groups = rows * cols / GROUP_SIZE;
9668 let mut bytes = Vec::with_capacity(groups * 16 + groups * 2);
9669 for i in 0..groups * 16 {
9670 bytes.push(((i * 11 + 5) % 256) as u8);
9671 }
9672 for g in 0..groups {
9673 let s = 0.02 + 0.002 * g as f32;
9674 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
9675 }
9676 let mut x: Vec<f32> = (0..cols)
9677 .map(|i| match i % 3 {
9678 0 => 1.0,
9679 1 => -1.0,
9680 _ => 0.0,
9681 })
9682 .collect();
9683 x[17] = 300.0; let mut reference = vec![0.0f32; rows * cols];
9686 cortiq_core::quant::dequant_q4_block(&bytes, &mut reference);
9687 let mut expect = vec![0.0f32; rows];
9688 for r in 0..rows {
9689 expect[r] = reference[r * cols..(r + 1) * cols]
9690 .iter()
9691 .zip(&x)
9692 .map(|(w, xv)| w * xv)
9693 .sum();
9694 }
9695 let mut got = vec![0.0f32; rows];
9696 q4matvec(&bytes, &x, rows, cols, &mut got, None);
9697 let scale = expect.iter().fold(0f32, |m, v| m.max(v.abs())).max(1.0);
9698 for r in 0..rows {
9699 assert!(
9700 (got[r] - expect[r]).abs() < 2e-3 * scale,
9701 "row {r}: {} vs {} (outlier term must be exact)",
9702 got[r],
9703 expect[r]
9704 );
9705 }
9706 }
9707
9708 #[test]
9712 fn q1t_matvec_matches_reference() {
9713 use cortiq_core::quant::{dequant_q1t, f32_to_f16};
9714 let (rows, cols) = (3usize, 64usize); let gpr = cols / GROUP_SIZE;
9716 let scales = [0.5f32, 0.3, 0.7, 0.2, 0.6, 0.15];
9717 let outliers: [(u32, f32); 3] = [(5, 9.0), (70, -4.5), (150, 3.25)];
9719 let is_out = |flat: usize| outliers.iter().any(|&(i, _)| i as usize == flat);
9720 let mut bytes = Vec::new();
9721 for r in 0..rows {
9722 for g in 0..gpr {
9723 bytes.extend_from_slice(&f32_to_f16(scales[r * gpr + g]).to_le_bytes());
9724 let mut c = [0u8; 7];
9725 for k in 0..GROUP_SIZE {
9726 let code = if is_out(r * cols + g * GROUP_SIZE + k) {
9728 0
9729 } else {
9730 ((k + r * 3 + g) % 3) as u8 };
9732 cortiq_core::quant::q1t_pack(&mut c, k, code);
9733 }
9734 bytes.extend_from_slice(&c);
9735 }
9736 }
9737 let mut row_ptr = vec![0u32; rows + 1];
9740 for &(idx, _) in &outliers {
9741 row_ptr[idx as usize / cols + 1] += 1;
9742 }
9743 for r in 0..rows {
9744 row_ptr[r + 1] += row_ptr[r];
9745 }
9746 for &p in &row_ptr {
9747 bytes.extend_from_slice(&p.to_le_bytes());
9748 }
9749 for &(idx, v) in &outliers {
9750 bytes.extend_from_slice(&((idx as usize % cols) as u16).to_le_bytes());
9751 bytes.extend_from_slice(&f32_to_f16(v).to_le_bytes());
9752 }
9753
9754 let mut refw = vec![0f32; rows * cols];
9755 dequant_q1t(&bytes, rows, cols, &mut refw);
9756 let x: Vec<f32> = (0..cols)
9759 .map(|j| if j % 3 == 0 { 1.0 } else { -1.0 })
9760 .collect();
9761 let mut expect = vec![0f32; rows];
9762 for r in 0..rows {
9763 let mut a = 0.0f32;
9764 for j in 0..cols {
9765 a += refw[r * cols + j] * x[j];
9766 }
9767 expect[r] = a;
9768 }
9769 let tol = |e: f32| 1e-3 * e.abs().max(1e-3);
9770 let mut got = vec![0f32; rows];
9771 q1t_matvec(&bytes, &x, rows, cols, &mut got, None);
9772 for r in 0..rows {
9773 assert!(
9774 (got[r] - expect[r]).abs() < tol(expect[r]),
9775 "row {r}: {} vs {}",
9776 got[r],
9777 expect[r]
9778 );
9779 }
9780 let x2: Vec<f32> = x.iter().chain(x.iter().map(|v| v)).copied().collect();
9782 let mut gm = vec![0f32; 2 * rows];
9783 q1t_matmat(&bytes, &x2, 2, rows, cols, &mut gm, None);
9784 for r in 0..rows {
9785 assert!((gm[r] - expect[r]).abs() < tol(expect[r]));
9786 assert!((gm[rows + r] - expect[r]).abs() < tol(expect[r]));
9787 }
9788 let xb: Vec<f32> = (0..cols)
9792 .map(|j| if j % 5 == 0 { -1.0 } else { 1.0 })
9793 .collect();
9794 let (mut s1, mut s2) = (vec![0f32; rows], vec![0f32; rows]);
9795 q1t_matvec(&bytes, &x, rows, cols, &mut s1, None);
9796 q1t_matvec(&bytes, &xb, rows, cols, &mut s2, None);
9797 let (mut p1, mut p2) = (vec![0f32; rows], vec![0f32; rows]);
9798 q1t_matvec2(&bytes, &x, &xb, rows, cols, &mut p1, &mut p2, None);
9799 assert_eq!(p1, s1, "q1t pair lane 1 ≠ single matvec");
9800 assert_eq!(p2, s2, "q1t pair lane 2 ≠ single matvec");
9801 }
9802
9803 #[test]
9806 fn q1t_matvec2_odd_gpr_matches_singles() {
9807 use cortiq_core::quant::{Q1T_TILE, f32_to_f16, q1t_pack};
9808 let (rows, cols) = (5usize, 96usize); let gpr = cols / GROUP_SIZE;
9810 let mut bytes = Vec::with_capacity(rows * gpr * Q1T_TILE);
9811 for r in 0..rows {
9812 for g in 0..gpr {
9813 bytes.extend_from_slice(&f32_to_f16(0.1 + 0.05 * (r + g) as f32).to_le_bytes());
9814 let mut c = [0u8; 7];
9815 for k in 0..GROUP_SIZE {
9816 q1t_pack(&mut c, k, ((k * 7 + r * 5 + g * 3) % 3) as u8);
9817 }
9818 bytes.extend_from_slice(&c);
9819 }
9820 }
9821 let x1: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.31).sin()).collect();
9822 let x2: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.17).cos()).collect();
9823 let (mut s1, mut s2) = (vec![0f32; rows], vec![0f32; rows]);
9824 q1t_matvec(&bytes, &x1, rows, cols, &mut s1, None);
9825 q1t_matvec(&bytes, &x2, rows, cols, &mut s2, None);
9826 let (mut p1, mut p2) = (vec![0f32; rows], vec![0f32; rows]);
9827 q1t_matvec2(&bytes, &x1, &x2, rows, cols, &mut p1, &mut p2, None);
9828 assert_eq!(p1, s1, "odd-gpr pair lane 1 ≠ single");
9829 assert_eq!(p2, s2, "odd-gpr pair lane 2 ≠ single");
9830 }
9831
9832 #[test]
9836 #[ignore]
9837 fn q1t_matvec2_speed() {
9838 use cortiq_core::quant::{Q1T_TILE, f32_to_f16, q1t_pack};
9839 use std::time::Instant;
9840 let (rows, cols) = (8192usize, 4096usize);
9841 let gpr = cols / GROUP_SIZE;
9842 let mut bytes = Vec::with_capacity(rows * gpr * Q1T_TILE);
9843 for r in 0..rows {
9844 for g in 0..gpr {
9845 let s = 0.1 + ((r + g) % 7) as f32 * 0.01;
9846 bytes.extend_from_slice(&f32_to_f16(s).to_le_bytes());
9847 let mut c = [0u8; 7];
9848 for k in 0..GROUP_SIZE {
9849 q1t_pack(&mut c, k, ((k * 7 + r + g) % 3) as u8);
9850 }
9851 bytes.extend_from_slice(&c);
9852 }
9853 }
9854 let x1: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.31).sin()).collect();
9855 let x2: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.17).cos()).collect();
9856 let (mut s1, mut s2) = (vec![0f32; rows], vec![0f32; rows]);
9857 let (mut p1, mut p2) = (vec![0f32; rows], vec![0f32; rows]);
9858 q1t_matvec(&bytes, &x1, rows, cols, &mut s1, None);
9860 q1t_matvec2(&bytes, &x1, &x2, rows, cols, &mut p1, &mut p2, None);
9861 let (mut t_pair, mut t_two) = (f64::MAX, f64::MAX);
9862 for _ in 0..8 {
9863 let t0 = Instant::now();
9864 q1t_matvec2(&bytes, &x1, &x2, rows, cols, &mut p1, &mut p2, None);
9865 t_pair = t_pair.min(t0.elapsed().as_secs_f64() * 1000.0);
9866 let t1 = Instant::now();
9867 q1t_matvec(&bytes, &x1, rows, cols, &mut s1, None);
9868 q1t_matvec(&bytes, &x2, rows, cols, &mut s2, None);
9869 t_two = t_two.min(t1.elapsed().as_secs_f64() * 1000.0);
9870 }
9871 assert_eq!(p1, s1);
9872 assert_eq!(p2, s2);
9873 println!("q1t pair {rows}x{cols}: fused {t_pair:.2} ms | two singles {t_two:.2} ms");
9874 }
9875
9876 #[test]
9880 #[ignore]
9881 fn q1t_matvec_speed() {
9882 use cortiq_core::quant::{Q1T_TILE, f32_to_f16, q1t_code, q1t_pack};
9883 use std::time::Instant;
9884 let (rows, cols) = (8192usize, 4096usize); let gpr = cols / GROUP_SIZE;
9886 let mut bytes = Vec::with_capacity(rows * gpr * Q1T_TILE + 16);
9887 for r in 0..rows {
9888 for g in 0..gpr {
9889 let s = 0.1 + ((r + g) % 7) as f32 * 0.01;
9890 bytes.extend_from_slice(&f32_to_f16(s).to_le_bytes());
9891 let mut c = [0u8; 7];
9892 for k in 0..GROUP_SIZE {
9893 q1t_pack(&mut c, k, ((k * 7 + r + g) % 3) as u8);
9894 }
9895 bytes.extend_from_slice(&c);
9896 }
9897 }
9898 let (n, stride) = (rows * cols, 40usize); let mut row_ptr = vec![0u32; rows + 1];
9900 let mut idx = 0usize;
9901 while idx < n {
9902 row_ptr[idx / cols + 1] += 1;
9903 idx += stride;
9904 }
9905 for r in 0..rows {
9906 row_ptr[r + 1] += row_ptr[r];
9907 }
9908 for &p in &row_ptr {
9909 bytes.extend_from_slice(&p.to_le_bytes());
9910 }
9911 let mut idx = 0usize;
9912 while idx < n {
9913 bytes.extend_from_slice(&((idx % cols) as u16).to_le_bytes());
9914 bytes.extend_from_slice(&f32_to_f16((idx % 13) as f32 * 0.1 - 0.6).to_le_bytes());
9915 idx += stride;
9916 }
9917 let x: Vec<f32> = (0..cols)
9920 .map(|j| if j % 3 == 0 { 1.0 } else { -1.0 })
9921 .collect();
9922 let (rp_off, ent_off, has_ov) = q1t_overlay(&bytes, rows * gpr * Q1T_TILE, rows);
9923
9924 let slow = |out: &mut [f32]| {
9926 let mut buf = vec![0f32; cols];
9927 for r in 0..rows {
9928 for g in 0..gpr {
9929 let off = (r * gpr + g) * Q1T_TILE;
9930 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
9931 let codes = &bytes[off + 2..off + Q1T_TILE];
9932 for k in 0..GROUP_SIZE {
9933 buf[g * GROUP_SIZE + k] = match q1t_code(codes, k) {
9934 1 => s,
9935 2 => -s,
9936 _ => 0.0,
9937 };
9938 }
9939 }
9940 out[r] = q1t_row_outlier_correction(&bytes, r, rp_off, ent_off, has_ov, &x)
9941 + (0..cols).map(|j| buf[j] * x[j]).sum::<f32>();
9942 }
9943 };
9944 let iters = 5;
9945 let mut a = vec![0f32; rows];
9946 slow(&mut a); let t = Instant::now();
9948 for _ in 0..iters {
9949 slow(&mut a);
9950 }
9951 let slow_ms = t.elapsed().as_secs_f64() * 1e3 / iters as f64;
9952
9953 let mut b = vec![0f32; rows];
9954 q1t_matvec(&bytes, &x, rows, cols, &mut b, None); let t = Instant::now();
9956 for _ in 0..iters {
9957 q1t_matvec(&bytes, &x, rows, cols, &mut b, None);
9958 }
9959 let fast_ms = t.elapsed().as_secs_f64() * 1e3 / iters as f64;
9960
9961 for r in 0..rows {
9962 assert!((a[r] - b[r]).abs() < 1e-2, "mismatch row {r}");
9963 }
9964 println!(
9965 "q1t matvec {rows}x{cols} (1 thread): div-decode {slow_ms:.2} ms fused-LUT {fast_ms:.2} ms => {:.2}x",
9966 slow_ms / fast_ms
9967 );
9968 }
9969}