1use crate::pool::{Pool, matvec_rows, matvec_rows2};
15use cortiq_core::quant::{GROUP_SIZE, Q1_TILE, Q4_TILE, f16_to_f32};
16use cortiq_core::{CmfModel, TensorDtype};
17use std::sync::Arc;
18
19pub enum QTensor {
20 F32 {
21 data: Vec<f32>,
22 rows: usize,
23 cols: usize,
24 },
25 Mapped {
26 model: Arc<CmfModel>,
27 idx: usize,
29 dtype: TensorDtype,
30 rows: usize,
31 cols: usize,
32 row_scale: Vec<f32>,
34 col_field: Vec<f32>,
36 vbit_offsets: Vec<usize>,
40 repack: Vec<u8>,
48 },
49}
50
51fn repack_enabled() -> bool {
58 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
59 *ON.get_or_init(|| {
60 std::env::var("CMF_REPACK")
61 .map(|v| v == "1")
62 .unwrap_or(cfg!(target_os = "android"))
63 })
64}
65
66fn q8_repack(bytes: &[u8], rows: usize, cols: usize) -> Vec<u8> {
70 #[cfg(target_arch = "aarch64")]
71 let arch_ok = sdot_enabled();
72 #[cfg(not(target_arch = "aarch64"))]
73 let arch_ok = false;
74 if !arch_ok || !repack_enabled() || rows < 256 || cols % 16 != 0 {
75 return Vec::new();
76 }
77 q8_repack_layout(bytes, rows, cols)
78}
79
80fn q8_repack_layout(bytes: &[u8], rows: usize, cols: usize) -> Vec<u8> {
83 let groups = rows / 4;
84 let mut rep = vec![0u8; groups * 4 * cols];
85 for g in 0..groups {
86 let dst = &mut rep[g * 4 * cols..(g + 1) * 4 * cols];
87 for c in 0..cols / 16 {
88 for lane in 0..4 {
89 let src = (g * 4 + lane) * cols + c * 16;
90 dst[c * 64 + lane * 16..c * 64 + lane * 16 + 16]
91 .copy_from_slice(&bytes[src..src + 16]);
92 }
93 }
94 }
95 rep
96}
97
98fn vbit_row_offsets(bytes: &[u8], rows: usize, cols: usize) -> Vec<usize> {
101 let ng = cols / GROUP_SIZE;
102 let bits = &bytes[..rows];
103 let mut offsets = Vec::with_capacity(rows + 1);
104 let mut off = rows + rows * ng * 2;
105 for r in 0..rows {
106 offsets.push(off);
107 off += (cols * bits[r] as usize).div_ceil(8);
108 }
109 offsets.push(off);
110 offsets
111}
112
113impl QTensor {
114 pub fn from_f32(data: Vec<f32>, rows: usize, cols: usize) -> Self {
115 debug_assert_eq!(data.len(), rows * cols);
116 Self::F32 { data, rows, cols }
117 }
118
119 pub fn from_model(model: &Arc<CmfModel>, name: &str) -> Result<Self, String> {
122 let idx = model
125 .tensor_index(name)
126 .ok_or_else(|| format!("tensor '{name}' not found in CMF directory"))?;
127 let entry = &model.tensors[idx];
128 if entry.shape.len() != 2 {
129 return Err(format!("QTensor::from_model needs 2-D, got '{name}'"));
130 }
131 let (rows, cols) = (entry.shape[0], entry.shape[1]);
132 let bytes = model.entry_bytes(entry);
133
134 match entry.dtype {
135 TensorDtype::Q8Row | TensorDtype::Q8_2f => {
136 let n = rows * cols;
137 let scales_off = n;
138 let row_scale: Vec<f32> = (0..rows)
139 .map(|o| {
140 f16_to_f32(u16::from_le_bytes([
141 bytes[scales_off + o * 2],
142 bytes[scales_off + o * 2 + 1],
143 ]))
144 })
145 .collect();
146 let col_field: Vec<f32> = if entry.dtype == TensorDtype::Q8_2f {
147 let col_off = n + rows * 2;
148 (0..cols)
149 .map(|i| {
150 f16_to_f32(u16::from_le_bytes([
151 bytes[col_off + i * 2],
152 bytes[col_off + i * 2 + 1],
153 ]))
154 })
155 .collect()
156 } else {
157 Vec::new()
158 };
159 Ok(Self::Mapped {
160 model: model.clone(),
161 idx,
162 dtype: entry.dtype,
163 rows,
164 cols,
165 row_scale,
166 col_field,
167 vbit_offsets: Vec::new(),
168 repack: q8_repack(bytes, rows, cols),
169 })
170 }
171 TensorDtype::Vbit if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
173 model: model.clone(),
174 idx,
175 dtype: entry.dtype,
176 rows,
177 cols,
178 row_scale: Vec::new(),
179 col_field: Vec::new(),
180 vbit_offsets: vbit_row_offsets(bytes, rows, cols),
181 repack: Vec::new(),
182 }),
183 TensorDtype::VbitRo if cols % GROUP_SIZE == 0 => {
187 let (_, off_off, packed_off) = cortiq_core::quant::vbit_ro_sections(rows, cols);
188 let offsets: Vec<usize> = (0..=rows)
189 .map(|r| packed_off + cortiq_core::quant::vbit_ro_offset(bytes, off_off, r))
190 .collect();
191 Ok(Self::Mapped {
192 model: model.clone(),
193 idx,
194 dtype: entry.dtype,
195 rows,
196 cols,
197 row_scale: Vec::new(),
198 col_field: Vec::new(),
199 vbit_offsets: offsets,
200 repack: Vec::new(),
201 })
202 }
203 TensorDtype::Q4Tiled if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
209 model: model.clone(),
210 idx,
211 dtype: entry.dtype,
212 rows,
213 cols,
214 row_scale: Vec::new(),
215 col_field: Vec::new(),
216 vbit_offsets: Vec::new(),
217 repack: Vec::new(),
218 }),
219 TensorDtype::Q4Block if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
220 model: model.clone(),
221 idx,
222 dtype: entry.dtype,
223 rows,
224 cols,
225 row_scale: Vec::new(),
226 col_field: Vec::new(),
227 vbit_offsets: Vec::new(),
228 repack: Vec::new(),
229 }),
230 TensorDtype::Q1 if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
232 model: model.clone(),
233 idx,
234 dtype: entry.dtype,
235 rows,
236 cols,
237 row_scale: Vec::new(),
238 col_field: Vec::new(),
239 vbit_offsets: Vec::new(),
240 repack: Vec::new(),
241 }),
242 TensorDtype::Q1T if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
246 model: model.clone(),
247 idx,
248 dtype: entry.dtype,
249 rows,
250 cols,
251 row_scale: Vec::new(),
252 col_field: Vec::new(),
253 vbit_offsets: Vec::new(),
254 repack: Vec::new(),
255 }),
256 _ => {
258 let mut data = vec![0.0f32; rows * cols];
259 cortiq_core::quant::dequant_tensor(entry, bytes, &mut data)?;
260 Ok(Self::from_f32(data, rows, cols))
261 }
262 }
263 }
264
265 pub(crate) fn is_q1(&self) -> bool {
268 matches!(
269 self,
270 Self::Mapped {
271 dtype: TensorDtype::Q1,
272 ..
273 }
274 )
275 }
276
277 pub(crate) fn f32_parts(&self) -> Option<(&[f32], usize, usize)> {
280 match self {
281 Self::F32 { data, rows, cols } => Some((data, *rows, *cols)),
282 _ => None,
283 }
284 }
285
286 pub(crate) fn q1_parts(&self) -> Option<(usize, usize, usize)> {
293 match self {
294 #[cfg(target_os = "macos")]
295 Self::Mapped {
296 dtype: TensorDtype::Q1T,
297 ..
298 } if !crate::gpu::metal_q1t_enabled() => None,
299 Self::Mapped {
300 idx,
301 dtype:
302 TensorDtype::Q1
303 | TensorDtype::Q1T
304 | TensorDtype::Q4Block
305 | TensorDtype::Q4Tiled
306 | TensorDtype::Q8Row
307 | TensorDtype::Q8_2f,
308 rows,
309 cols,
310 ..
311 } => Some((*idx, *rows, *cols)),
312 _ => None,
313 }
314 }
315
316 pub(crate) fn q8_row_parts(&self) -> Option<(usize, usize, usize, &[f32])> {
321 match self {
322 Self::Mapped {
323 idx,
324 dtype: TensorDtype::Q8Row,
325 rows,
326 cols,
327 row_scale,
328 col_field,
329 ..
330 } if col_field.is_empty() => Some((*idx, *rows, *cols, row_scale)),
331 _ => None,
332 }
333 }
334
335 pub fn rows(&self) -> usize {
336 match self {
337 Self::F32 { rows, .. } | Self::Mapped { rows, .. } => *rows,
338 }
339 }
340
341 pub(crate) fn mapped_q4t(&self) -> Option<(&Arc<CmfModel>, usize)> {
344 match self {
345 Self::Mapped {
346 model,
347 idx,
348 dtype: TensorDtype::Q4Tiled,
349 ..
350 } => Some((model, *idx)),
351 _ => None,
352 }
353 }
354
355 pub fn cols(&self) -> usize {
356 match self {
357 Self::F32 { cols, .. } | Self::Mapped { cols, .. } => *cols,
358 }
359 }
360
361 pub fn mapped_q1(&self) -> Option<(&std::sync::Arc<CmfModel>, usize)> {
364 match self {
365 Self::Mapped {
366 model,
367 idx,
368 dtype: TensorDtype::Q1,
369 ..
370 } => Some((model, *idx)),
371 _ => None,
372 }
373 }
374
375 pub fn graph_weight(&self) -> Option<(&std::sync::Arc<CmfModel>, usize, u8, &[f32])> {
379 match self {
380 Self::Mapped {
381 model,
382 idx,
383 dtype: TensorDtype::Q8Row,
384 row_scale,
385 ..
386 } => Some((model, *idx, 0, row_scale.as_slice())),
387 Self::Mapped {
388 model,
389 idx,
390 dtype: TensorDtype::Q1,
391 ..
392 } => Some((model, *idx, 1, &[])),
393 Self::Mapped {
398 model,
399 idx,
400 dtype: TensorDtype::Q4Tiled,
401 ..
402 } => Some((model, *idx, 5, &[])),
403 Self::Mapped {
404 model,
405 idx,
406 dtype: TensorDtype::Q4Block,
407 ..
408 } => Some((model, *idx, 2, &[])),
409 Self::Mapped {
410 model,
411 idx,
412 dtype: TensorDtype::Q1T,
413 ..
414 } => Some((model, *idx, 3, &[])),
415 _ => None,
416 }
417 }
418
419 pub fn as_f32(&self) -> Option<&[f32]> {
422 match self {
423 Self::F32 { data, .. } => Some(data),
424 Self::Mapped { .. } => None,
425 }
426 }
427
428 fn quant_bytes(&self) -> &[u8] {
429 match self {
430 Self::Mapped { model, idx, .. } => model.entry_bytes(&model.tensors[*idx]),
431 Self::F32 { .. } => unreachable!("quant_bytes on F32"),
432 }
433 }
434
435 pub fn row_f32(&self, r: usize, dst: &mut [f32]) {
437 let cols = self.cols();
438 debug_assert_eq!(dst.len(), cols);
439 match self {
440 Self::F32 { data, .. } => dst.copy_from_slice(&data[r * cols..(r + 1) * cols]),
441 Self::Mapped {
442 dtype,
443 row_scale,
444 col_field,
445 vbit_offsets,
446 ..
447 } => {
448 if *dtype == TensorDtype::Q4Tiled {
449 let bytes = self.quant_bytes();
450 let gpr = cols / GROUP_SIZE;
451 for gi in 0..gpr {
452 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
453 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
454 for (k, &b) in tile[2..].iter().enumerate() {
455 dst[gi * GROUP_SIZE + k * 2] = ((b & 0x0F) as f32 - 8.0) * s;
456 dst[gi * GROUP_SIZE + k * 2 + 1] = (((b >> 4) & 0x0F) as f32 - 8.0) * s;
457 }
458 }
459 return;
460 }
461 if *dtype == TensorDtype::Q4Block {
462 let (packed, scales) = q4_split(self.quant_bytes(), self.rows(), cols);
463 let gpr = cols / GROUP_SIZE;
464 for gi in 0..gpr {
465 let g = r * gpr + gi;
466 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
467 for (k, &b) in packed[g * 16..(g + 1) * 16].iter().enumerate() {
468 dst[gi * GROUP_SIZE + k * 2] = ((b & 0x0F) as f32 - 8.0) * s;
469 dst[gi * GROUP_SIZE + k * 2 + 1] = (((b >> 4) & 0x0F) as f32 - 8.0) * s;
470 }
471 }
472 return;
473 }
474 if *dtype == TensorDtype::Q1 {
475 let bytes = self.quant_bytes();
476 let gpr = cols / GROUP_SIZE;
477 for gi in 0..gpr {
478 let tile = &bytes[(r * gpr + gi) * Q1_TILE..(r * gpr + gi + 1) * Q1_TILE];
479 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
480 for (j, &b) in tile[2..].iter().enumerate() {
481 for k in 0..8 {
482 dst[gi * GROUP_SIZE + j * 8 + k] =
483 (((b >> k) & 1) as f32 * 2.0 - 1.0) * s;
484 }
485 }
486 }
487 return;
488 }
489 if *dtype == TensorDtype::Q1T {
490 let bytes = self.quant_bytes();
491 let gpr = cols / GROUP_SIZE;
492 let base_len = self.rows() * gpr * cortiq_core::quant::Q1T_TILE;
493 for gi in 0..gpr {
494 let off = (r * gpr + gi) * cortiq_core::quant::Q1T_TILE;
495 let s = cortiq_core::quant::f16_to_f32(u16::from_le_bytes([
496 bytes[off],
497 bytes[off + 1],
498 ]));
499 let codes = &bytes[off + 2..off + cortiq_core::quant::Q1T_TILE];
500 for k in 0..GROUP_SIZE {
501 dst[gi * GROUP_SIZE + k] = match cortiq_core::quant::q1t_code(codes, k)
502 {
503 1 => s,
504 2 => -s,
505 _ => 0.0,
506 };
507 }
508 }
509 let rows = self.rows();
511 let entries = base_len + (rows + 1) * 4;
512 if entries <= bytes.len() {
513 let ptrs = &bytes[base_len..base_len + (rows + 1) * 4];
514 let r0 = u32::from_le_bytes([
515 ptrs[r * 4],
516 ptrs[r * 4 + 1],
517 ptrs[r * 4 + 2],
518 ptrs[r * 4 + 3],
519 ]) as usize;
520 let r1 = u32::from_le_bytes([
521 ptrs[(r + 1) * 4],
522 ptrs[(r + 1) * 4 + 1],
523 ptrs[(r + 1) * 4 + 2],
524 ptrs[(r + 1) * 4 + 3],
525 ]) as usize;
526 let off = entries + r0 * 4;
527 for i in 0..r1 - r0 {
528 let item = &bytes[off + i * 4..off + i * 4 + 4];
529 let c = u16::from_le_bytes([item[0], item[1]]) as usize;
530 let v = cortiq_core::quant::f16_to_f32(u16::from_le_bytes([
531 item[2], item[3],
532 ]));
533 if c < cols {
534 dst[c] = v;
535 }
536 }
537 }
538 return;
539 }
540 if matches!(dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
541 let bytes = self.quant_bytes();
542 let rows = self.rows();
543 let ng = cols / GROUP_SIZE;
544 let bits = &bytes[..rows];
545 let sc_off = rows;
546 let off = vbit_offsets[r];
549 let b = bits[r] as usize;
550 let l = ((1usize << (b - 1)) - 1) as f32;
551 let data = &bytes[off..];
552 let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
553 for (i, d) in dst.iter_mut().enumerate() {
554 while nbits < b {
555 acc = (acc << 8) | data[idx] as u64;
556 idx += 1;
557 nbits += 8;
558 }
559 let u = ((acc >> (nbits - b)) & ((1u64 << b) - 1)) as f32;
560 nbits -= b;
561 let so = (r * ng + i / GROUP_SIZE) * 2;
562 let sv = f16_to_f32(u16::from_le_bytes([
563 bytes[sc_off + so],
564 bytes[sc_off + so + 1],
565 ]));
566 *d = (u - l) * sv;
567 }
568 return;
569 }
570 let q = &self.quant_bytes()[r * cols..(r + 1) * cols];
571 let s = row_scale[r];
572 match dtype {
573 TensorDtype::Q8Row => {
574 for (d, &b) in dst.iter_mut().zip(q) {
575 *d = (b as i8) as f32 * s;
576 }
577 }
578 TensorDtype::Q8_2f => {
579 for (i, (d, &b)) in dst.iter_mut().zip(q).enumerate() {
580 *d = (b as i8) as f32 * s * col_field[i];
581 }
582 }
583 _ => unreachable!(),
584 }
585 }
586 }
587 }
588
589 pub fn sparse_col_ok(&self) -> bool {
594 match self {
595 Self::F32 { .. } => true,
596 Self::Mapped { dtype, .. } => {
597 matches!(dtype, TensorDtype::Q8Row | TensorDtype::Q8_2f)
598 }
599 }
600 }
601
602 pub fn add_col_scaled(&self, c: usize, w: f32, out: &mut [f32]) {
606 let inter = self.cols();
607 let hidden = self.rows();
608 debug_assert_eq!(out.len(), hidden);
609 match self {
610 Self::F32 { data, .. } => {
611 for (k, o) in out.iter_mut().enumerate() {
612 *o += w * data[k * inter + c];
613 }
614 }
615 Self::Mapped {
616 dtype,
617 row_scale,
618 col_field,
619 ..
620 } => {
621 let q = self.quant_bytes();
622 let colf = if *dtype == TensorDtype::Q8_2f {
623 col_field[c]
624 } else {
625 1.0
626 };
627 let wc = w * colf;
628 for (k, o) in out.iter_mut().enumerate() {
629 let b = q[k * inter + c] as i8 as f32;
630 *o += wc * b * row_scale[k];
631 }
632 }
633 }
634 }
635
636 pub fn row_dot(&self, r: usize, x: &[f32], scratch: &mut [f32]) -> f32 {
640 let cols = self.cols();
641 match self {
642 Self::F32 { data, .. } => {
643 let row = &data[r * cols..(r + 1) * cols];
644 row.iter().zip(x).map(|(w, v)| w * v).sum()
645 }
646 Self::Mapped {
647 dtype,
648 row_scale,
649 col_field,
650 ..
651 } => match dtype {
652 TensorDtype::Q8Row => {
653 let q = &self.quant_bytes()[r * cols..(r + 1) * cols];
654 dot_i8_f32(q, x) * row_scale[r]
655 }
656 TensorDtype::Q8_2f => {
657 let q = &self.quant_bytes()[r * cols..(r + 1) * cols];
658 dot_i8_col_f32(q, x, col_field) * row_scale[r]
659 }
660 _ => {
661 self.row_f32(r, scratch);
662 scratch.iter().zip(x).map(|(w, v)| w * v).sum()
663 }
664 },
665 }
666 }
667
668 pub fn matvec(&self, x: &[f32], out: &mut [f32], pool: Option<&Pool>) {
671 match self {
672 Self::F32 { data, .. } => matvec_rows(pool, data, x, out),
673 Self::Mapped {
674 model,
675 idx,
676 dtype,
677 rows,
678 cols,
679 row_scale,
680 col_field,
681 vbit_offsets,
682 repack,
683 } => {
684 let _ = (model, idx);
685 if *dtype == TensorDtype::Q4Block {
686 if *rows * *cols >= 8_388_608 && crate::gpu::enabled_here() {
690 let t0 = std::time::Instant::now();
691 match crate::gpu::probe_arm(crate::gpu::OpClass::Matvec) {
692 crate::gpu::ProbeArm::Gpu => {
693 if crate::gpu::q4b_matvec(model, *idx, x, *rows, *cols, out) {
694 crate::gpu::probe_record(
695 crate::gpu::OpClass::Matvec,
696 true,
697 t0.elapsed(),
698 );
699 return;
700 }
701 }
702 crate::gpu::ProbeArm::CpuTimed => {
703 q4matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
704 crate::gpu::probe_record(
705 crate::gpu::OpClass::Matvec,
706 false,
707 t0.elapsed(),
708 );
709 return;
710 }
711 crate::gpu::ProbeArm::Cpu => {}
712 }
713 }
714 q4matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
715 return;
716 }
717 if *dtype == TensorDtype::Q4Tiled {
718 q4t_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
719 return;
720 }
721 if *dtype == TensorDtype::Q1 {
722 if *rows * *cols >= 8_388_608 && crate::gpu::enabled_here() {
727 let t0 = std::time::Instant::now();
728 let arm = if crate::gpu::q1_force() {
729 crate::gpu::ProbeArm::Gpu
730 } else {
731 crate::gpu::probe_arm(crate::gpu::OpClass::Matvec)
732 };
733 match arm {
734 crate::gpu::ProbeArm::Gpu => {
735 if crate::gpu::q1_matvec(model, *idx, x, *rows, *cols, out) {
736 crate::gpu::probe_record(
737 crate::gpu::OpClass::Matvec,
738 true,
739 t0.elapsed(),
740 );
741 return;
742 }
743 }
744 crate::gpu::ProbeArm::CpuTimed => {
745 q1_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
746 crate::gpu::probe_record(
747 crate::gpu::OpClass::Matvec,
748 false,
749 t0.elapsed(),
750 );
751 return;
752 }
753 crate::gpu::ProbeArm::Cpu => {}
754 }
755 }
756 q1_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
757 return;
758 }
759 if *dtype == TensorDtype::Q1T {
760 if *rows * *cols >= 8_388_608 && crate::gpu::enabled_here() {
764 let t0 = std::time::Instant::now();
765 match crate::gpu::probe_arm(crate::gpu::OpClass::Matvec) {
766 crate::gpu::ProbeArm::Gpu => {
767 if crate::gpu::q1t_matvec(model, *idx, x, *rows, *cols, out) {
768 q1t_add_overlay(self.quant_bytes(), x, *rows, *cols, out, pool);
769 crate::gpu::probe_record(
770 crate::gpu::OpClass::Matvec,
771 true,
772 t0.elapsed(),
773 );
774 return;
775 }
776 }
777 crate::gpu::ProbeArm::CpuTimed => {
778 q1t_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
779 crate::gpu::probe_record(
780 crate::gpu::OpClass::Matvec,
781 false,
782 t0.elapsed(),
783 );
784 return;
785 }
786 crate::gpu::ProbeArm::Cpu => {}
787 }
788 }
789 q1t_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
790 return;
791 }
792 if matches!(dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
793 vbitmatvec(self.quant_bytes(), vbit_offsets, x, *rows, *cols, out, pool);
794 return;
795 }
796 let xs = prescale(x, col_field, *dtype);
797 if *rows >= crate::gpu::min_rows()
802 && matches!(dtype, TensorDtype::Q8Row | TensorDtype::Q8_2f)
803 && std::env::var("CMF_GPU_LMHEAD")
804 .map(|v| v != "0")
805 .unwrap_or(true)
806 && crate::gpu::enabled_here()
807 {
808 let t0 = std::time::Instant::now();
811 match crate::gpu::probe_arm(crate::gpu::OpClass::Matvec) {
812 crate::gpu::ProbeArm::Gpu => {}
813 crate::gpu::ProbeArm::CpuTimed => {
814 qmatvec(
815 self.quant_bytes(),
816 repack,
817 row_scale,
818 x,
819 col_field,
820 *dtype,
821 *rows,
822 *cols,
823 out,
824 pool,
825 );
826 crate::gpu::probe_record(
827 crate::gpu::OpClass::Matvec,
828 false,
829 t0.elapsed(),
830 );
831 return;
832 }
833 crate::gpu::ProbeArm::Cpu => {
834 qmatvec(
835 self.quant_bytes(),
836 repack,
837 row_scale,
838 x,
839 col_field,
840 *dtype,
841 *rows,
842 *cols,
843 out,
844 pool,
845 );
846 return;
847 }
848 }
849 let frac = std::env::var("CMF_GPU_SPLIT")
850 .ok()
851 .and_then(|v| v.parse::<f32>().ok())
852 .unwrap_or(0.5)
853 .clamp(0.0, 1.0);
854 let cpu_rows = ((*rows as f32) * (1.0 - frac)) as usize;
855 let (out_cpu, out_gpu) = out.split_at_mut(cpu_rows);
856 let bytes = self.quant_bytes();
857 let ok = std::thread::scope(|sc| {
858 let g = sc.spawn(|| {
859 crate::gpu::q8_matvec_range(
860 model,
861 *idx,
862 cpu_rows,
863 &row_scale[cpu_rows..],
864 &xs,
865 *rows - cpu_rows,
866 *cols,
867 out_gpu,
868 )
869 });
870 if cpu_rows > 0 {
871 let rep_cpu = if repack.is_empty() {
874 &[][..]
875 } else {
876 &repack[..(cpu_rows / 4) * 4 * *cols]
877 };
878 qmatvec(
879 &bytes[..cpu_rows * *cols],
880 rep_cpu,
881 &row_scale[..cpu_rows],
882 x,
883 col_field,
884 *dtype,
885 cpu_rows,
886 *cols,
887 out_cpu,
888 pool,
889 );
890 }
891 g.join().unwrap_or(false)
892 });
893 if ok {
894 crate::gpu::probe_record(crate::gpu::OpClass::Matvec, true, t0.elapsed());
895 return;
896 }
897 qmatvec(
900 &bytes[cpu_rows * *cols..(*rows) * *cols],
901 &[],
902 &row_scale[cpu_rows..],
903 x,
904 col_field,
905 *dtype,
906 *rows - cpu_rows,
907 *cols,
908 out_gpu,
909 pool,
910 );
911 return;
912 }
913 qmatvec(
914 self.quant_bytes(),
915 repack,
916 row_scale,
917 x,
918 col_field,
919 *dtype,
920 *rows,
921 *cols,
922 out,
923 pool,
924 );
925 }
926 }
927 }
928
929 pub fn matvec2(
931 &self,
932 x1: &[f32],
933 x2: &[f32],
934 o1: &mut [f32],
935 o2: &mut [f32],
936 pool: Option<&Pool>,
937 ) {
938 match self {
939 Self::F32 { data, .. } => matvec_rows2(pool, data, x1, x2, o1, o2),
940 Self::Mapped {
941 dtype,
942 rows,
943 cols,
944 row_scale,
945 col_field,
946 vbit_offsets,
947 ..
948 } => {
949 if *dtype == TensorDtype::Q4Block {
950 q4matvec2(self.quant_bytes(), x1, x2, *rows, *cols, o1, o2, pool);
951 return;
952 }
953 if *dtype == TensorDtype::Q4Tiled {
954 q4t_matvec2(self.quant_bytes(), x1, x2, *rows, *cols, o1, o2, pool);
955 return;
956 }
957 if *dtype == TensorDtype::Q1 {
958 q1_matvec2(self.quant_bytes(), x1, x2, *rows, *cols, o1, o2, pool);
959 return;
960 }
961 if *dtype == TensorDtype::Q1T {
962 q1t_matvec2(self.quant_bytes(), x1, x2, *rows, *cols, o1, o2, pool);
968 return;
969 }
970 if matches!(dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
971 vbitmatvec2(
972 self.quant_bytes(),
973 vbit_offsets,
974 x1,
975 x2,
976 *rows,
977 *cols,
978 o1,
979 o2,
980 pool,
981 );
982 return;
983 }
984 qmatvec2(
985 self.quant_bytes(),
986 row_scale,
987 x1,
988 x2,
989 col_field,
990 *dtype,
991 *rows,
992 *cols,
993 o1,
994 o2,
995 pool,
996 );
997 }
998 }
999 }
1000}
1001
1002impl QTensor {
1003 pub fn matmat(&self, xs_all: &[f32], b: usize, out: &mut [f32], pool: Option<&Pool>) {
1008 let cols = self.cols();
1009 let rows = self.rows();
1010 debug_assert_eq!(xs_all.len(), b * cols);
1011 debug_assert_eq!(out.len(), b * rows);
1012 if crate::gptq_capture::capturing() {
1016 if let Self::Mapped { model, idx, .. } = self {
1017 crate::gptq_capture::accumulate(&model.tensors[*idx].name, xs_all, b, cols);
1018 }
1019 }
1020 match self {
1021 Self::F32 { data, .. } => {
1022 let out_addr = SendMut(out.as_mut_ptr());
1023 let run = |start: usize, end: usize| {
1024 for o in start..end {
1025 let row = &data[o * cols..(o + 1) * cols];
1026 for bi in 0..b {
1027 let x = &xs_all[bi * cols..(bi + 1) * cols];
1028 let mut acc = 0f32;
1029 for j in 0..cols {
1030 acc += row[j] * x[j];
1031 }
1032 unsafe { *out_addr.at(bi * rows + o) = acc };
1033 }
1034 }
1035 };
1036 dispatch_rows(pool, rows, &run);
1037 }
1038 Self::Mapped {
1039 dtype,
1040 row_scale,
1041 col_field,
1042 vbit_offsets,
1043 ..
1044 } => {
1045 if *dtype == TensorDtype::Q4Block {
1046 q4matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
1047 return;
1048 }
1049 if *dtype == TensorDtype::Q4Tiled {
1050 if b >= 32
1062 && b * rows * cols >= 128_000_000
1063 && cols % 32 == 0
1064 && !crate::gpu::mm_killed()
1065 && crate::gpu::enabled_here()
1066 {
1067 let class = if b >= 128 {
1068 crate::gpu::OpClass::MatmatWide
1069 } else {
1070 crate::gpu::OpClass::Matmat
1071 };
1072 if let Self::Mapped { model, idx, .. } = self {
1073 let t0 = std::time::Instant::now();
1074 match crate::gpu::probe_arm(class) {
1075 crate::gpu::ProbeArm::Gpu => {
1076 if crate::gpu::q4t_matmat(
1077 model, *idx, xs_all, b, rows, cols, out,
1078 ) {
1079 let el = t0.elapsed();
1080 let flops = 2.0 * b as f64 * rows as f64 * cols as f64;
1090 let budget = std::time::Duration::from_secs_f64(
1091 flops / 1.5e12 * 8.0 + 0.020,
1092 );
1093 if el > budget && !crate::gpu::probe_was_cold() {
1094 tracing::warn!(
1095 "gpu q4t matmat took {el:?} (budget {budget:?}) — \
1096 device contended, CPU for the rest of the process"
1097 );
1098 crate::gpu::mm_kill();
1099 }
1100 crate::gpu::probe_record(class, true, el);
1101 return;
1102 }
1103 }
1104 crate::gpu::ProbeArm::CpuTimed => {
1105 q4t_matmat(
1106 self.quant_bytes(),
1107 xs_all,
1108 b,
1109 rows,
1110 cols,
1111 out,
1112 pool,
1113 );
1114 crate::gpu::probe_record(class, false, t0.elapsed());
1115 return;
1116 }
1117 crate::gpu::ProbeArm::Cpu => {}
1118 }
1119 }
1120 }
1121 q4t_matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
1122 return;
1123 }
1124 if *dtype == TensorDtype::Q1 {
1125 if b >= 32
1128 && b * rows * cols >= 128_000_000
1129 && cols % 64 == 0
1130 && crate::gpu::enabled_here()
1131 {
1132 if let Self::Mapped { model, idx, .. } = self {
1133 let t0 = std::time::Instant::now();
1134 match crate::gpu::probe_arm(crate::gpu::OpClass::Matmat) {
1135 crate::gpu::ProbeArm::Gpu => {
1136 if crate::gpu::q1_matmat(
1137 model, *idx, xs_all, b, rows, cols, out,
1138 ) {
1139 crate::gpu::probe_record(
1140 crate::gpu::OpClass::Matmat,
1141 true,
1142 t0.elapsed(),
1143 );
1144 return;
1145 }
1146 }
1147 crate::gpu::ProbeArm::CpuTimed => {
1148 q1_matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
1149 crate::gpu::probe_record(
1150 crate::gpu::OpClass::Matmat,
1151 false,
1152 t0.elapsed(),
1153 );
1154 return;
1155 }
1156 crate::gpu::ProbeArm::Cpu => {}
1157 }
1158 }
1159 }
1160 q1_matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
1161 return;
1162 }
1163 if *dtype == TensorDtype::Q1T {
1164 if b >= 32 && b * rows * cols >= 128_000_000 && crate::gpu::enabled_here() {
1167 if let Self::Mapped { model, idx, .. } = self {
1168 let t0 = std::time::Instant::now();
1169 match crate::gpu::probe_arm(crate::gpu::OpClass::Matmat) {
1170 crate::gpu::ProbeArm::Gpu => {
1171 if crate::gpu::q1t_matmat(
1172 model, *idx, xs_all, b, rows, cols, out,
1173 ) {
1174 crate::gpu::probe_record(
1175 crate::gpu::OpClass::Matmat,
1176 true,
1177 t0.elapsed(),
1178 );
1179 return;
1180 }
1181 }
1182 crate::gpu::ProbeArm::CpuTimed => {
1183 q1t_matmat(
1184 self.quant_bytes(),
1185 xs_all,
1186 b,
1187 rows,
1188 cols,
1189 out,
1190 pool,
1191 );
1192 crate::gpu::probe_record(
1193 crate::gpu::OpClass::Matmat,
1194 false,
1195 t0.elapsed(),
1196 );
1197 return;
1198 }
1199 crate::gpu::ProbeArm::Cpu => {}
1200 }
1201 }
1202 }
1203 q1t_matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
1204 return;
1205 }
1206 if matches!(dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
1207 vbitmatmat(
1208 self.quant_bytes(),
1209 vbit_offsets,
1210 xs_all,
1211 b,
1212 rows,
1213 cols,
1214 out,
1215 pool,
1216 );
1217 return;
1218 }
1219 let pre: Vec<std::borrow::Cow<'_, [f32]>> = (0..b)
1220 .map(|bi| prescale(&xs_all[bi * cols..(bi + 1) * cols], col_field, *dtype))
1221 .collect();
1222 if b >= 8 && b * rows * cols >= 128_000_000 && crate::gpu::enabled_here() {
1228 if let Self::Mapped { model, idx, .. } = self {
1229 let t0 = std::time::Instant::now();
1230 match crate::gpu::probe_arm(crate::gpu::OpClass::Matmat) {
1231 crate::gpu::ProbeArm::Gpu
1232 if crate::gpu::probe_deciding(crate::gpu::OpClass::Matmat)
1233 && !crate::gpu::q8_resident_or_upload(model, *idx) =>
1234 {
1235 let q = self.quant_bytes();
1239 qmatmat(q, row_scale, &pre, rows, cols, out, pool);
1240 return;
1241 }
1242 crate::gpu::ProbeArm::Gpu => {
1243 let flat: Vec<f32> =
1244 pre.iter().flat_map(|v| v.iter().copied()).collect();
1245 if crate::gpu::q8_matmat(
1246 model, *idx, row_scale, &flat, b, rows, cols, out,
1247 ) {
1248 crate::gpu::probe_record(
1249 crate::gpu::OpClass::Matmat,
1250 true,
1251 t0.elapsed(),
1252 );
1253 return;
1254 }
1255 }
1256 crate::gpu::ProbeArm::CpuTimed => {
1257 let q = self.quant_bytes();
1258 qmatmat(q, row_scale, &pre, rows, cols, out, pool);
1259 crate::gpu::probe_record(
1260 crate::gpu::OpClass::Matmat,
1261 false,
1262 t0.elapsed(),
1263 );
1264 return;
1265 }
1266 crate::gpu::ProbeArm::Cpu => {}
1267 }
1268 }
1269 }
1270 let q = self.quant_bytes();
1271 qmatmat(q, row_scale, &pre, rows, cols, out, pool);
1272 }
1273 }
1274 }
1275}
1276
1277impl QTensor {
1278 pub fn matvec_many<const N: usize>(
1285 ts: [&QTensor; N],
1286 x: &[f32],
1287 mut outs: [&mut [f32]; N],
1288 pool: Option<&Pool>,
1289 ) {
1290 let total_rows: usize = ts.iter().map(|t| t.rows()).sum();
1291 let uniform_q8 = ts.iter().all(|t| {
1292 matches!(
1293 t,
1294 Self::Mapped {
1295 dtype: TensorDtype::Q8Row | TensorDtype::Q8_2f,
1296 ..
1297 }
1298 )
1299 });
1300 let uniform_f32 = ts.iter().all(|t| matches!(t, Self::F32 { .. }));
1301 let uniform_q4 = ts.iter().all(|t| {
1302 matches!(
1303 t,
1304 Self::Mapped {
1305 dtype: TensorDtype::Q4Block,
1306 ..
1307 }
1308 )
1309 });
1310 let uniform_vbit = ts.iter().all(|t| {
1311 matches!(
1312 t,
1313 Self::Mapped {
1314 dtype: TensorDtype::Vbit | TensorDtype::VbitRo,
1315 ..
1316 }
1317 )
1318 });
1319 let uniform_q1 = ts.iter().all(|t| {
1320 matches!(
1321 t,
1322 Self::Mapped {
1323 dtype: TensorDtype::Q1,
1324 ..
1325 }
1326 )
1327 });
1328 let uniform_q1t = ts.iter().all(|t| {
1329 matches!(
1330 t,
1331 Self::Mapped {
1332 dtype: TensorDtype::Q1T,
1333 ..
1334 }
1335 )
1336 });
1337 let Some(pool) = pool else {
1338 for (t, o) in ts.iter().zip(outs.iter_mut()) {
1339 t.matvec(x, o, None);
1340 }
1341 return;
1342 };
1343 if total_rows < 256
1344 || !(uniform_q8
1345 || uniform_f32
1346 || uniform_q4
1347 || uniform_vbit
1348 || uniform_q1
1349 || uniform_q1t)
1350 {
1351 for (t, o) in ts.iter().zip(outs.iter_mut()) {
1352 t.matvec(x, o, Some(pool));
1353 }
1354 return;
1355 }
1356
1357 if uniform_q1 {
1358 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
1361 if a8w8_enabled() {
1362 let act = split_act(x);
1363 let gsum = q1_group_sums(&act.xq, ts[0].cols() / GROUP_SIZE);
1364 let (act, gsum) = (&act, &gsum);
1365 let closures: [_; N] = std::array::from_fn(|i| {
1366 let (bytes, gpr, out) =
1367 (ts[i].quant_bytes(), ts[i].cols() / GROUP_SIZE, outs_addr[i]);
1368 move |s: usize, e: usize| q1_range_a8w8(bytes, gpr, act, gsum, out, s, e)
1369 });
1370 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1371 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1372 pool.run_many(&parts);
1373 } else {
1374 let closures: [_; N] = std::array::from_fn(|i| {
1375 let (bytes, gpr, out) =
1376 (ts[i].quant_bytes(), ts[i].cols() / GROUP_SIZE, outs_addr[i]);
1377 move |s: usize, e: usize| q1_range_f32(bytes, gpr, x, out, s, e)
1378 });
1379 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1380 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1381 pool.run_many(&parts);
1382 }
1383 return;
1384 }
1385
1386 if uniform_q1t {
1387 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
1391 const TILE: usize = cortiq_core::quant::Q1T_TILE;
1392 if a8w8_enabled() {
1393 let act = split_act(x);
1394 let act = &act;
1395 let x_ref = x;
1396 let closures: [_; N] = std::array::from_fn(|i| {
1397 let bytes = ts[i].quant_bytes();
1398 let (rows, cols) = (ts[i].rows(), ts[i].cols());
1399 let gpr = cols / GROUP_SIZE;
1400 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
1401 let out = outs_addr[i];
1402 move |s: usize, e: usize| {
1403 q1t_range_a8w8(bytes, gpr, rp_off, ent_off, has_ov, act, x_ref, out, s, e)
1404 }
1405 });
1406 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1407 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1408 pool.run_many(&parts);
1409 } else {
1410 let x_ref = x;
1411 let closures: [_; N] = std::array::from_fn(|i| {
1412 let bytes = ts[i].quant_bytes();
1413 let (rows, cols) = (ts[i].rows(), ts[i].cols());
1414 let gpr = cols / GROUP_SIZE;
1415 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
1416 let out = outs_addr[i];
1417 move |s: usize, e: usize| {
1418 q1t_range_f32_batch(bytes, gpr, rp_off, ent_off, has_ov, x_ref, out, s, e)
1419 }
1420 });
1421 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1422 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1423 pool.run_many(&parts);
1424 }
1425 return;
1426 }
1427
1428 if uniform_q4 || uniform_vbit {
1429 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
1430 if a8w8_enabled() {
1432 let act = split_act(x);
1433 let act = &act;
1434 if uniform_q4 {
1435 let closures: [_; N] = std::array::from_fn(|i| {
1436 let (packed, scales) =
1437 q4_split(ts[i].quant_bytes(), ts[i].rows(), ts[i].cols());
1438 let (gpr, cols, out) =
1439 (ts[i].cols() / GROUP_SIZE, ts[i].cols(), outs_addr[i]);
1440 move |s: usize, e: usize| {
1441 q4_range_a8w8(packed, scales, gpr, cols, act, out, s, e)
1442 }
1443 });
1444 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1445 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1446 pool.run_many(&parts);
1447 } else {
1448 let closures: [_; N] = std::array::from_fn(|i| {
1449 let Self::Mapped { vbit_offsets, .. } = ts[i] else {
1450 unreachable!()
1451 };
1452 let (bytes, rows, cols, out) = (
1453 ts[i].quant_bytes(),
1454 ts[i].rows(),
1455 ts[i].cols(),
1456 outs_addr[i],
1457 );
1458 move |s: usize, e: usize| {
1459 vbit_range_a8w8(bytes, vbit_offsets, x, act, rows, cols, out, s, e)
1460 }
1461 });
1462 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1463 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1464 pool.run_many(&parts);
1465 }
1466 return;
1467 }
1468 if uniform_q4 {
1469 let closures: [_; N] = std::array::from_fn(|i| {
1470 let (packed, scales) =
1471 q4_split(ts[i].quant_bytes(), ts[i].rows(), ts[i].cols());
1472 let (gpr, out) = (ts[i].cols() / GROUP_SIZE, outs_addr[i]);
1473 move |s: usize, e: usize| q4_range_f32(packed, scales, gpr, x, out, s, e)
1474 });
1475 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1476 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1477 pool.run_many(&parts);
1478 } else {
1479 let closures: [_; N] = std::array::from_fn(|i| {
1480 let Self::Mapped { vbit_offsets, .. } = ts[i] else {
1481 unreachable!()
1482 };
1483 let (bytes, rows, cols, out) = (
1484 ts[i].quant_bytes(),
1485 ts[i].rows(),
1486 ts[i].cols(),
1487 outs_addr[i],
1488 );
1489 move |s: usize, e: usize| {
1490 vbit_range_f32(bytes, vbit_offsets, x, rows, cols, out, s, e)
1491 }
1492 });
1493 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1494 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1495 pool.run_many(&parts);
1496 }
1497 return;
1498 }
1499
1500 if uniform_f32 {
1501 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
1502 let closures: [_; N] = std::array::from_fn(|i| {
1503 let Self::F32 { data, cols, .. } = ts[i] else {
1504 unreachable!()
1505 };
1506 let out = outs_addr[i];
1507 move |start: usize, end: usize| {
1508 for o in start..end {
1509 let row = &data[o * cols..(o + 1) * cols];
1510 let mut sum = 0.0f32;
1511 for j in 0..*cols {
1512 sum += row[j] * x[j];
1513 }
1514 unsafe { *out.at(o) = sum };
1516 }
1517 }
1518 });
1519 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1520 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1521 pool.run_many(&parts);
1522 return;
1523 }
1524
1525 struct Ctx<'a> {
1528 bytes: &'a [u8],
1529 #[cfg_attr(not(target_arch = "aarch64"), allow(dead_code))]
1530 rep: &'a [u8],
1531 row_scale: &'a [f32],
1532 cols: usize,
1533 xs: std::borrow::Cow<'a, [f32]>,
1534 }
1535 let ctxs: [Ctx<'_>; N] = std::array::from_fn(|i| {
1536 let Self::Mapped {
1537 dtype,
1538 cols,
1539 row_scale,
1540 col_field,
1541 repack,
1542 ..
1543 } = ts[i]
1544 else {
1545 unreachable!()
1546 };
1547 Ctx {
1548 bytes: ts[i].quant_bytes(),
1549 rep: repack,
1550 row_scale,
1551 cols: *cols,
1552 xs: prescale(x, col_field, *dtype),
1553 }
1554 });
1555 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
1556 #[cfg(target_arch = "aarch64")]
1557 if sdot_enabled() {
1558 let acts: [SplitAct; N] = std::array::from_fn(|i| split_act(&ctxs[i].xs));
1559 let closures: [_; N] = std::array::from_fn(|i| {
1560 let (c, act, out) = (&ctxs[i], &acts[i], outs_addr[i]);
1561 move |start: usize, end: usize| {
1562 q8_range_sdot(c.bytes, c.rep, c.row_scale, act, c.cols, out, start, end)
1563 }
1564 });
1565 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1566 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1567 pool.run_many(&parts);
1568 return;
1569 }
1570 #[cfg(target_arch = "x86_64")]
1571 if avx2_a8w8_enabled() {
1572 let acts: [SplitAct; N] = std::array::from_fn(|i| split_act(&ctxs[i].xs));
1573 let closures: [_; N] = std::array::from_fn(|i| {
1574 let (c, act, out) = (&ctxs[i], &acts[i], outs_addr[i]);
1575 move |start: usize, end: usize| {
1576 q8_range_avx2(c.bytes, c.row_scale, act, c.cols, out, start, end)
1577 }
1578 });
1579 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1580 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1581 pool.run_many(&parts);
1582 return;
1583 }
1584 let closures: [_; N] = std::array::from_fn(|i| {
1585 let (c, out) = (&ctxs[i], outs_addr[i]);
1586 move |start: usize, end: usize| {
1587 q8_range_f32(c.bytes, c.row_scale, &c.xs, c.cols, out, start, end)
1588 }
1589 });
1590 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1591 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1592 pool.run_many(&parts);
1593 }
1594}
1595
1596impl QTensor {
1597 #[allow(clippy::needless_range_loop)]
1602 pub fn matvec2_many<const N: usize>(
1603 ts: [&QTensor; N],
1604 x1: &[f32],
1605 x2: &[f32],
1606 mut o1s: [&mut [f32]; N],
1607 mut o2s: [&mut [f32]; N],
1608 pool: Option<&Pool>,
1609 ) {
1610 let total_rows: usize = ts.iter().map(|t| t.rows()).sum();
1611 let uniform_q8 = ts.iter().all(|t| {
1612 matches!(
1613 t,
1614 Self::Mapped {
1615 dtype: TensorDtype::Q8Row | TensorDtype::Q8_2f,
1616 ..
1617 }
1618 )
1619 });
1620 let uniform_f32 = ts.iter().all(|t| matches!(t, Self::F32 { .. }));
1621 let uniform_q4 = ts.iter().all(|t| {
1622 matches!(
1623 t,
1624 Self::Mapped {
1625 dtype: TensorDtype::Q4Block,
1626 ..
1627 }
1628 )
1629 });
1630 let uniform_vbit = ts.iter().all(|t| {
1631 matches!(
1632 t,
1633 Self::Mapped {
1634 dtype: TensorDtype::Vbit | TensorDtype::VbitRo,
1635 ..
1636 }
1637 )
1638 });
1639 let fusable = pool.is_some()
1640 && total_rows >= 256
1641 && (uniform_q8 || uniform_f32 || uniform_q4 || uniform_vbit);
1642 if !fusable {
1643 for i in 0..N {
1644 ts[i].matvec2(x1, x2, o1s[i], o2s[i], pool);
1645 }
1646 return;
1647 }
1648 let pool = pool.unwrap();
1649
1650 if uniform_q4 || uniform_vbit {
1651 let p1: [SendMut; N] = std::array::from_fn(|i| SendMut(o1s[i].as_mut_ptr()));
1652 let p2: [SendMut; N] = std::array::from_fn(|i| SendMut(o2s[i].as_mut_ptr()));
1653 if a8w8_enabled() {
1655 let a1 = split_act(x1);
1656 let a2 = split_act(x2);
1657 let (a1, a2) = (&a1, &a2);
1658 if uniform_q4 {
1659 let closures: [_; N] = std::array::from_fn(|i| {
1660 let (packed, scales) =
1661 q4_split(ts[i].quant_bytes(), ts[i].rows(), ts[i].cols());
1662 let (gpr, cols, o1, o2) =
1663 (ts[i].cols() / GROUP_SIZE, ts[i].cols(), p1[i], p2[i]);
1664 move |s: usize, e: usize| {
1665 q4_range2_a8w8(packed, scales, gpr, cols, a1, a2, o1, o2, s, e)
1666 }
1667 });
1668 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1669 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1670 pool.run_many(&parts);
1671 } else {
1672 let closures: [_; N] = std::array::from_fn(|i| {
1673 let Self::Mapped { vbit_offsets, .. } = ts[i] else {
1674 unreachable!()
1675 };
1676 let (bytes, rows, cols, o1, o2) = (
1677 ts[i].quant_bytes(),
1678 ts[i].rows(),
1679 ts[i].cols(),
1680 p1[i],
1681 p2[i],
1682 );
1683 move |s: usize, e: usize| {
1684 vbit_range2_a8w8(
1685 bytes,
1686 vbit_offsets,
1687 x1,
1688 x2,
1689 a1,
1690 a2,
1691 rows,
1692 cols,
1693 o1,
1694 o2,
1695 s,
1696 e,
1697 )
1698 }
1699 });
1700 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1701 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1702 pool.run_many(&parts);
1703 }
1704 return;
1705 }
1706 if uniform_q4 {
1707 let closures: [_; N] = std::array::from_fn(|i| {
1708 let (packed, scales) =
1709 q4_split(ts[i].quant_bytes(), ts[i].rows(), ts[i].cols());
1710 let (gpr, o1, o2) = (ts[i].cols() / GROUP_SIZE, p1[i], p2[i]);
1711 move |s: usize, e: usize| {
1712 q4_range2_f32(packed, scales, gpr, x1, x2, o1, o2, s, e)
1713 }
1714 });
1715 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1716 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1717 pool.run_many(&parts);
1718 } else {
1719 let closures: [_; N] = std::array::from_fn(|i| {
1720 let Self::Mapped { vbit_offsets, .. } = ts[i] else {
1721 unreachable!()
1722 };
1723 let (bytes, rows, cols, o1, o2) = (
1724 ts[i].quant_bytes(),
1725 ts[i].rows(),
1726 ts[i].cols(),
1727 p1[i],
1728 p2[i],
1729 );
1730 move |s: usize, e: usize| {
1731 vbit_range2_f32(bytes, vbit_offsets, x1, x2, rows, cols, o1, o2, s, e)
1732 }
1733 });
1734 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1735 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1736 pool.run_many(&parts);
1737 }
1738 return;
1739 }
1740
1741 if uniform_f32 {
1742 let p1: [SendMut; N] = std::array::from_fn(|i| SendMut(o1s[i].as_mut_ptr()));
1743 let p2: [SendMut; N] = std::array::from_fn(|i| SendMut(o2s[i].as_mut_ptr()));
1744 let closures: [_; N] = std::array::from_fn(|i| {
1745 let Self::F32 { data, cols, .. } = ts[i] else {
1746 unreachable!()
1747 };
1748 let (o1, o2) = (p1[i], p2[i]);
1749 move |start: usize, end: usize| {
1750 for o in start..end {
1751 let row = &data[o * cols..(o + 1) * cols];
1752 let (mut s1, mut s2) = (0.0f32, 0.0f32);
1753 for j in 0..*cols {
1754 s1 += row[j] * x1[j];
1755 s2 += row[j] * x2[j];
1756 }
1757 unsafe {
1759 *o1.at(o) = s1;
1760 *o2.at(o) = s2;
1761 }
1762 }
1763 }
1764 });
1765 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1766 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1767 pool.run_many(&parts);
1768 return;
1769 }
1770
1771 struct Ctx<'a> {
1772 bytes: &'a [u8],
1773 row_scale: &'a [f32],
1774 cols: usize,
1775 xs1: std::borrow::Cow<'a, [f32]>,
1776 xs2: std::borrow::Cow<'a, [f32]>,
1777 }
1778 let ctxs: [Ctx<'_>; N] = std::array::from_fn(|i| {
1779 let Self::Mapped {
1780 dtype,
1781 cols,
1782 row_scale,
1783 col_field,
1784 ..
1785 } = ts[i]
1786 else {
1787 unreachable!()
1788 };
1789 Ctx {
1790 bytes: ts[i].quant_bytes(),
1791 row_scale,
1792 cols: *cols,
1793 xs1: prescale(x1, col_field, *dtype),
1794 xs2: prescale(x2, col_field, *dtype),
1795 }
1796 });
1797 let p1: [SendMut; N] = std::array::from_fn(|i| SendMut(o1s[i].as_mut_ptr()));
1798 let p2: [SendMut; N] = std::array::from_fn(|i| SendMut(o2s[i].as_mut_ptr()));
1799 #[cfg(target_arch = "aarch64")]
1800 if sdot_enabled() {
1801 let acts: [(SplitAct, SplitAct); N] =
1802 std::array::from_fn(|i| (split_act(&ctxs[i].xs1), split_act(&ctxs[i].xs2)));
1803 let closures: [_; N] = std::array::from_fn(|i| {
1804 let (c, a, o1, o2) = (&ctxs[i], &acts[i], p1[i], p2[i]);
1805 move |start: usize, end: usize| {
1806 q8_range2_sdot(c.bytes, c.row_scale, &a.0, &a.1, c.cols, o1, o2, start, end)
1807 }
1808 });
1809 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1810 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1811 pool.run_many(&parts);
1812 return;
1813 }
1814 #[cfg(target_arch = "x86_64")]
1815 if avx2_a8w8_enabled() {
1816 let acts: [(SplitAct, SplitAct); N] =
1817 std::array::from_fn(|i| (split_act(&ctxs[i].xs1), split_act(&ctxs[i].xs2)));
1818 let closures: [_; N] = std::array::from_fn(|i| {
1819 let (c, a, o1, o2) = (&ctxs[i], &acts[i], p1[i], p2[i]);
1820 move |start: usize, end: usize| {
1821 q8_range2_avx2(c.bytes, c.row_scale, &a.0, &a.1, c.cols, o1, o2, start, end)
1822 }
1823 });
1824 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1825 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1826 pool.run_many(&parts);
1827 return;
1828 }
1829 let closures: [_; N] = std::array::from_fn(|i| {
1830 let (c, o1, o2) = (&ctxs[i], p1[i], p2[i]);
1831 move |start: usize, end: usize| {
1832 q8_range2_f32(
1833 c.bytes,
1834 c.row_scale,
1835 &c.xs1,
1836 &c.xs2,
1837 c.cols,
1838 o1,
1839 o2,
1840 start,
1841 end,
1842 )
1843 }
1844 });
1845 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1846 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1847 pool.run_many(&parts);
1848 }
1849
1850 pub fn matvec_silu_mul(
1855 gate: &QTensor,
1856 up: &QTensor,
1857 x: &[f32],
1858 out: &mut [f32],
1859 pool: Option<&Pool>,
1860 ) -> bool {
1861 let inter = gate.rows();
1862 debug_assert_eq!(up.rows(), inter);
1863 debug_assert_eq!(out.len(), inter);
1864 debug_assert_eq!(gate.cols(), up.cols());
1865 if !a8w8_enabled() {
1866 return false;
1867 }
1868 let act = split_act(x);
1869 let act = &act;
1870 let x_ref = x;
1871 let out_addr = SendMut(out.as_mut_ptr());
1872
1873 match (gate, up) {
1874 (
1876 Self::Mapped {
1877 dtype: TensorDtype::Q4Block,
1878 ..
1879 },
1880 Self::Mapped {
1881 dtype: TensorDtype::Q4Block,
1882 ..
1883 },
1884 ) => {
1885 let (gp, gs) = q4_split(gate.quant_bytes(), gate.rows(), gate.cols());
1886 let (up_p, up_s) = q4_split(up.quant_bytes(), up.rows(), up.cols());
1887 let gpr = gate.cols() / GROUP_SIZE;
1888 let cols = gate.cols();
1889 let run = move |start: usize, end: usize| {
1890 for r in start..end {
1891 let mut gv = dot_q4_row_i8(gp, gs, r * gpr, gpr, &act.xq) * act.sx;
1892 let mut uv = dot_q4_row_i8(up_p, up_s, r * gpr, gpr, &act.xq) * act.sx;
1893 for &(j, xv) in &act.outliers {
1894 let flat = r * cols + j;
1895 let gb = gp[flat / 2];
1896 let gn = if flat & 1 == 0 { gb & 0x0F } else { gb >> 4 };
1897 let gsc = f16_to_f32(u16::from_le_bytes([
1898 gs[(flat / GROUP_SIZE) * 2],
1899 gs[(flat / GROUP_SIZE) * 2 + 1],
1900 ]));
1901 gv += ((gn as i32 - 8) as f32) * gsc * xv;
1902 let ub = up_p[flat / 2];
1903 let un = if flat & 1 == 0 { ub & 0x0F } else { ub >> 4 };
1904 let usc = f16_to_f32(u16::from_le_bytes([
1905 up_s[(flat / GROUP_SIZE) * 2],
1906 up_s[(flat / GROUP_SIZE) * 2 + 1],
1907 ]));
1908 uv += ((un as i32 - 8) as f32) * usc * xv;
1909 }
1910 let silu_g = gv / (1.0 + (-gv).exp());
1911 unsafe { *out_addr.at(r) = silu_g * uv };
1913 }
1914 };
1915 dispatch_rows(pool, inter, &run);
1916 true
1917 }
1918 (
1922 Self::Mapped {
1923 dtype: TensorDtype::Q4Tiled,
1924 ..
1925 },
1926 Self::Mapped {
1927 dtype: TensorDtype::Q4Tiled,
1928 ..
1929 },
1930 ) => {
1931 let g_bytes = gate.quant_bytes();
1932 let u_bytes = up.quant_bytes();
1933 let gpr = gate.cols() / GROUP_SIZE;
1934 let run = move |start: usize, end: usize| {
1935 for r in start..end {
1936 let mut gv = dot_q4t_row_i8(g_bytes, r, gpr, &act.xq) * act.sx;
1937 let mut uv = dot_q4t_row_i8(u_bytes, r, gpr, &act.xq) * act.sx;
1938 for &(j, xv) in &act.outliers {
1939 let (w, s) = q4t_outlier(g_bytes, r, gpr, j);
1940 gv += w * s * xv;
1941 let (w, s) = q4t_outlier(u_bytes, r, gpr, j);
1942 uv += w * s * xv;
1943 }
1944 let silu_g = gv / (1.0 + (-gv).exp());
1945 unsafe { *out_addr.at(r) = silu_g * uv };
1947 }
1948 };
1949 dispatch_rows(pool, inter, &run);
1950 true
1951 }
1952 (
1954 Self::Mapped {
1955 dtype: TensorDtype::Q1T,
1956 ..
1957 },
1958 Self::Mapped {
1959 dtype: TensorDtype::Q1T,
1960 ..
1961 },
1962 ) => {
1963 const TILE: usize = cortiq_core::quant::Q1T_TILE;
1964 let g_bytes = gate.quant_bytes();
1965 let u_bytes = up.quant_bytes();
1966 let gpr = gate.cols() / GROUP_SIZE;
1967 let (g_rp, g_ent, g_ov) = q1t_overlay(g_bytes, inter * gpr * TILE, inter);
1968 let (u_rp, u_ent, u_ov) = q1t_overlay(u_bytes, inter * gpr * TILE, inter);
1969 let run = move |start: usize, end: usize| {
1970 for r in start..end {
1971 let mut gv = q1t_dot_row_i8(g_bytes, r, gpr, &act.xq) * act.sx;
1972 let mut uv = q1t_dot_row_i8(u_bytes, r, gpr, &act.xq) * act.sx;
1973 for &(j, xv) in &act.outliers {
1974 gv += q1t_base_weight(g_bytes, r, gpr, j) * xv;
1975 uv += q1t_base_weight(u_bytes, r, gpr, j) * xv;
1976 }
1977 gv += q1t_row_outlier_correction(g_bytes, r, g_rp, g_ent, g_ov, x_ref);
1978 uv += q1t_row_outlier_correction(u_bytes, r, u_rp, u_ent, u_ov, x_ref);
1979 let silu_g = gv / (1.0 + (-gv).exp());
1980 unsafe { *out_addr.at(r) = silu_g * uv };
1982 }
1983 };
1984 dispatch_rows(pool, inter, &run);
1985 true
1986 }
1987 _ => false,
1988 }
1989 }
1990}
1991
1992#[cfg(target_os = "macos")]
1997mod accel_blas {
1998 #[link(name = "Accelerate", kind = "framework")]
1999 unsafe extern "C" {
2000 pub fn cblas_sgemm(
2001 order: i32,
2002 trans_a: i32,
2003 trans_b: i32,
2004 m: i32,
2005 n: i32,
2006 k: i32,
2007 alpha: f32,
2008 a: *const f32,
2009 lda: i32,
2010 b: *const f32,
2011 ldb: i32,
2012 beta: f32,
2013 c: *mut f32,
2014 ldc: i32,
2015 );
2016 }
2017}
2018
2019#[cfg(target_os = "macos")]
2020pub(crate) fn accel_gemm_enabled() -> bool {
2021 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2022 *ON.get_or_init(|| std::env::var("CMF_ACCEL").map(|v| v != "0").unwrap_or(true))
2023}
2024
2025#[cfg(all(target_arch = "aarch64", not(target_os = "macos")))]
2028pub(crate) fn accel_gemm_enabled() -> bool {
2029 true
2030}
2031
2032#[cfg(target_arch = "aarch64")]
2039#[allow(clippy::too_many_arguments)]
2040pub(crate) fn neon_gemm_rm(
2041 m: usize,
2042 n: usize,
2043 k: usize,
2044 alpha: f32,
2045 a: &[f32],
2046 lda: usize,
2047 b_mat: &[f32],
2048 ldb: usize,
2049 b_rows_are_n: bool,
2050 c: &mut [f32],
2051 ldc: usize,
2052) {
2053 debug_assert!(a.len() >= (m - 1) * lda + k);
2054 debug_assert!(c.len() >= (m - 1) * ldc + n);
2055 unsafe {
2057 use core::arch::aarch64::*;
2058 let mut i = 0usize;
2059 while i < m {
2060 let mi = (m - i).min(4);
2061 let mut j = 0usize;
2062 while j < n {
2063 let nj = (n - j).min(8);
2064 if mi == 4 && nj == 8 {
2065 let (mut c0a, mut c0b) = (vdupq_n_f32(0.0), vdupq_n_f32(0.0));
2066 let (mut c1a, mut c1b) = (vdupq_n_f32(0.0), vdupq_n_f32(0.0));
2067 let (mut c2a, mut c2b) = (vdupq_n_f32(0.0), vdupq_n_f32(0.0));
2068 let (mut c3a, mut c3b) = (vdupq_n_f32(0.0), vdupq_n_f32(0.0));
2069 for p in 0..k {
2070 let (b0, b1) = if b_rows_are_n {
2071 let base = b_mat.as_ptr().add(j * ldb + p);
2074 let g = |o: usize| *base.add(o * ldb);
2075 ([g(0), g(1), g(2), g(3)], [g(4), g(5), g(6), g(7)])
2076 } else {
2077 let base = b_mat.as_ptr().add(p * ldb + j);
2078 (
2079 [*base, *base.add(1), *base.add(2), *base.add(3)],
2080 [*base.add(4), *base.add(5), *base.add(6), *base.add(7)],
2081 )
2082 };
2083 let bv0 = vld1q_f32(b0.as_ptr());
2084 let bv1 = vld1q_f32(b1.as_ptr());
2085 let a0 = vdupq_n_f32(*a.as_ptr().add(i * lda + p));
2086 let a1 = vdupq_n_f32(*a.as_ptr().add((i + 1) * lda + p));
2087 let a2 = vdupq_n_f32(*a.as_ptr().add((i + 2) * lda + p));
2088 let a3 = vdupq_n_f32(*a.as_ptr().add((i + 3) * lda + p));
2089 c0a = vfmaq_f32(c0a, a0, bv0);
2090 c0b = vfmaq_f32(c0b, a0, bv1);
2091 c1a = vfmaq_f32(c1a, a1, bv0);
2092 c1b = vfmaq_f32(c1b, a1, bv1);
2093 c2a = vfmaq_f32(c2a, a2, bv0);
2094 c2b = vfmaq_f32(c2b, a2, bv1);
2095 c3a = vfmaq_f32(c3a, a3, bv0);
2096 c3b = vfmaq_f32(c3b, a3, bv1);
2097 }
2098 let al = vdupq_n_f32(alpha);
2099 for (r, (ca, cb)) in [(c0a, c0b), (c1a, c1b), (c2a, c2b), (c3a, c3b)]
2100 .iter()
2101 .enumerate()
2102 {
2103 let dst = c.as_mut_ptr().add((i + r) * ldc + j);
2104 vst1q_f32(dst, vmulq_f32(*ca, al));
2105 vst1q_f32(dst.add(4), vmulq_f32(*cb, al));
2106 }
2107 } else {
2108 for r in 0..mi {
2109 for q in 0..nj {
2110 let mut acc = 0f32;
2111 for p in 0..k {
2112 let bv = if b_rows_are_n {
2113 b_mat[(j + q) * ldb + p]
2114 } else {
2115 b_mat[p * ldb + j + q]
2116 };
2117 acc += a[(i + r) * lda + p] * bv;
2118 }
2119 c[(i + r) * ldc + j + q] = acc * alpha;
2120 }
2121 }
2122 }
2123 j += nj;
2124 }
2125 i += mi;
2126 }
2127 }
2128}
2129
2130#[cfg(all(target_arch = "aarch64", not(target_os = "macos")))]
2132#[allow(clippy::too_many_arguments)]
2133pub(crate) fn sgemm_rm(
2134 m: usize,
2135 n: usize,
2136 k: usize,
2137 alpha: f32,
2138 a: &[f32],
2139 lda: usize,
2140 b_mat: &[f32],
2141 ldb: usize,
2142 b_rows_are_n: bool,
2143 c: &mut [f32],
2144 ldc: usize,
2145) {
2146 neon_gemm_rm(m, n, k, alpha, a, lda, b_mat, ldb, b_rows_are_n, c, ldc);
2147}
2148
2149#[cfg(target_os = "macos")]
2152#[allow(clippy::too_many_arguments)]
2153pub(crate) fn sgemm_rm(
2154 m: usize,
2155 n: usize,
2156 k: usize,
2157 alpha: f32,
2158 a: &[f32],
2159 lda: usize,
2160 b_mat: &[f32],
2161 ldb: usize,
2162 b_rows_are_n: bool,
2163 c: &mut [f32],
2164 ldc: usize,
2165) {
2166 debug_assert!(a.len() >= (m - 1) * lda + k);
2167 debug_assert!(c.len() >= (m - 1) * ldc + n);
2168 #[cfg(target_arch = "aarch64")]
2173 if std::env::var("CMF_FORCE_NEON_GEMM")
2174 .map(|v| v == "1")
2175 .unwrap_or(false)
2176 {
2177 return neon_gemm_rm(m, n, k, alpha, a, lda, b_mat, ldb, b_rows_are_n, c, ldc);
2178 }
2179 unsafe {
2180 accel_blas::cblas_sgemm(
2181 101, 111, if b_rows_are_n { 112 } else { 111 },
2184 m as i32,
2185 n as i32,
2186 k as i32,
2187 alpha,
2188 a.as_ptr(),
2189 lda as i32,
2190 b_mat.as_ptr(),
2191 ldb as i32,
2192 0.0,
2193 c.as_mut_ptr(),
2194 ldc as i32,
2195 );
2196 }
2197}
2198
2199#[cfg(target_os = "macos")]
2206fn qmatmat_accel(
2207 q: &[u8],
2208 row_scale: &[f32],
2209 pre: &[std::borrow::Cow<'_, [f32]>],
2210 rows: usize,
2211 cols: usize,
2212 out: &mut [f32],
2213 pool: Option<&Pool>,
2214) {
2215 const TR: usize = 2048;
2220 let b = pre.len();
2221 thread_local! {
2222 static XPANEL: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
2223 static WTILE: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
2224 }
2225 XPANEL.with(|xp| {
2226 WTILE.with(|wt| {
2227 let mut xpanel = xp.borrow_mut();
2228 xpanel.clear();
2229 for x in pre {
2230 xpanel.extend_from_slice(x);
2231 }
2232 let mut wtile = wt.borrow_mut();
2233 wtile.resize(TR * cols, 0.0);
2234 let mut r0 = 0usize;
2235 while r0 < rows {
2236 let tr = TR.min(rows - r0);
2237 let wt_addr = SendMut(wtile.as_mut_ptr());
2239 let run = |start: usize, end: usize| {
2240 for r in start..end {
2241 let row = &q[(r0 + r) * cols..(r0 + r + 1) * cols];
2242 let s = row_scale[r0 + r];
2243 let dst =
2245 unsafe { std::slice::from_raw_parts_mut(wt_addr.at(r * cols), cols) };
2246 for (d, &v) in dst.iter_mut().zip(row) {
2247 *d = (v as i8) as f32 * s;
2248 }
2249 }
2250 };
2251 dispatch_rows(pool, tr, &run);
2252 unsafe {
2254 accel_blas::cblas_sgemm(
2255 101, 111, 112, b as i32,
2259 tr as i32,
2260 cols as i32,
2261 1.0,
2262 xpanel.as_ptr(),
2263 cols as i32,
2264 wtile.as_ptr(),
2265 cols as i32,
2266 0.0,
2267 out.as_mut_ptr().add(r0),
2268 rows as i32,
2269 );
2270 }
2271 r0 += tr;
2272 }
2273 })
2274 });
2275}
2276
2277fn qmatmat(
2278 q: &[u8],
2279 row_scale: &[f32],
2280 pre: &[std::borrow::Cow<'_, [f32]>],
2281 rows: usize,
2282 cols: usize,
2283 out: &mut [f32],
2284 pool: Option<&Pool>,
2285) {
2286 let b = pre.len();
2287 debug_assert_eq!(out.len(), b * rows);
2288 #[cfg(target_os = "macos")]
2293 if b >= 8 && rows * cols >= 500_000 && accel_gemm_enabled() {
2294 qmatmat_accel(q, row_scale, pre, rows, cols, out, pool);
2295 return;
2296 }
2297 #[cfg(target_arch = "aarch64")]
2298 if sdot_enabled() {
2299 let acts: Vec<SplitAct> = pre.iter().map(|x| split_act(x)).collect();
2300 let out_addr = SendMut(out.as_mut_ptr());
2301 let blocked_ok = std::env::var("CMF_X86_BLOCKED")
2304 .map(|v| v != "0")
2305 .unwrap_or(true);
2306 let use_i8mm = i8mm_enabled();
2307 if blocked_ok {
2308 let run = |start: usize, end: usize| {
2309 let mut o = start;
2310 while o < end {
2311 if o + 2 <= end {
2312 let r0 = &q[o * cols..(o + 1) * cols];
2313 let r1 = &q[(o + 1) * cols..(o + 2) * cols];
2314 let mut bi = 0usize;
2315 while bi + 4 <= acts.len() {
2316 let xs = [
2317 acts[bi].xq.as_slice(),
2318 acts[bi + 1].xq.as_slice(),
2319 acts[bi + 2].xq.as_slice(),
2320 acts[bi + 3].xq.as_slice(),
2321 ];
2322 let d = if use_i8mm {
2323 unsafe { dot_i8_smmla_2x4(r0, r1, xs) }
2324 } else {
2325 unsafe { dot_i8_sdot_2x4(r0, r1, xs) }
2326 };
2327 for (r, row) in [r0, r1].into_iter().enumerate() {
2328 for k in 0..4 {
2329 let act = &acts[bi + k];
2330 let mut v = d[r][k] as f32 * act.sx;
2331 for &(j, xv) in &act.outliers {
2332 v += (row[j] as i8) as f32 * xv;
2333 }
2334 unsafe {
2335 *out_addr.at((bi + k) * rows + o + r) = v * row_scale[o + r]
2336 };
2337 }
2338 }
2339 bi += 4;
2340 }
2341 while bi < acts.len() {
2342 for (r, row) in [r0, r1].into_iter().enumerate() {
2343 let v = row_dot_sdot(row, &acts[bi]) * row_scale[o + r];
2344 unsafe { *out_addr.at(bi * rows + o + r) = v };
2345 }
2346 bi += 1;
2347 }
2348 o += 2;
2349 } else {
2350 let row = &q[o * cols..(o + 1) * cols];
2351 for (bi, act) in acts.iter().enumerate() {
2352 let v = row_dot_sdot(row, act) * row_scale[o];
2353 unsafe { *out_addr.at(bi * rows + o) = v };
2354 }
2355 o += 1;
2356 }
2357 }
2358 };
2359 dispatch_rows(pool, rows, &run);
2360 return;
2361 }
2362 let run = |start: usize, end: usize| {
2363 for o in start..end {
2364 let row = &q[o * cols..(o + 1) * cols];
2365 for (bi, act) in acts.iter().enumerate() {
2366 let v = row_dot_sdot(row, act) * row_scale[o];
2367 unsafe { *out_addr.at(bi * rows + o) = v };
2368 }
2369 }
2370 };
2371 dispatch_rows(pool, rows, &run);
2372 return;
2373 }
2374 #[cfg(target_arch = "x86_64")]
2379 if avx2_a8w8_enabled() {
2380 let acts: Vec<SplitAct> = pre.iter().map(|x| split_act(x)).collect();
2381 let out_addr = SendMut(out.as_mut_ptr());
2382 let blocked_ok = std::env::var("CMF_X86_BLOCKED")
2385 .map(|v| v != "0")
2386 .unwrap_or(true);
2387 if !avx512vnni_enabled() && blocked_ok {
2388 let run = |start: usize, end: usize| {
2389 let mut o = start;
2390 while o < end {
2391 if o + 2 <= end {
2392 let r0 = &q[o * cols..(o + 1) * cols];
2393 let r1 = &q[(o + 1) * cols..(o + 2) * cols];
2394 let mut bi = 0usize;
2395 while bi + 4 <= acts.len() {
2396 let xs = [
2397 acts[bi].xq.as_slice(),
2398 acts[bi + 1].xq.as_slice(),
2399 acts[bi + 2].xq.as_slice(),
2400 acts[bi + 3].xq.as_slice(),
2401 ];
2402 let d = unsafe { dot_i8_i8_avx2_2x4(r0, r1, xs) };
2403 for (r, row) in [r0, r1].into_iter().enumerate() {
2404 for k in 0..4 {
2405 let act = &acts[bi + k];
2406 let mut v = d[r][k] as f32 * act.sx;
2407 for &(j, xv) in &act.outliers {
2408 v += (row[j] as i8) as f32 * xv;
2409 }
2410 unsafe {
2411 *out_addr.at((bi + k) * rows + o + r) = v * row_scale[o + r]
2412 };
2413 }
2414 }
2415 bi += 4;
2416 }
2417 while bi < acts.len() {
2418 for (r, row) in [r0, r1].into_iter().enumerate() {
2419 let v = row_dot_avx2(row, &acts[bi]) * row_scale[o + r];
2420 unsafe { *out_addr.at(bi * rows + o + r) = v };
2421 }
2422 bi += 1;
2423 }
2424 o += 2;
2425 } else {
2426 let row = &q[o * cols..(o + 1) * cols];
2427 for (bi, act) in acts.iter().enumerate() {
2428 let v = row_dot_avx2(row, act) * row_scale[o];
2429 unsafe { *out_addr.at(bi * rows + o) = v };
2430 }
2431 o += 1;
2432 }
2433 }
2434 };
2435 dispatch_rows(pool, rows, &run);
2436 return;
2437 }
2438 let run = |start: usize, end: usize| {
2439 for o in start..end {
2440 let row = &q[o * cols..(o + 1) * cols];
2441 for (bi, act) in acts.iter().enumerate() {
2442 let v = row_dot_avx2(row, act) * row_scale[o];
2443 unsafe { *out_addr.at(bi * rows + o) = v };
2444 }
2445 }
2446 };
2447 dispatch_rows(pool, rows, &run);
2448 return;
2449 }
2450 let out_addr = SendMut(out.as_mut_ptr());
2451 let run = |start: usize, end: usize| {
2452 for o in start..end {
2453 let row = &q[o * cols..(o + 1) * cols];
2454 for (bi, x) in pre.iter().enumerate() {
2455 let mut acc = 0f32;
2456 for j in 0..cols {
2457 acc += (row[j] as i8) as f32 * x[j];
2458 }
2459 unsafe { *out_addr.at(bi * rows + o) = acc * row_scale[o] };
2460 }
2461 }
2462 };
2463 dispatch_rows(pool, rows, &run);
2464}
2465
2466fn dispatch_rows(pool: Option<&Pool>, rows: usize, run: &(dyn Fn(usize, usize) + Sync)) {
2469 match pool {
2470 Some(pool) if rows >= 256 => pool.run_rows(rows, run),
2471 _ => run(0, rows),
2472 }
2473}
2474
2475fn q4_split(bytes: &[u8], rows: usize, cols: usize) -> (&[u8], &[u8]) {
2477 let groups = rows * cols / GROUP_SIZE;
2478 bytes.split_at(groups * 16)
2479}
2480
2481#[inline]
2486fn vbit_fill4(data: &[u8], buf: &mut [u8]) {
2487 #[cfg(target_arch = "aarch64")]
2488 unsafe {
2489 return vbit_fill4_neon(data, buf);
2490 }
2491 #[cfg(target_arch = "x86_64")]
2492 if avx2_enabled() {
2493 return unsafe { vbit_fill4_avx2(data, buf) };
2494 }
2495 #[allow(unreachable_code)]
2496 for (blk, chunk) in buf.chunks_exact_mut(8).enumerate() {
2497 let u = unpack8::<4>(&data[blk * 4..]);
2498 for k in 0..8 {
2499 chunk[k] = (u[k] - 7) as i8 as u8;
2500 }
2501 }
2502}
2503
2504#[cfg(target_arch = "aarch64")]
2505#[target_feature(enable = "neon")]
2506unsafe fn vbit_fill4_neon(data: &[u8], buf: &mut [u8]) {
2507 unsafe {
2510 use core::arch::aarch64::*;
2511 let n = buf.len();
2512 let mask = vdupq_n_u8(0x0F);
2513 let seven = vdupq_n_s8(7);
2514 let mut g = 0usize;
2515 while g * 32 + 32 <= n {
2516 let b = vld1q_u8(data.as_ptr().add(g * 16));
2517 let hi = vshrq_n_u8::<4>(b);
2518 let lo = vandq_u8(b, mask);
2519 let z0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(hi, lo)), seven);
2520 let z1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(hi, lo)), seven);
2521 vst1q_u8(buf.as_mut_ptr().add(g * 32), vreinterpretq_u8_s8(z0));
2522 vst1q_u8(buf.as_mut_ptr().add(g * 32 + 16), vreinterpretq_u8_s8(z1));
2523 g += 1;
2524 }
2525 }
2526}
2527
2528#[cfg(target_arch = "x86_64")]
2529#[target_feature(enable = "avx2")]
2530unsafe fn vbit_fill4_avx2(data: &[u8], buf: &mut [u8]) {
2531 unsafe {
2533 use core::arch::x86_64::*;
2534 let n = buf.len();
2535 let mask = _mm_set1_epi8(0x0F);
2536 let seven = _mm256_set1_epi8(7);
2537 let mut g = 0usize;
2538 while g * 32 + 32 <= n {
2539 let b = _mm_loadu_si128(data.as_ptr().add(g * 16) as *const __m128i);
2540 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), mask);
2541 let lo = _mm_and_si128(b, mask);
2542 let z = _mm256_sub_epi8(
2543 _mm256_set_m128i(_mm_unpackhi_epi8(hi, lo), _mm_unpacklo_epi8(hi, lo)),
2544 seven,
2545 );
2546 _mm256_storeu_si256(buf.as_mut_ptr().add(g * 32) as *mut __m256i, z);
2547 g += 1;
2548 }
2549 }
2550}
2551
2552#[inline(always)]
2557fn unpack8<const B: usize>(data: &[u8]) -> [i32; 8] {
2558 let mut acc = 0u64;
2559 for i in 0..B {
2560 acc = (acc << 8) | data[i] as u64;
2561 }
2562 let mask = (1u64 << B) - 1;
2563 let mut out = [0i32; 8];
2564 for (k, o) in out.iter_mut().enumerate() {
2565 *o = ((acc >> ((7 - k) * B)) & mask) as i32;
2566 }
2567 out
2568}
2569
2570#[allow(clippy::too_many_arguments)]
2576fn vbitmatvec(
2577 bytes: &[u8],
2578 offsets: &[usize],
2579 x: &[f32],
2580 rows: usize,
2581 cols: usize,
2582 out: &mut [f32],
2583 pool: Option<&Pool>,
2584) {
2585 debug_assert_eq!(out.len(), rows);
2586 debug_assert_eq!(offsets.len(), rows + 1);
2587
2588 if a8w8_enabled() {
2592 let act = split_act(x);
2593 let out_addr = SendMut(out.as_mut_ptr());
2594 let run = move |start: usize, end: usize| {
2595 vbit_range_a8w8(bytes, offsets, x, &act, rows, cols, out_addr, start, end)
2596 };
2597 dispatch_rows(pool, rows, &run);
2598 return;
2599 }
2600
2601 let out_addr = SendMut(out.as_mut_ptr());
2602 let run = move |start: usize, end: usize| {
2603 vbit_range_f32(bytes, offsets, x, rows, cols, out_addr, start, end)
2604 };
2605 dispatch_rows(pool, rows, &run);
2606}
2607
2608#[allow(clippy::too_many_arguments)]
2612fn vbit_range_a8w8(
2613 bytes: &[u8],
2614 offsets: &[usize],
2615 x: &[f32],
2616 act: &SplitAct,
2617 rows: usize,
2618 cols: usize,
2619 out: SendMut,
2620 start: usize,
2621 end: usize,
2622) {
2623 let ng = cols / GROUP_SIZE;
2624 let bits = &bytes[..rows];
2625 let sc_off = rows;
2626 let row_dot = |r: usize| -> f32 {
2627 let b = bits[r] as usize;
2628 let l = (1i32 << (b - 1)) - 1;
2629 let mask = (1u64 << b) - 1;
2630 let data = &bytes[offsets[r]..offsets[r + 1]];
2631 if b == 8 {
2632 let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
2634 let mut dot = 0f32;
2635 for g in 0..ng {
2636 let so = (r * ng + g) * 2;
2637 let sgf = f16_to_f32(u16::from_le_bytes([
2638 bytes[sc_off + so],
2639 bytes[sc_off + so + 1],
2640 ]));
2641 let xg = &x[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
2642 let mut gd = 0f32;
2643 for &xv in xg.iter() {
2644 if nbits < 8 {
2645 acc = (acc << 8) | data[idx] as u64;
2646 idx += 1;
2647 nbits += 8;
2648 }
2649 let u = ((acc >> (nbits - 8)) & 0xFF) as i32;
2650 nbits -= 8;
2651 gd += (u - l) as f32 * xv;
2652 }
2653 dot += gd * sgf;
2654 }
2655 return dot;
2656 }
2657 thread_local! {
2661 static VBIT_SCRATCH: std::cell::RefCell<Vec<u8>> =
2662 const { std::cell::RefCell::new(Vec::new()) };
2663 }
2664 #[inline(always)]
2665 fn fill<const B: usize>(data: &[u8], l: i32, buf: &mut [u8]) {
2666 for (blk, chunk) in buf.chunks_exact_mut(8).enumerate() {
2667 let u = unpack8::<B>(&data[blk * B..]);
2668 for k in 0..8 {
2669 chunk[k] = (u[k] - l) as i8 as u8;
2670 }
2671 }
2672 }
2673 let _ = mask;
2674 VBIT_SCRATCH.with(|scratch| {
2675 let mut buf = scratch.borrow_mut();
2676 buf.resize(cols, 0);
2677 match b {
2678 3 => fill::<3>(data, l, &mut buf),
2679 4 => vbit_fill4(data, &mut buf),
2680 5 => fill::<5>(data, l, &mut buf),
2681 6 => fill::<6>(data, l, &mut buf),
2682 _ => unreachable!(),
2683 }
2684 let mut dot = 0f32;
2685 for g in 0..ng {
2686 let so = (r * ng + g) * 2;
2687 let s = f16_to_f32(u16::from_le_bytes([
2688 bytes[sc_off + so],
2689 bytes[sc_off + so + 1],
2690 ]));
2691 let d = dot_i8_i8(
2692 &buf[g * GROUP_SIZE..(g + 1) * GROUP_SIZE],
2693 &act.xq[g * GROUP_SIZE..(g + 1) * GROUP_SIZE],
2694 ) as f32
2695 * act.sx;
2696 dot += d * s;
2697 }
2698 for &(j, xv) in &act.outliers {
2699 let so = (r * ng + j / GROUP_SIZE) * 2;
2700 let s = f16_to_f32(u16::from_le_bytes([
2701 bytes[sc_off + so],
2702 bytes[sc_off + so + 1],
2703 ]));
2704 dot += (buf[j] as i8) as f32 * s * xv;
2706 }
2707 dot
2708 })
2709 };
2710 for r in start..end {
2711 unsafe { *out.at(r) = row_dot(r) };
2713 }
2714}
2715
2716#[allow(clippy::too_many_arguments)]
2718fn vbit_range_f32(
2719 bytes: &[u8],
2720 offsets: &[usize],
2721 x: &[f32],
2722 rows: usize,
2723 cols: usize,
2724 out: SendMut,
2725 start: usize,
2726 end: usize,
2727) {
2728 let ng = cols / GROUP_SIZE;
2729 let bits = &bytes[..rows];
2730 let sc_off = rows;
2731 #[inline(always)]
2735 fn dot_row<const B: usize>(
2736 data: &[u8],
2737 bytes: &[u8],
2738 sc_off: usize,
2739 r: usize,
2740 ng: usize,
2741 x: &[f32],
2742 ) -> f32 {
2743 let l = ((1i32 << (B - 1)) - 1) as f32;
2744 let gbytes = GROUP_SIZE * B / 8;
2745 let mut dot = 0f32;
2746 for g in 0..ng {
2747 let so = (r * ng + g) * 2;
2748 let s = f16_to_f32(u16::from_le_bytes([
2749 bytes[sc_off + so],
2750 bytes[sc_off + so + 1],
2751 ]));
2752 let xg = &x[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
2753 let gd0 = &data[g * gbytes..(g + 1) * gbytes];
2754 let mut gd = 0f32;
2755 for blk in 0..GROUP_SIZE / 8 {
2756 let u = unpack8::<B>(&gd0[blk * B..]);
2757 let xb = &xg[blk * 8..blk * 8 + 8];
2758 for k in 0..8 {
2759 gd += (u[k] as f32 - l) * xb[k];
2760 }
2761 }
2762 dot += gd * s;
2763 }
2764 dot
2765 }
2766 for r in start..end {
2767 let data = &bytes[offsets[r]..offsets[r + 1]];
2768 let v = match bits[r] {
2769 3 => dot_row::<3>(data, bytes, sc_off, r, ng, x),
2770 4 => dot_row::<4>(data, bytes, sc_off, r, ng, x),
2771 5 => dot_row::<5>(data, bytes, sc_off, r, ng, x),
2772 6 => dot_row::<6>(data, bytes, sc_off, r, ng, x),
2773 8 => dot_row::<8>(data, bytes, sc_off, r, ng, x),
2774 b => unreachable!("vbit bit-width {b} (validated at load)"),
2775 };
2776 unsafe { *out.at(r) = v };
2778 }
2779}
2780
2781#[allow(clippy::too_many_arguments)]
2786fn vbitmatvec2(
2787 bytes: &[u8],
2788 offsets: &[usize],
2789 x1: &[f32],
2790 x2: &[f32],
2791 rows: usize,
2792 cols: usize,
2793 o1: &mut [f32],
2794 o2: &mut [f32],
2795 pool: Option<&Pool>,
2796) {
2797 debug_assert_eq!(o1.len(), rows);
2798 debug_assert_eq!(o2.len(), rows);
2799
2800 if a8w8_enabled() {
2801 let a1 = split_act(x1);
2802 let a2 = split_act(x2);
2803 let p1 = SendMut(o1.as_mut_ptr());
2804 let p2 = SendMut(o2.as_mut_ptr());
2805 let run = move |start: usize, end: usize| {
2806 vbit_range2_a8w8(
2807 bytes, offsets, x1, x2, &a1, &a2, rows, cols, p1, p2, start, end,
2808 )
2809 };
2810 dispatch_rows(pool, rows, &run);
2811 return;
2812 }
2813
2814 let p1 = SendMut(o1.as_mut_ptr());
2815 let p2 = SendMut(o2.as_mut_ptr());
2816 let run = move |start: usize, end: usize| {
2817 vbit_range2_f32(bytes, offsets, x1, x2, rows, cols, p1, p2, start, end)
2818 };
2819 dispatch_rows(pool, rows, &run);
2820}
2821
2822#[allow(clippy::too_many_arguments)]
2826fn vbit_range2_a8w8(
2827 bytes: &[u8],
2828 offsets: &[usize],
2829 x1: &[f32],
2830 x2: &[f32],
2831 a1: &SplitAct,
2832 a2: &SplitAct,
2833 rows: usize,
2834 cols: usize,
2835 p1: SendMut,
2836 p2: SendMut,
2837 start: usize,
2838 end: usize,
2839) {
2840 let ng = cols / GROUP_SIZE;
2841 let bits = &bytes[..rows];
2842 let sc_off = rows;
2843 let row_dots = |r: usize| -> (f32, f32) {
2844 let b = bits[r] as usize;
2845 let l = (1i32 << (b - 1)) - 1;
2846 let data = &bytes[offsets[r]..offsets[r + 1]];
2847 if b == 8 {
2848 let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
2851 let (mut d1, mut d2) = (0f32, 0f32);
2852 for g in 0..ng {
2853 let so = (r * ng + g) * 2;
2854 let sgf = f16_to_f32(u16::from_le_bytes([
2855 bytes[sc_off + so],
2856 bytes[sc_off + so + 1],
2857 ]));
2858 let (mut g1, mut g2) = (0f32, 0f32);
2859 for k in 0..GROUP_SIZE {
2860 if nbits < 8 {
2861 acc = (acc << 8) | data[idx] as u64;
2862 idx += 1;
2863 nbits += 8;
2864 }
2865 let u = ((acc >> (nbits - 8)) & 0xFF) as i32;
2866 nbits -= 8;
2867 let w = (u - l) as f32;
2868 g1 += w * x1[g * GROUP_SIZE + k];
2869 g2 += w * x2[g * GROUP_SIZE + k];
2870 }
2871 d1 += g1 * sgf;
2872 d2 += g2 * sgf;
2873 }
2874 return (d1, d2);
2875 }
2876 thread_local! {
2877 static VBIT_SCRATCH2: std::cell::RefCell<Vec<u8>> =
2878 const { std::cell::RefCell::new(Vec::new()) };
2879 }
2880 #[inline(always)]
2881 fn fill<const B: usize>(data: &[u8], l: i32, buf: &mut [u8]) {
2882 for (blk, chunk) in buf.chunks_exact_mut(8).enumerate() {
2883 let u = unpack8::<B>(&data[blk * B..]);
2884 for k in 0..8 {
2885 chunk[k] = (u[k] - l) as i8 as u8;
2886 }
2887 }
2888 }
2889 VBIT_SCRATCH2.with(|scratch| {
2890 let mut buf = scratch.borrow_mut();
2891 buf.resize(cols, 0);
2892 match b {
2893 3 => fill::<3>(data, l, &mut buf),
2894 4 => vbit_fill4(data, &mut buf),
2895 5 => fill::<5>(data, l, &mut buf),
2896 6 => fill::<6>(data, l, &mut buf),
2897 _ => unreachable!(),
2898 }
2899 let (mut d1, mut d2) = (0f32, 0f32);
2900 for g in 0..ng {
2901 let so = (r * ng + g) * 2;
2902 let s = f16_to_f32(u16::from_le_bytes([
2903 bytes[sc_off + so],
2904 bytes[sc_off + so + 1],
2905 ]));
2906 let wg = &buf[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
2907 let v1 = dot_i8_i8(wg, &a1.xq[g * GROUP_SIZE..(g + 1) * GROUP_SIZE]) as f32 * a1.sx;
2908 let v2 = dot_i8_i8(wg, &a2.xq[g * GROUP_SIZE..(g + 1) * GROUP_SIZE]) as f32 * a2.sx;
2909 d1 += v1 * s;
2910 d2 += v2 * s;
2911 }
2912 for &(j, xv) in &a1.outliers {
2913 let so = (r * ng + j / GROUP_SIZE) * 2;
2914 let s = f16_to_f32(u16::from_le_bytes([
2915 bytes[sc_off + so],
2916 bytes[sc_off + so + 1],
2917 ]));
2918 d1 += (buf[j] as i8) as f32 * s * xv;
2919 }
2920 for &(j, xv) in &a2.outliers {
2921 let so = (r * ng + j / GROUP_SIZE) * 2;
2922 let s = f16_to_f32(u16::from_le_bytes([
2923 bytes[sc_off + so],
2924 bytes[sc_off + so + 1],
2925 ]));
2926 d2 += (buf[j] as i8) as f32 * s * xv;
2927 }
2928 (d1, d2)
2929 })
2930 };
2931 for r in start..end {
2932 let (v1, v2) = row_dots(r);
2933 unsafe {
2935 *p1.at(r) = v1;
2936 *p2.at(r) = v2;
2937 }
2938 }
2939}
2940
2941#[allow(clippy::too_many_arguments)]
2945fn vbit_range2_f32(
2946 bytes: &[u8],
2947 offsets: &[usize],
2948 x1: &[f32],
2949 x2: &[f32],
2950 rows: usize,
2951 cols: usize,
2952 p1: SendMut,
2953 p2: SendMut,
2954 start: usize,
2955 end: usize,
2956) {
2957 let ng = cols / GROUP_SIZE;
2958 let bits = &bytes[..rows];
2959 let sc_off = rows;
2960 #[inline(always)]
2961 #[allow(clippy::too_many_arguments)]
2962 fn dot_row2<const B: usize>(
2963 data: &[u8],
2964 bytes: &[u8],
2965 sc_off: usize,
2966 r: usize,
2967 ng: usize,
2968 x1: &[f32],
2969 x2: &[f32],
2970 ) -> (f32, f32) {
2971 let l = ((1i32 << (B - 1)) - 1) as f32;
2972 let gbytes = GROUP_SIZE * B / 8;
2973 let (mut d1, mut d2) = (0f32, 0f32);
2974 for g in 0..ng {
2975 let so = (r * ng + g) * 2;
2976 let s = f16_to_f32(u16::from_le_bytes([
2977 bytes[sc_off + so],
2978 bytes[sc_off + so + 1],
2979 ]));
2980 let x1g = &x1[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
2981 let x2g = &x2[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
2982 let gd0 = &data[g * gbytes..(g + 1) * gbytes];
2983 let (mut g1, mut g2) = (0f32, 0f32);
2984 for blk in 0..GROUP_SIZE / 8 {
2985 let u = unpack8::<B>(&gd0[blk * B..]);
2986 for k in 0..8 {
2987 let w = u[k] as f32 - l;
2988 g1 += w * x1g[blk * 8 + k];
2989 g2 += w * x2g[blk * 8 + k];
2990 }
2991 }
2992 d1 += g1 * s;
2993 d2 += g2 * s;
2994 }
2995 (d1, d2)
2996 }
2997 for r in start..end {
2998 let data = &bytes[offsets[r]..offsets[r + 1]];
2999 let (v1, v2) = match bits[r] {
3000 3 => dot_row2::<3>(data, bytes, sc_off, r, ng, x1, x2),
3001 4 => dot_row2::<4>(data, bytes, sc_off, r, ng, x1, x2),
3002 5 => dot_row2::<5>(data, bytes, sc_off, r, ng, x1, x2),
3003 6 => dot_row2::<6>(data, bytes, sc_off, r, ng, x1, x2),
3004 8 => dot_row2::<8>(data, bytes, sc_off, r, ng, x1, x2),
3005 b => unreachable!("vbit bit-width {b} (validated at load)"),
3006 };
3007 unsafe {
3009 *p1.at(r) = v1;
3010 *p2.at(r) = v2;
3011 }
3012 }
3013}
3014
3015#[inline]
3022#[allow(unreachable_code)]
3023fn dot_q4t_row_i8(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
3024 #[cfg(target_arch = "aarch64")]
3025 unsafe {
3026 return dot_q4t_row_sdot(bytes, r, gpr, xq);
3027 }
3028 #[cfg(target_arch = "x86_64")]
3029 unsafe {
3030 if vnni_tiles_enabled() {
3031 return dot_q4t_row_vnni(bytes, r, gpr, xq);
3032 }
3033 return dot_q4t_row_avx2(bytes, r, gpr, xq);
3034 }
3035 let mut acc = 0f32;
3036 for gi in 0..gpr {
3037 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
3038 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
3039 let mut d = 0i32;
3040 for (k, &b) in tile[2..].iter().enumerate() {
3041 d += ((b & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2] as i32
3042 + (((b >> 4) & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2 + 1] as i32;
3043 }
3044 acc += d as f32 * s;
3045 }
3046 acc
3047}
3048
3049#[cfg(target_arch = "aarch64")]
3050#[target_feature(enable = "neon,dotprod")]
3051unsafe fn dot_q4t_row_sdot(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
3052 unsafe {
3055 use core::arch::aarch64::*;
3056 use core::arch::asm;
3057 let lomask = vdupq_n_u8(0x0F);
3058 let eight = vdupq_n_s8(8);
3059 let mut acc = 0f32;
3060 for gi in 0..gpr {
3061 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
3062 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3063 let b = vld1q_u8(t.add(2));
3064 let lo = vandq_u8(b, lomask);
3065 let hi = vshrq_n_u8::<4>(b);
3066 let e0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(lo, hi)), eight);
3067 let e1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(lo, hi)), eight);
3068 let x0 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE));
3069 let x1 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE + 16));
3070 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
3071 asm!(
3072 "sdot {a0:v}.4s, {e0:v}.16b, {x0:v}.16b",
3073 "sdot {a1:v}.4s, {e1:v}.16b, {x1:v}.16b",
3074 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
3075 e0 = in(vreg) e0, x0 = in(vreg) x0, e1 = in(vreg) e1, x1 = in(vreg) x1,
3076 options(pure, nomem, nostack),
3077 );
3078 acc += vaddvq_s32(vaddq_s32(a0, a1)) as f32 * s;
3079 }
3080 acc
3081 }
3082}
3083
3084#[cfg(target_arch = "x86_64")]
3085#[target_feature(enable = "avx2")]
3086unsafe fn dot_q4t_row_avx2(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
3087 unsafe {
3089 use core::arch::x86_64::*;
3090 let lomask = _mm_set1_epi8(0x0F);
3091 let eight = _mm256_set1_epi8(8);
3092 let ones = _mm256_set1_epi16(1);
3093 let mut acc = 0f32;
3094 for gi in 0..gpr {
3095 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
3096 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3097 let b = _mm_loadu_si128(t.add(2) as *const __m128i);
3098 let lo = _mm_and_si128(b, lomask);
3099 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), lomask);
3100 let w = _mm256_sub_epi8(
3101 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
3102 eight,
3103 );
3104 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
3105 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(w), _mm256_sign_epi8(x, w));
3106 let d = _mm256_madd_epi16(p16, ones);
3107 let hi128 = _mm256_extracti128_si256::<1>(d);
3108 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
3109 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
3110 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
3111 acc += _mm_cvtsi128_si32(s32) as f32 * s;
3112 }
3113 acc
3114 }
3115}
3116
3117#[cfg(target_arch = "x86_64")]
3121#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
3122unsafe fn dot_q4t_row_vnni(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
3123 unsafe {
3125 use core::arch::x86_64::*;
3126 let lomask = _mm_set1_epi8(0x0F);
3127 let eight = _mm256_set1_epi8(8);
3128 let mut acc = 0f32;
3129 for gi in 0..gpr {
3130 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
3131 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3132 let b = _mm_loadu_si128(t.add(2) as *const __m128i);
3133 let lo = _mm_and_si128(b, lomask);
3134 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), lomask);
3135 let w = _mm256_sub_epi8(
3136 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
3137 eight,
3138 );
3139 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
3140 let d = dpbusd_hsum(_mm256_abs_epi8(w), _mm256_sign_epi8(x, w));
3141 acc += d as f32 * s;
3142 }
3143 acc
3144 }
3145}
3146
3147#[cfg(target_arch = "x86_64")]
3152#[target_feature(enable = "avx2")]
3153unsafe fn dot_q4t_row_1x4_avx2(bytes: &[u8], r: usize, gpr: usize, xs: [&[i8]; 4]) -> [f32; 4] {
3154 unsafe {
3156 use core::arch::x86_64::*;
3157 let lomask = _mm_set1_epi8(0x0F);
3158 let eight = _mm256_set1_epi8(8);
3159 let ones = _mm256_set1_epi16(1);
3160 let mut acc = [0f32; 4];
3161 for gi in 0..gpr {
3162 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
3163 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3164 let bb = _mm_loadu_si128(t.add(2) as *const __m128i);
3165 let lo = _mm_and_si128(bb, lomask);
3166 let hi = _mm_and_si128(_mm_srli_epi16::<4>(bb), lomask);
3167 let w = _mm256_sub_epi8(
3168 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
3169 eight,
3170 );
3171 let aw = _mm256_abs_epi8(w);
3172 for (k, xq) in xs.iter().enumerate() {
3173 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
3174 let p16 = _mm256_maddubs_epi16(aw, _mm256_sign_epi8(x, w));
3175 let d = _mm256_madd_epi16(p16, ones);
3176 let hi128 = _mm256_extracti128_si256::<1>(d);
3177 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
3178 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
3179 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
3180 acc[k] += _mm_cvtsi128_si32(s32) as f32 * s;
3181 }
3182 }
3183 acc
3184 }
3185}
3186
3187#[cfg(target_arch = "x86_64")]
3189#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
3190unsafe fn dot_q4t_row_1x4_vnni(bytes: &[u8], r: usize, gpr: usize, xs: [&[i8]; 4]) -> [f32; 4] {
3191 unsafe {
3193 use core::arch::x86_64::*;
3194 let lomask = _mm_set1_epi8(0x0F);
3195 let eight = _mm256_set1_epi8(8);
3196 let mut acc = [0f32; 4];
3197 for gi in 0..gpr {
3198 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
3199 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3200 let bb = _mm_loadu_si128(t.add(2) as *const __m128i);
3201 let lo = _mm_and_si128(bb, lomask);
3202 let hi = _mm_and_si128(_mm_srli_epi16::<4>(bb), lomask);
3203 let w = _mm256_sub_epi8(
3204 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
3205 eight,
3206 );
3207 let aw = _mm256_abs_epi8(w);
3208 for (k, xq) in xs.iter().enumerate() {
3209 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
3210 let d = dpbusd_hsum(aw, _mm256_sign_epi8(x, w));
3211 acc[k] += d as f32 * s;
3212 }
3213 }
3214 acc
3215 }
3216}
3217
3218#[cfg(target_arch = "aarch64")]
3223#[target_feature(enable = "neon,dotprod")]
3224unsafe fn dot_q4t_row_1x4_sdot(bytes: &[u8], r: usize, gpr: usize, xs: [&[i8]; 4]) -> [f32; 4] {
3225 unsafe {
3227 use core::arch::aarch64::*;
3228 use core::arch::asm;
3229 let lomask = vdupq_n_u8(0x0F);
3230 let eight = vdupq_n_s8(8);
3231 let mut acc = [0f32; 4];
3232 for gi in 0..gpr {
3233 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
3234 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3235 let b = vld1q_u8(t.add(2));
3236 let lo = vandq_u8(b, lomask);
3237 let hi = vshrq_n_u8::<4>(b);
3238 let e0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(lo, hi)), eight);
3239 let e1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(lo, hi)), eight);
3240 for (k, xq) in xs.iter().enumerate() {
3241 let x0 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE));
3242 let x1 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE + 16));
3243 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
3244 asm!(
3245 "sdot {a0:v}.4s, {e0:v}.16b, {x0:v}.16b",
3246 "sdot {a1:v}.4s, {e1:v}.16b, {x1:v}.16b",
3247 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
3248 e0 = in(vreg) e0, x0 = in(vreg) x0, e1 = in(vreg) e1, x1 = in(vreg) x1,
3249 options(pure, nomem, nostack),
3250 );
3251 acc[k] += vaddvq_s32(vaddq_s32(a0, a1)) as f32 * s;
3252 }
3253 }
3254 acc
3255 }
3256}
3257
3258#[inline]
3260fn q4t_outlier(bytes: &[u8], r: usize, gpr: usize, j: usize) -> (f32, f32) {
3261 let gi = j / GROUP_SIZE;
3262 let k = j % GROUP_SIZE;
3263 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
3264 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
3265 let byte = tile[2 + k / 2];
3266 let nib = if k & 1 == 0 { byte & 0x0F } else { byte >> 4 };
3267 ((nib as i32 - 8) as f32, s)
3268}
3269
3270#[inline]
3273fn q4t_row_exact(bytes: &[u8], r: usize, gpr: usize, x: &[f32]) -> f32 {
3274 let mut acc = 0f32;
3275 for gi in 0..gpr {
3276 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
3277 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
3278 let xg = &x[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
3279 let mut ga = 0f32;
3280 for (k, &b) in tile[2..].iter().enumerate() {
3281 ga += ((b & 0x0F) as f32 - 8.0) * xg[k * 2]
3282 + (((b >> 4) & 0x0F) as f32 - 8.0) * xg[k * 2 + 1];
3283 }
3284 acc += ga * s;
3285 }
3286 acc
3287}
3288
3289fn q4t_matvec(
3291 bytes: &[u8],
3292 x: &[f32],
3293 rows: usize,
3294 cols: usize,
3295 out: &mut [f32],
3296 pool: Option<&Pool>,
3297) {
3298 debug_assert_eq!(out.len(), rows);
3299 let gpr = cols / GROUP_SIZE;
3300 let out_addr = SendMut(out.as_mut_ptr());
3301 if a8w8_enabled() {
3302 let act = split_act(x);
3303 let run = move |start: usize, end: usize| {
3304 for r in start..end {
3305 let mut acc = dot_q4t_row_i8(bytes, r, gpr, &act.xq) * act.sx;
3306 for &(j, xv) in &act.outliers {
3307 let (w, s) = q4t_outlier(bytes, r, gpr, j);
3308 acc += w * s * xv;
3309 }
3310 unsafe { *out_addr.at(r) = acc };
3312 }
3313 };
3314 dispatch_rows(pool, rows, &run);
3315 return;
3316 }
3317 let run = move |start: usize, end: usize| {
3318 for r in start..end {
3319 unsafe { *out_addr.at(r) = q4t_row_exact(bytes, r, gpr, x) };
3321 }
3322 };
3323 dispatch_rows(pool, rows, &run);
3324}
3325
3326#[allow(clippy::too_many_arguments)]
3328fn q4t_matvec2(
3329 bytes: &[u8],
3330 x1: &[f32],
3331 x2: &[f32],
3332 rows: usize,
3333 cols: usize,
3334 o1: &mut [f32],
3335 o2: &mut [f32],
3336 pool: Option<&Pool>,
3337) {
3338 let gpr = cols / GROUP_SIZE;
3339 let p1 = SendMut(o1.as_mut_ptr());
3340 let p2 = SendMut(o2.as_mut_ptr());
3341 if a8w8_enabled() {
3342 let a1 = split_act(x1);
3343 let a2 = split_act(x2);
3344 let run = move |start: usize, end: usize| {
3345 for r in start..end {
3346 let mut v1 = dot_q4t_row_i8(bytes, r, gpr, &a1.xq) * a1.sx;
3347 let mut v2 = dot_q4t_row_i8(bytes, r, gpr, &a2.xq) * a2.sx;
3348 for &(j, xv) in &a1.outliers {
3349 let (w, s) = q4t_outlier(bytes, r, gpr, j);
3350 v1 += w * s * xv;
3351 }
3352 for &(j, xv) in &a2.outliers {
3353 let (w, s) = q4t_outlier(bytes, r, gpr, j);
3354 v2 += w * s * xv;
3355 }
3356 unsafe {
3358 *p1.at(r) = v1;
3359 *p2.at(r) = v2;
3360 }
3361 }
3362 };
3363 dispatch_rows(pool, rows, &run);
3364 return;
3365 }
3366 let run = move |start: usize, end: usize| {
3367 for r in start..end {
3368 unsafe {
3370 *p1.at(r) = q4t_row_exact(bytes, r, gpr, x1);
3371 *p2.at(r) = q4t_row_exact(bytes, r, gpr, x2);
3372 }
3373 }
3374 };
3375 dispatch_rows(pool, rows, &run);
3376}
3377
3378#[allow(clippy::too_many_arguments)]
3380#[cfg(target_os = "macos")]
3386fn dequant_matmat_accel(
3387 dequant_row: &(dyn Fn(usize, &mut [f32]) + Sync),
3388 xs_all: &[f32],
3389 b: usize,
3390 rows: usize,
3391 cols: usize,
3392 out: &mut [f32],
3393 pool: Option<&Pool>,
3394) {
3395 const TR: usize = 2048;
3396 thread_local! {
3397 static WTILE: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
3398 }
3399 WTILE.with(|wt| {
3400 let mut wtile = wt.borrow_mut();
3401 wtile.resize(TR * cols, 0.0);
3402 let mut r0 = 0usize;
3403 while r0 < rows {
3404 let tr = TR.min(rows - r0);
3405 let wt_addr = SendMut(wtile.as_mut_ptr());
3406 let run = |start: usize, end: usize| {
3407 for r in start..end {
3408 let dst = unsafe { std::slice::from_raw_parts_mut(wt_addr.at(r * cols), cols) };
3410 dequant_row(r0 + r, dst);
3411 }
3412 };
3413 dispatch_rows(pool, tr, &run);
3414 unsafe {
3415 accel_blas::cblas_sgemm(
3416 101, 111, 112, b as i32,
3420 tr as i32,
3421 cols as i32,
3422 1.0,
3423 xs_all.as_ptr(),
3424 cols as i32,
3425 wtile.as_ptr(),
3426 cols as i32,
3427 0.0,
3428 out.as_mut_ptr().add(r0),
3429 rows as i32,
3430 );
3431 }
3432 r0 += tr;
3433 }
3434 });
3435}
3436
3437fn q4t_matmat(
3438 bytes: &[u8],
3439 xs_all: &[f32],
3440 b: usize,
3441 rows: usize,
3442 cols: usize,
3443 out: &mut [f32],
3444 pool: Option<&Pool>,
3445) {
3446 debug_assert_eq!(out.len(), b * rows);
3447 let gpr = cols / GROUP_SIZE;
3448 #[cfg(target_os = "macos")]
3452 if b >= 8 && rows * cols >= 500_000 && accel_gemm_enabled() {
3453 dequant_matmat_accel(
3454 &|r, dst| {
3455 for gi in 0..gpr {
3456 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
3457 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
3458 for (k, &bb) in tile[2..].iter().enumerate() {
3459 dst[gi * GROUP_SIZE + k * 2] = ((bb & 0x0F) as f32 - 8.0) * s;
3460 dst[gi * GROUP_SIZE + k * 2 + 1] = (((bb >> 4) & 0x0F) as f32 - 8.0) * s;
3461 }
3462 }
3463 },
3464 xs_all,
3465 b,
3466 rows,
3467 cols,
3468 out,
3469 pool,
3470 );
3471 return;
3472 }
3473 let out_addr = SendMut(out.as_mut_ptr());
3474 if a8w8_enabled() {
3475 let acts: Vec<SplitAct> = (0..b)
3476 .map(|bi| split_act(&xs_all[bi * cols..(bi + 1) * cols]))
3477 .collect();
3478 let acts = &acts;
3479 #[cfg(target_arch = "x86_64")]
3480 let blocked_ok = avx2_enabled()
3481 && std::env::var("CMF_X86_BLOCKED")
3482 .map(|v| v != "0")
3483 .unwrap_or(true);
3484 #[cfg(target_arch = "aarch64")]
3485 let blocked_ok = sdot_enabled()
3486 && std::env::var("CMF_X86_BLOCKED")
3487 .map(|v| v != "0")
3488 .unwrap_or(true);
3489 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
3490 let blocked_ok = false;
3491 let run = move |start: usize, end: usize| {
3492 for r in start..end {
3493 let mut bi = 0usize;
3494 #[cfg(target_arch = "aarch64")]
3495 if blocked_ok {
3496 while bi + 4 <= acts.len() {
3497 let xs = [
3498 acts[bi].xq.as_slice(),
3499 acts[bi + 1].xq.as_slice(),
3500 acts[bi + 2].xq.as_slice(),
3501 acts[bi + 3].xq.as_slice(),
3502 ];
3503 let d = unsafe { dot_q4t_row_1x4_sdot(bytes, r, gpr, xs) };
3504 for k in 0..4 {
3505 let act = &acts[bi + k];
3506 let mut acc = d[k] * act.sx;
3507 for &(j, xv) in &act.outliers {
3508 let (w, sc) = q4t_outlier(bytes, r, gpr, j);
3509 acc += w * sc * xv;
3510 }
3511 unsafe { *out_addr.at((bi + k) * rows + r) = acc };
3513 }
3514 bi += 4;
3515 }
3516 }
3517 #[cfg(target_arch = "x86_64")]
3518 if blocked_ok {
3519 while bi + 4 <= acts.len() {
3520 let xs = [
3521 acts[bi].xq.as_slice(),
3522 acts[bi + 1].xq.as_slice(),
3523 acts[bi + 2].xq.as_slice(),
3524 acts[bi + 3].xq.as_slice(),
3525 ];
3526 let d = unsafe {
3527 if vnni_tiles_enabled() {
3528 dot_q4t_row_1x4_vnni(bytes, r, gpr, xs)
3529 } else {
3530 dot_q4t_row_1x4_avx2(bytes, r, gpr, xs)
3531 }
3532 };
3533 for k in 0..4 {
3534 let act = &acts[bi + k];
3535 let mut acc = d[k] * act.sx;
3536 for &(j, xv) in &act.outliers {
3537 let (w, sc) = q4t_outlier(bytes, r, gpr, j);
3538 acc += w * sc * xv;
3539 }
3540 unsafe { *out_addr.at((bi + k) * rows + r) = acc };
3542 }
3543 bi += 4;
3544 }
3545 }
3546 let _ = blocked_ok;
3547 while bi < acts.len() {
3548 let act = &acts[bi];
3549 let mut acc = dot_q4t_row_i8(bytes, r, gpr, &act.xq) * act.sx;
3550 for &(j, xv) in &act.outliers {
3551 let (w, s) = q4t_outlier(bytes, r, gpr, j);
3552 acc += w * s * xv;
3553 }
3554 unsafe { *out_addr.at(bi * rows + r) = acc };
3556 bi += 1;
3557 }
3558 }
3559 };
3560 dispatch_rows(pool, rows, &run);
3561 return;
3562 }
3563 let run = move |start: usize, end: usize| {
3564 for r in start..end {
3565 for bi in 0..b {
3566 let x = &xs_all[bi * cols..(bi + 1) * cols];
3567 unsafe { *out_addr.at(bi * rows + r) = q4t_row_exact(bytes, r, gpr, x) };
3569 }
3570 }
3571 };
3572 dispatch_rows(pool, rows, &run);
3573}
3574
3575fn q1_group_sums(xq: &[i8], gpr: usize) -> Vec<i32> {
3584 (0..gpr)
3585 .map(|gi| {
3586 xq[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE]
3587 .iter()
3588 .map(|&v| v as i32)
3589 .sum()
3590 })
3591 .collect()
3592}
3593
3594#[inline]
3598#[allow(unreachable_code)]
3599#[cfg(target_arch = "x86_64")]
3604#[target_feature(enable = "avx2")]
3605unsafe fn dot_q1_row_avx2(bytes: &[u8], r: usize, gpr: usize, xq: &[i8], gsum: &[i32]) -> f32 {
3606 unsafe {
3608 use core::arch::x86_64::*;
3609 let expand = _mm256_setr_epi8(
3611 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,
3612 3, 3, 3,
3613 );
3614 let bitsel = _mm256_setr_epi8(
3615 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64,
3616 -128, 1, 2, 4, 8, 16, 32, 64, -128,
3617 );
3618 let ones8 = _mm256_set1_epi8(1);
3619 let ones16 = _mm256_set1_epi16(1);
3620 let mut acc = 0f32;
3621 for gi in 0..gpr {
3622 let t = bytes.as_ptr().add((r * gpr + gi) * Q1_TILE);
3623 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3624 let bits = u32::from_le_bytes([*t.add(2), *t.add(3), *t.add(4), *t.add(5)]);
3625 let bc = _mm256_shuffle_epi8(_mm256_set1_epi32(bits as i32), expand);
3626 let mask = _mm256_cmpeq_epi8(_mm256_and_si256(bc, bitsel), bitsel);
3627 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
3628 let sel = _mm256_and_si256(x, mask);
3629 let p16 = _mm256_maddubs_epi16(ones8, sel);
3631 let d32 = _mm256_madd_epi16(p16, ones16);
3632 let hi128 = _mm256_extracti128_si256::<1>(d32);
3633 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d32), hi128);
3634 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
3635 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
3636 let msum = _mm_cvtsi128_si32(s32);
3637 let d = 2 * msum - gsum[gi];
3640 acc += d as f32 * s;
3641 }
3642 acc
3643 }
3644}
3645
3646#[cfg(target_arch = "x86_64")]
3649#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
3650unsafe fn dot_q1_row_vnni(bytes: &[u8], r: usize, gpr: usize, xq: &[i8], gsum: &[i32]) -> f32 {
3651 unsafe {
3653 use core::arch::x86_64::*;
3654 let expand = _mm256_setr_epi8(
3655 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,
3656 3, 3, 3,
3657 );
3658 let bitsel = _mm256_setr_epi8(
3659 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64,
3660 -128, 1, 2, 4, 8, 16, 32, 64, -128,
3661 );
3662 let ones8 = _mm256_set1_epi8(1);
3663 let mut acc = 0f32;
3664 for gi in 0..gpr {
3665 let t = bytes.as_ptr().add((r * gpr + gi) * Q1_TILE);
3666 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3667 let bits = u32::from_le_bytes([*t.add(2), *t.add(3), *t.add(4), *t.add(5)]);
3668 let bc = _mm256_shuffle_epi8(_mm256_set1_epi32(bits as i32), expand);
3669 let mask = _mm256_cmpeq_epi8(_mm256_and_si256(bc, bitsel), bitsel);
3670 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
3671 let msum = dpbusd_hsum(ones8, _mm256_and_si256(x, mask));
3672 let d = 2 * msum - gsum[gi];
3673 acc += d as f32 * s;
3674 }
3675 acc
3676 }
3677}
3678
3679#[cfg(target_arch = "x86_64")]
3681#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
3682unsafe fn dot_q1_row_1x4_vnni(
3683 bytes: &[u8],
3684 r: usize,
3685 gpr: usize,
3686 xs: [&[i8]; 4],
3687 gsums: [&[i32]; 4],
3688) -> [f32; 4] {
3689 unsafe {
3691 use core::arch::x86_64::*;
3692 let expand = _mm256_setr_epi8(
3693 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,
3694 3, 3, 3,
3695 );
3696 let bitsel = _mm256_setr_epi8(
3697 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64,
3698 -128, 1, 2, 4, 8, 16, 32, 64, -128,
3699 );
3700 let ones8 = _mm256_set1_epi8(1);
3701 let mut acc = [0f32; 4];
3702 for gi in 0..gpr {
3703 let t = bytes.as_ptr().add((r * gpr + gi) * Q1_TILE);
3704 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3705 let bits = u32::from_le_bytes([*t.add(2), *t.add(3), *t.add(4), *t.add(5)]);
3706 let bc = _mm256_shuffle_epi8(_mm256_set1_epi32(bits as i32), expand);
3707 let mask = _mm256_cmpeq_epi8(_mm256_and_si256(bc, bitsel), bitsel);
3708 for (k, xq) in xs.iter().enumerate() {
3709 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
3710 let msum = dpbusd_hsum(ones8, _mm256_and_si256(x, mask));
3711 let d = 2 * msum - gsums[k][gi];
3712 acc[k] += d as f32 * s;
3713 }
3714 }
3715 acc
3716 }
3717}
3718
3719#[cfg(target_arch = "x86_64")]
3722#[target_feature(enable = "avx2")]
3723unsafe fn dot_q1_row_1x4_avx2(
3724 bytes: &[u8],
3725 r: usize,
3726 gpr: usize,
3727 xs: [&[i8]; 4],
3728 gsums: [&[i32]; 4],
3729) -> [f32; 4] {
3730 unsafe {
3732 use core::arch::x86_64::*;
3733 let expand = _mm256_setr_epi8(
3734 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,
3735 3, 3, 3,
3736 );
3737 let bitsel = _mm256_setr_epi8(
3738 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64,
3739 -128, 1, 2, 4, 8, 16, 32, 64, -128,
3740 );
3741 let ones8 = _mm256_set1_epi8(1);
3742 let ones16 = _mm256_set1_epi16(1);
3743 let mut acc = [0f32; 4];
3744 for gi in 0..gpr {
3745 let t = bytes.as_ptr().add((r * gpr + gi) * Q1_TILE);
3746 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3747 let bits = u32::from_le_bytes([*t.add(2), *t.add(3), *t.add(4), *t.add(5)]);
3748 let bc = _mm256_shuffle_epi8(_mm256_set1_epi32(bits as i32), expand);
3749 let mask = _mm256_cmpeq_epi8(_mm256_and_si256(bc, bitsel), bitsel);
3750 for (k, xq) in xs.iter().enumerate() {
3751 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
3752 let sel = _mm256_and_si256(x, mask);
3753 let p16 = _mm256_maddubs_epi16(ones8, sel);
3754 let d32 = _mm256_madd_epi16(p16, ones16);
3755 let hi128 = _mm256_extracti128_si256::<1>(d32);
3756 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d32), hi128);
3757 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
3758 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
3759 let msum = _mm_cvtsi128_si32(s32);
3760 let d = 2 * msum - gsums[k][gi];
3761 acc[k] += d as f32 * s;
3762 }
3763 }
3764 acc
3765 }
3766}
3767
3768#[allow(unreachable_code)]
3769fn dot_q1_row_i8(bytes: &[u8], r: usize, gpr: usize, xq: &[i8], gsum: &[i32]) -> f32 {
3770 #[cfg(target_arch = "aarch64")]
3771 unsafe {
3772 return dot_q1_row_sdot(bytes, r, gpr, xq, gsum);
3773 }
3774 #[cfg(target_arch = "x86_64")]
3775 if avx2_enabled() {
3776 unsafe {
3777 if vnni_tiles_enabled() {
3778 return dot_q1_row_vnni(bytes, r, gpr, xq, gsum);
3779 }
3780 return dot_q1_row_avx2(bytes, r, gpr, xq, gsum);
3781 }
3782 }
3783 let _ = gsum;
3784 let mut acc = 0f32;
3785 for gi in 0..gpr {
3786 let tile = &bytes[(r * gpr + gi) * Q1_TILE..(r * gpr + gi + 1) * Q1_TILE];
3787 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
3788 let mut d = 0i32;
3789 for (j, &b) in tile[2..].iter().enumerate() {
3790 for k in 0..8 {
3791 let w = ((b >> k) & 1) as i32 * 2 - 1;
3792 d += w * xq[gi * GROUP_SIZE + j * 8 + k] as i32;
3793 }
3794 }
3795 acc += d as f32 * s;
3796 }
3797 acc
3798}
3799
3800#[cfg(target_arch = "aarch64")]
3809#[target_feature(enable = "neon,dotprod")]
3810unsafe fn dot_q1_row_sdot(bytes: &[u8], r: usize, gpr: usize, xq: &[i8], gsum: &[i32]) -> f32 {
3811 unsafe {
3814 use core::arch::aarch64::*;
3815 use core::arch::asm;
3816 const MASKS: [u8; 16] = [1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128];
3817 let m = vld1q_u8(MASKS.as_ptr());
3818 macro_rules! tile_dot {
3820 ($t:expr, $x:expr) => {{
3821 let v0 = vcombine_u8(vdup_n_u8(*$t.add(2)), vdup_n_u8(*$t.add(3)));
3822 let v1 = vcombine_u8(vdup_n_u8(*$t.add(4)), vdup_n_u8(*$t.add(5)));
3823 let w0 = vreinterpretq_s8_u8(vtstq_u8(v0, m));
3824 let w1 = vreinterpretq_s8_u8(vtstq_u8(v1, m));
3825 let x0 = vld1q_s8($x);
3826 let x1 = vld1q_s8($x.add(16));
3827 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
3828 asm!(
3829 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
3830 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
3831 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
3832 w0 = in(vreg) w0, x0 = in(vreg) x0, w1 = in(vreg) w1, x1 = in(vreg) x1,
3833 options(pure, nomem, nostack),
3834 );
3835 vaddq_s32(a0, a1)
3836 }};
3837 }
3838 const IW00: [u8; 16] = [2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3];
3847 const IW01: [u8; 16] = [4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5];
3848 const IW10: [u8; 16] = [8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9];
3849 const IW11: [u8; 16] = [
3850 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11,
3851 ];
3852 const ISC: [u8; 8] = [0, 1, 6, 7, 16, 17, 22, 23];
3853 let (iw00, iw01) = (vld1q_u8(IW00.as_ptr()), vld1q_u8(IW01.as_ptr()));
3854 let (iw10, iw11) = (vld1q_u8(IW10.as_ptr()), vld1q_u8(IW11.as_ptr()));
3855 let isc = vld1_u8(ISC.as_ptr());
3856 macro_rules! tile_dot_tbl {
3858 ($ld:expr, $i0:expr, $i1:expr, $x:expr) => {{
3859 let w0 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8($ld, $i0), m));
3860 let w1 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8($ld, $i1), m));
3861 let x0 = vld1q_s8($x);
3862 let x1 = vld1q_s8($x.add(16));
3863 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
3864 asm!(
3865 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
3866 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
3867 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
3868 w0 = in(vreg) w0, x0 = in(vreg) x0, w1 = in(vreg) w1, x1 = in(vreg) x1,
3869 options(pure, nomem, nostack),
3870 );
3871 vaddq_s32(a0, a1)
3872 }};
3873 }
3874 let base = bytes.as_ptr().add(r * gpr * Q1_TILE);
3875 let row_base = r * gpr * Q1_TILE;
3876 let abs_end = bytes.len();
3877 let xp = xq.as_ptr();
3878 let gp = gsum.as_ptr();
3879 let mut accv = vdupq_n_f32(0.0);
3880 let mut gi = 0;
3881 while gi + 4 <= gpr && row_base + (gi + 4) * Q1_TILE + 4 <= abs_end {
3884 let t0 = base.add(gi * Q1_TILE);
3885 let ld_a = vld1q_u8(t0);
3886 let ld_b = vld1q_u8(t0.add(2 * Q1_TILE));
3887 let d0 = tile_dot_tbl!(ld_a, iw00, iw01, xp.add(gi * GROUP_SIZE));
3888 let d1 = tile_dot_tbl!(ld_a, iw10, iw11, xp.add((gi + 1) * GROUP_SIZE));
3889 let d2 = tile_dot_tbl!(ld_b, iw00, iw01, xp.add((gi + 2) * GROUP_SIZE));
3890 let d3 = tile_dot_tbl!(ld_b, iw10, iw11, xp.add((gi + 3) * GROUP_SIZE));
3891 let neg = vpaddq_s32(vpaddq_s32(d0, d1), vpaddq_s32(d2, d3));
3893 let g = vld1q_s32(gp.add(gi));
3894 let dots = vnegq_s32(vaddq_s32(vshlq_n_s32::<1>(neg), g));
3895 let sc16 = vqtbl2_u8(uint8x16x2_t(ld_a, ld_b), isc);
3896 let scf: float32x4_t;
3897 asm!(
3898 "fcvtl {o:v}.4s, {i:v}.4h",
3899 o = out(vreg) scf, i = in(vreg) sc16,
3900 options(pure, nomem, nostack),
3901 );
3902 accv = vfmaq_f32(accv, vcvtq_f32_s32(dots), scf);
3903 gi += 4;
3904 }
3905 let mut acc = vaddvq_f32(accv);
3906 while gi < gpr {
3907 let t = base.add(gi * Q1_TILE);
3908 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3909 let d = vaddvq_s32(tile_dot!(t, xp.add(gi * GROUP_SIZE)));
3910 acc += (-(2 * d + *gp.add(gi))) as f32 * s;
3911 gi += 1;
3912 }
3913 acc
3914 }
3915}
3916
3917#[cfg(target_arch = "aarch64")]
3922#[target_feature(enable = "neon,dotprod")]
3923unsafe fn dot_q1_row_1x4_sdot(
3924 bytes: &[u8],
3925 r: usize,
3926 gpr: usize,
3927 xs: [&[i8]; 4],
3928 gs: [&[i32]; 4],
3929) -> [f32; 4] {
3930 unsafe {
3932 use core::arch::aarch64::*;
3933 use core::arch::asm;
3934 const MASKS: [u8; 16] = [1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128];
3935 const IW00: [u8; 16] = [2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3];
3936 const IW01: [u8; 16] = [4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5];
3937 const IW10: [u8; 16] = [8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9];
3938 const IW11: [u8; 16] = [
3939 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11,
3940 ];
3941 const ISC: [u8; 8] = [0, 1, 6, 7, 16, 17, 22, 23];
3942 let m = vld1q_u8(MASKS.as_ptr());
3943 let (iw00, iw01) = (vld1q_u8(IW00.as_ptr()), vld1q_u8(IW01.as_ptr()));
3944 let (iw10, iw11) = (vld1q_u8(IW10.as_ptr()), vld1q_u8(IW11.as_ptr()));
3945 let isc = vld1_u8(ISC.as_ptr());
3946 macro_rules! sdot2 {
3947 ($w0:expr, $w1:expr, $x:expr) => {{
3948 let x0 = vld1q_s8($x);
3949 let x1 = vld1q_s8($x.add(16));
3950 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
3951 asm!(
3952 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
3953 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
3954 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
3955 w0 = in(vreg) $w0, x0 = in(vreg) x0, w1 = in(vreg) $w1, x1 = in(vreg) x1,
3956 options(pure, nomem, nostack),
3957 );
3958 vaddq_s32(a0, a1)
3959 }};
3960 }
3961 let base = bytes.as_ptr().add(r * gpr * Q1_TILE);
3962 let row_base = r * gpr * Q1_TILE;
3963 let abs_end = bytes.len();
3964 let mut accv = [vdupq_n_f32(0.0); 4];
3965 let mut gi = 0;
3966 while gi + 4 <= gpr && row_base + (gi + 4) * Q1_TILE + 4 <= abs_end {
3967 let t0 = base.add(gi * Q1_TILE);
3968 let ld_a = vld1q_u8(t0);
3969 let ld_b = vld1q_u8(t0.add(2 * Q1_TILE));
3970 let w00 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_a, iw00), m));
3972 let w01 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_a, iw01), m));
3973 let w10 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_a, iw10), m));
3974 let w11 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_a, iw11), m));
3975 let w20 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_b, iw00), m));
3976 let w21 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_b, iw01), m));
3977 let w30 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_b, iw10), m));
3978 let w31 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_b, iw11), m));
3979 let sc16 = vqtbl2_u8(uint8x16x2_t(ld_a, ld_b), isc);
3980 let scf: float32x4_t;
3981 asm!(
3982 "fcvtl {o:v}.4s, {i:v}.4h",
3983 o = out(vreg) scf, i = in(vreg) sc16,
3984 options(pure, nomem, nostack),
3985 );
3986 for k in 0..4 {
3987 let xp = xs[k].as_ptr();
3988 let d0 = sdot2!(w00, w01, xp.add(gi * GROUP_SIZE));
3989 let d1 = sdot2!(w10, w11, xp.add((gi + 1) * GROUP_SIZE));
3990 let d2 = sdot2!(w20, w21, xp.add((gi + 2) * GROUP_SIZE));
3991 let d3 = sdot2!(w30, w31, xp.add((gi + 3) * GROUP_SIZE));
3992 let neg = vpaddq_s32(vpaddq_s32(d0, d1), vpaddq_s32(d2, d3));
3993 let g = vld1q_s32(gs[k].as_ptr().add(gi));
3994 let dots = vnegq_s32(vaddq_s32(vshlq_n_s32::<1>(neg), g));
3995 accv[k] = vfmaq_f32(accv[k], vcvtq_f32_s32(dots), scf);
3996 }
3997 gi += 4;
3998 }
3999 let mut acc = [
4000 vaddvq_f32(accv[0]),
4001 vaddvq_f32(accv[1]),
4002 vaddvq_f32(accv[2]),
4003 vaddvq_f32(accv[3]),
4004 ];
4005 while gi < gpr {
4006 let t = base.add(gi * Q1_TILE);
4007 let sc = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
4008 let v0 = vcombine_u8(vdup_n_u8(*t.add(2)), vdup_n_u8(*t.add(3)));
4009 let v1 = vcombine_u8(vdup_n_u8(*t.add(4)), vdup_n_u8(*t.add(5)));
4010 let w0 = vreinterpretq_s8_u8(vtstq_u8(v0, m));
4011 let w1 = vreinterpretq_s8_u8(vtstq_u8(v1, m));
4012 for k in 0..4 {
4013 let d = vaddvq_s32(sdot2!(w0, w1, xs[k].as_ptr().add(gi * GROUP_SIZE)));
4014 acc[k] += (-(2 * d + *gs[k].as_ptr().add(gi))) as f32 * sc;
4015 }
4016 gi += 1;
4017 }
4018 acc
4019 }
4020}
4021
4022#[inline]
4024fn q1_outlier(bytes: &[u8], r: usize, gpr: usize, j: usize) -> (f32, f32) {
4025 let gi = j / GROUP_SIZE;
4026 let k = j % GROUP_SIZE;
4027 let tile = &bytes[(r * gpr + gi) * Q1_TILE..(r * gpr + gi + 1) * Q1_TILE];
4028 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
4029 let bit = (tile[2 + k / 8] >> (k % 8)) & 1;
4030 ((bit as i32 * 2 - 1) as f32, s)
4031}
4032
4033#[inline]
4035fn q1_row_exact(bytes: &[u8], r: usize, gpr: usize, x: &[f32]) -> f32 {
4036 let mut acc = 0f32;
4037 for gi in 0..gpr {
4038 let tile = &bytes[(r * gpr + gi) * Q1_TILE..(r * gpr + gi + 1) * Q1_TILE];
4039 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
4040 let xg = &x[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
4041 let mut ga = 0f32;
4042 for (j, &b) in tile[2..].iter().enumerate() {
4043 for k in 0..8 {
4044 ga += (((b >> k) & 1) as f32 * 2.0 - 1.0) * xg[j * 8 + k];
4045 }
4046 }
4047 acc += ga * s;
4048 }
4049 acc
4050}
4051
4052#[allow(clippy::too_many_arguments)]
4055fn q1_range_a8w8(
4056 bytes: &[u8],
4057 gpr: usize,
4058 act: &SplitAct,
4059 gsum: &[i32],
4060 out: SendMut,
4061 start: usize,
4062 end: usize,
4063) {
4064 for r in start..end {
4065 let mut acc = dot_q1_row_i8(bytes, r, gpr, &act.xq, gsum) * act.sx;
4066 for &(j, xv) in &act.outliers {
4067 let (w, s) = q1_outlier(bytes, r, gpr, j);
4068 acc += w * s * xv;
4069 }
4070 unsafe { *out.at(r) = acc };
4072 }
4073}
4074
4075fn q1_range_f32(bytes: &[u8], gpr: usize, x: &[f32], out: SendMut, start: usize, end: usize) {
4077 for r in start..end {
4078 unsafe { *out.at(r) = q1_row_exact(bytes, r, gpr, x) };
4080 }
4081}
4082
4083fn q1t_overlay(bytes: &[u8], base_len: usize, rows: usize) -> (usize, usize, bool) {
4088 let entries = base_len + (rows + 1) * 4;
4089 (base_len, entries, entries <= bytes.len())
4090}
4091
4092#[inline]
4094fn q1t_rowptr(bytes: &[u8], rp_off: usize, r: usize) -> usize {
4095 let o = rp_off + r * 4;
4096 u32::from_le_bytes([bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]]) as usize
4097}
4098
4099const SIGN5: [[f32; 5]; 256] = {
4103 let mut lut = [[0.0f32; 5]; 256];
4104 let pow3 = [1u16, 3, 9, 27, 81];
4105 let mut byte = 0usize;
4106 while byte < 256 {
4107 let mut i = 0usize;
4108 while i < 5 {
4109 let code = (byte as u16 / pow3[i]) % 3;
4110 lut[byte][i] = if code == 1 {
4111 1.0
4112 } else if code == 2 {
4113 -1.0
4114 } else {
4115 0.0
4116 };
4117 i += 1;
4118 }
4119 byte += 1;
4120 }
4121 lut
4122};
4123
4124const SIGN5_I8: [[i8; 5]; 256] = {
4126 let mut lut = [[0i8; 5]; 256];
4127 let pow3 = [1u16, 3, 9, 27, 81];
4128 let mut byte = 0usize;
4129 while byte < 256 {
4130 let mut i = 0usize;
4131 while i < 5 {
4132 let code = (byte as u16 / pow3[i]) % 3;
4133 lut[byte][i] = if code == 1 {
4134 1
4135 } else if code == 2 {
4136 -1
4137 } else {
4138 0
4139 };
4140 i += 1;
4141 }
4142 byte += 1;
4143 }
4144 lut
4145};
4146
4147const SIGN5_U64: [u64; 256] = {
4153 let mut lut = [0u64; 256];
4154 let pow3 = [1u16, 3, 9, 27, 81];
4155 let mut byte = 0usize;
4156 while byte < 256 {
4157 let mut v = 0u64;
4158 let mut i = 0usize;
4159 while i < 5 {
4160 let code = (byte as u16 / pow3[i]) % 3;
4161 let s: u8 = if code == 1 {
4162 1
4163 } else if code == 2 {
4164 0xFF
4165 } else {
4166 0
4167 };
4168 v |= (s as u64) << (i * 8);
4169 i += 1;
4170 }
4171 lut[byte] = v;
4172 byte += 1;
4173 }
4174 lut
4175};
4176
4177#[inline]
4182fn q1t_base_weight(bytes: &[u8], r: usize, gpr: usize, j: usize) -> f32 {
4183 const TILE: usize = cortiq_core::quant::Q1T_TILE;
4184 let off = (r * gpr + j / GROUP_SIZE) * TILE;
4185 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
4186 let within = j % GROUP_SIZE;
4187 SIGN5[bytes[off + 2 + within / 5] as usize][within % 5] * s
4188}
4189
4190#[cfg(target_arch = "aarch64")]
4193#[target_feature(enable = "neon,dotprod")]
4194#[inline]
4195unsafe fn sdot32_i8(w: *const i8, x: *const i8) -> i32 {
4196 unsafe {
4198 use core::arch::aarch64::*;
4199 use core::arch::asm;
4200 let w0 = vld1q_s8(w);
4201 let w1 = vld1q_s8(w.add(16));
4202 let x0 = vld1q_s8(x);
4203 let x1 = vld1q_s8(x.add(16));
4204 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
4205 asm!(
4206 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
4207 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
4208 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
4209 w0 = in(vreg) w0, x0 = in(vreg) x0, w1 = in(vreg) w1, x1 = in(vreg) x1,
4210 options(pure, nomem, nostack),
4211 );
4212 vaddvq_s32(vaddq_s32(a0, a1))
4213 }
4214}
4215
4216#[cfg(target_arch = "x86_64")]
4219#[target_feature(enable = "avx2")]
4220#[inline]
4221unsafe fn i8dot32_avx2(w: *const i8, x: *const i8) -> i32 {
4222 unsafe {
4224 use core::arch::x86_64::*;
4225 let wv = _mm256_loadu_si256(w as *const __m256i);
4226 let xv = _mm256_loadu_si256(x as *const __m256i);
4227 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(wv), _mm256_sign_epi8(xv, wv));
4228 let d = _mm256_madd_epi16(p16, _mm256_set1_epi16(1));
4229 let hi128 = _mm256_extracti128_si256::<1>(d);
4230 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
4231 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
4232 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
4233 _mm_cvtsi128_si32(s32)
4234 }
4235}
4236
4237#[inline]
4242fn q1t_unpack_group_i8(codes: *const u8, dst: &mut [i8]) {
4243 debug_assert!(dst.len() >= 40);
4244 unsafe {
4247 let p = dst.as_mut_ptr();
4248 for bi in 0..7 {
4249 core::ptr::write_unaligned(
4250 p.add(bi * 5) as *mut u64,
4251 SIGN5_U64[*codes.add(bi) as usize],
4252 );
4253 }
4254 }
4255}
4256
4257#[inline]
4262fn q1t_i8dot32(w: *const i8, x: *const i8) -> i32 {
4263 #[cfg(target_arch = "aarch64")]
4264 unsafe {
4265 return sdot32_i8(w, x);
4266 }
4267 #[cfg(target_arch = "x86_64")]
4268 unsafe {
4269 return i8dot32_avx2(w, x);
4270 }
4271 #[allow(unreachable_code)]
4272 unsafe {
4273 let mut s = 0i32;
4274 for k in 0..GROUP_SIZE {
4275 s += *w.add(k) as i32 * *x.add(k) as i32;
4276 }
4277 s
4278 }
4279}
4280
4281#[inline]
4282unsafe fn q1t_unpack_reg_u64s(codes: *const u8) -> (u64, u64, u64, u64) {
4283 let (s0, s1, s2, s3, s4, s5, s6) = unsafe {
4284 (
4285 SIGN5_U64[*codes as usize],
4286 SIGN5_U64[*codes.add(1) as usize],
4287 SIGN5_U64[*codes.add(2) as usize],
4288 SIGN5_U64[*codes.add(3) as usize],
4289 SIGN5_U64[*codes.add(4) as usize],
4290 SIGN5_U64[*codes.add(5) as usize],
4291 SIGN5_U64[*codes.add(6) as usize],
4292 )
4293 };
4294
4295 let u0 = s0 | (s1 << 40);
4296 let u1 = (s1 >> 24) | (s2 << 16) | (s3 << 56);
4297 let u2 = (s3 >> 8) | (s4 << 32);
4298 let u3 = (s4 >> 32) | (s5 << 8) | (s6 << 48);
4299
4300 (u0, u1, u2, u3)
4301}
4302
4303#[cfg(target_arch = "aarch64")]
4307#[target_feature(enable = "neon,dotprod")]
4308unsafe fn q1t_dot_row_sdot(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
4309 use core::arch::aarch64::*;
4310 use core::arch::asm;
4311 unsafe {
4312 const TILE: usize = cortiq_core::quant::Q1T_TILE;
4313 let mut acc = 0f32;
4314 let bytes_ptr = bytes.as_ptr();
4315 let xq_ptr = xq.as_ptr();
4316 let row_off = r * gpr * TILE;
4317
4318 let gpr2 = gpr & !1;
4319 let mut gi = 0;
4320 while gi < gpr2 {
4321 let off0 = row_off + gi * TILE;
4322 let off1 = off0 + TILE;
4323 let s0 = f16_to_f32(u16::from_le_bytes([
4324 *bytes_ptr.add(off0),
4325 *bytes_ptr.add(off0 + 1),
4326 ]));
4327 let s1 = f16_to_f32(u16::from_le_bytes([
4328 *bytes_ptr.add(off1),
4329 *bytes_ptr.add(off1 + 1),
4330 ]));
4331
4332 let (u0_0, u1_0, u2_0, u3_0) = q1t_unpack_reg_u64s(bytes_ptr.add(off0 + 2));
4333 let (u0_1, u1_1, u2_1, u3_1) = q1t_unpack_reg_u64s(bytes_ptr.add(off1 + 2));
4334
4335 let w0_0 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u0_0), vcreate_u64(u1_0)));
4336 let w1_0 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u2_0), vcreate_u64(u3_0)));
4337 let w0_1 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u0_1), vcreate_u64(u1_1)));
4338 let w1_1 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u2_1), vcreate_u64(u3_1)));
4339
4340 let x0_0 = vld1q_s8(xq_ptr.add(gi * GROUP_SIZE));
4341 let x1_0 = vld1q_s8(xq_ptr.add(gi * GROUP_SIZE + 16));
4342 let x0_1 = vld1q_s8(xq_ptr.add((gi + 1) * GROUP_SIZE));
4343 let x1_1 = vld1q_s8(xq_ptr.add((gi + 1) * GROUP_SIZE + 16));
4344
4345 let (mut a0_0, mut a1_0) = (vdupq_n_s32(0), vdupq_n_s32(0));
4346 let (mut a0_1, mut a1_1) = (vdupq_n_s32(0), vdupq_n_s32(0));
4347 asm!(
4348 "sdot {a0_0:v}.4s, {w0_0:v}.16b, {x0_0:v}.16b",
4349 "sdot {a1_0:v}.4s, {w1_0:v}.16b, {x1_0:v}.16b",
4350 "sdot {a0_1:v}.4s, {w0_1:v}.16b, {x0_1:v}.16b",
4351 "sdot {a1_1:v}.4s, {w1_1:v}.16b, {x1_1:v}.16b",
4352 a0_0 = inout(vreg) a0_0, a1_0 = inout(vreg) a1_0,
4353 a0_1 = inout(vreg) a0_1, a1_1 = inout(vreg) a1_1,
4354 w0_0 = in(vreg) w0_0, x0_0 = in(vreg) x0_0, w1_0 = in(vreg) w1_0, x1_0 = in(vreg) x1_0,
4355 w0_1 = in(vreg) w0_1, x0_1 = in(vreg) x0_1, w1_1 = in(vreg) w1_1, x1_1 = in(vreg) x1_1,
4356 options(pure, nomem, nostack),
4357 );
4358 let d0 = vaddvq_s32(vaddq_s32(a0_0, a1_0));
4359 let d1 = vaddvq_s32(vaddq_s32(a0_1, a1_1));
4360 acc += d0 as f32 * s0 + d1 as f32 * s1;
4361 gi += 2;
4362 }
4363
4364 if gi < gpr {
4365 let off = row_off + gi * TILE;
4366 let s = f16_to_f32(u16::from_le_bytes([
4367 *bytes_ptr.add(off),
4368 *bytes_ptr.add(off + 1),
4369 ]));
4370 let (u0, u1, u2, u3) = q1t_unpack_reg_u64s(bytes_ptr.add(off + 2));
4371 let w0 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u0), vcreate_u64(u1)));
4372 let w1 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u2), vcreate_u64(u3)));
4373 let x0 = vld1q_s8(xq_ptr.add(gi * GROUP_SIZE));
4374 let x1 = vld1q_s8(xq_ptr.add(gi * GROUP_SIZE + 16));
4375 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
4376 asm!(
4377 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
4378 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
4379 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
4380 w0 = in(vreg) w0, x0 = in(vreg) x0, w1 = in(vreg) w1, x1 = in(vreg) x1,
4381 options(pure, nomem, nostack),
4382 );
4383 let d = vaddvq_s32(vaddq_s32(a0, a1));
4384 acc += d as f32 * s;
4385 }
4386 acc
4387 }
4388}
4389
4390#[cfg(target_arch = "x86_64")]
4392#[target_feature(enable = "avx2")]
4393unsafe fn q1t_dot_row_avx2(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
4394 use core::arch::x86_64::*;
4395 unsafe {
4396 const TILE: usize = cortiq_core::quant::Q1T_TILE;
4397 let mut acc = 0f32;
4398 let bytes_ptr = bytes.as_ptr();
4399 let xq_ptr = xq.as_ptr();
4400 let row_off = r * gpr * TILE;
4401
4402 let ones = _mm256_set1_epi16(1);
4403 for gi in 0..gpr {
4404 let off = row_off + gi * TILE;
4405 let s = f16_to_f32(u16::from_le_bytes([
4406 *bytes_ptr.add(off),
4407 *bytes_ptr.add(off + 1),
4408 ]));
4409 let (u0, u1, u2, u3) = q1t_unpack_reg_u64s(bytes_ptr.add(off + 2));
4410 let wv = _mm256_set_epi64x(u3 as i64, u2 as i64, u1 as i64, u0 as i64);
4411 let xv = _mm256_loadu_si256(xq_ptr.add(gi * GROUP_SIZE) as *const __m256i);
4412 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(wv), _mm256_sign_epi8(xv, wv));
4413 let d256 = _mm256_madd_epi16(p16, ones);
4414 let d128 = _mm_add_epi32(
4415 _mm256_castsi256_si128(d256),
4416 _mm256_extracti128_si256(d256, 1),
4417 );
4418 let d64 = _mm_add_epi32(d128, _mm_shuffle_epi32(d128, 0xee));
4419 let d32 = _mm_cvtsi128_si32(_mm_add_epi32(d64, _mm_shuffle_epi32(d64, 0x55)));
4420 acc += d32 as f32 * s;
4421 }
4422 acc
4423 }
4424}
4425
4426#[cfg(target_arch = "x86_64")]
4428#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
4429unsafe fn q1t_dot_row_vnni(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
4430 use core::arch::x86_64::*;
4431 unsafe {
4433 const TILE: usize = cortiq_core::quant::Q1T_TILE;
4434 let mut acc = 0f32;
4435 let bytes_ptr = bytes.as_ptr();
4436 let xq_ptr = xq.as_ptr();
4437 let row_off = r * gpr * TILE;
4438 for gi in 0..gpr {
4439 let off = row_off + gi * TILE;
4440 let s = f16_to_f32(u16::from_le_bytes([
4441 *bytes_ptr.add(off),
4442 *bytes_ptr.add(off + 1),
4443 ]));
4444 let (u0, u1, u2, u3) = q1t_unpack_reg_u64s(bytes_ptr.add(off + 2));
4445 let wv = _mm256_set_epi64x(u3 as i64, u2 as i64, u1 as i64, u0 as i64);
4446 let xv = _mm256_loadu_si256(xq_ptr.add(gi * GROUP_SIZE) as *const __m256i);
4447 let d = dpbusd_hsum(_mm256_abs_epi8(wv), _mm256_sign_epi8(xv, wv));
4448 acc += d as f32 * s;
4449 }
4450 acc
4451 }
4452}
4453
4454#[inline]
4458fn q1t_dot_row_i8(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
4459 #[cfg(target_arch = "aarch64")]
4460 unsafe {
4461 return q1t_dot_row_sdot(bytes, r, gpr, xq);
4462 }
4463 #[cfg(target_arch = "x86_64")]
4464 unsafe {
4465 if vnni_tiles_enabled() {
4466 return q1t_dot_row_vnni(bytes, r, gpr, xq);
4467 }
4468 return q1t_dot_row_avx2(bytes, r, gpr, xq);
4469 }
4470 #[allow(unreachable_code)]
4471 {
4472 const TILE: usize = cortiq_core::quant::Q1T_TILE;
4473 let mut acc = 0f32;
4474 let mut sg = [0i8; GROUP_SIZE + 8]; for gi in 0..gpr {
4476 let off = (r * gpr + gi) * TILE;
4477 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
4478 q1t_unpack_group_i8(bytes.as_ptr().wrapping_add(off + 2), &mut sg);
4479 let mut d = 0i32;
4480 for k in 0..GROUP_SIZE {
4481 d += sg[k] as i32 * xq[gi * GROUP_SIZE + k] as i32;
4482 }
4483 acc += d as f32 * s;
4484 }
4485 acc
4486 }
4487}
4488
4489fn q1t_row_outlier_correction(
4496 bytes: &[u8],
4497 r: usize,
4498 rp_off: usize,
4499 entries_off: usize,
4500 has_ov: bool,
4501 x: &[f32],
4502) -> f32 {
4503 if !has_ov {
4504 return 0.0;
4505 }
4506 let (c0, c1) = (
4507 q1t_rowptr(bytes, rp_off, r),
4508 q1t_rowptr(bytes, rp_off, r + 1),
4509 );
4510 let mut corr = 0f32;
4511 for p in c0..c1 {
4512 let e = entries_off + p * 4;
4513 let col = u16::from_le_bytes([bytes[e], bytes[e + 1]]) as usize;
4514 let val = f16_to_f32(u16::from_le_bytes([bytes[e + 2], bytes[e + 3]]));
4515 corr += val * x[col];
4516 }
4517 corr
4518}
4519
4520fn q1t_dequant_row(
4524 bytes: &[u8],
4525 r: usize,
4526 gpr: usize,
4527 rp_off: usize,
4528 entries_off: usize,
4529 has_ov: bool,
4530 buf: &mut [f32],
4531) {
4532 const TILE: usize = cortiq_core::quant::Q1T_TILE;
4533 for g in 0..gpr {
4534 let off = (r * gpr + g) * TILE;
4535 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
4536 let codes = &bytes[off + 2..off + TILE];
4537 let bc = g * GROUP_SIZE;
4538 for bi in 0..6 {
4540 let lut = &SIGN5[codes[bi] as usize];
4541 let d = &mut buf[bc + bi * 5..bc + bi * 5 + 5];
4542 for i in 0..5 {
4543 d[i] = lut[i] * s;
4544 }
4545 }
4546 let lut = &SIGN5[codes[6] as usize];
4547 buf[bc + 30] = lut[0] * s;
4548 buf[bc + 31] = lut[1] * s;
4549 }
4550 if !has_ov {
4551 return;
4552 }
4553 let (c0, c1) = (
4554 q1t_rowptr(bytes, rp_off, r),
4555 q1t_rowptr(bytes, rp_off, r + 1),
4556 );
4557 for p in c0..c1 {
4558 let e = entries_off + p * 4;
4559 let col = u16::from_le_bytes([bytes[e], bytes[e + 1]]) as usize;
4560 buf[col] = f16_to_f32(u16::from_le_bytes([bytes[e + 2], bytes[e + 3]]));
4561 }
4562}
4563
4564fn q1t_add_overlay(
4568 bytes: &[u8],
4569 x: &[f32],
4570 rows: usize,
4571 cols: usize,
4572 out: &mut [f32],
4573 pool: Option<&Pool>,
4574) {
4575 const TILE: usize = cortiq_core::quant::Q1T_TILE;
4576 let gpr = cols / GROUP_SIZE;
4577 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
4578 if !has_ov {
4579 return;
4580 }
4581 let out_addr = SendMut(out.as_mut_ptr());
4582 let run = move |start: usize, end: usize| {
4583 for r in start..end {
4584 let corr = q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x);
4585 unsafe { *out_addr.at(r) += corr };
4587 }
4588 };
4589 dispatch_rows(pool, rows, &run);
4590}
4591
4592#[allow(clippy::too_many_arguments)]
4595fn q1t_range_a8w8(
4596 bytes: &[u8],
4597 gpr: usize,
4598 rp_off: usize,
4599 ent_off: usize,
4600 has_ov: bool,
4601 act: &SplitAct,
4602 x: &[f32],
4603 out: SendMut,
4604 start: usize,
4605 end: usize,
4606) {
4607 for r in start..end {
4608 let mut acc = q1t_dot_row_i8(bytes, r, gpr, &act.xq) * act.sx;
4609 for &(j, xv) in &act.outliers {
4610 acc += q1t_base_weight(bytes, r, gpr, j) * xv;
4611 }
4612 acc += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x);
4613 unsafe { *out.at(r) = acc };
4615 }
4616}
4617
4618#[allow(clippy::too_many_arguments)]
4621fn q1t_range_f32_batch(
4622 bytes: &[u8],
4623 gpr: usize,
4624 rp_off: usize,
4625 ent_off: usize,
4626 has_ov: bool,
4627 x: &[f32],
4628 out: SendMut,
4629 start: usize,
4630 end: usize,
4631) {
4632 const TILE: usize = cortiq_core::quant::Q1T_TILE;
4633 let mut sg = [0f32; GROUP_SIZE];
4634 for r in start..end {
4635 let mut acc = 0f32;
4636 for g in 0..gpr {
4637 let off = (r * gpr + g) * TILE;
4638 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
4639 let codes = &bytes[off + 2..off + TILE];
4640 let xg = &x[g * GROUP_SIZE..g * GROUP_SIZE + GROUP_SIZE];
4641 for bi in 0..6 {
4642 sg[bi * 5..bi * 5 + 5].copy_from_slice(&SIGN5[codes[bi] as usize]);
4643 }
4644 let lut = &SIGN5[codes[6] as usize];
4645 sg[30] = lut[0];
4646 sg[31] = lut[1];
4647 let mut gsum = 0f32;
4648 for k in 0..GROUP_SIZE {
4649 gsum += sg[k] * xg[k];
4650 }
4651 acc += s * gsum;
4652 }
4653 acc += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x);
4654 unsafe { *out.at(r) = acc };
4656 }
4657}
4658
4659fn q1t_matvec(
4663 bytes: &[u8],
4664 x: &[f32],
4665 rows: usize,
4666 cols: usize,
4667 out: &mut [f32],
4668 pool: Option<&Pool>,
4669) {
4670 debug_assert_eq!(out.len(), rows);
4671 const TILE: usize = cortiq_core::quant::Q1T_TILE;
4672 let gpr = cols / GROUP_SIZE;
4673 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
4674 let out_addr = SendMut(out.as_mut_ptr());
4675 if a8w8_enabled() {
4679 let act = split_act(x);
4680 let act = &act;
4681 let run = move |start: usize, end: usize| {
4682 for r in start..end {
4683 let mut acc = q1t_dot_row_i8(bytes, r, gpr, &act.xq) * act.sx;
4684 for &(j, xv) in &act.outliers {
4685 acc += q1t_base_weight(bytes, r, gpr, j) * xv;
4686 }
4687 acc += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x);
4688 unsafe { *out_addr.at(r) = acc };
4690 }
4691 };
4692 dispatch_rows(pool, rows, &run);
4693 return;
4694 }
4695 let run = move |start: usize, end: usize| {
4696 let mut sg = [0f32; GROUP_SIZE];
4700 for r in start..end {
4701 let mut acc = 0f32;
4702 for g in 0..gpr {
4703 let off = (r * gpr + g) * TILE;
4704 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
4705 let codes = &bytes[off + 2..off + TILE];
4706 let xg = &x[g * GROUP_SIZE..g * GROUP_SIZE + GROUP_SIZE];
4707 for bi in 0..6 {
4708 sg[bi * 5..bi * 5 + 5].copy_from_slice(&SIGN5[codes[bi] as usize]);
4709 }
4710 let lut = &SIGN5[codes[6] as usize];
4711 sg[30] = lut[0];
4712 sg[31] = lut[1];
4713 let mut gsum = 0f32;
4714 for k in 0..GROUP_SIZE {
4715 gsum += sg[k] * xg[k];
4716 }
4717 acc += s * gsum;
4718 }
4719 acc += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x);
4720 unsafe { *out_addr.at(r) = acc };
4721 }
4722 };
4723 dispatch_rows(pool, rows, &run);
4724}
4725
4726#[cfg(target_arch = "aarch64")]
4732#[target_feature(enable = "neon,dotprod")]
4733unsafe fn q1t_dot_row_sdot2(bytes: &[u8], r: usize, gpr: usize, xa: &[i8], xb: &[i8]) -> [f32; 2] {
4734 use core::arch::aarch64::*;
4735 use core::arch::asm;
4736 unsafe {
4738 const TILE: usize = cortiq_core::quant::Q1T_TILE;
4739 let bytes_ptr = bytes.as_ptr();
4740 let row_off = r * gpr * TILE;
4741 let xp = [xa.as_ptr(), xb.as_ptr()];
4742 let mut acc = [0f32; 2];
4743 macro_rules! sdot2 {
4744 ($w0:expr, $w1:expr, $x:expr) => {{
4745 let x0 = vld1q_s8($x);
4746 let x1 = vld1q_s8($x.add(16));
4747 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
4748 asm!(
4749 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
4750 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
4751 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
4752 w0 = in(vreg) $w0, x0 = in(vreg) x0, w1 = in(vreg) $w1, x1 = in(vreg) x1,
4753 options(pure, nomem, nostack),
4754 );
4755 vaddvq_s32(vaddq_s32(a0, a1))
4756 }};
4757 }
4758 let gpr2 = gpr & !1;
4759 let mut gi = 0;
4760 while gi < gpr2 {
4761 let off0 = row_off + gi * TILE;
4762 let off1 = off0 + TILE;
4763 let s0 = f16_to_f32(u16::from_le_bytes([
4764 *bytes_ptr.add(off0),
4765 *bytes_ptr.add(off0 + 1),
4766 ]));
4767 let s1 = f16_to_f32(u16::from_le_bytes([
4768 *bytes_ptr.add(off1),
4769 *bytes_ptr.add(off1 + 1),
4770 ]));
4771 let (u0_0, u1_0, u2_0, u3_0) = q1t_unpack_reg_u64s(bytes_ptr.add(off0 + 2));
4772 let (u0_1, u1_1, u2_1, u3_1) = q1t_unpack_reg_u64s(bytes_ptr.add(off1 + 2));
4773 let w0_0 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u0_0), vcreate_u64(u1_0)));
4774 let w1_0 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u2_0), vcreate_u64(u3_0)));
4775 let w0_1 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u0_1), vcreate_u64(u1_1)));
4776 let w1_1 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u2_1), vcreate_u64(u3_1)));
4777 for k in 0..2 {
4778 let d0 = sdot2!(w0_0, w1_0, xp[k].add(gi * GROUP_SIZE));
4779 let d1 = sdot2!(w0_1, w1_1, xp[k].add((gi + 1) * GROUP_SIZE));
4780 acc[k] += d0 as f32 * s0 + d1 as f32 * s1;
4781 }
4782 gi += 2;
4783 }
4784 if gi < gpr {
4785 let off = row_off + gi * TILE;
4786 let s = f16_to_f32(u16::from_le_bytes([
4787 *bytes_ptr.add(off),
4788 *bytes_ptr.add(off + 1),
4789 ]));
4790 let (u0, u1, u2, u3) = q1t_unpack_reg_u64s(bytes_ptr.add(off + 2));
4791 let w0 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u0), vcreate_u64(u1)));
4792 let w1 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u2), vcreate_u64(u3)));
4793 for k in 0..2 {
4794 let d = sdot2!(w0, w1, xp[k].add(gi * GROUP_SIZE));
4795 acc[k] += d as f32 * s;
4796 }
4797 }
4798 acc
4799 }
4800}
4801
4802fn q1t_matvec2(
4808 bytes: &[u8],
4809 x1: &[f32],
4810 x2: &[f32],
4811 rows: usize,
4812 cols: usize,
4813 o1: &mut [f32],
4814 o2: &mut [f32],
4815 pool: Option<&Pool>,
4816) {
4817 debug_assert_eq!(o1.len(), rows);
4818 debug_assert_eq!(o2.len(), rows);
4819 const TILE: usize = cortiq_core::quant::Q1T_TILE;
4820 let gpr = cols / GROUP_SIZE;
4821 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
4822 let out1 = SendMut(o1.as_mut_ptr());
4823 let out2 = SendMut(o2.as_mut_ptr());
4824 if a8w8_enabled() {
4825 let a1 = split_act(x1);
4826 let a2 = split_act(x2);
4827 let (a1, a2) = (&a1, &a2);
4828 let run = move |start: usize, end: usize| {
4829 for r in start..end {
4830 #[cfg(target_arch = "aarch64")]
4831 let ds = unsafe { q1t_dot_row_sdot2(bytes, r, gpr, &a1.xq, &a2.xq) };
4834 #[cfg(not(target_arch = "aarch64"))]
4835 let ds = [
4836 q1t_dot_row_i8(bytes, r, gpr, &a1.xq),
4837 q1t_dot_row_i8(bytes, r, gpr, &a2.xq),
4838 ];
4839 let mut acc1 = ds[0] * a1.sx;
4840 for &(j, xv) in &a1.outliers {
4841 acc1 += q1t_base_weight(bytes, r, gpr, j) * xv;
4842 }
4843 acc1 += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x1);
4844 let mut acc2 = ds[1] * a2.sx;
4845 for &(j, xv) in &a2.outliers {
4846 acc2 += q1t_base_weight(bytes, r, gpr, j) * xv;
4847 }
4848 acc2 += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x2);
4849 unsafe {
4851 *out1.at(r) = acc1;
4852 *out2.at(r) = acc2;
4853 }
4854 }
4855 };
4856 dispatch_rows(pool, rows, &run);
4857 return;
4858 }
4859 let run = move |start: usize, end: usize| {
4860 let mut sg = [0f32; GROUP_SIZE];
4863 for r in start..end {
4864 let mut acc1 = 0f32;
4865 let mut acc2 = 0f32;
4866 for g in 0..gpr {
4867 let off = (r * gpr + g) * TILE;
4868 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
4869 let codes = &bytes[off + 2..off + TILE];
4870 for bi in 0..6 {
4871 sg[bi * 5..bi * 5 + 5].copy_from_slice(&SIGN5[codes[bi] as usize]);
4872 }
4873 let lut = &SIGN5[codes[6] as usize];
4874 sg[30] = lut[0];
4875 sg[31] = lut[1];
4876 let xg1 = &x1[g * GROUP_SIZE..g * GROUP_SIZE + GROUP_SIZE];
4877 let xg2 = &x2[g * GROUP_SIZE..g * GROUP_SIZE + GROUP_SIZE];
4878 let mut gsum1 = 0f32;
4879 for k in 0..GROUP_SIZE {
4880 gsum1 += sg[k] * xg1[k];
4881 }
4882 acc1 += s * gsum1;
4883 let mut gsum2 = 0f32;
4884 for k in 0..GROUP_SIZE {
4885 gsum2 += sg[k] * xg2[k];
4886 }
4887 acc2 += s * gsum2;
4888 }
4889 acc1 += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x1);
4890 acc2 += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x2);
4891 unsafe {
4893 *out1.at(r) = acc1;
4894 *out2.at(r) = acc2;
4895 }
4896 }
4897 };
4898 dispatch_rows(pool, rows, &run);
4899}
4900
4901fn q1t_matmat(
4904 bytes: &[u8],
4905 xs: &[f32],
4906 b: usize,
4907 rows: usize,
4908 cols: usize,
4909 out: &mut [f32],
4910 pool: Option<&Pool>,
4911) {
4912 debug_assert_eq!(out.len(), b * rows);
4913 const TILE: usize = cortiq_core::quant::Q1T_TILE;
4914 let gpr = cols / GROUP_SIZE;
4915 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
4916 let out_addr = SendMut(out.as_mut_ptr());
4917 if a8w8_enabled() {
4921 let acts: Vec<SplitAct> = (0..b)
4922 .map(|bi| split_act(&xs[bi * cols..(bi + 1) * cols]))
4923 .collect();
4924 let acts = &acts;
4925 let run = move |start: usize, end: usize| {
4926 let mut sg = vec![0i8; cols + 8]; let mut sc = vec![0f32; gpr]; let mut accs = vec![0f32; b]; for r in start..end {
4930 for g in 0..gpr {
4931 let off = (r * gpr + g) * TILE;
4932 sc[g] = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
4933 q1t_unpack_group_i8(
4934 bytes.as_ptr().wrapping_add(off + 2),
4935 &mut sg[g * GROUP_SIZE..],
4936 );
4937 }
4938 for bi in 0..b {
4939 let act = &acts[bi];
4940 let mut isum = 0f32;
4941 for g in 0..gpr {
4942 let d = q1t_i8dot32(
4943 sg.as_ptr().wrapping_add(g * GROUP_SIZE),
4944 act.xq.as_ptr().wrapping_add(g * GROUP_SIZE),
4945 );
4946 isum += d as f32 * sc[g];
4947 }
4948 let mut acc = isum * act.sx;
4949 for &(j, xv) in &act.outliers {
4950 acc += q1t_base_weight(bytes, r, gpr, j) * xv;
4951 }
4952 accs[bi] = acc;
4953 }
4954 if has_ov {
4958 let (c0, c1) = (
4959 q1t_rowptr(bytes, rp_off, r),
4960 q1t_rowptr(bytes, rp_off, r + 1),
4961 );
4962 for p in c0..c1 {
4963 let e = ent_off + p * 4;
4964 let col = u16::from_le_bytes([bytes[e], bytes[e + 1]]) as usize;
4965 let val = f16_to_f32(u16::from_le_bytes([bytes[e + 2], bytes[e + 3]]));
4966 for bi in 0..b {
4967 accs[bi] += val * xs[bi * cols + col];
4968 }
4969 }
4970 }
4971 for bi in 0..b {
4972 unsafe { *out_addr.at(bi * rows + r) = accs[bi] };
4973 }
4974 }
4975 };
4976 dispatch_rows(pool, rows, &run);
4977 return;
4978 }
4979 let run = move |start: usize, end: usize| {
4980 let mut buf = vec![0f32; cols];
4981 for r in start..end {
4982 q1t_dequant_row(bytes, r, gpr, rp_off, ent_off, has_ov, &mut buf);
4983 for bi in 0..b {
4984 let xr = &xs[bi * cols..(bi + 1) * cols];
4985 let mut acc = 0f32;
4986 for j in 0..cols {
4987 acc += buf[j] * xr[j];
4988 }
4989 unsafe { *out_addr.at(bi * rows + r) = acc };
4990 }
4991 }
4992 };
4993 dispatch_rows(pool, rows, &run);
4994}
4995
4996fn q1_matvec(
4997 bytes: &[u8],
4998 x: &[f32],
4999 rows: usize,
5000 cols: usize,
5001 out: &mut [f32],
5002 pool: Option<&Pool>,
5003) {
5004 debug_assert_eq!(out.len(), rows);
5005 let gpr = cols / GROUP_SIZE;
5006 let out_addr = SendMut(out.as_mut_ptr());
5007 if a8w8_enabled() {
5008 let act = split_act(x);
5009 let gsum = q1_group_sums(&act.xq, gpr);
5010 let (act, gsum) = (&act, &gsum);
5011 let run = move |start: usize, end: usize| {
5012 q1_range_a8w8(bytes, gpr, act, gsum, out_addr, start, end)
5013 };
5014 dispatch_rows(pool, rows, &run);
5015 return;
5016 }
5017 let run = move |start: usize, end: usize| q1_range_f32(bytes, gpr, x, out_addr, start, end);
5018 dispatch_rows(pool, rows, &run);
5019}
5020
5021#[allow(clippy::too_many_arguments)]
5023fn q1_matvec2(
5024 bytes: &[u8],
5025 x1: &[f32],
5026 x2: &[f32],
5027 rows: usize,
5028 cols: usize,
5029 o1: &mut [f32],
5030 o2: &mut [f32],
5031 pool: Option<&Pool>,
5032) {
5033 let gpr = cols / GROUP_SIZE;
5034 let p1 = SendMut(o1.as_mut_ptr());
5035 let p2 = SendMut(o2.as_mut_ptr());
5036 if a8w8_enabled() {
5037 let a1 = split_act(x1);
5038 let a2 = split_act(x2);
5039 let g1 = q1_group_sums(&a1.xq, gpr);
5040 let g2 = q1_group_sums(&a2.xq, gpr);
5041 let (a1, a2, g1, g2) = (&a1, &a2, &g1, &g2);
5042 let run = move |start: usize, end: usize| {
5043 for r in start..end {
5044 let mut v1 = dot_q1_row_i8(bytes, r, gpr, &a1.xq, g1) * a1.sx;
5045 let mut v2 = dot_q1_row_i8(bytes, r, gpr, &a2.xq, g2) * a2.sx;
5046 for &(j, xv) in &a1.outliers {
5047 let (w, s) = q1_outlier(bytes, r, gpr, j);
5048 v1 += w * s * xv;
5049 }
5050 for &(j, xv) in &a2.outliers {
5051 let (w, s) = q1_outlier(bytes, r, gpr, j);
5052 v2 += w * s * xv;
5053 }
5054 unsafe {
5056 *p1.at(r) = v1;
5057 *p2.at(r) = v2;
5058 }
5059 }
5060 };
5061 dispatch_rows(pool, rows, &run);
5062 return;
5063 }
5064 let run = move |start: usize, end: usize| {
5065 for r in start..end {
5066 unsafe {
5068 *p1.at(r) = q1_row_exact(bytes, r, gpr, x1);
5069 *p2.at(r) = q1_row_exact(bytes, r, gpr, x2);
5070 }
5071 }
5072 };
5073 dispatch_rows(pool, rows, &run);
5074}
5075
5076#[allow(clippy::too_many_arguments)]
5078fn q1_matmat(
5079 bytes: &[u8],
5080 xs_all: &[f32],
5081 b: usize,
5082 rows: usize,
5083 cols: usize,
5084 out: &mut [f32],
5085 pool: Option<&Pool>,
5086) {
5087 debug_assert_eq!(out.len(), b * rows);
5088 let gpr = cols / GROUP_SIZE;
5089 let out_addr = SendMut(out.as_mut_ptr());
5090 if a8w8_enabled() {
5091 let acts: Vec<(SplitAct, Vec<i32>)> = (0..b)
5092 .map(|bi| {
5093 let act = split_act(&xs_all[bi * cols..(bi + 1) * cols]);
5094 let gsum = q1_group_sums(&act.xq, gpr);
5095 (act, gsum)
5096 })
5097 .collect();
5098 let acts = &acts;
5099 #[cfg(target_arch = "x86_64")]
5100 let blocked_ok = avx2_enabled()
5101 && std::env::var("CMF_X86_BLOCKED")
5102 .map(|v| v != "0")
5103 .unwrap_or(true);
5104 #[cfg(target_arch = "aarch64")]
5105 let blocked_ok = sdot_enabled()
5106 && std::env::var("CMF_X86_BLOCKED")
5107 .map(|v| v != "0")
5108 .unwrap_or(true);
5109 let run = move |start: usize, end: usize| {
5110 for r in start..end {
5111 let mut bi = 0usize;
5112 #[cfg(target_arch = "aarch64")]
5115 if blocked_ok {
5116 while bi + 4 <= acts.len() {
5117 let xs = [
5118 acts[bi].0.xq.as_slice(),
5119 acts[bi + 1].0.xq.as_slice(),
5120 acts[bi + 2].0.xq.as_slice(),
5121 acts[bi + 3].0.xq.as_slice(),
5122 ];
5123 let gs = [
5124 acts[bi].1.as_slice(),
5125 acts[bi + 1].1.as_slice(),
5126 acts[bi + 2].1.as_slice(),
5127 acts[bi + 3].1.as_slice(),
5128 ];
5129 let d = unsafe { dot_q1_row_1x4_sdot(bytes, r, gpr, xs, gs) };
5130 for k in 0..4 {
5131 let (act, _) = &acts[bi + k];
5132 let mut acc = d[k] * act.sx;
5133 for &(j, xv) in &act.outliers {
5134 let (w, sc) = q1_outlier(bytes, r, gpr, j);
5135 acc += w * sc * xv;
5136 }
5137 unsafe { *out_addr.at((bi + k) * rows + r) = acc };
5139 }
5140 bi += 4;
5141 }
5142 }
5143 #[cfg(target_arch = "x86_64")]
5144 if blocked_ok {
5145 while bi + 4 <= acts.len() {
5146 let xs = [
5147 acts[bi].0.xq.as_slice(),
5148 acts[bi + 1].0.xq.as_slice(),
5149 acts[bi + 2].0.xq.as_slice(),
5150 acts[bi + 3].0.xq.as_slice(),
5151 ];
5152 let gs = [
5153 acts[bi].1.as_slice(),
5154 acts[bi + 1].1.as_slice(),
5155 acts[bi + 2].1.as_slice(),
5156 acts[bi + 3].1.as_slice(),
5157 ];
5158 let d = unsafe {
5159 if vnni_tiles_enabled() {
5160 dot_q1_row_1x4_vnni(bytes, r, gpr, xs, gs)
5161 } else {
5162 dot_q1_row_1x4_avx2(bytes, r, gpr, xs, gs)
5163 }
5164 };
5165 for k in 0..4 {
5166 let (act, _) = &acts[bi + k];
5167 let mut acc = d[k] * act.sx;
5168 for &(j, xv) in &act.outliers {
5169 let (w, sc) = q1_outlier(bytes, r, gpr, j);
5170 acc += w * sc * xv;
5171 }
5172 unsafe { *out_addr.at((bi + k) * rows + r) = acc };
5174 }
5175 bi += 4;
5176 }
5177 }
5178 while bi < acts.len() {
5179 let (act, gsum) = &acts[bi];
5180 let mut acc = dot_q1_row_i8(bytes, r, gpr, &act.xq, gsum) * act.sx;
5181 for &(j, xv) in &act.outliers {
5182 let (w, s) = q1_outlier(bytes, r, gpr, j);
5183 acc += w * s * xv;
5184 }
5185 unsafe { *out_addr.at(bi * rows + r) = acc };
5187 bi += 1;
5188 }
5189 }
5190 };
5191 dispatch_rows(pool, rows, &run);
5192 return;
5193 }
5194 let run = move |start: usize, end: usize| {
5195 for r in start..end {
5196 for bi in 0..b {
5197 let x = &xs_all[bi * cols..(bi + 1) * cols];
5198 unsafe { *out_addr.at(bi * rows + r) = q1_row_exact(bytes, r, gpr, x) };
5200 }
5201 }
5202 };
5203 dispatch_rows(pool, rows, &run);
5204}
5205
5206fn q4matvec(
5212 bytes: &[u8],
5213 x: &[f32],
5214 rows: usize,
5215 cols: usize,
5216 out: &mut [f32],
5217 pool: Option<&Pool>,
5218) {
5219 debug_assert_eq!(out.len(), rows);
5220 let (packed, scales) = q4_split(bytes, rows, cols);
5221 let gpr = cols / GROUP_SIZE;
5222 let out_addr = SendMut(out.as_mut_ptr());
5223
5224 if a8w8_enabled() {
5225 let act = split_act(x);
5226 let run = move |start: usize, end: usize| {
5227 q4_range_a8w8(packed, scales, gpr, cols, &act, out_addr, start, end)
5228 };
5229 dispatch_rows(pool, rows, &run);
5230 return;
5231 }
5232
5233 let run =
5234 move |start: usize, end: usize| q4_range_f32(packed, scales, gpr, x, out_addr, start, end);
5235 dispatch_rows(pool, rows, &run);
5236}
5237
5238#[inline]
5241#[allow(unreachable_code)]
5242#[cfg(target_arch = "x86_64")]
5247#[target_feature(enable = "avx2")]
5248unsafe fn dot_q4b_row_1x4_avx2(
5249 buf: &[u8],
5250 scales: &[u8],
5251 g0: usize,
5252 gpr: usize,
5253 xs: [&[i8]; 4],
5254) -> [f32; 4] {
5255 unsafe {
5257 use core::arch::x86_64::*;
5258 let ones = _mm256_set1_epi16(1);
5259 let mut acc = [0f32; 4];
5260 for gi in 0..gpr {
5261 let s = f16_to_f32(u16::from_le_bytes([
5262 scales[(g0 + gi) * 2],
5263 scales[(g0 + gi) * 2 + 1],
5264 ]));
5265 let w = _mm256_loadu_si256(buf.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
5266 let aw = _mm256_abs_epi8(w);
5267 for (k, xq) in xs.iter().enumerate() {
5268 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
5269 let p16 = _mm256_maddubs_epi16(aw, _mm256_sign_epi8(x, w));
5270 let d = _mm256_madd_epi16(p16, ones);
5271 let hi128 = _mm256_extracti128_si256::<1>(d);
5272 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
5273 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
5274 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
5275 acc[k] += _mm_cvtsi128_si32(s32) as f32 * s;
5276 }
5277 }
5278 acc
5279 }
5280}
5281
5282#[cfg(target_arch = "x86_64")]
5284#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
5285unsafe fn dot_q4b_row_1x4_vnni(
5286 buf: &[u8],
5287 scales: &[u8],
5288 g0: usize,
5289 gpr: usize,
5290 xs: [&[i8]; 4],
5291) -> [f32; 4] {
5292 unsafe {
5294 use core::arch::x86_64::*;
5295 let mut acc = [0f32; 4];
5296 for gi in 0..gpr {
5297 let s = f16_to_f32(u16::from_le_bytes([
5298 scales[(g0 + gi) * 2],
5299 scales[(g0 + gi) * 2 + 1],
5300 ]));
5301 let w = _mm256_loadu_si256(buf.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
5302 let aw = _mm256_abs_epi8(w);
5303 for (k, xq) in xs.iter().enumerate() {
5304 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
5305 let d = dpbusd_hsum(aw, _mm256_sign_epi8(x, w));
5306 acc[k] += d as f32 * s;
5307 }
5308 }
5309 acc
5310 }
5311}
5312
5313#[cfg(target_arch = "x86_64")]
5319#[target_feature(enable = "avx2")]
5320unsafe fn dot_q4b_row_1x4_sx_avx2(
5321 buf: &[u8],
5322 scales: &[u8],
5323 g0: usize,
5324 gpr: usize,
5325 xs: [&[i8]; 4],
5326 sxs: [f32; 4],
5327) -> [f32; 4] {
5328 unsafe {
5330 use core::arch::x86_64::*;
5331 let ones = _mm256_set1_epi16(1);
5332 let mut acc = [0f32; 4];
5333 for gi in 0..gpr {
5334 let s = f16_to_f32(u16::from_le_bytes([
5335 scales[(g0 + gi) * 2],
5336 scales[(g0 + gi) * 2 + 1],
5337 ]));
5338 let w = _mm256_loadu_si256(buf.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
5339 let aw = _mm256_abs_epi8(w);
5340 for (k, xq) in xs.iter().enumerate() {
5341 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
5342 let p16 = _mm256_maddubs_epi16(aw, _mm256_sign_epi8(x, w));
5343 let d = _mm256_madd_epi16(p16, ones);
5344 let hi128 = _mm256_extracti128_si256::<1>(d);
5345 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
5346 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
5347 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
5348 acc[k] += (_mm_cvtsi128_si32(s32) as f32 * sxs[k]) * s;
5349 }
5350 }
5351 acc
5352 }
5353}
5354
5355#[cfg(target_arch = "x86_64")]
5358#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
5359unsafe fn dot_q4b_row_1x4_sx_vnni(
5360 buf: &[u8],
5361 scales: &[u8],
5362 g0: usize,
5363 gpr: usize,
5364 xs: [&[i8]; 4],
5365 sxs: [f32; 4],
5366) -> [f32; 4] {
5367 unsafe {
5369 use core::arch::x86_64::*;
5370 let mut acc = [0f32; 4];
5371 for gi in 0..gpr {
5372 let s = f16_to_f32(u16::from_le_bytes([
5373 scales[(g0 + gi) * 2],
5374 scales[(g0 + gi) * 2 + 1],
5375 ]));
5376 let w = _mm256_loadu_si256(buf.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
5377 let aw = _mm256_abs_epi8(w);
5378 for (k, xq) in xs.iter().enumerate() {
5379 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
5380 let d = dpbusd_hsum(aw, _mm256_sign_epi8(x, w));
5381 acc[k] += (d as f32 * sxs[k]) * s;
5382 }
5383 }
5384 acc
5385 }
5386}
5387
5388#[allow(unreachable_code)]
5389fn dot_q4_row_i8(packed: &[u8], scales: &[u8], g0: usize, gpr: usize, xq: &[i8]) -> f32 {
5390 #[cfg(target_arch = "aarch64")]
5391 unsafe {
5392 return dot_q4_row_sdot(packed, scales, g0, gpr, xq);
5393 }
5394 #[cfg(target_arch = "x86_64")]
5395 unsafe {
5396 return dot_q4_row_avx2(packed, scales, g0, gpr, xq);
5397 }
5398 let mut acc = 0f32;
5399 for gi in 0..gpr {
5400 let g = g0 + gi;
5401 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
5402 let mut d = 0i32;
5403 for (k, &b) in packed[g * 16..(g + 1) * 16].iter().enumerate() {
5404 d += ((b & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2] as i32
5405 + (((b >> 4) & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2 + 1] as i32;
5406 }
5407 acc += d as f32 * s;
5408 }
5409 acc
5410}
5411
5412#[inline]
5414#[allow(unreachable_code)]
5415fn dot_q4_row_i8_2(
5416 packed: &[u8],
5417 scales: &[u8],
5418 g0: usize,
5419 gpr: usize,
5420 xq1: &[i8],
5421 xq2: &[i8],
5422) -> (f32, f32) {
5423 #[cfg(target_arch = "aarch64")]
5424 unsafe {
5425 return dot_q4_row_sdot2(packed, scales, g0, gpr, xq1, xq2);
5426 }
5427 #[cfg(target_arch = "x86_64")]
5428 unsafe {
5429 return dot_q4_row_avx2_2(packed, scales, g0, gpr, xq1, xq2);
5430 }
5431 (
5432 dot_q4_row_i8(packed, scales, g0, gpr, xq1),
5433 dot_q4_row_i8(packed, scales, g0, gpr, xq2),
5434 )
5435}
5436
5437#[allow(clippy::too_many_arguments)]
5440fn q4_range_a8w8(
5441 packed: &[u8],
5442 scales: &[u8],
5443 gpr: usize,
5444 cols: usize,
5445 act: &SplitAct,
5446 out: SendMut,
5447 start: usize,
5448 end: usize,
5449) {
5450 for r in start..end {
5451 let mut acc = dot_q4_row_i8(packed, scales, r * gpr, gpr, &act.xq) * act.sx;
5452 for &(j, xv) in &act.outliers {
5454 let flat = r * cols + j;
5455 let byte = packed[flat / 2];
5456 let nib = if flat & 1 == 0 {
5457 byte & 0x0F
5458 } else {
5459 byte >> 4
5460 };
5461 let s = f16_to_f32(u16::from_le_bytes([
5462 scales[(flat / GROUP_SIZE) * 2],
5463 scales[(flat / GROUP_SIZE) * 2 + 1],
5464 ]));
5465 acc += ((nib as i32 - 8) as f32) * s * xv;
5466 }
5467 unsafe { *out.at(r) = acc };
5469 }
5470}
5471
5472#[allow(clippy::too_many_arguments)]
5475fn q4_range2_a8w8(
5476 packed: &[u8],
5477 scales: &[u8],
5478 gpr: usize,
5479 cols: usize,
5480 a1: &SplitAct,
5481 a2: &SplitAct,
5482 p1: SendMut,
5483 p2: SendMut,
5484 start: usize,
5485 end: usize,
5486) {
5487 for r in start..end {
5488 let (s1, s2) = dot_q4_row_i8_2(packed, scales, r * gpr, gpr, &a1.xq, &a2.xq);
5489 let mut acc1 = s1 * a1.sx;
5490 let mut acc2 = s2 * a2.sx;
5491 let fix = |outliers: &[(usize, f32)], acc: &mut f32| {
5493 for &(j, xv) in outliers {
5494 let flat = r * cols + j;
5495 let byte = packed[flat / 2];
5496 let nib = if flat & 1 == 0 {
5497 byte & 0x0F
5498 } else {
5499 byte >> 4
5500 };
5501 let s = f16_to_f32(u16::from_le_bytes([
5502 scales[(flat / GROUP_SIZE) * 2],
5503 scales[(flat / GROUP_SIZE) * 2 + 1],
5504 ]));
5505 *acc += ((nib as i32 - 8) as f32) * s * xv;
5506 }
5507 };
5508 fix(&a1.outliers, &mut acc1);
5509 fix(&a2.outliers, &mut acc2);
5510 unsafe {
5512 *p1.at(r) = acc1;
5513 *p2.at(r) = acc2;
5514 }
5515 }
5516}
5517
5518fn q4_range_f32(
5520 packed: &[u8],
5521 scales: &[u8],
5522 gpr: usize,
5523 x: &[f32],
5524 out: SendMut,
5525 start: usize,
5526 end: usize,
5527) {
5528 for r in start..end {
5529 let mut acc = 0f32;
5530 for gi in 0..gpr {
5531 let g = r * gpr + gi;
5532 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
5533 let pk = &packed[g * 16..(g + 1) * 16];
5534 let xg = &x[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
5535 let mut ga = 0f32;
5536 for (k, &b) in pk.iter().enumerate() {
5537 ga += ((b & 0x0F) as f32 - 8.0) * xg[k * 2]
5538 + (((b >> 4) & 0x0F) as f32 - 8.0) * xg[k * 2 + 1];
5539 }
5540 acc += ga * s;
5541 }
5542 unsafe { *out.at(r) = acc };
5544 }
5545}
5546
5547#[allow(clippy::too_many_arguments)]
5551fn q4matvec2(
5552 bytes: &[u8],
5553 x1: &[f32],
5554 x2: &[f32],
5555 rows: usize,
5556 cols: usize,
5557 o1: &mut [f32],
5558 o2: &mut [f32],
5559 pool: Option<&Pool>,
5560) {
5561 debug_assert_eq!(o1.len(), rows);
5562 debug_assert_eq!(o2.len(), rows);
5563 let (packed, scales) = q4_split(bytes, rows, cols);
5564 let gpr = cols / GROUP_SIZE;
5565
5566 if a8w8_enabled() {
5567 let a1 = split_act(x1);
5568 let a2 = split_act(x2);
5569 let p1 = SendMut(o1.as_mut_ptr());
5570 let p2 = SendMut(o2.as_mut_ptr());
5571 let run = move |start: usize, end: usize| {
5572 q4_range2_a8w8(packed, scales, gpr, cols, &a1, &a2, p1, p2, start, end)
5573 };
5574 dispatch_rows(pool, rows, &run);
5575 return;
5576 }
5577
5578 let p1 = SendMut(o1.as_mut_ptr());
5579 let p2 = SendMut(o2.as_mut_ptr());
5580 let run = move |start: usize, end: usize| {
5581 q4_range2_f32(packed, scales, gpr, x1, x2, p1, p2, start, end)
5582 };
5583 dispatch_rows(pool, rows, &run);
5584}
5585
5586#[allow(clippy::too_many_arguments)]
5588fn q4_range2_f32(
5589 packed: &[u8],
5590 scales: &[u8],
5591 gpr: usize,
5592 x1: &[f32],
5593 x2: &[f32],
5594 p1: SendMut,
5595 p2: SendMut,
5596 start: usize,
5597 end: usize,
5598) {
5599 for r in start..end {
5600 let (mut acc1, mut acc2) = (0f32, 0f32);
5601 for gi in 0..gpr {
5602 let g = r * gpr + gi;
5603 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
5604 let pk = &packed[g * 16..(g + 1) * 16];
5605 let x1g = &x1[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
5606 let x2g = &x2[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
5607 let (mut g1, mut g2) = (0f32, 0f32);
5608 for (k, &b) in pk.iter().enumerate() {
5609 let wl = (b & 0x0F) as f32 - 8.0;
5610 let wh = ((b >> 4) & 0x0F) as f32 - 8.0;
5611 g1 += wl * x1g[k * 2] + wh * x1g[k * 2 + 1];
5612 g2 += wl * x2g[k * 2] + wh * x2g[k * 2 + 1];
5613 }
5614 acc1 += g1 * s;
5615 acc2 += g2 * s;
5616 }
5617 unsafe {
5619 *p1.at(r) = acc1;
5620 *p2.at(r) = acc2;
5621 }
5622 }
5623}
5624
5625thread_local! {
5626 static ROW_I8: std::cell::RefCell<Vec<u8>> = const { std::cell::RefCell::new(Vec::new()) };
5629 static ROW_F32: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
5630}
5631
5632#[allow(clippy::too_many_arguments)]
5638fn q4matmat(
5639 bytes: &[u8],
5640 xs_all: &[f32],
5641 b: usize,
5642 rows: usize,
5643 cols: usize,
5644 out: &mut [f32],
5645 pool: Option<&Pool>,
5646) {
5647 debug_assert_eq!(xs_all.len(), b * cols);
5648 debug_assert_eq!(out.len(), b * rows);
5649 let (packed, scales) = q4_split(bytes, rows, cols);
5650 let gpr = cols / GROUP_SIZE;
5651 let gscale = |g: usize| f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
5652
5653 if a8w8_enabled() {
5654 let acts: Vec<SplitAct> = (0..b)
5655 .map(|bi| split_act(&xs_all[bi * cols..(bi + 1) * cols]))
5656 .collect();
5657 let acts = &acts;
5658 let out_addr = SendMut(out.as_mut_ptr());
5659 let run = move |start: usize, end: usize| {
5660 ROW_I8.with(|rb| {
5661 let mut buf = rb.borrow_mut();
5662 buf.resize(cols, 0);
5663 for r in start..end {
5664 for gi in 0..gpr {
5668 let g = r * gpr + gi;
5669 for (k, &bt) in packed[g * 16..(g + 1) * 16].iter().enumerate() {
5670 buf[gi * GROUP_SIZE + k * 2] = ((bt & 0x0F) as i32 - 8) as i8 as u8;
5671 buf[gi * GROUP_SIZE + k * 2 + 1] =
5672 (((bt >> 4) & 0x0F) as i32 - 8) as i8 as u8;
5673 }
5674 }
5675 let mut bi = 0usize;
5676 #[cfg(target_arch = "x86_64")]
5677 if avx2_enabled()
5678 && std::env::var("CMF_X86_BLOCKED")
5679 .map(|v| v != "0")
5680 .unwrap_or(true)
5681 {
5682 while bi + 4 <= acts.len() {
5683 let xs = [
5684 acts[bi].xq.as_slice(),
5685 acts[bi + 1].xq.as_slice(),
5686 acts[bi + 2].xq.as_slice(),
5687 acts[bi + 3].xq.as_slice(),
5688 ];
5689 let d = unsafe {
5690 if vnni_tiles_enabled() {
5691 dot_q4b_row_1x4_vnni(&buf, scales, r * gpr, gpr, xs)
5692 } else {
5693 dot_q4b_row_1x4_avx2(&buf, scales, r * gpr, gpr, xs)
5694 }
5695 };
5696 for k in 0..4 {
5697 let act = &acts[bi + k];
5698 let mut acc = d[k] * act.sx;
5699 for &(j, xv) in &act.outliers {
5700 acc += (buf[j] as i8) as f32
5701 * gscale((r * cols + j) / GROUP_SIZE)
5702 * xv;
5703 }
5704 unsafe { *out_addr.at((bi + k) * rows + r) = acc };
5706 }
5707 bi += 4;
5708 }
5709 }
5710 while bi < acts.len() {
5711 let act = &acts[bi];
5712 let mut acc = 0f32;
5713 for gi in 0..gpr {
5714 let d = dot_i8_i8(
5715 &buf[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE],
5716 &act.xq[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE],
5717 );
5718 acc += d as f32 * gscale(r * gpr + gi);
5719 }
5720 acc *= act.sx;
5721 for &(j, xv) in &act.outliers {
5723 acc += (buf[j] as i8) as f32 * gscale((r * cols + j) / GROUP_SIZE) * xv;
5724 }
5725 unsafe { *out_addr.at(bi * rows + r) = acc };
5727 bi += 1;
5728 }
5729 }
5730 })
5731 };
5732 dispatch_rows(pool, rows, &run);
5733 return;
5734 }
5735
5736 let out_addr = SendMut(out.as_mut_ptr());
5737 let run = move |start: usize, end: usize| {
5738 ROW_F32.with(|rb| {
5739 let mut buf = rb.borrow_mut();
5740 buf.resize(cols, 0.0);
5741 for r in start..end {
5742 for gi in 0..gpr {
5745 let g = r * gpr + gi;
5746 for (k, &bt) in packed[g * 16..(g + 1) * 16].iter().enumerate() {
5747 buf[gi * GROUP_SIZE + k * 2] = (bt & 0x0F) as f32 - 8.0;
5748 buf[gi * GROUP_SIZE + k * 2 + 1] = ((bt >> 4) & 0x0F) as f32 - 8.0;
5749 }
5750 }
5751 for bi in 0..b {
5752 let x = &xs_all[bi * cols..(bi + 1) * cols];
5753 let mut acc = 0f32;
5754 for gi in 0..gpr {
5755 let mut ga = 0f32;
5756 for k in 0..GROUP_SIZE / 2 {
5761 let e = gi * GROUP_SIZE + k * 2;
5762 ga += buf[e] * x[e] + buf[e + 1] * x[e + 1];
5763 }
5764 acc += ga * gscale(r * gpr + gi);
5765 }
5766 unsafe { *out_addr.at(bi * rows + r) = acc };
5768 }
5769 }
5770 })
5771 };
5772 dispatch_rows(pool, rows, &run);
5773}
5774
5775#[allow(clippy::too_many_arguments)]
5780fn vbitmatmat(
5781 bytes: &[u8],
5782 offsets: &[usize],
5783 xs_all: &[f32],
5784 b: usize,
5785 rows: usize,
5786 cols: usize,
5787 out: &mut [f32],
5788 pool: Option<&Pool>,
5789) {
5790 debug_assert_eq!(xs_all.len(), b * cols);
5791 debug_assert_eq!(out.len(), b * rows);
5792 debug_assert_eq!(offsets.len(), rows + 1);
5793 let ng = cols / GROUP_SIZE;
5794 let bits = &bytes[..rows];
5795 let sc_off = rows;
5796 let gscale = |r: usize, g: usize| {
5797 let so = (r * ng + g) * 2;
5798 f16_to_f32(u16::from_le_bytes([
5799 bytes[sc_off + so],
5800 bytes[sc_off + so + 1],
5801 ]))
5802 };
5803
5804 let decode_f32 = |r: usize, dst: &mut [f32]| {
5806 let bw = bits[r] as usize;
5807 let l = ((1i32 << (bw - 1)) - 1) as f32;
5808 let data = &bytes[offsets[r]..offsets[r + 1]];
5809 let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
5810 for d in dst.iter_mut() {
5811 while nbits < bw {
5812 acc = (acc << 8) | data[idx] as u64;
5813 idx += 1;
5814 nbits += 8;
5815 }
5816 let u = ((acc >> (nbits - bw)) & ((1u64 << bw) - 1)) as f32;
5817 nbits -= bw;
5818 *d = u - l;
5819 }
5820 };
5821
5822 if a8w8_enabled() {
5823 let acts: Vec<SplitAct> = (0..b)
5824 .map(|bi| split_act(&xs_all[bi * cols..(bi + 1) * cols]))
5825 .collect();
5826 let acts = &acts;
5827 let out_addr = SendMut(out.as_mut_ptr());
5828 let run = move |start: usize, end: usize| {
5829 for r in start..end {
5830 let bw = bits[r] as usize;
5831 if bw == 8 {
5832 ROW_F32.with(|rb| {
5835 let mut buf = rb.borrow_mut();
5836 buf.resize(cols, 0.0);
5837 decode_f32(r, &mut buf);
5838 for bi in 0..b {
5839 let x = &xs_all[bi * cols..(bi + 1) * cols];
5840 let mut dot = 0f32;
5841 for g in 0..ng {
5842 let mut gd = 0f32;
5843 for k in 0..GROUP_SIZE {
5844 gd += buf[g * GROUP_SIZE + k] * x[g * GROUP_SIZE + k];
5845 }
5846 dot += gd * gscale(r, g);
5847 }
5848 unsafe { *out_addr.at(bi * rows + r) = dot };
5850 }
5851 });
5852 continue;
5853 }
5854 let l = (1i32 << (bw - 1)) - 1;
5855 let data = &bytes[offsets[r]..offsets[r + 1]];
5856 ROW_I8.with(|rb| {
5857 let mut buf = rb.borrow_mut();
5858 buf.resize(cols, 0);
5859 #[inline(always)]
5860 fn fill<const B: usize>(data: &[u8], l: i32, buf: &mut [u8]) {
5861 for (blk, chunk) in buf.chunks_exact_mut(8).enumerate() {
5862 let u = unpack8::<B>(&data[blk * B..]);
5863 for k in 0..8 {
5864 chunk[k] = (u[k] - l) as i8 as u8;
5865 }
5866 }
5867 }
5868 match bw {
5869 3 => fill::<3>(data, l, &mut buf),
5870 4 => vbit_fill4(data, &mut buf),
5871 5 => fill::<5>(data, l, &mut buf),
5872 6 => fill::<6>(data, l, &mut buf),
5873 _ => unreachable!("vbit bit-width {bw} (validated at load)"),
5874 }
5875 let mut bi = 0usize;
5876 #[cfg(target_arch = "x86_64")]
5880 if avx2_enabled()
5881 && std::env::var("CMF_X86_BLOCKED")
5882 .map(|v| v != "0")
5883 .unwrap_or(true)
5884 {
5885 while bi + 4 <= acts.len() {
5886 let xs = [
5887 acts[bi].xq.as_slice(),
5888 acts[bi + 1].xq.as_slice(),
5889 acts[bi + 2].xq.as_slice(),
5890 acts[bi + 3].xq.as_slice(),
5891 ];
5892 let sxs = [
5893 acts[bi].sx,
5894 acts[bi + 1].sx,
5895 acts[bi + 2].sx,
5896 acts[bi + 3].sx,
5897 ];
5898 let d = unsafe {
5899 if vnni_tiles_enabled() {
5900 dot_q4b_row_1x4_sx_vnni(
5901 &buf,
5902 &bytes[sc_off..],
5903 r * ng,
5904 ng,
5905 xs,
5906 sxs,
5907 )
5908 } else {
5909 dot_q4b_row_1x4_sx_avx2(
5910 &buf,
5911 &bytes[sc_off..],
5912 r * ng,
5913 ng,
5914 xs,
5915 sxs,
5916 )
5917 }
5918 };
5919 for k in 0..4 {
5920 let act = &acts[bi + k];
5921 let mut dot = d[k];
5922 for &(j, xv) in &act.outliers {
5923 dot += (buf[j] as i8) as f32 * gscale(r, j / GROUP_SIZE) * xv;
5924 }
5925 unsafe { *out_addr.at((bi + k) * rows + r) = dot };
5927 }
5928 bi += 4;
5929 }
5930 }
5931 while bi < acts.len() {
5932 let act = &acts[bi];
5933 let mut dot = 0f32;
5934 for g in 0..ng {
5935 let d = dot_i8_i8(
5936 &buf[g * GROUP_SIZE..(g + 1) * GROUP_SIZE],
5937 &act.xq[g * GROUP_SIZE..(g + 1) * GROUP_SIZE],
5938 ) as f32
5939 * act.sx;
5940 dot += d * gscale(r, g);
5941 }
5942 for &(j, xv) in &act.outliers {
5943 dot += (buf[j] as i8) as f32 * gscale(r, j / GROUP_SIZE) * xv;
5944 }
5945 unsafe { *out_addr.at(bi * rows + r) = dot };
5947 bi += 1;
5948 }
5949 });
5950 }
5951 };
5952 dispatch_rows(pool, rows, &run);
5953 return;
5954 }
5955
5956 let out_addr = SendMut(out.as_mut_ptr());
5957 let run = move |start: usize, end: usize| {
5958 ROW_F32.with(|rb| {
5959 let mut buf = rb.borrow_mut();
5960 buf.resize(cols, 0.0);
5961 for r in start..end {
5962 decode_f32(r, &mut buf);
5963 for bi in 0..b {
5964 let x = &xs_all[bi * cols..(bi + 1) * cols];
5965 let mut dot = 0f32;
5966 for g in 0..ng {
5967 let mut gd = 0f32;
5968 for k in 0..GROUP_SIZE {
5969 gd += buf[g * GROUP_SIZE + k] * x[g * GROUP_SIZE + k];
5970 }
5971 dot += gd * gscale(r, g);
5972 }
5973 unsafe { *out_addr.at(bi * rows + r) = dot };
5975 }
5976 }
5977 })
5978 };
5979 dispatch_rows(pool, rows, &run);
5980}
5981
5982pub(crate) fn gpu_batch_job<'a>(
5986 t: &'a QTensor,
5987 x: &[f32],
5988) -> Option<(std::sync::Arc<CmfModel>, crate::gpu::BatchJob<'a>)> {
5989 match t {
5990 QTensor::Mapped {
5991 model,
5992 idx,
5993 dtype: dt @ (TensorDtype::Q8Row | TensorDtype::Q8_2f),
5994 rows,
5995 cols,
5996 row_scale,
5997 col_field,
5998 ..
5999 } => Some((
6000 model.clone(),
6001 crate::gpu::BatchJob {
6002 idx: *idx,
6003 rows: *rows,
6004 cols: *cols,
6005 row_scale,
6006 xs: prescale(x, col_field, *dt).into_owned(),
6007 q1: false,
6008 },
6009 )),
6010 QTensor::Mapped {
6012 model,
6013 idx,
6014 dtype: TensorDtype::Q1,
6015 rows,
6016 cols,
6017 ..
6018 } => Some((
6019 model.clone(),
6020 crate::gpu::BatchJob {
6021 idx: *idx,
6022 rows: *rows,
6023 cols: *cols,
6024 row_scale: &[],
6025 xs: x.to_vec(),
6026 q1: true,
6027 },
6028 )),
6029 _ => None,
6030 }
6031}
6032
6033thread_local! {
6034 static PRESCALE_BUF1: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
6035 static PRESCALE_BUF2: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
6036}
6037
6038pub(crate) fn prescale<'a>(
6039 x: &'a [f32],
6040 col_field: &[f32],
6041 dtype: TensorDtype,
6042) -> std::borrow::Cow<'a, [f32]> {
6043 if dtype == TensorDtype::Q8_2f {
6044 x.iter().zip(col_field).map(|(a, c)| a * c).collect()
6045 } else {
6046 std::borrow::Cow::Borrowed(x)
6047 }
6048}
6049
6050pub(crate) fn prescale_with<R, F: FnOnce(&[f32]) -> R>(
6053 x: &[f32],
6054 col_field: &[f32],
6055 dtype: TensorDtype,
6056 buf_id: u8,
6057 f: F,
6058) -> R {
6059 if dtype == TensorDtype::Q8_2f {
6060 if buf_id == 1 {
6061 PRESCALE_BUF1.with(|b| {
6062 let mut buf = b.borrow_mut();
6063 buf.clear();
6064 buf.extend(x.iter().zip(col_field).map(|(a, c)| a * c));
6065 f(&buf)
6066 })
6067 } else {
6068 PRESCALE_BUF2.with(|b| {
6069 let mut buf = b.borrow_mut();
6070 buf.clear();
6071 buf.extend(x.iter().zip(col_field).map(|(a, c)| a * c));
6072 f(&buf)
6073 })
6074 }
6075 } else {
6076 f(x)
6077 }
6078}
6079
6080#[cfg(target_arch = "x86_64")]
6085pub(crate) fn avx2_enabled() -> bool {
6086 use std::sync::OnceLock;
6087 static ON: OnceLock<bool> = OnceLock::new();
6088 *ON.get_or_init(|| {
6089 std::env::var("CMF_AVX2").map(|v| v != "0").unwrap_or(true)
6090 && std::arch::is_x86_feature_detected!("avx2")
6091 && std::arch::is_x86_feature_detected!("fma")
6092 })
6093}
6094
6095#[cfg(target_arch = "x86_64")]
6100fn avx2_a8w8_enabled() -> bool {
6101 use std::sync::OnceLock;
6102 static ON: OnceLock<bool> = OnceLock::new();
6103 *ON.get_or_init(|| {
6104 avx2_enabled() && std::env::var("CMF_SDOT").map(|v| v != "0").unwrap_or(true)
6105 })
6106}
6107
6108#[inline]
6112pub(crate) fn a8w8_enabled() -> bool {
6113 #[cfg(target_arch = "aarch64")]
6114 {
6115 sdot_enabled()
6116 }
6117 #[cfg(target_arch = "x86_64")]
6118 {
6119 avx2_a8w8_enabled()
6120 }
6121 #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
6122 {
6123 false
6124 }
6125}
6126
6127#[inline]
6130#[allow(unreachable_code)]
6131fn dot_i8_i8(w: &[u8], xq: &[i8]) -> i32 {
6132 #[cfg(target_arch = "aarch64")]
6133 unsafe {
6134 return dot_i8_sdot(w, xq);
6135 }
6136 #[cfg(target_arch = "x86_64")]
6137 unsafe {
6138 if avx512vnni_enabled() {
6139 return dot_i8_i8_vnni(w, xq);
6140 }
6141 return dot_i8_i8_avx2(w, xq);
6142 }
6143 w.iter()
6144 .zip(xq)
6145 .map(|(&a, &b)| (a as i8) as i32 * b as i32)
6146 .sum()
6147}
6148
6149#[cfg(target_arch = "x86_64")]
6153fn avx512vnni_enabled() -> bool {
6154 use std::sync::OnceLock;
6155 static ON: OnceLock<bool> = OnceLock::new();
6156 *ON.get_or_init(|| {
6157 std::env::var("CMF_AVX512")
6158 .map(|v| v != "0")
6159 .unwrap_or(true)
6160 && std::arch::is_x86_feature_detected!("avx512f")
6161 && std::arch::is_x86_feature_detected!("avx512bw")
6162 && std::arch::is_x86_feature_detected!("avx512vl")
6163 && std::arch::is_x86_feature_detected!("avx512vnni")
6164 })
6165}
6166
6167#[cfg(target_arch = "x86_64")]
6175fn vnni_tiles_enabled() -> bool {
6176 use std::sync::OnceLock;
6177 static ON: OnceLock<bool> = OnceLock::new();
6178 *ON.get_or_init(|| {
6179 std::env::var("CMF_VNNI_TILES")
6180 .map(|v| v != "0")
6181 .unwrap_or(true)
6182 && avx512vnni_enabled()
6183 })
6184}
6185
6186#[cfg(target_arch = "x86_64")]
6191#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
6192#[inline]
6193unsafe fn dpbusd_hsum(aw: core::arch::x86_64::__m256i, xs: core::arch::x86_64::__m256i) -> i32 {
6194 unsafe {
6196 use core::arch::x86_64::*;
6197 let d = _mm256_dpbusd_epi32(_mm256_setzero_si256(), aw, xs);
6198 let hi128 = _mm256_extracti128_si256::<1>(d);
6199 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
6200 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
6201 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
6202 _mm_cvtsi128_si32(s32)
6203 }
6204}
6205
6206#[cfg(target_arch = "x86_64")]
6211#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
6212unsafe fn dot_i8_i8_vnni(w: &[u8], xq: &[i8]) -> i32 {
6213 unsafe {
6215 use core::arch::x86_64::*;
6216 let n = w.len();
6217 let mut j = 0usize;
6218 let mut total: i32;
6219 {
6224 #[inline(always)]
6225 unsafe fn step(
6226 w: *const u8,
6227 x: *const i8,
6228 acc: core::arch::x86_64::__m512i,
6229 ) -> core::arch::x86_64::__m512i {
6230 unsafe {
6231 use core::arch::x86_64::*;
6232 let wv = _mm512_loadu_si512(w as *const _);
6233 let xv = _mm512_loadu_si512(x as *const _);
6234 let aw = _mm512_abs_epi8(wv);
6235 let neg = _mm512_movepi8_mask(wv);
6236 let sx = _mm512_mask_sub_epi8(xv, neg, _mm512_setzero_si512(), xv);
6237 _mm512_dpbusd_epi32(acc, aw, sx)
6238 }
6239 }
6240 let (mut a0, mut a1, mut a2, mut a3) = (
6241 _mm512_setzero_si512(),
6242 _mm512_setzero_si512(),
6243 _mm512_setzero_si512(),
6244 _mm512_setzero_si512(),
6245 );
6246 while j + 256 <= n {
6247 a0 = step(w.as_ptr().add(j), xq.as_ptr().add(j), a0);
6248 a1 = step(w.as_ptr().add(j + 64), xq.as_ptr().add(j + 64), a1);
6249 a2 = step(w.as_ptr().add(j + 128), xq.as_ptr().add(j + 128), a2);
6250 a3 = step(w.as_ptr().add(j + 192), xq.as_ptr().add(j + 192), a3);
6251 j += 256;
6252 }
6253 while j + 64 <= n {
6254 a0 = step(w.as_ptr().add(j), xq.as_ptr().add(j), a0);
6255 j += 64;
6256 }
6257 let s01 = _mm512_add_epi32(a0, a1);
6258 let s23 = _mm512_add_epi32(a2, a3);
6259 total = _mm512_reduce_add_epi32(_mm512_add_epi32(s01, s23));
6260 }
6261 if j + 32 <= n {
6263 let wv = _mm256_loadu_si256(w.as_ptr().add(j) as *const __m256i);
6264 let xv = _mm256_loadu_si256(xq.as_ptr().add(j) as *const __m256i);
6265 let d = _mm256_dpbusd_epi32(
6266 _mm256_setzero_si256(),
6267 _mm256_abs_epi8(wv),
6268 _mm256_sign_epi8(xv, wv),
6269 );
6270 let hi128 = _mm256_extracti128_si256::<1>(d);
6271 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
6272 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
6273 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
6274 total += _mm_cvtsi128_si32(s32);
6275 j += 32;
6276 }
6277 while j < n {
6278 total += (w[j] as i8) as i32 * xq[j] as i32;
6279 j += 1;
6280 }
6281 total
6282 }
6283}
6284
6285#[cfg(target_arch = "x86_64")]
6287#[target_feature(enable = "avx2,fma")]
6288unsafe fn dot_i8_f32_avx2(w: &[u8], x: &[f32]) -> f32 {
6289 unsafe {
6291 use core::arch::x86_64::*;
6292 let n = x.len();
6293 let wp = w.as_ptr();
6294 let xp = x.as_ptr();
6295 let (mut a0, mut a1) = (_mm256_setzero_ps(), _mm256_setzero_ps());
6296 let mut j = 0usize;
6297 while j + 16 <= n {
6298 let wb = _mm_loadu_si128(wp.add(j) as *const __m128i);
6299 let lo = _mm256_cvtepi8_epi32(wb);
6300 let hi = _mm256_cvtepi8_epi32(_mm_srli_si128::<8>(wb));
6301 a0 = _mm256_fmadd_ps(_mm256_cvtepi32_ps(lo), _mm256_loadu_ps(xp.add(j)), a0);
6302 a1 = _mm256_fmadd_ps(_mm256_cvtepi32_ps(hi), _mm256_loadu_ps(xp.add(j + 8)), a1);
6303 j += 16;
6304 }
6305 let acc = _mm256_add_ps(a0, a1);
6306 let hi128 = _mm256_extractf128_ps::<1>(acc);
6307 let s128 = _mm_add_ps(_mm256_castps256_ps128(acc), hi128);
6308 let s64 = _mm_add_ps(s128, _mm_movehl_ps(s128, s128));
6309 let s32 = _mm_add_ss(s64, _mm_shuffle_ps::<1>(s64, s64));
6310 let mut sum = _mm_cvtss_f32(s32);
6311 while j < n {
6312 sum += (*wp.add(j) as i8) as f32 * *xp.add(j);
6313 j += 1;
6314 }
6315 sum
6316 }
6317}
6318
6319#[cfg(target_arch = "x86_64")]
6324#[target_feature(enable = "avx2")]
6325unsafe fn dot_i8_i8_avx2(w: &[u8], xq: &[i8]) -> i32 {
6326 unsafe {
6328 use core::arch::x86_64::*;
6329 let n = w.len();
6330 let ones = _mm256_set1_epi16(1);
6331 let mut acc = _mm256_setzero_si256();
6332 let mut j = 0usize;
6333 while j + 32 <= n {
6334 let wv = _mm256_loadu_si256(w.as_ptr().add(j) as *const __m256i);
6335 let xv = _mm256_loadu_si256(xq.as_ptr().add(j) as *const __m256i);
6336 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(wv), _mm256_sign_epi8(xv, wv));
6337 acc = _mm256_add_epi32(acc, _mm256_madd_epi16(p16, ones));
6338 j += 32;
6339 }
6340 let hi128 = _mm256_extracti128_si256::<1>(acc);
6341 let s128 = _mm_add_epi32(_mm256_castsi256_si128(acc), hi128);
6342 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
6343 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
6344 let mut s = _mm_cvtsi128_si32(s32);
6345 while j < n {
6346 s += (w[j] as i8) as i32 * xq[j] as i32;
6347 j += 1;
6348 }
6349 s
6350 }
6351}
6352
6353#[cfg(target_arch = "aarch64")]
6357#[target_feature(enable = "neon,i8mm")]
6358unsafe fn dot_i8_smmla_2x4(w0: &[u8], w1: &[u8], xs: [&[i8]; 4]) -> [[i32; 4]; 2] {
6359 unsafe {
6361 use core::arch::aarch64::*;
6362 use core::arch::asm;
6363 let n = w0.len();
6364 let w0p = w0.as_ptr() as *const i8;
6365 let w1p = w1.as_ptr() as *const i8;
6366 let mut acc01 = vdupq_n_s32(0);
6369 let mut acc23 = vdupq_n_s32(0);
6370 let mut i = 0usize;
6371 while i + 8 <= n {
6372 let wa = vcombine_s8(vld1_s8(w0p.add(i)), vld1_s8(w1p.add(i)));
6373 let xb01 = vcombine_s8(
6374 vld1_s8(xs[0].as_ptr().add(i)),
6375 vld1_s8(xs[1].as_ptr().add(i)),
6376 );
6377 let xb23 = vcombine_s8(
6378 vld1_s8(xs[2].as_ptr().add(i)),
6379 vld1_s8(xs[3].as_ptr().add(i)),
6380 );
6381 asm!(
6382 "smmla {a01:v}.4s, {w:v}.16b, {x01:v}.16b",
6383 "smmla {a23:v}.4s, {w:v}.16b, {x23:v}.16b",
6384 a01 = inout(vreg) acc01, a23 = inout(vreg) acc23,
6385 w = in(vreg) wa, x01 = in(vreg) xb01, x23 = in(vreg) xb23,
6386 options(pure, nomem, nostack),
6387 );
6388 i += 8;
6389 }
6390 let mut out = [[0i32; 4]; 2];
6391 let a01: [i32; 4] = core::mem::transmute(acc01);
6392 let a23: [i32; 4] = core::mem::transmute(acc23);
6393 out[0][0] = a01[0];
6394 out[0][1] = a01[1];
6395 out[1][0] = a01[2];
6396 out[1][1] = a01[3];
6397 out[0][2] = a23[0];
6398 out[0][3] = a23[1];
6399 out[1][2] = a23[2];
6400 out[1][3] = a23[3];
6401 if i < n {
6402 for (k, x) in xs.iter().enumerate() {
6403 for j in i..n {
6404 out[0][k] += (w0[j] as i8) as i32 * x[j] as i32;
6405 out[1][k] += (w1[j] as i8) as i32 * x[j] as i32;
6406 }
6407 }
6408 }
6409 out
6410 }
6411}
6412
6413#[cfg(target_arch = "aarch64")]
6417#[target_feature(enable = "neon,dotprod")]
6418unsafe fn dot_i8_sdot_2x4(w0: &[u8], w1: &[u8], xs: [&[i8]; 4]) -> [[i32; 4]; 2] {
6419 unsafe {
6421 use core::arch::aarch64::*;
6422 use core::arch::asm;
6423 let n = w0.len();
6424 let w0p = w0.as_ptr() as *const i8;
6425 let w1p = w1.as_ptr() as *const i8;
6426 let mut acc = [[vdupq_n_s32(0); 4]; 2];
6427 let mut i = 0usize;
6428 while i + 16 <= n {
6429 let wv0 = vld1q_s8(w0p.add(i));
6430 let wv1 = vld1q_s8(w1p.add(i));
6431 for (k, x) in xs.iter().enumerate() {
6432 let xv = vld1q_s8(x.as_ptr().add(i));
6433 let (mut a0, mut a1) = (acc[0][k], acc[1][k]);
6434 asm!(
6435 "sdot {a0:v}.4s, {w0:v}.16b, {x:v}.16b",
6436 "sdot {a1:v}.4s, {w1:v}.16b, {x:v}.16b",
6437 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
6438 w0 = in(vreg) wv0, w1 = in(vreg) wv1, x = in(vreg) xv,
6439 options(pure, nomem, nostack),
6440 );
6441 acc[0][k] = a0;
6442 acc[1][k] = a1;
6443 }
6444 i += 16;
6445 }
6446 let mut out = [[0i32; 4]; 2];
6447 for r in 0..2 {
6448 for k in 0..4 {
6449 out[r][k] = vaddvq_s32(acc[r][k]);
6450 }
6451 }
6452 if i < n {
6453 for (k, x) in xs.iter().enumerate() {
6454 for j in i..n {
6455 out[0][k] += (w0[j] as i8) as i32 * x[j] as i32;
6456 out[1][k] += (w1[j] as i8) as i32 * x[j] as i32;
6457 }
6458 }
6459 }
6460 out
6461 }
6462}
6463
6464#[cfg(target_arch = "x86_64")]
6470#[target_feature(enable = "avx2")]
6471unsafe fn dot_i8_i8_avx2_2x4(w0: &[u8], w1: &[u8], xs: [&[i8]; 4]) -> [[i32; 4]; 2] {
6472 unsafe {
6474 use core::arch::x86_64::*;
6475 let n = w0.len();
6476 let ones = _mm256_set1_epi16(1);
6477 let mut acc = [[_mm256_setzero_si256(); 4]; 2];
6478 let mut j = 0usize;
6479 while j + 32 <= n {
6480 let wv0 = _mm256_loadu_si256(w0.as_ptr().add(j) as *const __m256i);
6481 let wv1 = _mm256_loadu_si256(w1.as_ptr().add(j) as *const __m256i);
6482 let aw0 = _mm256_abs_epi8(wv0);
6483 let aw1 = _mm256_abs_epi8(wv1);
6484 for (k, x) in xs.iter().enumerate() {
6485 let xv = _mm256_loadu_si256(x.as_ptr().add(j) as *const __m256i);
6486 let p0 = _mm256_maddubs_epi16(aw0, _mm256_sign_epi8(xv, wv0));
6487 acc[0][k] = _mm256_add_epi32(acc[0][k], _mm256_madd_epi16(p0, ones));
6488 let p1 = _mm256_maddubs_epi16(aw1, _mm256_sign_epi8(xv, wv1));
6489 acc[1][k] = _mm256_add_epi32(acc[1][k], _mm256_madd_epi16(p1, ones));
6490 }
6491 j += 32;
6492 }
6493 let mut out = [[0i32; 4]; 2];
6494 for r in 0..2 {
6495 for k in 0..4 {
6496 let a = acc[r][k];
6497 let hi128 = _mm256_extracti128_si256::<1>(a);
6498 let s128 = _mm_add_epi32(_mm256_castsi256_si128(a), hi128);
6499 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
6500 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
6501 out[r][k] = _mm_cvtsi128_si32(s32);
6502 }
6503 }
6504 if j < n {
6505 for (k, x) in xs.iter().enumerate() {
6506 for i in j..n {
6507 out[0][k] += (w0[i] as i8) as i32 * x[i] as i32;
6508 out[1][k] += (w1[i] as i8) as i32 * x[i] as i32;
6509 }
6510 }
6511 }
6512 out
6513 }
6514}
6515
6516#[cfg(target_arch = "x86_64")]
6521#[inline]
6522fn row_dot_avx2(row: &[u8], act: &SplitAct) -> f32 {
6523 let dot = if avx512vnni_enabled() && row.len() >= 64 {
6524 (unsafe { dot_u8p128_i8_vnni(row, &act.xq) }) - 128 * act.xsum
6525 } else {
6526 unsafe { dot_i8_i8_avx2(row, &act.xq) }
6527 };
6528 let mut acc = dot as f32 * act.sx;
6529 for &(j, xv) in &act.outliers {
6530 acc += (row[j] as i8) as f32 * xv;
6531 }
6532 acc
6533}
6534
6535#[cfg(target_arch = "x86_64")]
6539#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
6540unsafe fn dot_u8p128_i8_vnni(w: &[u8], xq: &[i8]) -> i32 {
6541 unsafe {
6543 use core::arch::x86_64::*;
6544 let n = w.len();
6545 let flip = _mm512_set1_epi8(-128); #[inline(always)]
6547 unsafe fn step(
6548 w: *const u8,
6549 x: *const i8,
6550 flip: core::arch::x86_64::__m512i,
6551 acc: core::arch::x86_64::__m512i,
6552 ) -> core::arch::x86_64::__m512i {
6553 unsafe {
6554 use core::arch::x86_64::*;
6555 let wv = _mm512_xor_si512(_mm512_loadu_si512(w as *const _), flip);
6556 _mm512_dpbusd_epi32(acc, wv, _mm512_loadu_si512(x as *const _))
6557 }
6558 }
6559 let (mut a0, mut a1, mut a2, mut a3) = (
6560 _mm512_setzero_si512(),
6561 _mm512_setzero_si512(),
6562 _mm512_setzero_si512(),
6563 _mm512_setzero_si512(),
6564 );
6565 let mut j = 0usize;
6566 while j + 256 <= n {
6567 a0 = step(w.as_ptr().add(j), xq.as_ptr().add(j), flip, a0);
6568 a1 = step(w.as_ptr().add(j + 64), xq.as_ptr().add(j + 64), flip, a1);
6569 a2 = step(w.as_ptr().add(j + 128), xq.as_ptr().add(j + 128), flip, a2);
6570 a3 = step(w.as_ptr().add(j + 192), xq.as_ptr().add(j + 192), flip, a3);
6571 j += 256;
6572 }
6573 while j + 64 <= n {
6574 a0 = step(w.as_ptr().add(j), xq.as_ptr().add(j), flip, a0);
6575 j += 64;
6576 }
6577 let mut total = _mm512_reduce_add_epi32(_mm512_add_epi32(
6578 _mm512_add_epi32(a0, a1),
6579 _mm512_add_epi32(a2, a3),
6580 ));
6581 while j < n {
6583 total += ((w[j] ^ 0x80) as i32) * xq[j] as i32;
6584 j += 1;
6585 }
6586 total
6587 }
6588}
6589
6590#[cfg(target_arch = "x86_64")]
6596#[target_feature(enable = "avx2")]
6597unsafe fn dot_q4_row_avx2(packed: &[u8], scales: &[u8], g0: usize, gpr: usize, xq: &[i8]) -> f32 {
6598 unsafe {
6601 use core::arch::x86_64::*;
6602 let lomask = _mm_set1_epi8(0x0F);
6603 let eight = _mm256_set1_epi8(8);
6604 let ones = _mm256_set1_epi16(1);
6605 let mut acc = 0f32;
6606 for gi in 0..gpr {
6607 let g = g0 + gi;
6608 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
6609 let b = _mm_loadu_si128(packed.as_ptr().add(g * 16) as *const __m128i);
6610 let lo = _mm_and_si128(b, lomask);
6611 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), lomask);
6612 let w = _mm256_sub_epi8(
6613 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
6614 eight,
6615 );
6616 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
6617 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(w), _mm256_sign_epi8(x, w));
6618 let d = _mm256_madd_epi16(p16, ones);
6619 let hi128 = _mm256_extracti128_si256::<1>(d);
6620 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
6621 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
6622 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
6623 acc += _mm_cvtsi128_si32(s32) as f32 * s;
6624 }
6625 acc
6626 }
6627}
6628
6629#[cfg(target_arch = "x86_64")]
6632#[target_feature(enable = "avx2")]
6633unsafe fn dot_q4_row_avx2_2(
6634 packed: &[u8],
6635 scales: &[u8],
6636 g0: usize,
6637 gpr: usize,
6638 xq1: &[i8],
6639 xq2: &[i8],
6640) -> (f32, f32) {
6641 unsafe {
6643 use core::arch::x86_64::*;
6644 let lomask = _mm_set1_epi8(0x0F);
6645 let eight = _mm256_set1_epi8(8);
6646 let ones = _mm256_set1_epi16(1);
6647 let (mut acc1, mut acc2) = (0f32, 0f32);
6648 #[inline(always)]
6649 unsafe fn hsum(d: core::arch::x86_64::__m256i) -> i32 {
6650 unsafe {
6651 use core::arch::x86_64::*;
6652 let hi128 = _mm256_extracti128_si256::<1>(d);
6653 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
6654 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
6655 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
6656 _mm_cvtsi128_si32(s32)
6657 }
6658 }
6659 for gi in 0..gpr {
6660 let g = g0 + gi;
6661 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
6662 let b = _mm_loadu_si128(packed.as_ptr().add(g * 16) as *const __m128i);
6663 let lo = _mm_and_si128(b, lomask);
6664 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), lomask);
6665 let w = _mm256_sub_epi8(
6666 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
6667 eight,
6668 );
6669 let aw = _mm256_abs_epi8(w);
6670 let x1 = _mm256_loadu_si256(xq1.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
6671 let x2 = _mm256_loadu_si256(xq2.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
6672 let d1 = _mm256_madd_epi16(_mm256_maddubs_epi16(aw, _mm256_sign_epi8(x1, w)), ones);
6673 let d2 = _mm256_madd_epi16(_mm256_maddubs_epi16(aw, _mm256_sign_epi8(x2, w)), ones);
6674 acc1 += hsum(d1) as f32 * s;
6675 acc2 += hsum(d2) as f32 * s;
6676 }
6677 (acc1, acc2)
6678 }
6679}
6680
6681#[cfg(target_arch = "x86_64")]
6683fn q8_range_avx2(
6684 q: &[u8],
6685 row_scale: &[f32],
6686 act: &SplitAct,
6687 cols: usize,
6688 out_addr: SendMut,
6689 start: usize,
6690 end: usize,
6691) {
6692 for o in start..end {
6693 let v = row_dot_avx2(&q[o * cols..(o + 1) * cols], act) * row_scale[o];
6694 unsafe { *out_addr.at(o) = v };
6696 }
6697}
6698
6699#[cfg(target_arch = "x86_64")]
6701#[allow(clippy::too_many_arguments)]
6702fn q8_range2_avx2(
6703 q: &[u8],
6704 row_scale: &[f32],
6705 a1: &SplitAct,
6706 a2: &SplitAct,
6707 cols: usize,
6708 p1: SendMut,
6709 p2: SendMut,
6710 start: usize,
6711 end: usize,
6712) {
6713 for o in start..end {
6714 let row = &q[o * cols..(o + 1) * cols];
6715 unsafe {
6717 *p1.at(o) = row_dot_avx2(row, a1) * row_scale[o];
6718 *p2.at(o) = row_dot_avx2(row, a2) * row_scale[o];
6719 }
6720 }
6721}
6722
6723#[cfg(target_arch = "aarch64")]
6734fn i8mm_enabled() -> bool {
6735 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6736 *ON.get_or_init(|| {
6737 std::env::var("CMF_I8MM").map(|v| v == "1").unwrap_or(false)
6738 && std::arch::is_aarch64_feature_detected!("i8mm")
6739 })
6740}
6741
6742#[cfg_attr(not(target_arch = "aarch64"), allow(dead_code))]
6746fn sdot_enabled() -> bool {
6747 use std::sync::OnceLock;
6748 static ON: OnceLock<bool> = OnceLock::new();
6749 *ON.get_or_init(|| {
6750 let want = std::env::var("CMF_SDOT").map(|v| v != "0").unwrap_or(true);
6751 if !want {
6752 return false;
6753 }
6754
6755 #[cfg(target_arch = "aarch64")]
6756 {
6757 if std::arch::is_aarch64_feature_detected!("dotprod") {
6758 return true;
6759 }
6760 #[cfg(target_os = "android")]
6761 {
6762 if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") {
6763 if cpuinfo.lines().any(|l| {
6764 (l.starts_with("Features") || l.starts_with("features"))
6765 && l.contains("asimddp")
6766 }) {
6767 return true;
6768 }
6769 }
6770 }
6771 false
6772 }
6773 #[cfg(not(target_arch = "aarch64"))]
6774 {
6775 false
6776 }
6777 })
6778}
6779
6780struct SplitAct {
6785 xq: Vec<i8>,
6786 sx: f32,
6787 outliers: Vec<(usize, f32)>,
6788 #[cfg_attr(not(target_arch = "x86_64"), allow(dead_code))]
6791 xsum: i32,
6792}
6793
6794thread_local! {
6795 static XQ_FREE: std::cell::RefCell<Vec<Vec<i8>>> =
6798 const { std::cell::RefCell::new(Vec::new()) };
6799}
6800
6801impl Drop for SplitAct {
6802 fn drop(&mut self) {
6803 let buf = std::mem::take(&mut self.xq);
6804 if buf.capacity() > 0 {
6805 XQ_FREE.with(|f| {
6806 let mut f = f.borrow_mut();
6807 if f.len() < 16 {
6808 f.push(buf);
6809 }
6810 });
6811 }
6812 }
6813}
6814
6815fn split_act(x: &[f32]) -> SplitAct {
6816 let n = x.len();
6817 let rms = (x.iter().map(|&v| (v * v) as f64).sum::<f64>() / n.max(1) as f64).sqrt() as f32;
6818 let thr = 8.0 * rms;
6819 let mut outliers: Vec<(usize, f32)> = Vec::new();
6823 let mut amax = 0f32;
6824 for (j, &v) in x.iter().enumerate() {
6825 let a = v.abs();
6826 if a > thr {
6827 outliers.push((j, v));
6828 } else if a > amax {
6829 amax = a;
6830 }
6831 }
6832 let sx = if amax > 0.0 { amax / 127.0 } else { 1.0 };
6833 let inv = 1.0 / sx;
6834 let mut xq = XQ_FREE.with(|f| f.borrow_mut().pop()).unwrap_or_default();
6835 xq.clear();
6836 xq.reserve(n);
6837 if outliers.is_empty() {
6838 xq.extend(
6839 x.iter()
6840 .map(|&v| (v * inv).round().clamp(-127.0, 127.0) as i8),
6841 );
6842 } else {
6843 xq.extend(x.iter().map(|&v| {
6845 if v.abs() > thr {
6846 0
6847 } else {
6848 (v * inv).round().clamp(-127.0, 127.0) as i8
6849 }
6850 }));
6851 }
6852 let xsum = xq.iter().map(|&v| v as i32).sum();
6853 SplitAct {
6854 xq,
6855 sx,
6856 outliers,
6857 xsum,
6858 }
6859}
6860
6861fn split_act_q8_2f(x: &[f32], col: &[f32]) -> SplitAct {
6862 let n = x.len();
6863 let rms = (x
6864 .iter()
6865 .zip(col)
6866 .map(|(&a, &c)| {
6867 let v = a * c;
6868 (v * v) as f64
6869 })
6870 .sum::<f64>()
6871 / n.max(1) as f64)
6872 .sqrt() as f32;
6873 let thr = 8.0 * rms;
6874
6875 let mut outliers = Vec::new();
6876 let mut amax = 0f32;
6877 for (j, (&a, &c)) in x.iter().zip(col).enumerate() {
6878 let v = a * c;
6879 let s = v.abs();
6880 if s > thr {
6881 outliers.push((j, v));
6882 } else if s > amax {
6883 amax = s;
6884 }
6885 }
6886
6887 let sx = if amax > 0.0 { amax / 127.0 } else { 1.0 };
6888 let inv = 1.0 / sx;
6889 let mut xq = XQ_FREE.with(|f| f.borrow_mut().pop()).unwrap_or_default();
6890 xq.clear();
6891 xq.reserve(n);
6892 if outliers.is_empty() {
6893 xq.extend(
6894 x.iter()
6895 .zip(col)
6896 .map(|(&a, &c)| ((a * c) * inv).round().clamp(-127.0, 127.0) as i8),
6897 );
6898 } else {
6899 xq.extend(x.iter().zip(col).map(|(&a, &c)| {
6900 let v = a * c;
6901 if v.abs() > thr {
6902 0
6903 } else {
6904 (v * inv).round().clamp(-127.0, 127.0) as i8
6905 }
6906 }));
6907 }
6908 let xsum = xq.iter().map(|&v| v as i32).sum();
6909 SplitAct {
6910 xq,
6911 sx,
6912 outliers,
6913 xsum,
6914 }
6915}
6916
6917#[cfg(target_arch = "aarch64")]
6920#[target_feature(enable = "neon,dotprod")]
6921unsafe fn dot_i8_sdot(w: &[u8], xq: &[i8]) -> i32 {
6922 unsafe {
6924 use core::arch::aarch64::*;
6925 use core::arch::asm;
6926 let wp = w.as_ptr() as *const i8;
6927 let n = w.len();
6928 let (mut a0, mut a1, mut a2, mut a3) = (
6929 vdupq_n_s32(0),
6930 vdupq_n_s32(0),
6931 vdupq_n_s32(0),
6932 vdupq_n_s32(0),
6933 );
6934 let mut i = 0;
6935 while i + 64 <= n {
6936 let (w0, x0) = (vld1q_s8(wp.add(i)), vld1q_s8(xq.as_ptr().add(i)));
6937 let (w1, x1) = (vld1q_s8(wp.add(i + 16)), vld1q_s8(xq.as_ptr().add(i + 16)));
6938 let (w2, x2) = (vld1q_s8(wp.add(i + 32)), vld1q_s8(xq.as_ptr().add(i + 32)));
6939 let (w3, x3) = (vld1q_s8(wp.add(i + 48)), vld1q_s8(xq.as_ptr().add(i + 48)));
6940 asm!(
6941 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
6942 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
6943 "sdot {a2:v}.4s, {w2:v}.16b, {x2:v}.16b",
6944 "sdot {a3:v}.4s, {w3:v}.16b, {x3:v}.16b",
6945 a0 = inout(vreg) a0, a1 = inout(vreg) a1, a2 = inout(vreg) a2, a3 = inout(vreg) a3,
6946 w0 = in(vreg) w0, x0 = in(vreg) x0, w1 = in(vreg) w1, x1 = in(vreg) x1,
6947 w2 = in(vreg) w2, x2 = in(vreg) x2, w3 = in(vreg) w3, x3 = in(vreg) x3,
6948 options(pure, nomem, nostack),
6949 );
6950 i += 64;
6951 }
6952 while i + 16 <= n {
6953 let (wv, xv) = (vld1q_s8(wp.add(i)), vld1q_s8(xq.as_ptr().add(i)));
6954 asm!("sdot {a:v}.4s, {w:v}.16b, {x:v}.16b",
6955 a = inout(vreg) a0, w = in(vreg) wv, x = in(vreg) xv, options(pure, nomem, nostack));
6956 i += 16;
6957 }
6958 let mut s = vaddvq_s32(vaddq_s32(vaddq_s32(a0, a1), vaddq_s32(a2, a3)));
6959 while i < n {
6960 s += (*wp.add(i)) as i32 * xq[i] as i32;
6961 i += 1;
6962 }
6963 s
6964 }
6965}
6966
6967#[cfg(target_arch = "aarch64")]
6971#[target_feature(enable = "neon,dotprod")]
6972unsafe fn dot_i8_sdot_4rows(w0: &[u8], w1: &[u8], w2: &[u8], w3: &[u8], xq: &[i8]) -> [i32; 4] {
6973 unsafe {
6975 use core::arch::aarch64::*;
6976 use core::arch::asm;
6977 let n = xq.len();
6978 let px = xq.as_ptr();
6979 let (p0, p1, p2, p3) = (
6980 w0.as_ptr() as *const i8,
6981 w1.as_ptr() as *const i8,
6982 w2.as_ptr() as *const i8,
6983 w3.as_ptr() as *const i8,
6984 );
6985 let (mut a0, mut a1, mut a2, mut a3) = (
6986 vdupq_n_s32(0),
6987 vdupq_n_s32(0),
6988 vdupq_n_s32(0),
6989 vdupq_n_s32(0),
6990 );
6991 let mut i = 0;
6992 while i + 16 <= n {
6993 let x = vld1q_s8(px.add(i));
6994 let v0 = vld1q_s8(p0.add(i));
6995 let v1 = vld1q_s8(p1.add(i));
6996 let v2 = vld1q_s8(p2.add(i));
6997 let v3 = vld1q_s8(p3.add(i));
6998 asm!(
6999 "sdot {a0:v}.4s, {v0:v}.16b, {x:v}.16b",
7000 "sdot {a1:v}.4s, {v1:v}.16b, {x:v}.16b",
7001 "sdot {a2:v}.4s, {v2:v}.16b, {x:v}.16b",
7002 "sdot {a3:v}.4s, {v3:v}.16b, {x:v}.16b",
7003 a0 = inout(vreg) a0, a1 = inout(vreg) a1, a2 = inout(vreg) a2, a3 = inout(vreg) a3,
7004 v0 = in(vreg) v0, v1 = in(vreg) v1, v2 = in(vreg) v2, v3 = in(vreg) v3, x = in(vreg) x,
7005 options(pure, nomem, nostack),
7006 );
7007 i += 16;
7008 }
7009 let mut r = [
7010 vaddvq_s32(a0),
7011 vaddvq_s32(a1),
7012 vaddvq_s32(a2),
7013 vaddvq_s32(a3),
7014 ];
7015 while i < n {
7016 let xi = *px.add(i) as i32;
7017 r[0] += (*p0.add(i)) as i32 * xi;
7018 r[1] += (*p1.add(i)) as i32 * xi;
7019 r[2] += (*p2.add(i)) as i32 * xi;
7020 r[3] += (*p3.add(i)) as i32 * xi;
7021 i += 1;
7022 }
7023 r
7024 }
7025}
7026
7027#[cfg(target_arch = "aarch64")]
7034#[target_feature(enable = "neon,dotprod")]
7035unsafe fn dot_i8_sdot_4rows_il(g: &[u8], xq: &[i8]) -> [i32; 4] {
7036 unsafe {
7039 use core::arch::aarch64::*;
7040 use core::arch::asm;
7041 let n = xq.len();
7042 let px = xq.as_ptr();
7043 let pg = g.as_ptr() as *const i8;
7044 let (mut a0, mut a1, mut a2, mut a3) = (
7045 vdupq_n_s32(0),
7046 vdupq_n_s32(0),
7047 vdupq_n_s32(0),
7048 vdupq_n_s32(0),
7049 );
7050 let mut i = 0;
7051 while i + 16 <= n {
7052 let x = vld1q_s8(px.add(i));
7053 let base = pg.add(4 * i);
7054 let v0 = vld1q_s8(base);
7055 let v1 = vld1q_s8(base.add(16));
7056 let v2 = vld1q_s8(base.add(32));
7057 let v3 = vld1q_s8(base.add(48));
7058 asm!(
7059 "sdot {a0:v}.4s, {v0:v}.16b, {x:v}.16b",
7060 "sdot {a1:v}.4s, {v1:v}.16b, {x:v}.16b",
7061 "sdot {a2:v}.4s, {v2:v}.16b, {x:v}.16b",
7062 "sdot {a3:v}.4s, {v3:v}.16b, {x:v}.16b",
7063 a0 = inout(vreg) a0, a1 = inout(vreg) a1, a2 = inout(vreg) a2, a3 = inout(vreg) a3,
7064 v0 = in(vreg) v0, v1 = in(vreg) v1, v2 = in(vreg) v2, v3 = in(vreg) v3, x = in(vreg) x,
7065 options(pure, nomem, nostack),
7066 );
7067 i += 16;
7068 }
7069 [
7070 vaddvq_s32(a0),
7071 vaddvq_s32(a1),
7072 vaddvq_s32(a2),
7073 vaddvq_s32(a3),
7074 ]
7075 }
7076}
7077
7078#[cfg(target_arch = "aarch64")]
7084fn q8_range_sdot(
7085 q: &[u8],
7086 rep: &[u8],
7087 row_scale: &[f32],
7088 act: &SplitAct,
7089 cols: usize,
7090 out_addr: SendMut,
7091 start: usize,
7092 end: usize,
7093) {
7094 let mut o = start;
7095 if !rep.is_empty() {
7098 while o < end && o % 4 != 0 {
7099 let v = row_dot_sdot(&q[o * cols..(o + 1) * cols], act) * row_scale[o];
7100 unsafe { *out_addr.at(o) = v };
7101 o += 1;
7102 }
7103 }
7104 while o + 4 <= end {
7105 let r = if rep.is_empty() {
7106 unsafe {
7107 dot_i8_sdot_4rows(
7108 &q[o * cols..(o + 1) * cols],
7109 &q[(o + 1) * cols..(o + 2) * cols],
7110 &q[(o + 2) * cols..(o + 3) * cols],
7111 &q[(o + 3) * cols..(o + 4) * cols],
7112 &act.xq,
7113 )
7114 }
7115 } else {
7116 unsafe { dot_i8_sdot_4rows_il(&rep[o * cols..(o + 4) * cols], &act.xq) }
7117 };
7118 for k in 0..4 {
7119 let mut acc = r[k] as f32 * act.sx;
7120 for &(j, xv) in &act.outliers {
7121 acc += (q[(o + k) * cols + j] as i8) as f32 * xv;
7122 }
7123 unsafe { *out_addr.at(o + k) = acc * row_scale[o + k] };
7125 }
7126 o += 4;
7127 }
7128 while o < end {
7129 let v = row_dot_sdot(&q[o * cols..(o + 1) * cols], act) * row_scale[o];
7130 unsafe { *out_addr.at(o) = v };
7131 o += 1;
7132 }
7133}
7134
7135#[cfg(target_arch = "aarch64")]
7138#[allow(clippy::too_many_arguments)]
7139fn q8_range2_sdot(
7140 q: &[u8],
7141 row_scale: &[f32],
7142 a1: &SplitAct,
7143 a2: &SplitAct,
7144 cols: usize,
7145 p1: SendMut,
7146 p2: SendMut,
7147 start: usize,
7148 end: usize,
7149) {
7150 for o in start..end {
7151 let row = &q[o * cols..(o + 1) * cols];
7152 unsafe {
7154 *p1.at(o) = row_dot_sdot(row, a1) * row_scale[o];
7155 *p2.at(o) = row_dot_sdot(row, a2) * row_scale[o];
7156 }
7157 }
7158}
7159
7160#[allow(clippy::too_many_arguments)]
7162fn q8_range2_f32(
7163 q: &[u8],
7164 row_scale: &[f32],
7165 x1: &[f32],
7166 x2: &[f32],
7167 cols: usize,
7168 p1: SendMut,
7169 p2: SendMut,
7170 start: usize,
7171 end: usize,
7172) {
7173 for o in start..end {
7174 let row = &q[o * cols..(o + 1) * cols];
7175 unsafe {
7177 *p1.at(o) = dot_i8_f32(row, x1) * row_scale[o];
7178 *p2.at(o) = dot_i8_f32(row, x2) * row_scale[o];
7179 }
7180 }
7181}
7182
7183fn q8_range_f32(
7185 q: &[u8],
7186 row_scale: &[f32],
7187 xs: &[f32],
7188 cols: usize,
7189 out_addr: SendMut,
7190 start: usize,
7191 end: usize,
7192) {
7193 for o in start..end {
7194 let v = dot_i8_f32(&q[o * cols..(o + 1) * cols], xs) * row_scale[o];
7195 unsafe { *out_addr.at(o) = v };
7197 }
7198}
7199
7200#[cfg(target_arch = "aarch64")]
7203#[inline]
7204fn row_dot_sdot(row: &[u8], act: &SplitAct) -> f32 {
7205 let mut acc = unsafe { dot_i8_sdot(row, &act.xq) } as f32 * act.sx;
7206 for &(j, xv) in &act.outliers {
7207 acc += (row[j] as i8) as f32 * xv;
7208 }
7209 acc
7210}
7211
7212#[cfg(target_arch = "aarch64")]
7220#[target_feature(enable = "neon,dotprod")]
7221unsafe fn dot_q4_row_sdot(packed: &[u8], scales: &[u8], g0: usize, gpr: usize, xq: &[i8]) -> f32 {
7222 unsafe {
7225 use core::arch::aarch64::*;
7226 use core::arch::asm;
7227 let lomask = vdupq_n_u8(0x0F);
7228 let eight = vdupq_n_s8(8);
7229 let mut acc = 0f32;
7230 for gi in 0..gpr {
7231 let g = g0 + gi;
7232 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
7233 let b = vld1q_u8(packed.as_ptr().add(g * 16));
7234 let lo = vandq_u8(b, lomask);
7235 let hi = vshrq_n_u8::<4>(b);
7236 let e0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(lo, hi)), eight);
7237 let e1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(lo, hi)), eight);
7238 let x0 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE));
7239 let x1 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE + 16));
7240 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
7241 asm!(
7242 "sdot {a0:v}.4s, {e0:v}.16b, {x0:v}.16b",
7243 "sdot {a1:v}.4s, {e1:v}.16b, {x1:v}.16b",
7244 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
7245 e0 = in(vreg) e0, x0 = in(vreg) x0, e1 = in(vreg) e1, x1 = in(vreg) x1,
7246 options(pure, nomem, nostack),
7247 );
7248 acc += vaddvq_s32(vaddq_s32(a0, a1)) as f32 * s;
7249 }
7250 acc
7251 }
7252}
7253
7254#[cfg(target_arch = "aarch64")]
7259#[target_feature(enable = "neon,dotprod")]
7260unsafe fn dot_q4_row_sdot2(
7261 packed: &[u8],
7262 scales: &[u8],
7263 g0: usize,
7264 gpr: usize,
7265 xq1: &[i8],
7266 xq2: &[i8],
7267) -> (f32, f32) {
7268 unsafe {
7271 use core::arch::aarch64::*;
7272 use core::arch::asm;
7273 let lomask = vdupq_n_u8(0x0F);
7274 let eight = vdupq_n_s8(8);
7275 let (mut acc1, mut acc2) = (0f32, 0f32);
7276 for gi in 0..gpr {
7277 let g = g0 + gi;
7278 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
7279 let b = vld1q_u8(packed.as_ptr().add(g * 16));
7280 let lo = vandq_u8(b, lomask);
7281 let hi = vshrq_n_u8::<4>(b);
7282 let e0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(lo, hi)), eight);
7283 let e1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(lo, hi)), eight);
7284 let x10 = vld1q_s8(xq1.as_ptr().add(gi * GROUP_SIZE));
7285 let x11 = vld1q_s8(xq1.as_ptr().add(gi * GROUP_SIZE + 16));
7286 let x20 = vld1q_s8(xq2.as_ptr().add(gi * GROUP_SIZE));
7287 let x21 = vld1q_s8(xq2.as_ptr().add(gi * GROUP_SIZE + 16));
7288 let (mut a0, mut a1, mut b0, mut b1) = (
7289 vdupq_n_s32(0),
7290 vdupq_n_s32(0),
7291 vdupq_n_s32(0),
7292 vdupq_n_s32(0),
7293 );
7294 asm!(
7295 "sdot {a0:v}.4s, {e0:v}.16b, {x10:v}.16b",
7296 "sdot {a1:v}.4s, {e1:v}.16b, {x11:v}.16b",
7297 "sdot {b0:v}.4s, {e0:v}.16b, {x20:v}.16b",
7298 "sdot {b1:v}.4s, {e1:v}.16b, {x21:v}.16b",
7299 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
7300 b0 = inout(vreg) b0, b1 = inout(vreg) b1,
7301 e0 = in(vreg) e0, e1 = in(vreg) e1,
7302 x10 = in(vreg) x10, x11 = in(vreg) x11,
7303 x20 = in(vreg) x20, x21 = in(vreg) x21,
7304 options(pure, nomem, nostack),
7305 );
7306 acc1 += vaddvq_s32(vaddq_s32(a0, a1)) as f32 * s;
7307 acc2 += vaddvq_s32(vaddq_s32(b0, b1)) as f32 * s;
7308 }
7309 (acc1, acc2)
7310 }
7311}
7312
7313#[inline]
7318pub(crate) fn axpy_i8_f32(acc: &mut [f32], row: &[i8], w: f32) {
7319 #[cfg(target_arch = "aarch64")]
7320 unsafe {
7321 return axpy_i8_f32_neon(acc, row, w);
7322 }
7323 #[cfg(target_arch = "x86_64")]
7324 if avx2_enabled() {
7325 return unsafe { axpy_i8_f32_avx2(acc, row, w) };
7326 }
7327 #[allow(unreachable_code)]
7328 {
7329 for (a, &b) in acc.iter_mut().zip(row) {
7330 *a += w * b as f32;
7331 }
7332 }
7333}
7334
7335#[cfg(target_arch = "x86_64")]
7337#[target_feature(enable = "avx2,fma")]
7338unsafe fn axpy_i8_f32_avx2(acc: &mut [f32], row: &[i8], w: f32) {
7339 unsafe {
7341 use core::arch::x86_64::*;
7342 let n = acc.len().min(row.len());
7343 let ap = acc.as_mut_ptr();
7344 let rp = row.as_ptr();
7345 let wv = _mm256_set1_ps(w);
7346 let mut j = 0usize;
7347 while j + 16 <= n {
7348 let rb = _mm_loadu_si128(rp.add(j) as *const __m128i);
7349 let lo = _mm256_cvtepi32_ps(_mm256_cvtepi8_epi32(rb));
7350 let hi = _mm256_cvtepi32_ps(_mm256_cvtepi8_epi32(_mm_srli_si128::<8>(rb)));
7351 let v0 = _mm256_fmadd_ps(wv, lo, _mm256_loadu_ps(ap.add(j)));
7352 let v1 = _mm256_fmadd_ps(wv, hi, _mm256_loadu_ps(ap.add(j + 8)));
7353 _mm256_storeu_ps(ap.add(j), v0);
7354 _mm256_storeu_ps(ap.add(j + 8), v1);
7355 j += 16;
7356 }
7357 while j < n {
7358 *ap.add(j) += w * (*rp.add(j)) as f32;
7359 j += 1;
7360 }
7361 }
7362}
7363
7364#[cfg(target_arch = "aarch64")]
7365#[target_feature(enable = "neon")]
7366unsafe fn axpy_i8_f32_neon(acc: &mut [f32], row: &[i8], w: f32) {
7367 unsafe {
7369 use core::arch::aarch64::*;
7370 let n = acc.len().min(row.len());
7371 let ap = acc.as_mut_ptr();
7372 let rp = row.as_ptr();
7373 let wv = vdupq_n_f32(w);
7374 let mut j = 0usize;
7375 while j + 16 <= n {
7376 let rb = vld1q_s8(rp.add(j));
7377 let lo = vmovl_s8(vget_low_s8(rb));
7378 let hi = vmovl_s8(vget_high_s8(rb));
7379 for (off, half) in [(0, lo), (8, hi)] {
7380 let f0 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(half)));
7381 let f1 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(half)));
7382 let o = j + off;
7383 vst1q_f32(ap.add(o), vfmaq_f32(vld1q_f32(ap.add(o)), wv, f0));
7384 vst1q_f32(ap.add(o + 4), vfmaq_f32(vld1q_f32(ap.add(o + 4)), wv, f1));
7385 }
7386 j += 16;
7387 }
7388 while j < n {
7389 *ap.add(j) += w * (*rp.add(j)) as f32;
7390 j += 1;
7391 }
7392 }
7393}
7394
7395#[inline]
7398pub(crate) fn dot_i8_f32(w: &[u8], x: &[f32]) -> f32 {
7399 #[cfg(target_arch = "aarch64")]
7400 unsafe {
7401 return dot_i8_f32_neon(w, x);
7402 }
7403 #[cfg(target_arch = "x86_64")]
7404 if avx2_enabled() {
7405 return unsafe { dot_i8_f32_avx2(w, x) };
7406 }
7407 #[allow(unreachable_code)]
7408 {
7409 let mut sum = 0.0f32;
7410 for (j, &b) in w.iter().enumerate() {
7411 sum += (b as i8) as f32 * x[j];
7412 }
7413 sum
7414 }
7415}
7416
7417#[inline]
7421fn dot_i8_col_f32(w: &[u8], x: &[f32], col: &[f32]) -> f32 {
7422 #[cfg(target_arch = "aarch64")]
7423 unsafe {
7424 return dot_i8_col_f32_neon(w, x, col);
7425 }
7426 #[allow(unreachable_code)]
7427 {
7428 let mut sum = 0.0f32;
7429 for (j, &b) in w.iter().enumerate() {
7430 sum += (b as i8) as f32 * x[j] * col[j];
7431 }
7432 sum
7433 }
7434}
7435
7436#[cfg(target_arch = "aarch64")]
7437#[target_feature(enable = "neon")]
7438unsafe fn dot_i8_col_f32_neon(w: &[u8], x: &[f32], col: &[f32]) -> f32 {
7439 unsafe {
7441 use core::arch::aarch64::*;
7442 let n = x.len();
7443 let wp = w.as_ptr() as *const i8;
7444 let xp = x.as_ptr();
7445 let cp = col.as_ptr();
7446 let (mut a0, mut a1, mut a2, mut a3) = (
7447 vdupq_n_f32(0.0),
7448 vdupq_n_f32(0.0),
7449 vdupq_n_f32(0.0),
7450 vdupq_n_f32(0.0),
7451 );
7452 let mut j = 0usize;
7453 while j + 16 <= n {
7454 let wb = vld1q_s8(wp.add(j));
7455 let lo = vmovl_s8(vget_low_s8(wb));
7456 let hi = vmovl_s8(vget_high_s8(wb));
7457 let w0 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(lo)));
7458 let w1 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(lo)));
7459 let w2 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(hi)));
7460 let w3 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(hi)));
7461 a0 = vfmaq_f32(
7462 a0,
7463 w0,
7464 vmulq_f32(vld1q_f32(xp.add(j)), vld1q_f32(cp.add(j))),
7465 );
7466 a1 = vfmaq_f32(
7467 a1,
7468 w1,
7469 vmulq_f32(vld1q_f32(xp.add(j + 4)), vld1q_f32(cp.add(j + 4))),
7470 );
7471 a2 = vfmaq_f32(
7472 a2,
7473 w2,
7474 vmulq_f32(vld1q_f32(xp.add(j + 8)), vld1q_f32(cp.add(j + 8))),
7475 );
7476 a3 = vfmaq_f32(
7477 a3,
7478 w3,
7479 vmulq_f32(vld1q_f32(xp.add(j + 12)), vld1q_f32(cp.add(j + 12))),
7480 );
7481 j += 16;
7482 }
7483 let mut sum = vaddvq_f32(vaddq_f32(vaddq_f32(a0, a1), vaddq_f32(a2, a3)));
7484 while j < n {
7485 sum += (*wp.add(j)) as f32 * *xp.add(j) * *cp.add(j);
7486 j += 1;
7487 }
7488 sum
7489 }
7490}
7491
7492#[cfg(target_arch = "aarch64")]
7493#[target_feature(enable = "neon")]
7494unsafe fn dot_i8_f32_neon(w: &[u8], x: &[f32]) -> f32 {
7495 unsafe {
7497 use core::arch::aarch64::*;
7498 let n = x.len();
7499 let wp = w.as_ptr() as *const i8;
7500 let xp = x.as_ptr();
7501 let (mut a0, mut a1, mut a2, mut a3) = (
7502 vdupq_n_f32(0.0),
7503 vdupq_n_f32(0.0),
7504 vdupq_n_f32(0.0),
7505 vdupq_n_f32(0.0),
7506 );
7507 let mut j = 0usize;
7508 while j + 16 <= n {
7509 let wb = vld1q_s8(wp.add(j));
7510 let lo = vmovl_s8(vget_low_s8(wb));
7511 let hi = vmovl_s8(vget_high_s8(wb));
7512 let w0 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(lo)));
7513 let w1 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(lo)));
7514 let w2 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(hi)));
7515 let w3 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(hi)));
7516 a0 = vfmaq_f32(a0, w0, vld1q_f32(xp.add(j)));
7517 a1 = vfmaq_f32(a1, w1, vld1q_f32(xp.add(j + 4)));
7518 a2 = vfmaq_f32(a2, w2, vld1q_f32(xp.add(j + 8)));
7519 a3 = vfmaq_f32(a3, w3, vld1q_f32(xp.add(j + 12)));
7520 j += 16;
7521 }
7522 let mut sum = vaddvq_f32(vaddq_f32(vaddq_f32(a0, a1), vaddq_f32(a2, a3)));
7523 while j < n {
7524 sum += (*wp.add(j)) as f32 * *xp.add(j);
7525 j += 1;
7526 }
7527 sum
7528 }
7529}
7530
7531#[allow(clippy::too_many_arguments)]
7532fn qmatvec(
7533 q: &[u8],
7534 rep: &[u8],
7535 row_scale: &[f32],
7536 x: &[f32],
7537 col_field: &[f32],
7538 dtype: TensorDtype,
7539 rows: usize,
7540 cols: usize,
7541 out: &mut [f32],
7542 pool: Option<&Pool>,
7543) {
7544 debug_assert_eq!(out.len(), rows);
7545 #[cfg(not(target_arch = "aarch64"))]
7546 let _ = rep;
7547
7548 #[cfg(target_arch = "aarch64")]
7549 if sdot_enabled() {
7550 let act = if dtype == TensorDtype::Q8_2f {
7551 split_act_q8_2f(x, col_field)
7552 } else {
7553 split_act(x)
7554 };
7555 let out_addr = SendMut(out.as_mut_ptr());
7556 let run_range = |start: usize, end: usize| {
7557 q8_range_sdot(q, rep, row_scale, &act, cols, out_addr, start, end)
7558 };
7559 match pool {
7560 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
7561 _ => run_range(0, rows),
7562 }
7563 return;
7564 }
7565 #[cfg(target_arch = "x86_64")]
7568 if avx2_a8w8_enabled() {
7569 let act = if dtype == TensorDtype::Q8_2f {
7570 split_act_q8_2f(x, col_field)
7571 } else {
7572 split_act(x)
7573 };
7574 let out_addr = SendMut(out.as_mut_ptr());
7575 let run_range = |start: usize, end: usize| {
7576 q8_range_avx2(q, row_scale, &act, cols, out_addr, start, end)
7577 };
7578 match pool {
7579 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
7580 _ => run_range(0, rows),
7581 }
7582 return;
7583 }
7584
7585 prescale_with(x, col_field, dtype, 1, |xs| {
7586 let out_addr = SendMut(out.as_mut_ptr());
7587 let run_range = move |start: usize, end: usize| {
7588 for o in start..end {
7589 let v = dot_i8_f32(&q[o * cols..(o + 1) * cols], xs) * row_scale[o];
7590 unsafe { *out_addr.at(o) = v };
7592 }
7593 };
7594 match pool {
7595 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
7596 _ => run_range(0, rows),
7597 }
7598 });
7599}
7600
7601#[allow(clippy::too_many_arguments)]
7602fn qmatvec2(
7603 q: &[u8],
7604 row_scale: &[f32],
7605 x1: &[f32],
7606 x2: &[f32],
7607 col_field: &[f32],
7608 dtype: TensorDtype,
7609 rows: usize,
7610 cols: usize,
7611 o1: &mut [f32],
7612 o2: &mut [f32],
7613 pool: Option<&Pool>,
7614) {
7615 #[cfg(target_arch = "aarch64")]
7616 if sdot_enabled() {
7617 let a1s = if dtype == TensorDtype::Q8_2f {
7618 split_act_q8_2f(x1, col_field)
7619 } else {
7620 split_act(x1)
7621 };
7622 let a2s = if dtype == TensorDtype::Q8_2f {
7623 split_act_q8_2f(x2, col_field)
7624 } else {
7625 split_act(x2)
7626 };
7627 let p1 = SendMut(o1.as_mut_ptr());
7628 let p2 = SendMut(o2.as_mut_ptr());
7629 let run_range = |start: usize, end: usize| {
7630 q8_range2_sdot(q, row_scale, &a1s, &a2s, cols, p1, p2, start, end)
7631 };
7632 match pool {
7633 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
7634 _ => run_range(0, rows),
7635 }
7636 return;
7637 }
7638 #[cfg(target_arch = "x86_64")]
7639 if avx2_a8w8_enabled() {
7640 let a1s = if dtype == TensorDtype::Q8_2f {
7641 split_act_q8_2f(x1, col_field)
7642 } else {
7643 split_act(x1)
7644 };
7645 let a2s = if dtype == TensorDtype::Q8_2f {
7646 split_act_q8_2f(x2, col_field)
7647 } else {
7648 split_act(x2)
7649 };
7650 let p1 = SendMut(o1.as_mut_ptr());
7651 let p2 = SendMut(o2.as_mut_ptr());
7652 let run_range = |start: usize, end: usize| {
7653 q8_range2_avx2(q, row_scale, &a1s, &a2s, cols, p1, p2, start, end)
7654 };
7655 match pool {
7656 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
7657 _ => run_range(0, rows),
7658 }
7659 return;
7660 }
7661
7662 prescale_with(x1, col_field, dtype, 1, |x1s| {
7663 prescale_with(x2, col_field, dtype, 2, |x2s| {
7664 let p1 = SendMut(o1.as_mut_ptr());
7665 let p2 = SendMut(o2.as_mut_ptr());
7666 let run_range = move |start: usize, end: usize| {
7667 for o in start..end {
7668 let row = &q[o * cols..(o + 1) * cols];
7669 let s1 = dot_i8_f32(row, x1s) * row_scale[o];
7670 let s2 = dot_i8_f32(row, x2s) * row_scale[o];
7671 unsafe {
7673 *p1.at(o) = s1;
7674 *p2.at(o) = s2;
7675 }
7676 }
7677 };
7678 match pool {
7679 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
7680 _ => run_range(0, rows),
7681 }
7682 });
7683 });
7684}
7685
7686#[derive(Clone, Copy)]
7687struct SendMut(*mut f32);
7688unsafe impl Send for SendMut {}
7689unsafe impl Sync for SendMut {}
7690
7691impl SendMut {
7692 #[inline]
7693 fn at(self, i: usize) -> *mut f32 {
7694 unsafe { self.0.add(i) }
7695 }
7696}
7697
7698#[cfg(test)]
7699mod tests {
7700 use super::*;
7701
7702 #[test]
7703 fn f32_matvec_matches_matvec_rows_bitexact() {
7704 let (rows, cols) = (300, 40);
7705 let w: Vec<f32> = (0..rows * cols).map(|i| (i as f32 * 0.017).sin()).collect();
7706 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.05).cos()).collect();
7707 let qt = QTensor::from_f32(w.clone(), rows, cols);
7708
7709 let mut a = vec![0.0f32; rows];
7710 matvec_rows(None, &w, &x, &mut a);
7711 let mut b = vec![0.0f32; rows];
7712 qt.matvec(&x, &mut b, None);
7713 assert_eq!(a, b);
7714 }
7715
7716 #[test]
7717 fn sdot_kernel_exact_on_grid() {
7718 eprintln!("sdot_enabled = {}", sdot_enabled());
7723 let (rows, cols) = (9, 80); let w: Vec<u8> = (0..rows * cols)
7725 .map(|i| (((i * 37) % 251) as i32 - 125) as i8 as u8)
7726 .collect();
7727 let scales: Vec<f32> = (0..rows).map(|o| 0.005 + o as f32 * 0.001).collect();
7728 let x: Vec<f32> = (0..cols)
7729 .map(|i| match i % 3 {
7730 0 => 1.0,
7731 1 => -1.0,
7732 _ => 0.0,
7733 })
7734 .collect();
7735 let mut a = vec![0.0f32; rows];
7736 qmatvec(
7737 &w,
7738 &[],
7739 &scales,
7740 &x,
7741 &[],
7742 TensorDtype::Q8Row,
7743 rows,
7744 cols,
7745 &mut a,
7746 None,
7747 );
7748 for o in 0..rows {
7749 let mut acc = 0.0f32;
7750 for j in 0..cols {
7751 acc += (w[o * cols + j] as i8) as f32 * x[j];
7752 }
7753 let expect = acc * scales[o];
7754 assert!(
7755 (a[o] - expect).abs() < 1e-3 * expect.abs().max(1e-3),
7756 "row {o}: {} vs {expect}",
7757 a[o]
7758 );
7759 }
7760 }
7761
7762 #[test]
7763 fn q1_tbl_fast_path_matches_reference() {
7764 let (rows, cols) = (5, 256);
7769 let gpr = cols / GROUP_SIZE;
7770 let mut bytes = Vec::new();
7771 for t in 0..rows * gpr {
7772 let s = 0.007 + (t % 11) as f32 * 0.004;
7773 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
7774 for j in 0..4 {
7775 bytes.push(((t * 53 + j * 89 + 7) % 249) as u8);
7776 }
7777 }
7778 let x: Vec<f32> = (0..cols)
7779 .map(|i| if (i * 5) % 7 < 3 { 1.0 } else { -1.0 })
7780 .collect();
7781 let mut w = vec![0.0f32; rows * cols];
7782 cortiq_core::quant::dequant_q1(&bytes, &mut w);
7783 let mut got = vec![0.0f32; rows];
7784 q1_matvec(&bytes, &x, rows, cols, &mut got, None);
7785 for o in 0..rows {
7786 let expect: f32 = (0..cols).map(|j| w[o * cols + j] * x[j]).sum();
7787 assert!(
7788 (got[o] - expect).abs() < 1e-3 * expect.abs().max(1e-3),
7789 "row {o}: {} vs {expect}",
7790 got[o]
7791 );
7792 }
7793 let b = 5usize;
7796 let mut xs_all = Vec::new();
7797 for bi in 0..b {
7798 xs_all.extend(x.iter().map(|v| if bi % 2 == 0 { *v } else { -*v }));
7799 }
7800 let mut mm = vec![0.0f32; b * rows];
7801 q1_matmat(&bytes, &xs_all, b, rows, cols, &mut mm, None);
7802 for bi in 0..b {
7803 let mut single = vec![0.0f32; rows];
7804 q1_matvec(
7805 &bytes,
7806 &xs_all[bi * cols..(bi + 1) * cols],
7807 rows,
7808 cols,
7809 &mut single,
7810 None,
7811 );
7812 assert_eq!(&mm[bi * rows..(bi + 1) * rows], &single[..], "stream {bi}");
7813 }
7814 }
7815
7816 #[test]
7817 fn q1_kernels_match_exact_reference() {
7818 let (rows, cols) = (7, 96);
7820 let gpr = cols / GROUP_SIZE;
7821 let mut bytes = Vec::new();
7822 for t in 0..rows * gpr {
7823 let s = 0.01 + (t % 13) as f32 * 0.003;
7824 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
7825 for j in 0..4 {
7826 bytes.push(((t * 31 + j * 97) % 251) as u8);
7827 }
7828 }
7829 let x: Vec<f32> = (0..cols)
7831 .map(|i| if i % 3 == 0 { 1.0 } else { -1.0 })
7832 .collect();
7833 let mut w = vec![0.0f32; rows * cols];
7835 cortiq_core::quant::dequant_q1(&bytes, &mut w);
7836 let mut expect = vec![0.0f32; rows];
7837 for o in 0..rows {
7838 expect[o] = (0..cols).map(|j| w[o * cols + j] * x[j]).sum();
7839 }
7840 let mut got = vec![0.0f32; rows];
7841 q1_matvec(&bytes, &x, rows, cols, &mut got, None);
7842 for o in 0..rows {
7843 assert!(
7844 (got[o] - expect[o]).abs() < 1e-3 * expect[o].abs().max(1e-3),
7845 "row {o}: {} vs {}",
7846 got[o],
7847 expect[o]
7848 );
7849 }
7850 let x2: Vec<f32> = x.iter().map(|v| -v).collect();
7852 let (mut a1, mut a2) = (vec![0.0f32; rows], vec![0.0f32; rows]);
7853 q1_matvec2(&bytes, &x, &x2, rows, cols, &mut a1, &mut a2, None);
7854 assert_eq!(a1, got);
7855 let mut xs = x.clone();
7856 xs.extend_from_slice(&x2);
7857 let mut mm = vec![0.0f32; 2 * rows];
7858 q1_matmat(&bytes, &xs, 2, rows, cols, &mut mm, None);
7859 assert_eq!(&mm[..rows], got.as_slice());
7860 assert_eq!(&mm[rows..], a2.as_slice());
7861 }
7862
7863 #[test]
7864 fn repack_is_bit_identical() {
7865 let (rows, cols) = (267, 96); let w: Vec<u8> = (0..rows * cols)
7871 .map(|i| (((i * 89) % 253) as i32 - 126) as i8 as u8)
7872 .collect();
7873 let scales: Vec<f32> = (0..rows).map(|o| 0.003 + o as f32 * 0.0007).collect();
7874 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.37).sin() * 2.0).collect();
7875 let rep = q8_repack_layout(&w, rows, cols);
7876 for g in 0..rows / 4 {
7878 for c in 0..cols / 16 {
7879 for lane in 0..4 {
7880 assert_eq!(
7881 &rep[g * 4 * cols + c * 64 + lane * 16
7882 ..g * 4 * cols + c * 64 + lane * 16 + 16],
7883 &w[(g * 4 + lane) * cols + c * 16..(g * 4 + lane) * cols + c * 16 + 16],
7884 );
7885 }
7886 }
7887 }
7888 let mut a = vec![0.0f32; rows];
7889 qmatvec(
7890 &w,
7891 &[],
7892 &scales,
7893 &x,
7894 &[],
7895 TensorDtype::Q8Row,
7896 rows,
7897 cols,
7898 &mut a,
7899 None,
7900 );
7901 let mut b = vec![0.0f32; rows];
7902 qmatvec(
7903 &w,
7904 &rep,
7905 &scales,
7906 &x,
7907 &[],
7908 TensorDtype::Q8Row,
7909 rows,
7910 cols,
7911 &mut b,
7912 None,
7913 );
7914 assert_eq!(a, b, "full-range repack output diverged");
7915
7916 #[cfg(target_arch = "aarch64")]
7917 if sdot_enabled() {
7918 let act = split_act(&x);
7920 let mut c1 = vec![0.0f32; rows];
7921 let mut c2 = vec![0.0f32; rows];
7922 q8_range_sdot(
7923 &w,
7924 &[],
7925 &scales,
7926 &act,
7927 cols,
7928 SendMut(c1.as_mut_ptr()),
7929 3,
7930 rows - 2,
7931 );
7932 q8_range_sdot(
7933 &w,
7934 &rep,
7935 &scales,
7936 &act,
7937 cols,
7938 SendMut(c2.as_mut_ptr()),
7939 3,
7940 rows - 2,
7941 );
7942 assert_eq!(c1, c2, "unaligned-range repack output diverged");
7943 }
7944 }
7945
7946 #[test]
7947 fn sdot_a8w8_noise_is_bounded() {
7948 let (rows, cols) = (16, 512);
7952 let w: Vec<u8> = (0..rows * cols)
7953 .map(|i| (((i * 37) % 251) as i32 - 125) as i8 as u8)
7954 .collect();
7955 let scales = vec![0.01f32; rows];
7956 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.21).sin()).collect();
7957 let mut a = vec![0.0f32; rows];
7958 qmatvec(
7959 &w,
7960 &[],
7961 &scales,
7962 &x,
7963 &[],
7964 TensorDtype::Q8Row,
7965 rows,
7966 cols,
7967 &mut a,
7968 None,
7969 );
7970 let (mut num, mut den) = (0f64, 0f64);
7971 for o in 0..rows {
7972 let mut acc = 0.0f32;
7973 for j in 0..cols {
7974 acc += (w[o * cols + j] as i8) as f32 * x[j];
7975 }
7976 let expect = acc * scales[o];
7977 num += ((a[o] - expect) as f64).powi(2);
7978 den += (expect as f64).powi(2);
7979 }
7980 let rel = (num / den.max(1e-12)).sqrt();
7981 assert!(rel < 0.05, "A8W8 relative L2 error too high: {rel}");
7982 }
7983
7984 #[test]
7985 fn i8_dot_neon_matches_scalar() {
7986 let n = 100;
7987 let w: Vec<u8> = (0..n).map(|i| ((i * 37 + 11) % 251) as u8).collect();
7988 let x: Vec<f32> = (0..n).map(|i| (i as f32 * 0.13).sin()).collect();
7989 let mut scalar = 0.0f32;
7990 for j in 0..n {
7991 scalar += (w[j] as i8) as f32 * x[j];
7992 }
7993 let fast = dot_i8_f32(&w, &x);
7994 assert!((scalar - fast).abs() < 1e-3 * scalar.abs().max(1.0));
7995 }
7996
7997 #[test]
7999 fn vbitmatvec_matches_full_dequant() {
8000 let (rows, cols) = (6, 64);
8001 let ng = cols / GROUP_SIZE;
8002 let bits: Vec<u8> = vec![3, 4, 5, 6, 8, 4];
8004 let mut bytes = bits.clone();
8005 for g in 0..rows * ng {
8006 let s = 0.02 + 0.001 * g as f32;
8007 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
8008 }
8009 for r in 0..rows {
8010 let b = bits[r] as usize;
8011 let (mut acc, mut nb) = (0u64, 0usize);
8012 let mut rowbytes = Vec::new();
8013 for i in 0..cols {
8014 let v = ((i * 7 + r * 13) % (1 << b)) as u64;
8015 acc = (acc << b) | v;
8016 nb += b;
8017 while nb >= 8 {
8018 nb -= 8;
8019 rowbytes.push(((acc >> nb) & 0xFF) as u8);
8020 }
8021 }
8022 if nb > 0 {
8023 rowbytes.push(((acc << (8 - nb)) & 0xFF) as u8);
8024 }
8025 bytes.extend_from_slice(&rowbytes);
8026 }
8027 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.19).sin()).collect();
8028
8029 let mut reference = vec![0f32; rows * cols];
8030 cortiq_core::quant::dequant_vbit(&bytes, rows, cols, &mut reference).unwrap();
8031 let mut expect = vec![0f32; rows];
8032 for r in 0..rows {
8033 expect[r] = reference[r * cols..(r + 1) * cols]
8034 .iter()
8035 .zip(&x)
8036 .map(|(w, xv)| w * xv)
8037 .sum();
8038 }
8039 let mut got = vec![0f32; rows];
8040 let offsets = vbit_row_offsets(&bytes, rows, cols);
8041 vbitmatvec(&bytes, &offsets, &x, rows, cols, &mut got, None);
8042 let tol = if a8w8_enabled() { 6e-2 } else { 1e-4 };
8046 let scale = expect.iter().fold(0f32, |m, v| m.max(v.abs())).max(1e-6);
8047 for r in 0..rows {
8048 assert!(
8049 (got[r] - expect[r]).abs() < tol * scale,
8050 "row {r}: {} vs {}",
8051 got[r],
8052 expect[r]
8053 );
8054 }
8055 }
8056
8057 #[test]
8062 #[cfg(target_arch = "x86_64")]
8063 fn vbit_matmat_blocked_matches_per_row() {
8064 let (rows, cols, b) = (64usize, 128usize, 9usize);
8065 let ng = cols / GROUP_SIZE;
8066 let bits: Vec<u8> = (0..rows).map(|r| [3u8, 4, 5, 6][r % 4]).collect();
8067 let mut bytes = bits.clone();
8068 for g in 0..rows * ng {
8069 let sc = 0.02 + 0.0005 * g as f32;
8070 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(sc).to_le_bytes());
8071 }
8072 for r in 0..rows {
8073 let bw = bits[r] as usize;
8074 let (mut acc, mut nb) = (0u64, 0usize);
8075 let mut rowbytes = Vec::new();
8076 for i in 0..cols {
8077 let v = ((i * 7 + r * 13) % (1 << bw)) as u64;
8078 acc = (acc << bw) | v;
8079 nb += bw;
8080 while nb >= 8 {
8081 nb -= 8;
8082 rowbytes.push(((acc >> nb) & 0xFF) as u8);
8083 }
8084 }
8085 if nb > 0 {
8086 rowbytes.push(((acc << (8 - nb)) & 0xFF) as u8);
8087 }
8088 bytes.extend_from_slice(&rowbytes);
8089 }
8090 let x: Vec<f32> = (0..b * cols)
8091 .map(|i| ((i * 13 + 7) % 97) as f32 / 97.0 - 0.5)
8092 .collect();
8093 let offsets = vbit_row_offsets(&bytes, rows, cols);
8094 let mut y_a = vec![0f32; b * rows];
8095 let mut y_b = vec![0f32; b * rows];
8096 unsafe { std::env::set_var("CMF_X86_BLOCKED", "1") };
8097 vbitmatmat(&bytes, &offsets, &x, b, rows, cols, &mut y_a, None);
8098 unsafe { std::env::set_var("CMF_X86_BLOCKED", "0") };
8099 vbitmatmat(&bytes, &offsets, &x, b, rows, cols, &mut y_b, None);
8100 unsafe { std::env::remove_var("CMF_X86_BLOCKED") };
8101 let max_d = y_a
8102 .iter()
8103 .zip(&y_b)
8104 .map(|(p, q)| (p - q).abs())
8105 .fold(0.0f32, f32::max);
8106 assert!(max_d < 1e-4, "vbit blocked ≠ per-row: max|Δ| = {max_d}");
8107 }
8108
8109 #[test]
8117 fn q4t_matmat_blocked_matches_per_row() {
8118 let (rows, cols, b) = (16usize, 64usize, 9usize);
8119 let gpr = cols / GROUP_SIZE;
8120 let mut bytes = vec![0u8; rows * gpr * Q4_TILE];
8121 for r in 0..rows {
8122 for g in 0..gpr {
8123 let t = (r * gpr + g) * Q4_TILE;
8124 let sc = 0.02 + 0.001 * (r * gpr + g) as f32;
8125 bytes[t..t + 2].copy_from_slice(&cortiq_core::quant::f32_to_f16(sc).to_le_bytes());
8126 for k in 0..16 {
8127 bytes[t + 2 + k] = ((r * 31 + g * 7 + k * 13) % 251) as u8;
8128 }
8129 }
8130 }
8131 let x: Vec<f32> = (0..b * cols)
8132 .map(|i| ((i * 13 + 7) % 97) as f32 / 97.0 - 0.5)
8133 .collect();
8134 let mut y_blk = vec![0f32; b * rows];
8135 let mut y_row = vec![0f32; b * rows];
8136 unsafe { std::env::set_var("CMF_X86_BLOCKED", "1") };
8137 q4t_matmat(&bytes, &x, b, rows, cols, &mut y_blk, None);
8138 unsafe { std::env::set_var("CMF_X86_BLOCKED", "0") };
8139 q4t_matmat(&bytes, &x, b, rows, cols, &mut y_row, None);
8140 unsafe { std::env::remove_var("CMF_X86_BLOCKED") };
8141 assert_eq!(y_blk, y_row, "q4t blocked 1x4 ≠ per-row");
8142 }
8143
8144 #[cfg(target_os = "macos")]
8148 #[test]
8149 fn q4t_matmat_accel_matches_dequant_reference() {
8150 if !accel_gemm_enabled() {
8151 return; }
8153 let (rows, cols, b) = (512usize, 1024usize, 8usize); let gpr = cols / GROUP_SIZE;
8155 let mut bytes = vec![0u8; rows * gpr * Q4_TILE];
8156 for r in 0..rows {
8157 for g in 0..gpr {
8158 let t = (r * gpr + g) * Q4_TILE;
8159 let sc = 0.02 + 0.0005 * ((r * gpr + g) % 64) as f32;
8160 bytes[t..t + 2].copy_from_slice(&cortiq_core::quant::f32_to_f16(sc).to_le_bytes());
8161 for k in 0..16 {
8162 bytes[t + 2 + k] = ((r * 31 + g * 7 + k * 13) % 251) as u8;
8163 }
8164 }
8165 }
8166 let x: Vec<f32> = (0..b * cols)
8167 .map(|i| ((i * 13 + 7) % 97) as f32 / 97.0 - 0.5)
8168 .collect();
8169 let mut got = vec![0f32; b * rows];
8170 q4t_matmat(&bytes, &x, b, rows, cols, &mut got, None);
8171 let mut w = vec![0f32; rows * cols];
8173 for r in 0..rows {
8174 for g in 0..gpr {
8175 let t = (r * gpr + g) * Q4_TILE;
8176 let s = f16_to_f32(u16::from_le_bytes([bytes[t], bytes[t + 1]]));
8177 for (k, &bb) in bytes[t + 2..t + Q4_TILE].iter().enumerate() {
8178 w[r * cols + g * GROUP_SIZE + k * 2] = ((bb & 0x0F) as f32 - 8.0) * s;
8179 w[r * cols + g * GROUP_SIZE + k * 2 + 1] =
8180 (((bb >> 4) & 0x0F) as f32 - 8.0) * s;
8181 }
8182 }
8183 }
8184 for bi in 0..b {
8185 for r in 0..rows {
8186 let want: f32 = (0..cols).map(|j| x[bi * cols + j] * w[r * cols + j]).sum();
8187 let d = (got[bi * rows + r] - want).abs();
8188 assert!(
8189 d <= want.abs().max(1.0) * 1e-4,
8190 "accel q4t GEMM diverged at ({bi},{r}): {} vs {want}",
8191 got[bi * rows + r]
8192 );
8193 }
8194 }
8195 }
8196
8197 #[test]
8198 fn q4matvec_matches_full_dequant() {
8199 let (rows, cols) = (8, 64);
8200 let groups = rows * cols / GROUP_SIZE;
8201 let mut bytes = Vec::with_capacity(groups * 16 + groups * 2);
8203 for i in 0..groups * 16 {
8204 bytes.push((((i * 7 + 3) % 256) & 0xFF) as u8);
8205 }
8206 for g in 0..groups {
8207 let s = 0.01 + 0.003 * g as f32;
8208 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
8209 }
8210 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.17).sin()).collect();
8211
8212 let mut reference = vec![0.0f32; rows * cols];
8213 cortiq_core::quant::dequant_q4_block(&bytes, &mut reference);
8214 let mut expect = vec![0.0f32; rows];
8215 for r in 0..rows {
8216 expect[r] = reference[r * cols..(r + 1) * cols]
8217 .iter()
8218 .zip(&x)
8219 .map(|(w, xv)| w * xv)
8220 .sum();
8221 }
8222
8223 let mut got = vec![0.0f32; rows];
8224 q4matvec(&bytes, &x, rows, cols, &mut got, None);
8225 let tol = if a8w8_enabled() { 6e-2 } else { 1e-4 };
8229 let scale = expect.iter().fold(0f32, |m, v| m.max(v.abs())).max(1.0);
8230 for r in 0..rows {
8231 assert!(
8232 (got[r] - expect[r]).abs() < tol * scale,
8233 "row {r}: {} vs {}",
8234 got[r],
8235 expect[r]
8236 );
8237 }
8238 }
8239
8240 #[test]
8243 fn vbitmatvec2_equals_two_singles() {
8244 let (rows, cols) = (6, 64);
8245 let ng = cols / GROUP_SIZE;
8246 let bits: Vec<u8> = vec![3, 4, 5, 6, 8, 4];
8247 let mut bytes = bits.clone();
8248 for g in 0..rows * ng {
8249 let s = 0.02 + 0.001 * g as f32;
8250 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
8251 }
8252 for r in 0..rows {
8253 let b = bits[r] as usize;
8254 let (mut acc, mut nb) = (0u64, 0usize);
8255 let mut rowbytes = Vec::new();
8256 for i in 0..cols {
8257 let v = ((i * 7 + r * 13) % (1 << b)) as u64;
8258 acc = (acc << b) | v;
8259 nb += b;
8260 while nb >= 8 {
8261 nb -= 8;
8262 rowbytes.push(((acc >> nb) & 0xFF) as u8);
8263 }
8264 }
8265 if nb > 0 {
8266 rowbytes.push(((acc << (8 - nb)) & 0xFF) as u8);
8267 }
8268 bytes.extend_from_slice(&rowbytes);
8269 }
8270 let x1: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.19).sin()).collect();
8271 let x2: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.11).cos()).collect();
8272 let offsets = vbit_row_offsets(&bytes, rows, cols);
8273
8274 let (mut a1, mut a2) = (vec![0f32; rows], vec![0f32; rows]);
8275 vbitmatvec(&bytes, &offsets, &x1, rows, cols, &mut a1, None);
8276 vbitmatvec(&bytes, &offsets, &x2, rows, cols, &mut a2, None);
8277 let (mut b1, mut b2) = (vec![0f32; rows], vec![0f32; rows]);
8278 vbitmatvec2(
8279 &bytes, &offsets, &x1, &x2, rows, cols, &mut b1, &mut b2, None,
8280 );
8281 assert_eq!(a1, b1, "fused vbit lane 1 must be bit-identical");
8282 assert_eq!(a2, b2, "fused vbit lane 2 must be bit-identical");
8283 }
8284
8285 #[test]
8287 fn q4matvec2_equals_two_singles() {
8288 let (rows, cols) = (8, 128);
8289 let groups = rows * cols / GROUP_SIZE;
8290 let mut bytes = Vec::with_capacity(groups * 16 + groups * 2);
8291 for i in 0..groups * 16 {
8292 bytes.push((((i * 7 + 3) % 256) & 0xFF) as u8);
8293 }
8294 for g in 0..groups {
8295 let s = 0.01 + 0.003 * g as f32;
8296 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
8297 }
8298 let mut x1: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.17).sin()).collect();
8301 x1[9] = 250.0;
8302 let x2: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.23).cos()).collect();
8303
8304 let (mut a1, mut a2) = (vec![0f32; rows], vec![0f32; rows]);
8305 q4matvec(&bytes, &x1, rows, cols, &mut a1, None);
8306 q4matvec(&bytes, &x2, rows, cols, &mut a2, None);
8307 let (mut b1, mut b2) = (vec![0f32; rows], vec![0f32; rows]);
8308 q4matvec2(&bytes, &x1, &x2, rows, cols, &mut b1, &mut b2, None);
8309 assert_eq!(a1, b1, "fused q4 lane 1 must be bit-identical");
8310 assert_eq!(a2, b2, "fused q4 lane 2 must be bit-identical");
8311 }
8312
8313 #[test]
8316 fn matvec_many_equals_separate_matvecs() {
8317 use crate::pool::Pool;
8318 let (r1, r2, cols) = (300, 200, 64);
8319 let mk = |salt: usize, rows: usize| {
8320 QTensor::from_f32(
8321 (0..rows * cols)
8322 .map(|i| ((i * 7 + salt) % 97) as f32 / 97.0 - 0.5)
8323 .collect(),
8324 rows,
8325 cols,
8326 )
8327 };
8328 let (a, b) = (mk(1, r1), mk(5, r2));
8329 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.11).sin()).collect();
8330 let pool = Pool::new(3);
8331
8332 let (mut ea, mut eb) = (vec![0f32; r1], vec![0f32; r2]);
8333 a.matvec(&x, &mut ea, Some(&pool));
8334 b.matvec(&x, &mut eb, Some(&pool));
8335 let (mut ga, mut gb) = (vec![0f32; r1], vec![0f32; r2]);
8336 QTensor::matvec_many([&a, &b], &x, [&mut ga, &mut gb], Some(&pool));
8337 assert_eq!(ea, ga, "fused multi-matrix lane 1 must be bit-identical");
8338 assert_eq!(eb, gb, "fused multi-matrix lane 2 must be bit-identical");
8339 }
8340
8341 #[test]
8344 fn batched_matmat_equals_per_position_matvec() {
8345 let (rows, cols, b) = (8, 64, 5);
8346 let groups = rows * cols / GROUP_SIZE;
8348 let mut q4 = Vec::new();
8349 for i in 0..groups * 16 {
8350 q4.push((((i * 7 + 3) % 256) & 0xFF) as u8);
8351 }
8352 for g in 0..groups {
8353 q4.extend_from_slice(
8354 &cortiq_core::quant::f32_to_f16(0.01 + 0.003 * g as f32).to_le_bytes(),
8355 );
8356 }
8357 let ng = cols / GROUP_SIZE;
8359 let bits: Vec<u8> = vec![3, 4, 5, 6, 8, 4, 5, 3];
8360 let mut vb = bits.clone();
8361 for g in 0..rows * ng {
8362 vb.extend_from_slice(
8363 &cortiq_core::quant::f32_to_f16(0.02 + 0.001 * g as f32).to_le_bytes(),
8364 );
8365 }
8366 for r in 0..rows {
8367 let bw = bits[r] as usize;
8368 let (mut acc, mut nb) = (0u64, 0usize);
8369 let mut rowbytes = Vec::new();
8370 for i in 0..cols {
8371 let v = ((i * 7 + r * 13) % (1 << bw)) as u64;
8372 acc = (acc << bw) | v;
8373 nb += bw;
8374 while nb >= 8 {
8375 nb -= 8;
8376 rowbytes.push(((acc >> nb) & 0xFF) as u8);
8377 }
8378 }
8379 if nb > 0 {
8380 rowbytes.push(((acc << (8 - nb)) & 0xFF) as u8);
8381 }
8382 vb.extend_from_slice(&rowbytes);
8383 }
8384 let offsets = vbit_row_offsets(&vb, rows, cols);
8385
8386 let xs: Vec<f32> = (0..b * cols).map(|i| (i as f32 * 0.13).sin()).collect();
8387
8388 let mut got = vec![0f32; b * rows];
8390 q4matmat(&q4, &xs, b, rows, cols, &mut got, None);
8391 for bi in 0..b {
8392 let mut expect = vec![0f32; rows];
8393 q4matvec(
8394 &q4,
8395 &xs[bi * cols..(bi + 1) * cols],
8396 rows,
8397 cols,
8398 &mut expect,
8399 None,
8400 );
8401 assert_eq!(
8402 &got[bi * rows..(bi + 1) * rows],
8403 &expect[..],
8404 "q4 batch pos {bi}"
8405 );
8406 }
8407
8408 let mut got = vec![0f32; b * rows];
8410 vbitmatmat(&vb, &offsets, &xs, b, rows, cols, &mut got, None);
8411 for bi in 0..b {
8412 let mut expect = vec![0f32; rows];
8413 vbitmatvec(
8414 &vb,
8415 &offsets,
8416 &xs[bi * cols..(bi + 1) * cols],
8417 rows,
8418 cols,
8419 &mut expect,
8420 None,
8421 );
8422 assert_eq!(
8423 &got[bi * rows..(bi + 1) * rows],
8424 &expect[..],
8425 "vbit batch pos {bi}"
8426 );
8427 }
8428 }
8429
8430 #[test]
8434 fn q4_tiled_matches_q4_block_bitexact() {
8435 let (rows, cols, b) = (8usize, 128usize, 3usize);
8436 let groups = rows * cols / GROUP_SIZE;
8437 let mut split = Vec::with_capacity(groups * 18);
8438 for i in 0..groups * 16 {
8439 split.push((((i * 7 + 3) % 256) & 0xFF) as u8);
8440 }
8441 for g in 0..groups {
8442 split.extend_from_slice(
8443 &cortiq_core::quant::f32_to_f16(0.01 + 0.003 * g as f32).to_le_bytes(),
8444 );
8445 }
8446 let (packed, scales) = split.split_at(groups * 16);
8448 let mut tiled = Vec::with_capacity(groups * Q4_TILE);
8449 for g in 0..groups {
8450 tiled.extend_from_slice(&scales[g * 2..g * 2 + 2]);
8451 tiled.extend_from_slice(&packed[g * 16..(g + 1) * 16]);
8452 }
8453
8454 let mut x1: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.17).sin()).collect();
8455 x1[9] = 250.0; let x2: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.23).cos()).collect();
8457
8458 let (mut a, mut t) = (vec![0f32; rows], vec![0f32; rows]);
8459 q4matvec(&split, &x1, rows, cols, &mut a, None);
8460 q4t_matvec(&tiled, &x1, rows, cols, &mut t, None);
8461 assert_eq!(a, t, "q4t matvec must match q4 bit-for-bit");
8462
8463 let (mut a1, mut a2) = (vec![0f32; rows], vec![0f32; rows]);
8464 let (mut t1, mut t2) = (vec![0f32; rows], vec![0f32; rows]);
8465 q4matvec2(&split, &x1, &x2, rows, cols, &mut a1, &mut a2, None);
8466 q4t_matvec2(&tiled, &x1, &x2, rows, cols, &mut t1, &mut t2, None);
8467 assert_eq!(a1, t1);
8468 assert_eq!(a2, t2);
8469
8470 let xs: Vec<f32> = (0..b * cols).map(|i| (i as f32 * 0.13).sin()).collect();
8471 let (mut am, mut tm) = (vec![0f32; b * rows], vec![0f32; b * rows]);
8472 q4matmat(&split, &xs, b, rows, cols, &mut am, None);
8473 q4t_matmat(&tiled, &xs, b, rows, cols, &mut tm, None);
8474 assert_eq!(am, tm, "q4t matmat must match q4 bit-for-bit");
8475 }
8476
8477 #[test]
8484 fn q4matvec_sdot_outlier_exact() {
8485 let (rows, cols) = (4, 128);
8486 let groups = rows * cols / GROUP_SIZE;
8487 let mut bytes = Vec::with_capacity(groups * 16 + groups * 2);
8488 for i in 0..groups * 16 {
8489 bytes.push(((i * 11 + 5) % 256) as u8);
8490 }
8491 for g in 0..groups {
8492 let s = 0.02 + 0.002 * g as f32;
8493 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
8494 }
8495 let mut x: Vec<f32> = (0..cols)
8496 .map(|i| match i % 3 {
8497 0 => 1.0,
8498 1 => -1.0,
8499 _ => 0.0,
8500 })
8501 .collect();
8502 x[17] = 300.0; let mut reference = vec![0.0f32; rows * cols];
8505 cortiq_core::quant::dequant_q4_block(&bytes, &mut reference);
8506 let mut expect = vec![0.0f32; rows];
8507 for r in 0..rows {
8508 expect[r] = reference[r * cols..(r + 1) * cols]
8509 .iter()
8510 .zip(&x)
8511 .map(|(w, xv)| w * xv)
8512 .sum();
8513 }
8514 let mut got = vec![0.0f32; rows];
8515 q4matvec(&bytes, &x, rows, cols, &mut got, None);
8516 let scale = expect.iter().fold(0f32, |m, v| m.max(v.abs())).max(1.0);
8517 for r in 0..rows {
8518 assert!(
8519 (got[r] - expect[r]).abs() < 2e-3 * scale,
8520 "row {r}: {} vs {} (outlier term must be exact)",
8521 got[r],
8522 expect[r]
8523 );
8524 }
8525 }
8526
8527 #[test]
8531 fn q1t_matvec_matches_reference() {
8532 use cortiq_core::quant::{dequant_q1t, f32_to_f16};
8533 let (rows, cols) = (3usize, 64usize); let gpr = cols / GROUP_SIZE;
8535 let scales = [0.5f32, 0.3, 0.7, 0.2, 0.6, 0.15];
8536 let outliers: [(u32, f32); 3] = [(5, 9.0), (70, -4.5), (150, 3.25)];
8538 let is_out = |flat: usize| outliers.iter().any(|&(i, _)| i as usize == flat);
8539 let mut bytes = Vec::new();
8540 for r in 0..rows {
8541 for g in 0..gpr {
8542 bytes.extend_from_slice(&f32_to_f16(scales[r * gpr + g]).to_le_bytes());
8543 let mut c = [0u8; 7];
8544 for k in 0..GROUP_SIZE {
8545 let code = if is_out(r * cols + g * GROUP_SIZE + k) {
8547 0
8548 } else {
8549 ((k + r * 3 + g) % 3) as u8 };
8551 cortiq_core::quant::q1t_pack(&mut c, k, code);
8552 }
8553 bytes.extend_from_slice(&c);
8554 }
8555 }
8556 let mut row_ptr = vec![0u32; rows + 1];
8559 for &(idx, _) in &outliers {
8560 row_ptr[idx as usize / cols + 1] += 1;
8561 }
8562 for r in 0..rows {
8563 row_ptr[r + 1] += row_ptr[r];
8564 }
8565 for &p in &row_ptr {
8566 bytes.extend_from_slice(&p.to_le_bytes());
8567 }
8568 for &(idx, v) in &outliers {
8569 bytes.extend_from_slice(&((idx as usize % cols) as u16).to_le_bytes());
8570 bytes.extend_from_slice(&f32_to_f16(v).to_le_bytes());
8571 }
8572
8573 let mut refw = vec![0f32; rows * cols];
8574 dequant_q1t(&bytes, rows, cols, &mut refw);
8575 let x: Vec<f32> = (0..cols)
8578 .map(|j| if j % 3 == 0 { 1.0 } else { -1.0 })
8579 .collect();
8580 let mut expect = vec![0f32; rows];
8581 for r in 0..rows {
8582 let mut a = 0.0f32;
8583 for j in 0..cols {
8584 a += refw[r * cols + j] * x[j];
8585 }
8586 expect[r] = a;
8587 }
8588 let tol = |e: f32| 1e-3 * e.abs().max(1e-3);
8589 let mut got = vec![0f32; rows];
8590 q1t_matvec(&bytes, &x, rows, cols, &mut got, None);
8591 for r in 0..rows {
8592 assert!(
8593 (got[r] - expect[r]).abs() < tol(expect[r]),
8594 "row {r}: {} vs {}",
8595 got[r],
8596 expect[r]
8597 );
8598 }
8599 let x2: Vec<f32> = x.iter().chain(x.iter().map(|v| v)).copied().collect();
8601 let mut gm = vec![0f32; 2 * rows];
8602 q1t_matmat(&bytes, &x2, 2, rows, cols, &mut gm, None);
8603 for r in 0..rows {
8604 assert!((gm[r] - expect[r]).abs() < tol(expect[r]));
8605 assert!((gm[rows + r] - expect[r]).abs() < tol(expect[r]));
8606 }
8607 let xb: Vec<f32> = (0..cols)
8611 .map(|j| if j % 5 == 0 { -1.0 } else { 1.0 })
8612 .collect();
8613 let (mut s1, mut s2) = (vec![0f32; rows], vec![0f32; rows]);
8614 q1t_matvec(&bytes, &x, rows, cols, &mut s1, None);
8615 q1t_matvec(&bytes, &xb, rows, cols, &mut s2, None);
8616 let (mut p1, mut p2) = (vec![0f32; rows], vec![0f32; rows]);
8617 q1t_matvec2(&bytes, &x, &xb, rows, cols, &mut p1, &mut p2, None);
8618 assert_eq!(p1, s1, "q1t pair lane 1 ≠ single matvec");
8619 assert_eq!(p2, s2, "q1t pair lane 2 ≠ single matvec");
8620 }
8621
8622 #[test]
8625 fn q1t_matvec2_odd_gpr_matches_singles() {
8626 use cortiq_core::quant::{Q1T_TILE, f32_to_f16, q1t_pack};
8627 let (rows, cols) = (5usize, 96usize); let gpr = cols / GROUP_SIZE;
8629 let mut bytes = Vec::with_capacity(rows * gpr * Q1T_TILE);
8630 for r in 0..rows {
8631 for g in 0..gpr {
8632 bytes.extend_from_slice(&f32_to_f16(0.1 + 0.05 * (r + g) as f32).to_le_bytes());
8633 let mut c = [0u8; 7];
8634 for k in 0..GROUP_SIZE {
8635 q1t_pack(&mut c, k, ((k * 7 + r * 5 + g * 3) % 3) as u8);
8636 }
8637 bytes.extend_from_slice(&c);
8638 }
8639 }
8640 let x1: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.31).sin()).collect();
8641 let x2: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.17).cos()).collect();
8642 let (mut s1, mut s2) = (vec![0f32; rows], vec![0f32; rows]);
8643 q1t_matvec(&bytes, &x1, rows, cols, &mut s1, None);
8644 q1t_matvec(&bytes, &x2, rows, cols, &mut s2, None);
8645 let (mut p1, mut p2) = (vec![0f32; rows], vec![0f32; rows]);
8646 q1t_matvec2(&bytes, &x1, &x2, rows, cols, &mut p1, &mut p2, None);
8647 assert_eq!(p1, s1, "odd-gpr pair lane 1 ≠ single");
8648 assert_eq!(p2, s2, "odd-gpr pair lane 2 ≠ single");
8649 }
8650
8651 #[test]
8655 #[ignore]
8656 fn q1t_matvec2_speed() {
8657 use cortiq_core::quant::{Q1T_TILE, f32_to_f16, q1t_pack};
8658 use std::time::Instant;
8659 let (rows, cols) = (8192usize, 4096usize);
8660 let gpr = cols / GROUP_SIZE;
8661 let mut bytes = Vec::with_capacity(rows * gpr * Q1T_TILE);
8662 for r in 0..rows {
8663 for g in 0..gpr {
8664 let s = 0.1 + ((r + g) % 7) as f32 * 0.01;
8665 bytes.extend_from_slice(&f32_to_f16(s).to_le_bytes());
8666 let mut c = [0u8; 7];
8667 for k in 0..GROUP_SIZE {
8668 q1t_pack(&mut c, k, ((k * 7 + r + g) % 3) as u8);
8669 }
8670 bytes.extend_from_slice(&c);
8671 }
8672 }
8673 let x1: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.31).sin()).collect();
8674 let x2: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.17).cos()).collect();
8675 let (mut s1, mut s2) = (vec![0f32; rows], vec![0f32; rows]);
8676 let (mut p1, mut p2) = (vec![0f32; rows], vec![0f32; rows]);
8677 q1t_matvec(&bytes, &x1, rows, cols, &mut s1, None);
8679 q1t_matvec2(&bytes, &x1, &x2, rows, cols, &mut p1, &mut p2, None);
8680 let (mut t_pair, mut t_two) = (f64::MAX, f64::MAX);
8681 for _ in 0..8 {
8682 let t0 = Instant::now();
8683 q1t_matvec2(&bytes, &x1, &x2, rows, cols, &mut p1, &mut p2, None);
8684 t_pair = t_pair.min(t0.elapsed().as_secs_f64() * 1000.0);
8685 let t1 = Instant::now();
8686 q1t_matvec(&bytes, &x1, rows, cols, &mut s1, None);
8687 q1t_matvec(&bytes, &x2, rows, cols, &mut s2, None);
8688 t_two = t_two.min(t1.elapsed().as_secs_f64() * 1000.0);
8689 }
8690 assert_eq!(p1, s1);
8691 assert_eq!(p2, s2);
8692 println!("q1t pair {rows}x{cols}: fused {t_pair:.2} ms | two singles {t_two:.2} ms");
8693 }
8694
8695 #[test]
8699 #[ignore]
8700 fn q1t_matvec_speed() {
8701 use cortiq_core::quant::{Q1T_TILE, f32_to_f16, q1t_code, q1t_pack};
8702 use std::time::Instant;
8703 let (rows, cols) = (8192usize, 4096usize); let gpr = cols / GROUP_SIZE;
8705 let mut bytes = Vec::with_capacity(rows * gpr * Q1T_TILE + 16);
8706 for r in 0..rows {
8707 for g in 0..gpr {
8708 let s = 0.1 + ((r + g) % 7) as f32 * 0.01;
8709 bytes.extend_from_slice(&f32_to_f16(s).to_le_bytes());
8710 let mut c = [0u8; 7];
8711 for k in 0..GROUP_SIZE {
8712 q1t_pack(&mut c, k, ((k * 7 + r + g) % 3) as u8);
8713 }
8714 bytes.extend_from_slice(&c);
8715 }
8716 }
8717 let (n, stride) = (rows * cols, 40usize); let mut row_ptr = vec![0u32; rows + 1];
8719 let mut idx = 0usize;
8720 while idx < n {
8721 row_ptr[idx / cols + 1] += 1;
8722 idx += stride;
8723 }
8724 for r in 0..rows {
8725 row_ptr[r + 1] += row_ptr[r];
8726 }
8727 for &p in &row_ptr {
8728 bytes.extend_from_slice(&p.to_le_bytes());
8729 }
8730 let mut idx = 0usize;
8731 while idx < n {
8732 bytes.extend_from_slice(&((idx % cols) as u16).to_le_bytes());
8733 bytes.extend_from_slice(&f32_to_f16((idx % 13) as f32 * 0.1 - 0.6).to_le_bytes());
8734 idx += stride;
8735 }
8736 let x: Vec<f32> = (0..cols)
8739 .map(|j| if j % 3 == 0 { 1.0 } else { -1.0 })
8740 .collect();
8741 let (rp_off, ent_off, has_ov) = q1t_overlay(&bytes, rows * gpr * Q1T_TILE, rows);
8742
8743 let slow = |out: &mut [f32]| {
8745 let mut buf = vec![0f32; cols];
8746 for r in 0..rows {
8747 for g in 0..gpr {
8748 let off = (r * gpr + g) * Q1T_TILE;
8749 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
8750 let codes = &bytes[off + 2..off + Q1T_TILE];
8751 for k in 0..GROUP_SIZE {
8752 buf[g * GROUP_SIZE + k] = match q1t_code(codes, k) {
8753 1 => s,
8754 2 => -s,
8755 _ => 0.0,
8756 };
8757 }
8758 }
8759 out[r] = q1t_row_outlier_correction(&bytes, r, rp_off, ent_off, has_ov, &x)
8760 + (0..cols).map(|j| buf[j] * x[j]).sum::<f32>();
8761 }
8762 };
8763 let iters = 5;
8764 let mut a = vec![0f32; rows];
8765 slow(&mut a); let t = Instant::now();
8767 for _ in 0..iters {
8768 slow(&mut a);
8769 }
8770 let slow_ms = t.elapsed().as_secs_f64() * 1e3 / iters as f64;
8771
8772 let mut b = vec![0f32; rows];
8773 q1t_matvec(&bytes, &x, rows, cols, &mut b, None); let t = Instant::now();
8775 for _ in 0..iters {
8776 q1t_matvec(&bytes, &x, rows, cols, &mut b, None);
8777 }
8778 let fast_ms = t.elapsed().as_secs_f64() * 1e3 / iters as f64;
8779
8780 for r in 0..rows {
8781 assert!((a[r] - b[r]).abs() < 1e-2, "mismatch row {r}");
8782 }
8783 println!(
8784 "q1t matvec {rows}x{cols} (1 thread): div-decode {slow_ms:.2} ms fused-LUT {fast_ms:.2} ms => {:.2}x",
8785 slow_ms / fast_ms
8786 );
8787 }
8788}