1use crate::pool::{matvec_rows, matvec_rows2, Pool};
15use cortiq_core::quant::{f16_to_f32, GROUP_SIZE, Q1_TILE, Q4_TILE};
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").map(|v| v == "1").unwrap_or(cfg!(target_os = "android"))
61 })
62}
63
64fn q8_repack(bytes: &[u8], rows: usize, cols: usize) -> Vec<u8> {
68 #[cfg(target_arch = "aarch64")]
69 let arch_ok = sdot_enabled();
70 #[cfg(not(target_arch = "aarch64"))]
71 let arch_ok = false;
72 if !arch_ok || !repack_enabled() || rows < 256 || cols % 16 != 0 {
73 return Vec::new();
74 }
75 q8_repack_layout(bytes, rows, cols)
76}
77
78fn q8_repack_layout(bytes: &[u8], rows: usize, cols: usize) -> Vec<u8> {
81 let groups = rows / 4;
82 let mut rep = vec![0u8; groups * 4 * cols];
83 for g in 0..groups {
84 let dst = &mut rep[g * 4 * cols..(g + 1) * 4 * cols];
85 for c in 0..cols / 16 {
86 for lane in 0..4 {
87 let src = (g * 4 + lane) * cols + c * 16;
88 dst[c * 64 + lane * 16..c * 64 + lane * 16 + 16]
89 .copy_from_slice(&bytes[src..src + 16]);
90 }
91 }
92 }
93 rep
94}
95
96fn vbit_row_offsets(bytes: &[u8], rows: usize, cols: usize) -> Vec<usize> {
99 let ng = cols / GROUP_SIZE;
100 let bits = &bytes[..rows];
101 let mut offsets = Vec::with_capacity(rows + 1);
102 let mut off = rows + rows * ng * 2;
103 for r in 0..rows {
104 offsets.push(off);
105 off += (cols * bits[r] as usize).div_ceil(8);
106 }
107 offsets.push(off);
108 offsets
109}
110
111impl QTensor {
112 pub fn from_f32(data: Vec<f32>, rows: usize, cols: usize) -> Self {
113 debug_assert_eq!(data.len(), rows * cols);
114 Self::F32 { data, rows, cols }
115 }
116
117 pub fn from_model(model: &Arc<CmfModel>, name: &str) -> Result<Self, String> {
120 let idx = model
123 .tensor_index(name)
124 .ok_or_else(|| format!("tensor '{name}' not found in CMF directory"))?;
125 let entry = &model.tensors[idx];
126 if entry.shape.len() != 2 {
127 return Err(format!("QTensor::from_model needs 2-D, got '{name}'"));
128 }
129 let (rows, cols) = (entry.shape[0], entry.shape[1]);
130 let bytes = model.entry_bytes(entry);
131
132 match entry.dtype {
133 TensorDtype::Q8Row | TensorDtype::Q8_2f => {
134 let n = rows * cols;
135 let scales_off = n;
136 let row_scale: Vec<f32> = (0..rows)
137 .map(|o| {
138 f16_to_f32(u16::from_le_bytes([
139 bytes[scales_off + o * 2],
140 bytes[scales_off + o * 2 + 1],
141 ]))
142 })
143 .collect();
144 let col_field: Vec<f32> = if entry.dtype == TensorDtype::Q8_2f {
145 let col_off = n + rows * 2;
146 (0..cols)
147 .map(|i| {
148 f16_to_f32(u16::from_le_bytes([
149 bytes[col_off + i * 2],
150 bytes[col_off + i * 2 + 1],
151 ]))
152 })
153 .collect()
154 } else {
155 Vec::new()
156 };
157 Ok(Self::Mapped {
158 model: model.clone(),
159 idx,
160 dtype: entry.dtype,
161 rows,
162 cols,
163 row_scale,
164 col_field,
165 vbit_offsets: Vec::new(),
166 repack: q8_repack(bytes, rows, cols),
167 })
168 }
169 TensorDtype::Vbit if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
171 model: model.clone(),
172 idx,
173 dtype: entry.dtype,
174 rows,
175 cols,
176 row_scale: Vec::new(),
177 col_field: Vec::new(),
178 vbit_offsets: vbit_row_offsets(bytes, rows, cols),
179 repack: Vec::new(),
180 }),
181 TensorDtype::VbitRo if cols % GROUP_SIZE == 0 => {
185 let (_, off_off, packed_off) =
186 cortiq_core::quant::vbit_ro_sections(rows, cols);
187 let offsets: Vec<usize> = (0..=rows)
188 .map(|r| {
189 packed_off + cortiq_core::quant::vbit_ro_offset(bytes, off_off, r)
190 })
191 .collect();
192 Ok(Self::Mapped {
193 model: model.clone(),
194 idx,
195 dtype: entry.dtype,
196 rows,
197 cols,
198 row_scale: Vec::new(),
199 col_field: Vec::new(),
200 vbit_offsets: offsets,
201 repack: Vec::new(),
202 })
203 }
204 TensorDtype::Q4Tiled if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
210 model: model.clone(),
211 idx,
212 dtype: entry.dtype,
213 rows,
214 cols,
215 row_scale: Vec::new(),
216 col_field: Vec::new(),
217 vbit_offsets: Vec::new(),
218 repack: Vec::new(),
219 }),
220 TensorDtype::Q4Block if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
221 model: model.clone(),
222 idx,
223 dtype: entry.dtype,
224 rows,
225 cols,
226 row_scale: Vec::new(),
227 col_field: Vec::new(),
228 vbit_offsets: Vec::new(),
229 repack: Vec::new(),
230 }),
231 TensorDtype::Q1 if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
233 model: model.clone(),
234 idx,
235 dtype: entry.dtype,
236 rows,
237 cols,
238 row_scale: Vec::new(),
239 col_field: Vec::new(),
240 vbit_offsets: Vec::new(),
241 repack: Vec::new(),
242 }),
243 TensorDtype::Q1T if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
247 model: model.clone(),
248 idx,
249 dtype: entry.dtype,
250 rows,
251 cols,
252 row_scale: Vec::new(),
253 col_field: Vec::new(),
254 vbit_offsets: Vec::new(),
255 repack: Vec::new(),
256 }),
257 _ => {
259 let mut data = vec![0.0f32; rows * cols];
260 cortiq_core::quant::dequant_tensor(entry, bytes, &mut data)?;
261 Ok(Self::from_f32(data, rows, cols))
262 }
263 }
264 }
265
266 pub(crate) fn is_q1(&self) -> bool {
269 matches!(self, Self::Mapped { dtype: TensorDtype::Q1, .. })
270 }
271
272 pub(crate) fn f32_parts(&self) -> Option<(&[f32], usize, usize)> {
275 match self {
276 Self::F32 { data, rows, cols } => Some((data, *rows, *cols)),
277 _ => None,
278 }
279 }
280
281 pub(crate) fn q1_parts(&self) -> Option<(usize, usize, usize)> {
288 match self {
289 #[cfg(target_os = "macos")]
290 Self::Mapped {
291 dtype: TensorDtype::Q1T,
292 ..
293 } if !crate::gpu::metal_q1t_enabled() => None,
294 Self::Mapped {
295 idx,
296 dtype: TensorDtype::Q1
297 | TensorDtype::Q1T
298 | TensorDtype::Q4Block
299 | TensorDtype::Q8Row
300 | TensorDtype::Q8_2f,
301 rows,
302 cols,
303 ..
304 } => Some((*idx, *rows, *cols)),
305 _ => None,
306 }
307 }
308
309 pub(crate) fn q8_row_parts(&self) -> Option<(usize, usize, usize, &[f32])> {
314 match self {
315 Self::Mapped {
316 idx,
317 dtype: TensorDtype::Q8Row,
318 rows,
319 cols,
320 row_scale,
321 col_field,
322 ..
323 } if col_field.is_empty() => Some((*idx, *rows, *cols, row_scale)),
324 _ => None,
325 }
326 }
327
328 pub fn rows(&self) -> usize {
329 match self {
330 Self::F32 { rows, .. } | Self::Mapped { rows, .. } => *rows,
331 }
332 }
333
334 pub fn cols(&self) -> usize {
335 match self {
336 Self::F32 { cols, .. } | Self::Mapped { cols, .. } => *cols,
337 }
338 }
339
340 pub fn mapped_q1(&self) -> Option<(&std::sync::Arc<CmfModel>, usize)> {
343 match self {
344 Self::Mapped { model, idx, dtype: TensorDtype::Q1, .. } => Some((model, *idx)),
345 _ => None,
346 }
347 }
348
349 pub fn graph_weight(&self) -> Option<(&std::sync::Arc<CmfModel>, usize, u8, &[f32])> {
353 match self {
354 Self::Mapped { model, idx, dtype: TensorDtype::Q8Row, row_scale, .. } => {
355 Some((model, *idx, 0, row_scale.as_slice()))
356 }
357 Self::Mapped { model, idx, dtype: TensorDtype::Q1, .. } => Some((model, *idx, 1, &[])),
358 Self::Mapped { model, idx, dtype: TensorDtype::Q4Tiled, .. } => Some((model, *idx, 2, &[])),
359 Self::Mapped { model, idx, dtype: TensorDtype::Q4Block, .. } => Some((model, *idx, 2, &[])),
360 Self::Mapped { model, idx, dtype: TensorDtype::Q1T, .. } => Some((model, *idx, 3, &[])),
361 _ => None,
362 }
363 }
364
365 pub fn as_f32(&self) -> Option<&[f32]> {
368 match self {
369 Self::F32 { data, .. } => Some(data),
370 Self::Mapped { .. } => None,
371 }
372 }
373
374 fn quant_bytes(&self) -> &[u8] {
375 match self {
376 Self::Mapped { model, idx, .. } => model.entry_bytes(&model.tensors[*idx]),
377 Self::F32 { .. } => unreachable!("quant_bytes on F32"),
378 }
379 }
380
381 pub fn row_f32(&self, r: usize, dst: &mut [f32]) {
383 let cols = self.cols();
384 debug_assert_eq!(dst.len(), cols);
385 match self {
386 Self::F32 { data, .. } => dst.copy_from_slice(&data[r * cols..(r + 1) * cols]),
387 Self::Mapped {
388 dtype,
389 row_scale,
390 col_field,
391 vbit_offsets,
392 ..
393 } => {
394 if *dtype == TensorDtype::Q4Tiled {
395 let bytes = self.quant_bytes();
396 let gpr = cols / GROUP_SIZE;
397 for gi in 0..gpr {
398 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
399 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
400 for (k, &b) in tile[2..].iter().enumerate() {
401 dst[gi * GROUP_SIZE + k * 2] = ((b & 0x0F) as f32 - 8.0) * s;
402 dst[gi * GROUP_SIZE + k * 2 + 1] = (((b >> 4) & 0x0F) as f32 - 8.0) * s;
403 }
404 }
405 return;
406 }
407 if *dtype == TensorDtype::Q4Block {
408 let (packed, scales) = q4_split(self.quant_bytes(), self.rows(), cols);
409 let gpr = cols / GROUP_SIZE;
410 for gi in 0..gpr {
411 let g = r * gpr + gi;
412 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
413 for (k, &b) in packed[g * 16..(g + 1) * 16].iter().enumerate() {
414 dst[gi * GROUP_SIZE + k * 2] = ((b & 0x0F) as f32 - 8.0) * s;
415 dst[gi * GROUP_SIZE + k * 2 + 1] = (((b >> 4) & 0x0F) as f32 - 8.0) * s;
416 }
417 }
418 return;
419 }
420 if *dtype == TensorDtype::Q1 {
421 let bytes = self.quant_bytes();
422 let gpr = cols / GROUP_SIZE;
423 for gi in 0..gpr {
424 let tile =
425 &bytes[(r * gpr + gi) * Q1_TILE..(r * gpr + gi + 1) * Q1_TILE];
426 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
427 for (j, &b) in tile[2..].iter().enumerate() {
428 for k in 0..8 {
429 dst[gi * GROUP_SIZE + j * 8 + k] =
430 (((b >> k) & 1) as f32 * 2.0 - 1.0) * s;
431 }
432 }
433 }
434 return;
435 }
436 if *dtype == TensorDtype::Q1T {
437 let bytes = self.quant_bytes();
438 let gpr = cols / GROUP_SIZE;
439 let base_len = self.rows() * gpr * cortiq_core::quant::Q1T_TILE;
440 for gi in 0..gpr {
441 let off = (r * gpr + gi) * cortiq_core::quant::Q1T_TILE;
442 let s = cortiq_core::quant::f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
443 let codes = &bytes[off + 2..off + cortiq_core::quant::Q1T_TILE];
444 for k in 0..GROUP_SIZE {
445 dst[gi * GROUP_SIZE + k] = match cortiq_core::quant::q1t_code(codes, k) {
446 1 => s,
447 2 => -s,
448 _ => 0.0,
449 };
450 }
451 }
452 let rows = self.rows();
454 let entries = base_len + (rows + 1) * 4;
455 if entries <= bytes.len() {
456 let ptrs = &bytes[base_len..base_len + (rows + 1) * 4];
457 let r0 = u32::from_le_bytes([ptrs[r * 4], ptrs[r * 4 + 1], ptrs[r * 4 + 2], ptrs[r * 4 + 3]]) as usize;
458 let r1 = u32::from_le_bytes([ptrs[(r + 1) * 4], ptrs[(r + 1) * 4 + 1], ptrs[(r + 1) * 4 + 2], ptrs[(r + 1) * 4 + 3]]) as usize;
459 let off = entries + r0 * 4;
460 for i in 0..r1 - r0 {
461 let item = &bytes[off + i * 4..off + i * 4 + 4];
462 let c = u16::from_le_bytes([item[0], item[1]]) as usize;
463 let v = cortiq_core::quant::f16_to_f32(u16::from_le_bytes([item[2], item[3]]));
464 if c < cols {
465 dst[c] = v;
466 }
467 }
468 }
469 return;
470 }
471 if matches!(dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
472 let bytes = self.quant_bytes();
473 let rows = self.rows();
474 let ng = cols / GROUP_SIZE;
475 let bits = &bytes[..rows];
476 let sc_off = rows;
477 let off = vbit_offsets[r];
480 let b = bits[r] as usize;
481 let l = ((1usize << (b - 1)) - 1) as f32;
482 let data = &bytes[off..];
483 let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
484 for (i, d) in dst.iter_mut().enumerate() {
485 while nbits < b {
486 acc = (acc << 8) | data[idx] as u64;
487 idx += 1;
488 nbits += 8;
489 }
490 let u = ((acc >> (nbits - b)) & ((1u64 << b) - 1)) as f32;
491 nbits -= b;
492 let so = (r * ng + i / GROUP_SIZE) * 2;
493 let sv = f16_to_f32(u16::from_le_bytes([
494 bytes[sc_off + so],
495 bytes[sc_off + so + 1],
496 ]));
497 *d = (u - l) * sv;
498 }
499 return;
500 }
501 let q = &self.quant_bytes()[r * cols..(r + 1) * cols];
502 let s = row_scale[r];
503 match dtype {
504 TensorDtype::Q8Row => {
505 for (d, &b) in dst.iter_mut().zip(q) {
506 *d = (b as i8) as f32 * s;
507 }
508 }
509 TensorDtype::Q8_2f => {
510 for (i, (d, &b)) in dst.iter_mut().zip(q).enumerate() {
511 *d = (b as i8) as f32 * s * col_field[i];
512 }
513 }
514 _ => unreachable!(),
515 }
516 }
517 }
518 }
519
520 pub fn sparse_col_ok(&self) -> bool {
525 match self {
526 Self::F32 { .. } => true,
527 Self::Mapped { dtype, .. } => {
528 matches!(dtype, TensorDtype::Q8Row | TensorDtype::Q8_2f)
529 }
530 }
531 }
532
533 pub fn add_col_scaled(&self, c: usize, w: f32, out: &mut [f32]) {
537 let inter = self.cols();
538 let hidden = self.rows();
539 debug_assert_eq!(out.len(), hidden);
540 match self {
541 Self::F32 { data, .. } => {
542 for (k, o) in out.iter_mut().enumerate() {
543 *o += w * data[k * inter + c];
544 }
545 }
546 Self::Mapped {
547 dtype, row_scale, col_field, ..
548 } => {
549 let q = self.quant_bytes();
550 let colf = if *dtype == TensorDtype::Q8_2f {
551 col_field[c]
552 } else {
553 1.0
554 };
555 let wc = w * colf;
556 for (k, o) in out.iter_mut().enumerate() {
557 let b = q[k * inter + c] as i8 as f32;
558 *o += wc * b * row_scale[k];
559 }
560 }
561 }
562 }
563
564 pub fn row_dot(&self, r: usize, x: &[f32], scratch: &mut [f32]) -> f32 {
568 let cols = self.cols();
569 match self {
570 Self::F32 { data, .. } => {
571 let row = &data[r * cols..(r + 1) * cols];
572 row.iter().zip(x).map(|(w, v)| w * v).sum()
573 }
574 Self::Mapped { dtype, row_scale, col_field, .. } => match dtype {
575 TensorDtype::Q8Row => {
576 let q = &self.quant_bytes()[r * cols..(r + 1) * cols];
577 dot_i8_f32(q, x) * row_scale[r]
578 }
579 TensorDtype::Q8_2f => {
580 let q = &self.quant_bytes()[r * cols..(r + 1) * cols];
581 dot_i8_col_f32(q, x, col_field) * row_scale[r]
582 }
583 _ => {
584 self.row_f32(r, scratch);
585 scratch.iter().zip(x).map(|(w, v)| w * v).sum()
586 }
587 },
588 }
589 }
590
591 pub fn matvec(&self, x: &[f32], out: &mut [f32], pool: Option<&Pool>) {
594 match self {
595 Self::F32 { data, .. } => matvec_rows(pool, data, x, out),
596 Self::Mapped {
597 model,
598 idx,
599 dtype,
600 rows,
601 cols,
602 row_scale,
603 col_field,
604 vbit_offsets,
605 repack,
606 } => {
607 let _ = (model, idx);
608 if *dtype == TensorDtype::Q4Block {
609 if *rows * *cols >= 8_388_608 && crate::gpu::enabled_here() {
613 let t0 = std::time::Instant::now();
614 match crate::gpu::probe_arm(crate::gpu::OpClass::Matvec) {
615 crate::gpu::ProbeArm::Gpu => {
616 if crate::gpu::q4b_matvec(model, *idx, x, *rows, *cols, out) {
617 crate::gpu::probe_record(
618 crate::gpu::OpClass::Matvec,
619 true,
620 t0.elapsed(),
621 );
622 return;
623 }
624 }
625 crate::gpu::ProbeArm::CpuTimed => {
626 q4matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
627 crate::gpu::probe_record(
628 crate::gpu::OpClass::Matvec,
629 false,
630 t0.elapsed(),
631 );
632 return;
633 }
634 crate::gpu::ProbeArm::Cpu => {}
635 }
636 }
637 q4matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
638 return;
639 }
640 if *dtype == TensorDtype::Q4Tiled {
641 q4t_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
642 return;
643 }
644 if *dtype == TensorDtype::Q1 {
645 if *rows * *cols >= 8_388_608 && crate::gpu::enabled_here() {
650 let t0 = std::time::Instant::now();
651 let arm = if crate::gpu::q1_force() {
652 crate::gpu::ProbeArm::Gpu
653 } else {
654 crate::gpu::probe_arm(crate::gpu::OpClass::Matvec)
655 };
656 match arm {
657 crate::gpu::ProbeArm::Gpu => {
658 if crate::gpu::q1_matvec(model, *idx, x, *rows, *cols, out) {
659 crate::gpu::probe_record(
660 crate::gpu::OpClass::Matvec,
661 true,
662 t0.elapsed(),
663 );
664 return;
665 }
666 }
667 crate::gpu::ProbeArm::CpuTimed => {
668 q1_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
669 crate::gpu::probe_record(
670 crate::gpu::OpClass::Matvec,
671 false,
672 t0.elapsed(),
673 );
674 return;
675 }
676 crate::gpu::ProbeArm::Cpu => {}
677 }
678 }
679 q1_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
680 return;
681 }
682 if *dtype == TensorDtype::Q1T {
683 if *rows * *cols >= 8_388_608 && crate::gpu::enabled_here() {
687 let t0 = std::time::Instant::now();
688 match crate::gpu::probe_arm(crate::gpu::OpClass::Matvec) {
689 crate::gpu::ProbeArm::Gpu => {
690 if crate::gpu::q1t_matvec(model, *idx, x, *rows, *cols, out) {
691 q1t_add_overlay(self.quant_bytes(), x, *rows, *cols, out, pool);
692 crate::gpu::probe_record(
693 crate::gpu::OpClass::Matvec,
694 true,
695 t0.elapsed(),
696 );
697 return;
698 }
699 }
700 crate::gpu::ProbeArm::CpuTimed => {
701 q1t_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
702 crate::gpu::probe_record(
703 crate::gpu::OpClass::Matvec,
704 false,
705 t0.elapsed(),
706 );
707 return;
708 }
709 crate::gpu::ProbeArm::Cpu => {}
710 }
711 }
712 q1t_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
713 return;
714 }
715 if matches!(dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
716 vbitmatvec(self.quant_bytes(), vbit_offsets, x, *rows, *cols, out, pool);
717 return;
718 }
719 let xs = prescale(x, col_field, *dtype);
720 if *rows >= crate::gpu::min_rows()
725 && matches!(dtype, TensorDtype::Q8Row | TensorDtype::Q8_2f)
726 && std::env::var("CMF_GPU_LMHEAD").map(|v| v != "0").unwrap_or(true)
727 && crate::gpu::enabled_here()
728 {
729 let t0 = std::time::Instant::now();
732 match crate::gpu::probe_arm(crate::gpu::OpClass::Matvec) {
733 crate::gpu::ProbeArm::Gpu => {}
734 crate::gpu::ProbeArm::CpuTimed => {
735 qmatvec(self.quant_bytes(), repack, row_scale, x, col_field, *dtype, *rows, *cols, out, pool);
736 crate::gpu::probe_record(
737 crate::gpu::OpClass::Matvec,
738 false,
739 t0.elapsed(),
740 );
741 return;
742 }
743 crate::gpu::ProbeArm::Cpu => {
744 qmatvec(self.quant_bytes(), repack, row_scale, x, col_field, *dtype, *rows, *cols, out, pool);
745 return;
746 }
747 }
748 let frac = std::env::var("CMF_GPU_SPLIT")
749 .ok()
750 .and_then(|v| v.parse::<f32>().ok())
751 .unwrap_or(0.5)
752 .clamp(0.0, 1.0);
753 let cpu_rows = ((*rows as f32) * (1.0 - frac)) as usize;
754 let (out_cpu, out_gpu) = out.split_at_mut(cpu_rows);
755 let bytes = self.quant_bytes();
756 let ok = std::thread::scope(|sc| {
757 let g = sc.spawn(|| {
758 crate::gpu::q8_matvec_range(
759 model,
760 *idx,
761 cpu_rows,
762 &row_scale[cpu_rows..],
763 &xs,
764 *rows - cpu_rows,
765 *cols,
766 out_gpu,
767 )
768 });
769 if cpu_rows > 0 {
770 let rep_cpu = if repack.is_empty() {
773 &[][..]
774 } else {
775 &repack[..(cpu_rows / 4) * 4 * *cols]
776 };
777 qmatvec(
778 &bytes[..cpu_rows * *cols],
779 rep_cpu,
780 &row_scale[..cpu_rows],
781 x,
782 col_field,
783 *dtype,
784 cpu_rows,
785 *cols,
786 out_cpu,
787 pool,
788 );
789 }
790 g.join().unwrap_or(false)
791 });
792 if ok {
793 crate::gpu::probe_record(crate::gpu::OpClass::Matvec, true, t0.elapsed());
794 return;
795 }
796 qmatvec(
799 &bytes[cpu_rows * *cols..(*rows) * *cols],
800 &[],
801 &row_scale[cpu_rows..],
802 x,
803 col_field,
804 *dtype,
805 *rows - cpu_rows,
806 *cols,
807 out_gpu,
808 pool,
809 );
810 return;
811 }
812 qmatvec(self.quant_bytes(), repack, row_scale, x, col_field, *dtype, *rows, *cols, out, pool);
813 }
814 }
815 }
816
817 pub fn matvec2(&self, x1: &[f32], x2: &[f32], o1: &mut [f32], o2: &mut [f32], pool: Option<&Pool>) {
819 match self {
820 Self::F32 { data, .. } => matvec_rows2(pool, data, x1, x2, o1, o2),
821 Self::Mapped {
822 dtype,
823 rows,
824 cols,
825 row_scale,
826 col_field,
827 vbit_offsets,
828 ..
829 } => {
830 if *dtype == TensorDtype::Q4Block {
831 q4matvec2(self.quant_bytes(), x1, x2, *rows, *cols, o1, o2, pool);
832 return;
833 }
834 if *dtype == TensorDtype::Q4Tiled {
835 q4t_matvec2(self.quant_bytes(), x1, x2, *rows, *cols, o1, o2, pool);
836 return;
837 }
838 if *dtype == TensorDtype::Q1 {
839 q1_matvec2(self.quant_bytes(), x1, x2, *rows, *cols, o1, o2, pool);
840 return;
841 }
842 if *dtype == TensorDtype::Q1T {
843 q1t_matvec(self.quant_bytes(), x1, *rows, *cols, o1, pool);
848 q1t_matvec(self.quant_bytes(), x2, *rows, *cols, o2, pool);
849 return;
850 }
851 if matches!(dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
852 vbitmatvec2(self.quant_bytes(), vbit_offsets, x1, x2, *rows, *cols, o1, o2, pool);
853 return;
854 }
855 qmatvec2(self.quant_bytes(), row_scale, x1, x2, col_field, *dtype, *rows, *cols, o1, o2, pool);
856 }
857 }
858 }
859}
860
861impl QTensor {
862 pub fn matmat(&self, xs_all: &[f32], b: usize, out: &mut [f32], pool: Option<&Pool>) {
867 let cols = self.cols();
868 let rows = self.rows();
869 debug_assert_eq!(xs_all.len(), b * cols);
870 debug_assert_eq!(out.len(), b * rows);
871 if crate::gptq_capture::capturing() {
875 if let Self::Mapped { model, idx, .. } = self {
876 crate::gptq_capture::accumulate(&model.tensors[*idx].name, xs_all, b, cols);
877 }
878 }
879 match self {
880 Self::F32 { data, .. } => {
881 let out_addr = SendMut(out.as_mut_ptr());
882 let run = |start: usize, end: usize| {
883 for o in start..end {
884 let row = &data[o * cols..(o + 1) * cols];
885 for bi in 0..b {
886 let x = &xs_all[bi * cols..(bi + 1) * cols];
887 let mut acc = 0f32;
888 for j in 0..cols {
889 acc += row[j] * x[j];
890 }
891 unsafe { *out_addr.at(bi * rows + o) = acc };
892 }
893 }
894 };
895 dispatch_rows(pool, rows, &run);
896 }
897 Self::Mapped {
898 dtype,
899 row_scale,
900 col_field,
901 vbit_offsets,
902 ..
903 } => {
904 if *dtype == TensorDtype::Q4Block {
905 q4matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
906 return;
907 }
908 if *dtype == TensorDtype::Q4Tiled {
909 q4t_matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
910 return;
911 }
912 if *dtype == TensorDtype::Q1 {
913 if b >= 32
916 && b * rows * cols >= 128_000_000
917 && cols % 64 == 0
918 && crate::gpu::enabled_here()
919 {
920 if let Self::Mapped { model, idx, .. } = self {
921 let t0 = std::time::Instant::now();
922 match crate::gpu::probe_arm(crate::gpu::OpClass::Matmat) {
923 crate::gpu::ProbeArm::Gpu => {
924 if crate::gpu::q1_matmat(model, *idx, xs_all, b, rows, cols, out) {
925 crate::gpu::probe_record(
926 crate::gpu::OpClass::Matmat,
927 true,
928 t0.elapsed(),
929 );
930 return;
931 }
932 }
933 crate::gpu::ProbeArm::CpuTimed => {
934 q1_matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
935 crate::gpu::probe_record(
936 crate::gpu::OpClass::Matmat,
937 false,
938 t0.elapsed(),
939 );
940 return;
941 }
942 crate::gpu::ProbeArm::Cpu => {}
943 }
944 }
945 }
946 q1_matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
947 return;
948 }
949 if *dtype == TensorDtype::Q1T {
950 if b >= 32
953 && b * rows * cols >= 128_000_000
954 && crate::gpu::enabled_here()
955 {
956 if let Self::Mapped { model, idx, .. } = self {
957 let t0 = std::time::Instant::now();
958 match crate::gpu::probe_arm(crate::gpu::OpClass::Matmat) {
959 crate::gpu::ProbeArm::Gpu => {
960 if crate::gpu::q1t_matmat(model, *idx, xs_all, b, rows, cols, out) {
961 crate::gpu::probe_record(
962 crate::gpu::OpClass::Matmat,
963 true,
964 t0.elapsed(),
965 );
966 return;
967 }
968 }
969 crate::gpu::ProbeArm::CpuTimed => {
970 q1t_matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
971 crate::gpu::probe_record(
972 crate::gpu::OpClass::Matmat,
973 false,
974 t0.elapsed(),
975 );
976 return;
977 }
978 crate::gpu::ProbeArm::Cpu => {}
979 }
980 }
981 }
982 q1t_matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
983 return;
984 }
985 if matches!(dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
986 vbitmatmat(self.quant_bytes(), vbit_offsets, xs_all, b, rows, cols, out, pool);
987 return;
988 }
989 let pre: Vec<std::borrow::Cow<'_, [f32]>> = (0..b)
990 .map(|bi| {
991 prescale(&xs_all[bi * cols..(bi + 1) * cols], col_field, *dtype)
992 })
993 .collect();
994 if b >= 8
1000 && b * rows * cols >= 128_000_000
1001 && crate::gpu::enabled_here()
1002 {
1003 if let Self::Mapped { model, idx, .. } = self {
1004 let t0 = std::time::Instant::now();
1005 match crate::gpu::probe_arm(crate::gpu::OpClass::Matmat) {
1006 crate::gpu::ProbeArm::Gpu
1007 if crate::gpu::probe_deciding(crate::gpu::OpClass::Matmat)
1008 && !crate::gpu::q8_resident_or_upload(model, *idx) =>
1009 {
1010 let q = self.quant_bytes();
1014 qmatmat(q, row_scale, &pre, rows, cols, out, pool);
1015 return;
1016 }
1017 crate::gpu::ProbeArm::Gpu => {
1018 let flat: Vec<f32> =
1019 pre.iter().flat_map(|v| v.iter().copied()).collect();
1020 if crate::gpu::q8_matmat(
1021 model, *idx, row_scale, &flat, b, rows, cols, out)
1022 {
1023 crate::gpu::probe_record(
1024 crate::gpu::OpClass::Matmat,
1025 true,
1026 t0.elapsed(),
1027 );
1028 return;
1029 }
1030 }
1031 crate::gpu::ProbeArm::CpuTimed => {
1032 let q = self.quant_bytes();
1033 qmatmat(q, row_scale, &pre, rows, cols, out, pool);
1034 crate::gpu::probe_record(
1035 crate::gpu::OpClass::Matmat,
1036 false,
1037 t0.elapsed(),
1038 );
1039 return;
1040 }
1041 crate::gpu::ProbeArm::Cpu => {}
1042 }
1043 }
1044 }
1045 let q = self.quant_bytes();
1046 qmatmat(q, row_scale, &pre, rows, cols, out, pool);
1047 }
1048 }
1049 }
1050}
1051
1052impl QTensor {
1053 pub fn matvec_many<const N: usize>(
1060 ts: [&QTensor; N],
1061 x: &[f32],
1062 mut outs: [&mut [f32]; N],
1063 pool: Option<&Pool>,
1064 ) {
1065 let total_rows: usize = ts.iter().map(|t| t.rows()).sum();
1066 let uniform_q8 = ts.iter().all(|t| {
1067 matches!(
1068 t,
1069 Self::Mapped { dtype: TensorDtype::Q8Row | TensorDtype::Q8_2f, .. }
1070 )
1071 });
1072 let uniform_f32 = ts.iter().all(|t| matches!(t, Self::F32 { .. }));
1073 let uniform_q4 = ts
1074 .iter()
1075 .all(|t| matches!(t, Self::Mapped { dtype: TensorDtype::Q4Block, .. }));
1076 let uniform_vbit = ts
1077 .iter()
1078 .all(|t| matches!(
1079 t,
1080 Self::Mapped { dtype: TensorDtype::Vbit | TensorDtype::VbitRo, .. }
1081 ));
1082 let uniform_q1 = ts
1083 .iter()
1084 .all(|t| matches!(t, Self::Mapped { dtype: TensorDtype::Q1, .. }));
1085 let uniform_q1t = ts
1086 .iter()
1087 .all(|t| matches!(t, Self::Mapped { dtype: TensorDtype::Q1T, .. }));
1088 let Some(pool) = pool else {
1089 for (t, o) in ts.iter().zip(outs.iter_mut()) {
1090 t.matvec(x, o, None);
1091 }
1092 return;
1093 };
1094 if total_rows < 256
1095 || !(uniform_q8 || uniform_f32 || uniform_q4 || uniform_vbit || uniform_q1 || uniform_q1t)
1096 {
1097 for (t, o) in ts.iter().zip(outs.iter_mut()) {
1098 t.matvec(x, o, Some(pool));
1099 }
1100 return;
1101 }
1102
1103 if uniform_q1 {
1104 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
1107 if a8w8_enabled() {
1108 let act = split_act(x);
1109 let gsum = q1_group_sums(&act.xq, ts[0].cols() / GROUP_SIZE);
1110 let (act, gsum) = (&act, &gsum);
1111 let closures: [_; N] = std::array::from_fn(|i| {
1112 let (bytes, gpr, out) =
1113 (ts[i].quant_bytes(), ts[i].cols() / GROUP_SIZE, outs_addr[i]);
1114 move |s: usize, e: usize| q1_range_a8w8(bytes, gpr, act, gsum, out, s, e)
1115 });
1116 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1117 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1118 pool.run_many(&parts);
1119 } else {
1120 let closures: [_; N] = std::array::from_fn(|i| {
1121 let (bytes, gpr, out) =
1122 (ts[i].quant_bytes(), ts[i].cols() / GROUP_SIZE, outs_addr[i]);
1123 move |s: usize, e: usize| q1_range_f32(bytes, gpr, x, out, s, e)
1124 });
1125 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1126 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1127 pool.run_many(&parts);
1128 }
1129 return;
1130 }
1131
1132 if uniform_q1t {
1133 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
1137 const TILE: usize = cortiq_core::quant::Q1T_TILE;
1138 if a8w8_enabled() {
1139 let act = split_act(x);
1140 let act = &act;
1141 let x_ref = x;
1142 let closures: [_; N] = std::array::from_fn(|i| {
1143 let bytes = ts[i].quant_bytes();
1144 let (rows, cols) = (ts[i].rows(), ts[i].cols());
1145 let gpr = cols / GROUP_SIZE;
1146 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
1147 let out = outs_addr[i];
1148 move |s: usize, e: usize| {
1149 q1t_range_a8w8(bytes, gpr, rp_off, ent_off, has_ov, act, x_ref, out, s, e)
1150 }
1151 });
1152 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1153 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1154 pool.run_many(&parts);
1155 } else {
1156 let x_ref = x;
1157 let closures: [_; N] = std::array::from_fn(|i| {
1158 let bytes = ts[i].quant_bytes();
1159 let (rows, cols) = (ts[i].rows(), ts[i].cols());
1160 let gpr = cols / GROUP_SIZE;
1161 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
1162 let out = outs_addr[i];
1163 move |s: usize, e: usize| {
1164 q1t_range_f32_batch(bytes, gpr, rp_off, ent_off, has_ov, x_ref, out, s, e)
1165 }
1166 });
1167 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1168 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1169 pool.run_many(&parts);
1170 }
1171 return;
1172 }
1173
1174 if uniform_q4 || uniform_vbit {
1175 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
1176 if a8w8_enabled() {
1178 let act = split_act(x);
1179 let act = &act;
1180 if uniform_q4 {
1181 let closures: [_; N] = std::array::from_fn(|i| {
1182 let (packed, scales) =
1183 q4_split(ts[i].quant_bytes(), ts[i].rows(), ts[i].cols());
1184 let (gpr, cols, out) =
1185 (ts[i].cols() / GROUP_SIZE, ts[i].cols(), outs_addr[i]);
1186 move |s: usize, e: usize| {
1187 q4_range_a8w8(packed, scales, gpr, cols, act, out, s, e)
1188 }
1189 });
1190 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1191 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1192 pool.run_many(&parts);
1193 } else {
1194 let closures: [_; N] = std::array::from_fn(|i| {
1195 let Self::Mapped { vbit_offsets, .. } = ts[i] else { unreachable!() };
1196 let (bytes, rows, cols, out) =
1197 (ts[i].quant_bytes(), ts[i].rows(), ts[i].cols(), outs_addr[i]);
1198 move |s: usize, e: usize| {
1199 vbit_range_a8w8(bytes, vbit_offsets, x, act, rows, cols, out, s, e)
1200 }
1201 });
1202 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1203 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1204 pool.run_many(&parts);
1205 }
1206 return;
1207 }
1208 if uniform_q4 {
1209 let closures: [_; N] = std::array::from_fn(|i| {
1210 let (packed, scales) =
1211 q4_split(ts[i].quant_bytes(), ts[i].rows(), ts[i].cols());
1212 let (gpr, out) = (ts[i].cols() / GROUP_SIZE, outs_addr[i]);
1213 move |s: usize, e: usize| q4_range_f32(packed, scales, gpr, x, out, s, e)
1214 });
1215 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1216 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1217 pool.run_many(&parts);
1218 } else {
1219 let closures: [_; N] = std::array::from_fn(|i| {
1220 let Self::Mapped { vbit_offsets, .. } = ts[i] else { unreachable!() };
1221 let (bytes, rows, cols, out) =
1222 (ts[i].quant_bytes(), ts[i].rows(), ts[i].cols(), outs_addr[i]);
1223 move |s: usize, e: usize| {
1224 vbit_range_f32(bytes, vbit_offsets, x, rows, cols, out, s, e)
1225 }
1226 });
1227 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1228 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1229 pool.run_many(&parts);
1230 }
1231 return;
1232 }
1233
1234 if uniform_f32 {
1235 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
1236 let closures: [_; N] = std::array::from_fn(|i| {
1237 let Self::F32 { data, cols, .. } = ts[i] else { unreachable!() };
1238 let out = outs_addr[i];
1239 move |start: usize, end: usize| {
1240 for o in start..end {
1241 let row = &data[o * cols..(o + 1) * cols];
1242 let mut sum = 0.0f32;
1243 for j in 0..*cols {
1244 sum += row[j] * x[j];
1245 }
1246 unsafe { *out.at(o) = sum };
1248 }
1249 }
1250 });
1251 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1252 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1253 pool.run_many(&parts);
1254 return;
1255 }
1256
1257 struct Ctx<'a> {
1260 bytes: &'a [u8],
1261 #[cfg_attr(not(target_arch = "aarch64"), allow(dead_code))]
1262 rep: &'a [u8],
1263 row_scale: &'a [f32],
1264 cols: usize,
1265 xs: std::borrow::Cow<'a, [f32]>,
1266 }
1267 let ctxs: [Ctx<'_>; N] = std::array::from_fn(|i| {
1268 let Self::Mapped { dtype, cols, row_scale, col_field, repack, .. } = ts[i] else {
1269 unreachable!()
1270 };
1271 Ctx {
1272 bytes: ts[i].quant_bytes(),
1273 rep: repack,
1274 row_scale,
1275 cols: *cols,
1276 xs: prescale(x, col_field, *dtype),
1277 }
1278 });
1279 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
1280 #[cfg(target_arch = "aarch64")]
1281 if sdot_enabled() {
1282 let acts: [SplitAct; N] = std::array::from_fn(|i| split_act(&ctxs[i].xs));
1283 let closures: [_; N] = std::array::from_fn(|i| {
1284 let (c, act, out) = (&ctxs[i], &acts[i], outs_addr[i]);
1285 move |start: usize, end: usize| {
1286 q8_range_sdot(c.bytes, c.rep, c.row_scale, act, c.cols, out, start, end)
1287 }
1288 });
1289 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1290 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1291 pool.run_many(&parts);
1292 return;
1293 }
1294 #[cfg(target_arch = "x86_64")]
1295 if avx2_a8w8_enabled() {
1296 let acts: [SplitAct; N] = std::array::from_fn(|i| split_act(&ctxs[i].xs));
1297 let closures: [_; N] = std::array::from_fn(|i| {
1298 let (c, act, out) = (&ctxs[i], &acts[i], outs_addr[i]);
1299 move |start: usize, end: usize| {
1300 q8_range_avx2(c.bytes, c.row_scale, act, c.cols, out, start, end)
1301 }
1302 });
1303 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1304 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1305 pool.run_many(&parts);
1306 return;
1307 }
1308 let closures: [_; N] = std::array::from_fn(|i| {
1309 let (c, out) = (&ctxs[i], outs_addr[i]);
1310 move |start: usize, end: usize| {
1311 q8_range_f32(c.bytes, c.row_scale, &c.xs, c.cols, out, start, end)
1312 }
1313 });
1314 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1315 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1316 pool.run_many(&parts);
1317 }
1318}
1319
1320impl QTensor {
1321 #[allow(clippy::needless_range_loop)]
1326 pub fn matvec2_many<const N: usize>(
1327 ts: [&QTensor; N],
1328 x1: &[f32],
1329 x2: &[f32],
1330 mut o1s: [&mut [f32]; N],
1331 mut o2s: [&mut [f32]; N],
1332 pool: Option<&Pool>,
1333 ) {
1334 let total_rows: usize = ts.iter().map(|t| t.rows()).sum();
1335 let uniform_q8 = ts.iter().all(|t| {
1336 matches!(
1337 t,
1338 Self::Mapped { dtype: TensorDtype::Q8Row | TensorDtype::Q8_2f, .. }
1339 )
1340 });
1341 let uniform_f32 = ts.iter().all(|t| matches!(t, Self::F32 { .. }));
1342 let uniform_q4 = ts
1343 .iter()
1344 .all(|t| matches!(t, Self::Mapped { dtype: TensorDtype::Q4Block, .. }));
1345 let uniform_vbit = ts
1346 .iter()
1347 .all(|t| matches!(
1348 t,
1349 Self::Mapped { dtype: TensorDtype::Vbit | TensorDtype::VbitRo, .. }
1350 ));
1351 let fusable = pool.is_some()
1352 && total_rows >= 256
1353 && (uniform_q8 || uniform_f32 || uniform_q4 || uniform_vbit);
1354 if !fusable {
1355 for i in 0..N {
1356 ts[i].matvec2(x1, x2, o1s[i], o2s[i], pool);
1357 }
1358 return;
1359 }
1360 let pool = pool.unwrap();
1361
1362 if uniform_q4 || uniform_vbit {
1363 let p1: [SendMut; N] = std::array::from_fn(|i| SendMut(o1s[i].as_mut_ptr()));
1364 let p2: [SendMut; N] = std::array::from_fn(|i| SendMut(o2s[i].as_mut_ptr()));
1365 if a8w8_enabled() {
1367 let a1 = split_act(x1);
1368 let a2 = split_act(x2);
1369 let (a1, a2) = (&a1, &a2);
1370 if uniform_q4 {
1371 let closures: [_; N] = std::array::from_fn(|i| {
1372 let (packed, scales) =
1373 q4_split(ts[i].quant_bytes(), ts[i].rows(), ts[i].cols());
1374 let (gpr, cols, o1, o2) =
1375 (ts[i].cols() / GROUP_SIZE, ts[i].cols(), p1[i], p2[i]);
1376 move |s: usize, e: usize| {
1377 q4_range2_a8w8(packed, scales, gpr, cols, a1, a2, o1, o2, s, e)
1378 }
1379 });
1380 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1381 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1382 pool.run_many(&parts);
1383 } else {
1384 let closures: [_; N] = std::array::from_fn(|i| {
1385 let Self::Mapped { vbit_offsets, .. } = ts[i] else { unreachable!() };
1386 let (bytes, rows, cols, o1, o2) =
1387 (ts[i].quant_bytes(), ts[i].rows(), ts[i].cols(), p1[i], p2[i]);
1388 move |s: usize, e: usize| {
1389 vbit_range2_a8w8(
1390 bytes, vbit_offsets, x1, x2, a1, a2, rows, cols, o1, o2, s, e,
1391 )
1392 }
1393 });
1394 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1395 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1396 pool.run_many(&parts);
1397 }
1398 return;
1399 }
1400 if uniform_q4 {
1401 let closures: [_; N] = std::array::from_fn(|i| {
1402 let (packed, scales) =
1403 q4_split(ts[i].quant_bytes(), ts[i].rows(), ts[i].cols());
1404 let (gpr, o1, o2) = (ts[i].cols() / GROUP_SIZE, p1[i], p2[i]);
1405 move |s: usize, e: usize| {
1406 q4_range2_f32(packed, scales, gpr, x1, x2, o1, o2, s, e)
1407 }
1408 });
1409 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1410 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1411 pool.run_many(&parts);
1412 } else {
1413 let closures: [_; N] = std::array::from_fn(|i| {
1414 let Self::Mapped { vbit_offsets, .. } = ts[i] else { unreachable!() };
1415 let (bytes, rows, cols, o1, o2) =
1416 (ts[i].quant_bytes(), ts[i].rows(), ts[i].cols(), p1[i], p2[i]);
1417 move |s: usize, e: usize| {
1418 vbit_range2_f32(bytes, vbit_offsets, x1, x2, rows, cols, o1, o2, 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_f32 {
1429 let p1: [SendMut; N] = std::array::from_fn(|i| SendMut(o1s[i].as_mut_ptr()));
1430 let p2: [SendMut; N] = std::array::from_fn(|i| SendMut(o2s[i].as_mut_ptr()));
1431 let closures: [_; N] = std::array::from_fn(|i| {
1432 let Self::F32 { data, cols, .. } = ts[i] else { unreachable!() };
1433 let (o1, o2) = (p1[i], p2[i]);
1434 move |start: usize, end: usize| {
1435 for o in start..end {
1436 let row = &data[o * cols..(o + 1) * cols];
1437 let (mut s1, mut s2) = (0.0f32, 0.0f32);
1438 for j in 0..*cols {
1439 s1 += row[j] * x1[j];
1440 s2 += row[j] * x2[j];
1441 }
1442 unsafe {
1444 *o1.at(o) = s1;
1445 *o2.at(o) = s2;
1446 }
1447 }
1448 }
1449 });
1450 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1451 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1452 pool.run_many(&parts);
1453 return;
1454 }
1455
1456 struct Ctx<'a> {
1457 bytes: &'a [u8],
1458 row_scale: &'a [f32],
1459 cols: usize,
1460 xs1: std::borrow::Cow<'a, [f32]>,
1461 xs2: std::borrow::Cow<'a, [f32]>,
1462 }
1463 let ctxs: [Ctx<'_>; N] = std::array::from_fn(|i| {
1464 let Self::Mapped { dtype, cols, row_scale, col_field, .. } = ts[i] else {
1465 unreachable!()
1466 };
1467 Ctx {
1468 bytes: ts[i].quant_bytes(),
1469 row_scale,
1470 cols: *cols,
1471 xs1: prescale(x1, col_field, *dtype),
1472 xs2: prescale(x2, col_field, *dtype),
1473 }
1474 });
1475 let p1: [SendMut; N] = std::array::from_fn(|i| SendMut(o1s[i].as_mut_ptr()));
1476 let p2: [SendMut; N] = std::array::from_fn(|i| SendMut(o2s[i].as_mut_ptr()));
1477 #[cfg(target_arch = "aarch64")]
1478 if sdot_enabled() {
1479 let acts: [(SplitAct, SplitAct); N] =
1480 std::array::from_fn(|i| (split_act(&ctxs[i].xs1), split_act(&ctxs[i].xs2)));
1481 let closures: [_; N] = std::array::from_fn(|i| {
1482 let (c, a, o1, o2) = (&ctxs[i], &acts[i], p1[i], p2[i]);
1483 move |start: usize, end: usize| {
1484 q8_range2_sdot(c.bytes, c.row_scale, &a.0, &a.1, c.cols, o1, o2, start, end)
1485 }
1486 });
1487 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1488 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1489 pool.run_many(&parts);
1490 return;
1491 }
1492 #[cfg(target_arch = "x86_64")]
1493 if avx2_a8w8_enabled() {
1494 let acts: [(SplitAct, SplitAct); N] =
1495 std::array::from_fn(|i| (split_act(&ctxs[i].xs1), split_act(&ctxs[i].xs2)));
1496 let closures: [_; N] = std::array::from_fn(|i| {
1497 let (c, a, o1, o2) = (&ctxs[i], &acts[i], p1[i], p2[i]);
1498 move |start: usize, end: usize| {
1499 q8_range2_avx2(c.bytes, c.row_scale, &a.0, &a.1, c.cols, o1, o2, start, end)
1500 }
1501 });
1502 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1503 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1504 pool.run_many(&parts);
1505 return;
1506 }
1507 let closures: [_; N] = std::array::from_fn(|i| {
1508 let (c, o1, o2) = (&ctxs[i], p1[i], p2[i]);
1509 move |start: usize, end: usize| {
1510 q8_range2_f32(c.bytes, c.row_scale, &c.xs1, &c.xs2, c.cols, o1, o2, start, end)
1511 }
1512 });
1513 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1514 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1515 pool.run_many(&parts);
1516 }
1517
1518 pub fn matvec_silu_mul(
1523 gate: &QTensor,
1524 up: &QTensor,
1525 x: &[f32],
1526 out: &mut [f32],
1527 pool: Option<&Pool>,
1528 ) -> bool {
1529 let inter = gate.rows();
1530 debug_assert_eq!(up.rows(), inter);
1531 debug_assert_eq!(out.len(), inter);
1532 debug_assert_eq!(gate.cols(), up.cols());
1533 if !a8w8_enabled() {
1534 return false;
1535 }
1536 let act = split_act(x);
1537 let act = &act;
1538 let x_ref = x;
1539 let out_addr = SendMut(out.as_mut_ptr());
1540
1541 match (gate, up) {
1542 (
1544 Self::Mapped { dtype: TensorDtype::Q4Block, .. },
1545 Self::Mapped { dtype: TensorDtype::Q4Block, .. },
1546 ) => {
1547 let (gp, gs) = q4_split(gate.quant_bytes(), gate.rows(), gate.cols());
1548 let (up_p, up_s) = q4_split(up.quant_bytes(), up.rows(), up.cols());
1549 let gpr = gate.cols() / GROUP_SIZE;
1550 let cols = gate.cols();
1551 let run = move |start: usize, end: usize| {
1552 for r in start..end {
1553 let mut gv = dot_q4_row_i8(gp, gs, r * gpr, gpr, &act.xq) * act.sx;
1554 let mut uv = dot_q4_row_i8(up_p, up_s, r * gpr, gpr, &act.xq) * act.sx;
1555 for &(j, xv) in &act.outliers {
1556 let flat = r * cols + j;
1557 let gb = gp[flat / 2];
1558 let gn = if flat & 1 == 0 { gb & 0x0F } else { gb >> 4 };
1559 let gsc = f16_to_f32(u16::from_le_bytes([
1560 gs[(flat / GROUP_SIZE) * 2],
1561 gs[(flat / GROUP_SIZE) * 2 + 1],
1562 ]));
1563 gv += ((gn as i32 - 8) as f32) * gsc * xv;
1564 let ub = up_p[flat / 2];
1565 let un = if flat & 1 == 0 { ub & 0x0F } else { ub >> 4 };
1566 let usc = f16_to_f32(u16::from_le_bytes([
1567 up_s[(flat / GROUP_SIZE) * 2],
1568 up_s[(flat / GROUP_SIZE) * 2 + 1],
1569 ]));
1570 uv += ((un as i32 - 8) as f32) * usc * xv;
1571 }
1572 let silu_g = gv / (1.0 + (-gv).exp());
1573 unsafe { *out_addr.at(r) = silu_g * uv };
1575 }
1576 };
1577 dispatch_rows(pool, inter, &run);
1578 true
1579 }
1580 (
1582 Self::Mapped { dtype: TensorDtype::Q1T, .. },
1583 Self::Mapped { dtype: TensorDtype::Q1T, .. },
1584 ) => {
1585 const TILE: usize = cortiq_core::quant::Q1T_TILE;
1586 let g_bytes = gate.quant_bytes();
1587 let u_bytes = up.quant_bytes();
1588 let gpr = gate.cols() / GROUP_SIZE;
1589 let (g_rp, g_ent, g_ov) = q1t_overlay(g_bytes, inter * gpr * TILE, inter);
1590 let (u_rp, u_ent, u_ov) = q1t_overlay(u_bytes, inter * gpr * TILE, inter);
1591 let run = move |start: usize, end: usize| {
1592 for r in start..end {
1593 let mut gv = q1t_dot_row_i8(g_bytes, r, gpr, &act.xq) * act.sx;
1594 let mut uv = q1t_dot_row_i8(u_bytes, r, gpr, &act.xq) * act.sx;
1595 for &(j, xv) in &act.outliers {
1596 gv += q1t_base_weight(g_bytes, r, gpr, j) * xv;
1597 uv += q1t_base_weight(u_bytes, r, gpr, j) * xv;
1598 }
1599 gv += q1t_row_outlier_correction(g_bytes, r, g_rp, g_ent, g_ov, x_ref);
1600 uv += q1t_row_outlier_correction(u_bytes, r, u_rp, u_ent, u_ov, x_ref);
1601 let silu_g = gv / (1.0 + (-gv).exp());
1602 unsafe { *out_addr.at(r) = silu_g * uv };
1604 }
1605 };
1606 dispatch_rows(pool, inter, &run);
1607 true
1608 }
1609 _ => false,
1610 }
1611 }
1612}
1613
1614#[cfg(target_os = "macos")]
1619mod accel_blas {
1620 #[link(name = "Accelerate", kind = "framework")]
1621 unsafe extern "C" {
1622 pub fn cblas_sgemm(
1623 order: i32,
1624 trans_a: i32,
1625 trans_b: i32,
1626 m: i32,
1627 n: i32,
1628 k: i32,
1629 alpha: f32,
1630 a: *const f32,
1631 lda: i32,
1632 b: *const f32,
1633 ldb: i32,
1634 beta: f32,
1635 c: *mut f32,
1636 ldc: i32,
1637 );
1638 }
1639}
1640
1641#[cfg(target_os = "macos")]
1642pub(crate) fn accel_gemm_enabled() -> bool {
1643 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1644 *ON.get_or_init(|| std::env::var("CMF_ACCEL").map(|v| v != "0").unwrap_or(true))
1645}
1646
1647#[cfg(all(target_arch = "aarch64", not(target_os = "macos")))]
1650pub(crate) fn accel_gemm_enabled() -> bool {
1651 true
1652}
1653
1654#[cfg(target_arch = "aarch64")]
1661#[allow(clippy::too_many_arguments)]
1662pub(crate) fn neon_gemm_rm(
1663 m: usize,
1664 n: usize,
1665 k: usize,
1666 alpha: f32,
1667 a: &[f32],
1668 lda: usize,
1669 b_mat: &[f32],
1670 ldb: usize,
1671 b_rows_are_n: bool,
1672 c: &mut [f32],
1673 ldc: usize,
1674) {
1675 debug_assert!(a.len() >= (m - 1) * lda + k);
1676 debug_assert!(c.len() >= (m - 1) * ldc + n);
1677 unsafe {
1679 use core::arch::aarch64::*;
1680 let mut i = 0usize;
1681 while i < m {
1682 let mi = (m - i).min(4);
1683 let mut j = 0usize;
1684 while j < n {
1685 let nj = (n - j).min(8);
1686 if mi == 4 && nj == 8 {
1687 let (mut c0a, mut c0b) = (vdupq_n_f32(0.0), vdupq_n_f32(0.0));
1688 let (mut c1a, mut c1b) = (vdupq_n_f32(0.0), vdupq_n_f32(0.0));
1689 let (mut c2a, mut c2b) = (vdupq_n_f32(0.0), vdupq_n_f32(0.0));
1690 let (mut c3a, mut c3b) = (vdupq_n_f32(0.0), vdupq_n_f32(0.0));
1691 for p in 0..k {
1692 let (b0, b1) = if b_rows_are_n {
1693 let base = b_mat.as_ptr().add(j * ldb + p);
1696 let g = |o: usize| *base.add(o * ldb);
1697 (
1698 [g(0), g(1), g(2), g(3)],
1699 [g(4), g(5), g(6), g(7)],
1700 )
1701 } else {
1702 let base = b_mat.as_ptr().add(p * ldb + j);
1703 (
1704 [*base, *base.add(1), *base.add(2), *base.add(3)],
1705 [*base.add(4), *base.add(5), *base.add(6), *base.add(7)],
1706 )
1707 };
1708 let bv0 = vld1q_f32(b0.as_ptr());
1709 let bv1 = vld1q_f32(b1.as_ptr());
1710 let a0 = vdupq_n_f32(*a.as_ptr().add(i * lda + p));
1711 let a1 = vdupq_n_f32(*a.as_ptr().add((i + 1) * lda + p));
1712 let a2 = vdupq_n_f32(*a.as_ptr().add((i + 2) * lda + p));
1713 let a3 = vdupq_n_f32(*a.as_ptr().add((i + 3) * lda + p));
1714 c0a = vfmaq_f32(c0a, a0, bv0);
1715 c0b = vfmaq_f32(c0b, a0, bv1);
1716 c1a = vfmaq_f32(c1a, a1, bv0);
1717 c1b = vfmaq_f32(c1b, a1, bv1);
1718 c2a = vfmaq_f32(c2a, a2, bv0);
1719 c2b = vfmaq_f32(c2b, a2, bv1);
1720 c3a = vfmaq_f32(c3a, a3, bv0);
1721 c3b = vfmaq_f32(c3b, a3, bv1);
1722 }
1723 let al = vdupq_n_f32(alpha);
1724 for (r, (ca, cb)) in
1725 [(c0a, c0b), (c1a, c1b), (c2a, c2b), (c3a, c3b)].iter().enumerate()
1726 {
1727 let dst = c.as_mut_ptr().add((i + r) * ldc + j);
1728 vst1q_f32(dst, vmulq_f32(*ca, al));
1729 vst1q_f32(dst.add(4), vmulq_f32(*cb, al));
1730 }
1731 } else {
1732 for r in 0..mi {
1733 for q in 0..nj {
1734 let mut acc = 0f32;
1735 for p in 0..k {
1736 let bv = if b_rows_are_n {
1737 b_mat[(j + q) * ldb + p]
1738 } else {
1739 b_mat[p * ldb + j + q]
1740 };
1741 acc += a[(i + r) * lda + p] * bv;
1742 }
1743 c[(i + r) * ldc + j + q] = acc * alpha;
1744 }
1745 }
1746 }
1747 j += nj;
1748 }
1749 i += mi;
1750 }
1751 }
1752}
1753
1754#[cfg(all(target_arch = "aarch64", not(target_os = "macos")))]
1756#[allow(clippy::too_many_arguments)]
1757pub(crate) fn sgemm_rm(
1758 m: usize,
1759 n: usize,
1760 k: usize,
1761 alpha: f32,
1762 a: &[f32],
1763 lda: usize,
1764 b_mat: &[f32],
1765 ldb: usize,
1766 b_rows_are_n: bool,
1767 c: &mut [f32],
1768 ldc: usize,
1769) {
1770 neon_gemm_rm(m, n, k, alpha, a, lda, b_mat, ldb, b_rows_are_n, c, ldc);
1771}
1772
1773#[cfg(target_os = "macos")]
1776#[allow(clippy::too_many_arguments)]
1777pub(crate) fn sgemm_rm(
1778 m: usize,
1779 n: usize,
1780 k: usize,
1781 alpha: f32,
1782 a: &[f32],
1783 lda: usize,
1784 b_mat: &[f32],
1785 ldb: usize,
1786 b_rows_are_n: bool,
1787 c: &mut [f32],
1788 ldc: usize,
1789) {
1790 debug_assert!(a.len() >= (m - 1) * lda + k);
1791 debug_assert!(c.len() >= (m - 1) * ldc + n);
1792 #[cfg(target_arch = "aarch64")]
1797 if std::env::var("CMF_FORCE_NEON_GEMM").map(|v| v == "1").unwrap_or(false) {
1798 return neon_gemm_rm(m, n, k, alpha, a, lda, b_mat, ldb, b_rows_are_n, c, ldc);
1799 }
1800 unsafe {
1801 accel_blas::cblas_sgemm(
1802 101, 111, if b_rows_are_n { 112 } else { 111 },
1805 m as i32,
1806 n as i32,
1807 k as i32,
1808 alpha,
1809 a.as_ptr(),
1810 lda as i32,
1811 b_mat.as_ptr(),
1812 ldb as i32,
1813 0.0,
1814 c.as_mut_ptr(),
1815 ldc as i32,
1816 );
1817 }
1818}
1819
1820#[cfg(target_os = "macos")]
1827fn qmatmat_accel(
1828 q: &[u8],
1829 row_scale: &[f32],
1830 pre: &[std::borrow::Cow<'_, [f32]>],
1831 rows: usize,
1832 cols: usize,
1833 out: &mut [f32],
1834 pool: Option<&Pool>,
1835) {
1836 const TR: usize = 2048;
1841 let b = pre.len();
1842 thread_local! {
1843 static XPANEL: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
1844 static WTILE: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
1845 }
1846 XPANEL.with(|xp| {
1847 WTILE.with(|wt| {
1848 let mut xpanel = xp.borrow_mut();
1849 xpanel.clear();
1850 for x in pre {
1851 xpanel.extend_from_slice(x);
1852 }
1853 let mut wtile = wt.borrow_mut();
1854 wtile.resize(TR * cols, 0.0);
1855 let mut r0 = 0usize;
1856 while r0 < rows {
1857 let tr = TR.min(rows - r0);
1858 let wt_addr = SendMut(wtile.as_mut_ptr());
1860 let run = |start: usize, end: usize| {
1861 for r in start..end {
1862 let row = &q[(r0 + r) * cols..(r0 + r + 1) * cols];
1863 let s = row_scale[r0 + r];
1864 let dst = unsafe {
1866 std::slice::from_raw_parts_mut(wt_addr.at(r * cols), cols)
1867 };
1868 for (d, &v) in dst.iter_mut().zip(row) {
1869 *d = (v as i8) as f32 * s;
1870 }
1871 }
1872 };
1873 dispatch_rows(pool, tr, &run);
1874 unsafe {
1876 accel_blas::cblas_sgemm(
1877 101, 111, 112, b as i32,
1881 tr as i32,
1882 cols as i32,
1883 1.0,
1884 xpanel.as_ptr(),
1885 cols as i32,
1886 wtile.as_ptr(),
1887 cols as i32,
1888 0.0,
1889 out.as_mut_ptr().add(r0),
1890 rows as i32,
1891 );
1892 }
1893 r0 += tr;
1894 }
1895 })
1896 });
1897}
1898
1899fn qmatmat(
1900 q: &[u8],
1901 row_scale: &[f32],
1902 pre: &[std::borrow::Cow<'_, [f32]>],
1903 rows: usize,
1904 cols: usize,
1905 out: &mut [f32],
1906 pool: Option<&Pool>,
1907) {
1908 let b = pre.len();
1909 debug_assert_eq!(out.len(), b * rows);
1910 #[cfg(target_os = "macos")]
1915 if b >= 8 && rows * cols >= 500_000 && accel_gemm_enabled() {
1916 qmatmat_accel(q, row_scale, pre, rows, cols, out, pool);
1917 return;
1918 }
1919 #[cfg(target_arch = "aarch64")]
1920 if sdot_enabled() {
1921 let acts: Vec<SplitAct> = pre.iter().map(|x| split_act(x)).collect();
1922 let out_addr = SendMut(out.as_mut_ptr());
1923 let blocked_ok =
1926 std::env::var("CMF_X86_BLOCKED").map(|v| v != "0").unwrap_or(true);
1927 let use_i8mm = i8mm_enabled();
1928 if blocked_ok {
1929 let run = |start: usize, end: usize| {
1930 let mut o = start;
1931 while o < end {
1932 if o + 2 <= end {
1933 let r0 = &q[o * cols..(o + 1) * cols];
1934 let r1 = &q[(o + 1) * cols..(o + 2) * cols];
1935 let mut bi = 0usize;
1936 while bi + 4 <= acts.len() {
1937 let xs = [
1938 acts[bi].xq.as_slice(),
1939 acts[bi + 1].xq.as_slice(),
1940 acts[bi + 2].xq.as_slice(),
1941 acts[bi + 3].xq.as_slice(),
1942 ];
1943 let d = if use_i8mm {
1944 unsafe { dot_i8_smmla_2x4(r0, r1, xs) }
1945 } else {
1946 unsafe { dot_i8_sdot_2x4(r0, r1, xs) }
1947 };
1948 for (r, row) in [r0, r1].into_iter().enumerate() {
1949 for k in 0..4 {
1950 let act = &acts[bi + k];
1951 let mut v = d[r][k] as f32 * act.sx;
1952 for &(j, xv) in &act.outliers {
1953 v += (row[j] as i8) as f32 * xv;
1954 }
1955 unsafe {
1956 *out_addr.at((bi + k) * rows + o + r) =
1957 v * row_scale[o + r]
1958 };
1959 }
1960 }
1961 bi += 4;
1962 }
1963 while bi < acts.len() {
1964 for (r, row) in [r0, r1].into_iter().enumerate() {
1965 let v = row_dot_sdot(row, &acts[bi]) * row_scale[o + r];
1966 unsafe { *out_addr.at(bi * rows + o + r) = v };
1967 }
1968 bi += 1;
1969 }
1970 o += 2;
1971 } else {
1972 let row = &q[o * cols..(o + 1) * cols];
1973 for (bi, act) in acts.iter().enumerate() {
1974 let v = row_dot_sdot(row, act) * row_scale[o];
1975 unsafe { *out_addr.at(bi * rows + o) = v };
1976 }
1977 o += 1;
1978 }
1979 }
1980 };
1981 dispatch_rows(pool, rows, &run);
1982 return;
1983 }
1984 let run = |start: usize, end: usize| {
1985 for o in start..end {
1986 let row = &q[o * cols..(o + 1) * cols];
1987 for (bi, act) in acts.iter().enumerate() {
1988 let v = row_dot_sdot(row, act) * row_scale[o];
1989 unsafe { *out_addr.at(bi * rows + o) = v };
1990 }
1991 }
1992 };
1993 dispatch_rows(pool, rows, &run);
1994 return;
1995 }
1996 #[cfg(target_arch = "x86_64")]
2001 if avx2_a8w8_enabled() {
2002 let acts: Vec<SplitAct> = pre.iter().map(|x| split_act(x)).collect();
2003 let out_addr = SendMut(out.as_mut_ptr());
2004 let blocked_ok = std::env::var("CMF_X86_BLOCKED").map(|v| v != "0").unwrap_or(true);
2007 if !avx512vnni_enabled() && blocked_ok {
2008 let run = |start: usize, end: usize| {
2009 let mut o = start;
2010 while o < end {
2011 if o + 2 <= end {
2012 let r0 = &q[o * cols..(o + 1) * cols];
2013 let r1 = &q[(o + 1) * cols..(o + 2) * cols];
2014 let mut bi = 0usize;
2015 while bi + 4 <= acts.len() {
2016 let xs = [
2017 acts[bi].xq.as_slice(),
2018 acts[bi + 1].xq.as_slice(),
2019 acts[bi + 2].xq.as_slice(),
2020 acts[bi + 3].xq.as_slice(),
2021 ];
2022 let d = unsafe { dot_i8_i8_avx2_2x4(r0, r1, xs) };
2023 for (r, row) in [r0, r1].into_iter().enumerate() {
2024 for k in 0..4 {
2025 let act = &acts[bi + k];
2026 let mut v = d[r][k] as f32 * act.sx;
2027 for &(j, xv) in &act.outliers {
2028 v += (row[j] as i8) as f32 * xv;
2029 }
2030 unsafe {
2031 *out_addr.at((bi + k) * rows + o + r) =
2032 v * row_scale[o + r]
2033 };
2034 }
2035 }
2036 bi += 4;
2037 }
2038 while bi < acts.len() {
2039 for (r, row) in [r0, r1].into_iter().enumerate() {
2040 let v = row_dot_avx2(row, &acts[bi]) * row_scale[o + r];
2041 unsafe { *out_addr.at(bi * rows + o + r) = v };
2042 }
2043 bi += 1;
2044 }
2045 o += 2;
2046 } else {
2047 let row = &q[o * cols..(o + 1) * cols];
2048 for (bi, act) in acts.iter().enumerate() {
2049 let v = row_dot_avx2(row, act) * row_scale[o];
2050 unsafe { *out_addr.at(bi * rows + o) = v };
2051 }
2052 o += 1;
2053 }
2054 }
2055 };
2056 dispatch_rows(pool, rows, &run);
2057 return;
2058 }
2059 let run = |start: usize, end: usize| {
2060 for o in start..end {
2061 let row = &q[o * cols..(o + 1) * cols];
2062 for (bi, act) in acts.iter().enumerate() {
2063 let v = row_dot_avx2(row, act) * row_scale[o];
2064 unsafe { *out_addr.at(bi * rows + o) = v };
2065 }
2066 }
2067 };
2068 dispatch_rows(pool, rows, &run);
2069 return;
2070 }
2071 let out_addr = SendMut(out.as_mut_ptr());
2072 let run = |start: usize, end: usize| {
2073 for o in start..end {
2074 let row = &q[o * cols..(o + 1) * cols];
2075 for (bi, x) in pre.iter().enumerate() {
2076 let mut acc = 0f32;
2077 for j in 0..cols {
2078 acc += (row[j] as i8) as f32 * x[j];
2079 }
2080 unsafe { *out_addr.at(bi * rows + o) = acc * row_scale[o] };
2081 }
2082 }
2083 };
2084 dispatch_rows(pool, rows, &run);
2085}
2086
2087fn dispatch_rows(pool: Option<&Pool>, rows: usize, run: &(dyn Fn(usize, usize) + Sync)) {
2090 match pool {
2091 Some(pool) if rows >= 256 => pool.run_rows(rows, run),
2092 _ => run(0, rows),
2093 }
2094}
2095
2096fn q4_split(bytes: &[u8], rows: usize, cols: usize) -> (&[u8], &[u8]) {
2098 let groups = rows * cols / GROUP_SIZE;
2099 bytes.split_at(groups * 16)
2100}
2101
2102#[inline]
2107fn vbit_fill4(data: &[u8], buf: &mut [u8]) {
2108 #[cfg(target_arch = "aarch64")]
2109 unsafe {
2110 return vbit_fill4_neon(data, buf);
2111 }
2112 #[cfg(target_arch = "x86_64")]
2113 if avx2_enabled() {
2114 return unsafe { vbit_fill4_avx2(data, buf) };
2115 }
2116 #[allow(unreachable_code)]
2117 for (blk, chunk) in buf.chunks_exact_mut(8).enumerate() {
2118 let u = unpack8::<4>(&data[blk * 4..]);
2119 for k in 0..8 {
2120 chunk[k] = (u[k] - 7) as i8 as u8;
2121 }
2122 }
2123}
2124
2125#[cfg(target_arch = "aarch64")]
2126#[target_feature(enable = "neon")]
2127unsafe fn vbit_fill4_neon(data: &[u8], buf: &mut [u8]) {
2128 unsafe {
2131 use core::arch::aarch64::*;
2132 let n = buf.len();
2133 let mask = vdupq_n_u8(0x0F);
2134 let seven = vdupq_n_s8(7);
2135 let mut g = 0usize;
2136 while g * 32 + 32 <= n {
2137 let b = vld1q_u8(data.as_ptr().add(g * 16));
2138 let hi = vshrq_n_u8::<4>(b);
2139 let lo = vandq_u8(b, mask);
2140 let z0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(hi, lo)), seven);
2141 let z1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(hi, lo)), seven);
2142 vst1q_u8(buf.as_mut_ptr().add(g * 32), vreinterpretq_u8_s8(z0));
2143 vst1q_u8(buf.as_mut_ptr().add(g * 32 + 16), vreinterpretq_u8_s8(z1));
2144 g += 1;
2145 }
2146 }
2147}
2148
2149#[cfg(target_arch = "x86_64")]
2150#[target_feature(enable = "avx2")]
2151unsafe fn vbit_fill4_avx2(data: &[u8], buf: &mut [u8]) {
2152 unsafe {
2154 use core::arch::x86_64::*;
2155 let n = buf.len();
2156 let mask = _mm_set1_epi8(0x0F);
2157 let seven = _mm256_set1_epi8(7);
2158 let mut g = 0usize;
2159 while g * 32 + 32 <= n {
2160 let b = _mm_loadu_si128(data.as_ptr().add(g * 16) as *const __m128i);
2161 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), mask);
2162 let lo = _mm_and_si128(b, mask);
2163 let z = _mm256_sub_epi8(
2164 _mm256_set_m128i(_mm_unpackhi_epi8(hi, lo), _mm_unpacklo_epi8(hi, lo)),
2165 seven,
2166 );
2167 _mm256_storeu_si256(buf.as_mut_ptr().add(g * 32) as *mut __m256i, z);
2168 g += 1;
2169 }
2170 }
2171}
2172
2173#[inline(always)]
2178fn unpack8<const B: usize>(data: &[u8]) -> [i32; 8] {
2179 let mut acc = 0u64;
2180 for i in 0..B {
2181 acc = (acc << 8) | data[i] as u64;
2182 }
2183 let mask = (1u64 << B) - 1;
2184 let mut out = [0i32; 8];
2185 for (k, o) in out.iter_mut().enumerate() {
2186 *o = ((acc >> ((7 - k) * B)) & mask) as i32;
2187 }
2188 out
2189}
2190
2191#[allow(clippy::too_many_arguments)]
2197fn vbitmatvec(
2198 bytes: &[u8],
2199 offsets: &[usize],
2200 x: &[f32],
2201 rows: usize,
2202 cols: usize,
2203 out: &mut [f32],
2204 pool: Option<&Pool>,
2205) {
2206 debug_assert_eq!(out.len(), rows);
2207 debug_assert_eq!(offsets.len(), rows + 1);
2208
2209 if a8w8_enabled() {
2213 let act = split_act(x);
2214 let out_addr = SendMut(out.as_mut_ptr());
2215 let run = move |start: usize, end: usize| {
2216 vbit_range_a8w8(bytes, offsets, x, &act, rows, cols, out_addr, start, end)
2217 };
2218 dispatch_rows(pool, rows, &run);
2219 return;
2220 }
2221
2222 let out_addr = SendMut(out.as_mut_ptr());
2223 let run = move |start: usize, end: usize| {
2224 vbit_range_f32(bytes, offsets, x, rows, cols, out_addr, start, end)
2225 };
2226 dispatch_rows(pool, rows, &run);
2227}
2228
2229#[allow(clippy::too_many_arguments)]
2233fn vbit_range_a8w8(
2234 bytes: &[u8],
2235 offsets: &[usize],
2236 x: &[f32],
2237 act: &SplitAct,
2238 rows: usize,
2239 cols: usize,
2240 out: SendMut,
2241 start: usize,
2242 end: usize,
2243) {
2244 let ng = cols / GROUP_SIZE;
2245 let bits = &bytes[..rows];
2246 let sc_off = rows;
2247 let row_dot = |r: usize| -> f32 {
2248 let b = bits[r] as usize;
2249 let l = (1i32 << (b - 1)) - 1;
2250 let mask = (1u64 << b) - 1;
2251 let data = &bytes[offsets[r]..offsets[r + 1]];
2252 if b == 8 {
2253 let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
2255 let mut dot = 0f32;
2256 for g in 0..ng {
2257 let so = (r * ng + g) * 2;
2258 let sgf = f16_to_f32(u16::from_le_bytes([
2259 bytes[sc_off + so],
2260 bytes[sc_off + so + 1],
2261 ]));
2262 let xg = &x[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
2263 let mut gd = 0f32;
2264 for &xv in xg.iter() {
2265 if nbits < 8 {
2266 acc = (acc << 8) | data[idx] as u64;
2267 idx += 1;
2268 nbits += 8;
2269 }
2270 let u = ((acc >> (nbits - 8)) & 0xFF) as i32;
2271 nbits -= 8;
2272 gd += (u - l) as f32 * xv;
2273 }
2274 dot += gd * sgf;
2275 }
2276 return dot;
2277 }
2278 thread_local! {
2282 static VBIT_SCRATCH: std::cell::RefCell<Vec<u8>> =
2283 const { std::cell::RefCell::new(Vec::new()) };
2284 }
2285 #[inline(always)]
2286 fn fill<const B: usize>(data: &[u8], l: i32, buf: &mut [u8]) {
2287 for (blk, chunk) in buf.chunks_exact_mut(8).enumerate() {
2288 let u = unpack8::<B>(&data[blk * B..]);
2289 for k in 0..8 {
2290 chunk[k] = (u[k] - l) as i8 as u8;
2291 }
2292 }
2293 }
2294 let _ = mask;
2295 VBIT_SCRATCH.with(|scratch| {
2296 let mut buf = scratch.borrow_mut();
2297 buf.resize(cols, 0);
2298 match b {
2299 3 => fill::<3>(data, l, &mut buf),
2300 4 => vbit_fill4(data, &mut buf),
2301 5 => fill::<5>(data, l, &mut buf),
2302 6 => fill::<6>(data, l, &mut buf),
2303 _ => unreachable!(),
2304 }
2305 let mut dot = 0f32;
2306 for g in 0..ng {
2307 let so = (r * ng + g) * 2;
2308 let s = f16_to_f32(u16::from_le_bytes([
2309 bytes[sc_off + so],
2310 bytes[sc_off + so + 1],
2311 ]));
2312 let d = dot_i8_i8(
2313 &buf[g * GROUP_SIZE..(g + 1) * GROUP_SIZE],
2314 &act.xq[g * GROUP_SIZE..(g + 1) * GROUP_SIZE],
2315 ) as f32
2316 * act.sx;
2317 dot += d * s;
2318 }
2319 for &(j, xv) in &act.outliers {
2320 let so = (r * ng + j / GROUP_SIZE) * 2;
2321 let s = f16_to_f32(u16::from_le_bytes([
2322 bytes[sc_off + so],
2323 bytes[sc_off + so + 1],
2324 ]));
2325 dot += (buf[j] as i8) as f32 * s * xv;
2327 }
2328 dot
2329 })
2330 };
2331 for r in start..end {
2332 unsafe { *out.at(r) = row_dot(r) };
2334 }
2335}
2336
2337#[allow(clippy::too_many_arguments)]
2339fn vbit_range_f32(
2340 bytes: &[u8],
2341 offsets: &[usize],
2342 x: &[f32],
2343 rows: usize,
2344 cols: usize,
2345 out: SendMut,
2346 start: usize,
2347 end: usize,
2348) {
2349 let ng = cols / GROUP_SIZE;
2350 let bits = &bytes[..rows];
2351 let sc_off = rows;
2352 #[inline(always)]
2356 fn dot_row<const B: usize>(
2357 data: &[u8],
2358 bytes: &[u8],
2359 sc_off: usize,
2360 r: usize,
2361 ng: usize,
2362 x: &[f32],
2363 ) -> f32 {
2364 let l = ((1i32 << (B - 1)) - 1) as f32;
2365 let gbytes = GROUP_SIZE * B / 8;
2366 let mut dot = 0f32;
2367 for g in 0..ng {
2368 let so = (r * ng + g) * 2;
2369 let s = f16_to_f32(u16::from_le_bytes([bytes[sc_off + so], bytes[sc_off + so + 1]]));
2370 let xg = &x[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
2371 let gd0 = &data[g * gbytes..(g + 1) * gbytes];
2372 let mut gd = 0f32;
2373 for blk in 0..GROUP_SIZE / 8 {
2374 let u = unpack8::<B>(&gd0[blk * B..]);
2375 let xb = &xg[blk * 8..blk * 8 + 8];
2376 for k in 0..8 {
2377 gd += (u[k] as f32 - l) * xb[k];
2378 }
2379 }
2380 dot += gd * s;
2381 }
2382 dot
2383 }
2384 for r in start..end {
2385 let data = &bytes[offsets[r]..offsets[r + 1]];
2386 let v = match bits[r] {
2387 3 => dot_row::<3>(data, bytes, sc_off, r, ng, x),
2388 4 => dot_row::<4>(data, bytes, sc_off, r, ng, x),
2389 5 => dot_row::<5>(data, bytes, sc_off, r, ng, x),
2390 6 => dot_row::<6>(data, bytes, sc_off, r, ng, x),
2391 8 => dot_row::<8>(data, bytes, sc_off, r, ng, x),
2392 b => unreachable!("vbit bit-width {b} (validated at load)"),
2393 };
2394 unsafe { *out.at(r) = v };
2396 }
2397}
2398
2399#[allow(clippy::too_many_arguments)]
2404fn vbitmatvec2(
2405 bytes: &[u8],
2406 offsets: &[usize],
2407 x1: &[f32],
2408 x2: &[f32],
2409 rows: usize,
2410 cols: usize,
2411 o1: &mut [f32],
2412 o2: &mut [f32],
2413 pool: Option<&Pool>,
2414) {
2415 debug_assert_eq!(o1.len(), rows);
2416 debug_assert_eq!(o2.len(), rows);
2417
2418 if a8w8_enabled() {
2419 let a1 = split_act(x1);
2420 let a2 = split_act(x2);
2421 let p1 = SendMut(o1.as_mut_ptr());
2422 let p2 = SendMut(o2.as_mut_ptr());
2423 let run = move |start: usize, end: usize| {
2424 vbit_range2_a8w8(bytes, offsets, x1, x2, &a1, &a2, rows, cols, p1, p2, start, end)
2425 };
2426 dispatch_rows(pool, rows, &run);
2427 return;
2428 }
2429
2430 let p1 = SendMut(o1.as_mut_ptr());
2431 let p2 = SendMut(o2.as_mut_ptr());
2432 let run = move |start: usize, end: usize| {
2433 vbit_range2_f32(bytes, offsets, x1, x2, rows, cols, p1, p2, start, end)
2434 };
2435 dispatch_rows(pool, rows, &run);
2436}
2437
2438#[allow(clippy::too_many_arguments)]
2442fn vbit_range2_a8w8(
2443 bytes: &[u8],
2444 offsets: &[usize],
2445 x1: &[f32],
2446 x2: &[f32],
2447 a1: &SplitAct,
2448 a2: &SplitAct,
2449 rows: usize,
2450 cols: usize,
2451 p1: SendMut,
2452 p2: SendMut,
2453 start: usize,
2454 end: usize,
2455) {
2456 let ng = cols / GROUP_SIZE;
2457 let bits = &bytes[..rows];
2458 let sc_off = rows;
2459 let row_dots = |r: usize| -> (f32, f32) {
2460 let b = bits[r] as usize;
2461 let l = (1i32 << (b - 1)) - 1;
2462 let data = &bytes[offsets[r]..offsets[r + 1]];
2463 if b == 8 {
2464 let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
2467 let (mut d1, mut d2) = (0f32, 0f32);
2468 for g in 0..ng {
2469 let so = (r * ng + g) * 2;
2470 let sgf = f16_to_f32(u16::from_le_bytes([
2471 bytes[sc_off + so],
2472 bytes[sc_off + so + 1],
2473 ]));
2474 let (mut g1, mut g2) = (0f32, 0f32);
2475 for k in 0..GROUP_SIZE {
2476 if nbits < 8 {
2477 acc = (acc << 8) | data[idx] as u64;
2478 idx += 1;
2479 nbits += 8;
2480 }
2481 let u = ((acc >> (nbits - 8)) & 0xFF) as i32;
2482 nbits -= 8;
2483 let w = (u - l) as f32;
2484 g1 += w * x1[g * GROUP_SIZE + k];
2485 g2 += w * x2[g * GROUP_SIZE + k];
2486 }
2487 d1 += g1 * sgf;
2488 d2 += g2 * sgf;
2489 }
2490 return (d1, d2);
2491 }
2492 thread_local! {
2493 static VBIT_SCRATCH2: std::cell::RefCell<Vec<u8>> =
2494 const { std::cell::RefCell::new(Vec::new()) };
2495 }
2496 #[inline(always)]
2497 fn fill<const B: usize>(data: &[u8], l: i32, buf: &mut [u8]) {
2498 for (blk, chunk) in buf.chunks_exact_mut(8).enumerate() {
2499 let u = unpack8::<B>(&data[blk * B..]);
2500 for k in 0..8 {
2501 chunk[k] = (u[k] - l) as i8 as u8;
2502 }
2503 }
2504 }
2505 VBIT_SCRATCH2.with(|scratch| {
2506 let mut buf = scratch.borrow_mut();
2507 buf.resize(cols, 0);
2508 match b {
2509 3 => fill::<3>(data, l, &mut buf),
2510 4 => vbit_fill4(data, &mut buf),
2511 5 => fill::<5>(data, l, &mut buf),
2512 6 => fill::<6>(data, l, &mut buf),
2513 _ => unreachable!(),
2514 }
2515 let (mut d1, mut d2) = (0f32, 0f32);
2516 for g in 0..ng {
2517 let so = (r * ng + g) * 2;
2518 let s = f16_to_f32(u16::from_le_bytes([
2519 bytes[sc_off + so],
2520 bytes[sc_off + so + 1],
2521 ]));
2522 let wg = &buf[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
2523 let v1 =
2524 dot_i8_i8(wg, &a1.xq[g * GROUP_SIZE..(g + 1) * GROUP_SIZE]) as f32 * a1.sx;
2525 let v2 =
2526 dot_i8_i8(wg, &a2.xq[g * GROUP_SIZE..(g + 1) * GROUP_SIZE]) as f32 * a2.sx;
2527 d1 += v1 * s;
2528 d2 += v2 * s;
2529 }
2530 for &(j, xv) in &a1.outliers {
2531 let so = (r * ng + j / GROUP_SIZE) * 2;
2532 let s = f16_to_f32(u16::from_le_bytes([
2533 bytes[sc_off + so],
2534 bytes[sc_off + so + 1],
2535 ]));
2536 d1 += (buf[j] as i8) as f32 * s * xv;
2537 }
2538 for &(j, xv) in &a2.outliers {
2539 let so = (r * ng + j / GROUP_SIZE) * 2;
2540 let s = f16_to_f32(u16::from_le_bytes([
2541 bytes[sc_off + so],
2542 bytes[sc_off + so + 1],
2543 ]));
2544 d2 += (buf[j] as i8) as f32 * s * xv;
2545 }
2546 (d1, d2)
2547 })
2548 };
2549 for r in start..end {
2550 let (v1, v2) = row_dots(r);
2551 unsafe {
2553 *p1.at(r) = v1;
2554 *p2.at(r) = v2;
2555 }
2556 }
2557}
2558
2559#[allow(clippy::too_many_arguments)]
2563fn vbit_range2_f32(
2564 bytes: &[u8],
2565 offsets: &[usize],
2566 x1: &[f32],
2567 x2: &[f32],
2568 rows: usize,
2569 cols: usize,
2570 p1: SendMut,
2571 p2: SendMut,
2572 start: usize,
2573 end: usize,
2574) {
2575 let ng = cols / GROUP_SIZE;
2576 let bits = &bytes[..rows];
2577 let sc_off = rows;
2578 #[inline(always)]
2579 #[allow(clippy::too_many_arguments)]
2580 fn dot_row2<const B: usize>(
2581 data: &[u8],
2582 bytes: &[u8],
2583 sc_off: usize,
2584 r: usize,
2585 ng: usize,
2586 x1: &[f32],
2587 x2: &[f32],
2588 ) -> (f32, f32) {
2589 let l = ((1i32 << (B - 1)) - 1) as f32;
2590 let gbytes = GROUP_SIZE * B / 8;
2591 let (mut d1, mut d2) = (0f32, 0f32);
2592 for g in 0..ng {
2593 let so = (r * ng + g) * 2;
2594 let s = f16_to_f32(u16::from_le_bytes([bytes[sc_off + so], bytes[sc_off + so + 1]]));
2595 let x1g = &x1[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
2596 let x2g = &x2[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
2597 let gd0 = &data[g * gbytes..(g + 1) * gbytes];
2598 let (mut g1, mut g2) = (0f32, 0f32);
2599 for blk in 0..GROUP_SIZE / 8 {
2600 let u = unpack8::<B>(&gd0[blk * B..]);
2601 for k in 0..8 {
2602 let w = u[k] as f32 - l;
2603 g1 += w * x1g[blk * 8 + k];
2604 g2 += w * x2g[blk * 8 + k];
2605 }
2606 }
2607 d1 += g1 * s;
2608 d2 += g2 * s;
2609 }
2610 (d1, d2)
2611 }
2612 for r in start..end {
2613 let data = &bytes[offsets[r]..offsets[r + 1]];
2614 let (v1, v2) = match bits[r] {
2615 3 => dot_row2::<3>(data, bytes, sc_off, r, ng, x1, x2),
2616 4 => dot_row2::<4>(data, bytes, sc_off, r, ng, x1, x2),
2617 5 => dot_row2::<5>(data, bytes, sc_off, r, ng, x1, x2),
2618 6 => dot_row2::<6>(data, bytes, sc_off, r, ng, x1, x2),
2619 8 => dot_row2::<8>(data, bytes, sc_off, r, ng, x1, x2),
2620 b => unreachable!("vbit bit-width {b} (validated at load)"),
2621 };
2622 unsafe {
2624 *p1.at(r) = v1;
2625 *p2.at(r) = v2;
2626 }
2627 }
2628}
2629
2630#[inline]
2637#[allow(unreachable_code)]
2638fn dot_q4t_row_i8(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
2639 #[cfg(target_arch = "aarch64")]
2640 unsafe {
2641 return dot_q4t_row_sdot(bytes, r, gpr, xq);
2642 }
2643 #[cfg(target_arch = "x86_64")]
2644 unsafe {
2645 return dot_q4t_row_avx2(bytes, r, gpr, xq);
2646 }
2647 let mut acc = 0f32;
2648 for gi in 0..gpr {
2649 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
2650 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
2651 let mut d = 0i32;
2652 for (k, &b) in tile[2..].iter().enumerate() {
2653 d += ((b & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2] as i32
2654 + (((b >> 4) & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2 + 1] as i32;
2655 }
2656 acc += d as f32 * s;
2657 }
2658 acc
2659}
2660
2661#[cfg(target_arch = "aarch64")]
2662#[target_feature(enable = "neon,dotprod")]
2663unsafe fn dot_q4t_row_sdot(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
2664 unsafe {
2667 use core::arch::aarch64::*;
2668 use core::arch::asm;
2669 let lomask = vdupq_n_u8(0x0F);
2670 let eight = vdupq_n_s8(8);
2671 let mut acc = 0f32;
2672 for gi in 0..gpr {
2673 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
2674 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
2675 let b = vld1q_u8(t.add(2));
2676 let lo = vandq_u8(b, lomask);
2677 let hi = vshrq_n_u8::<4>(b);
2678 let e0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(lo, hi)), eight);
2679 let e1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(lo, hi)), eight);
2680 let x0 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE));
2681 let x1 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE + 16));
2682 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
2683 asm!(
2684 "sdot {a0:v}.4s, {e0:v}.16b, {x0:v}.16b",
2685 "sdot {a1:v}.4s, {e1:v}.16b, {x1:v}.16b",
2686 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
2687 e0 = in(vreg) e0, x0 = in(vreg) x0, e1 = in(vreg) e1, x1 = in(vreg) x1,
2688 options(pure, nomem, nostack),
2689 );
2690 acc += vaddvq_s32(vaddq_s32(a0, a1)) as f32 * s;
2691 }
2692 acc
2693 }
2694}
2695
2696#[cfg(target_arch = "x86_64")]
2697#[target_feature(enable = "avx2")]
2698unsafe fn dot_q4t_row_avx2(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
2699 unsafe {
2701 use core::arch::x86_64::*;
2702 let lomask = _mm_set1_epi8(0x0F);
2703 let eight = _mm256_set1_epi8(8);
2704 let ones = _mm256_set1_epi16(1);
2705 let mut acc = 0f32;
2706 for gi in 0..gpr {
2707 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
2708 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
2709 let b = _mm_loadu_si128(t.add(2) as *const __m128i);
2710 let lo = _mm_and_si128(b, lomask);
2711 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), lomask);
2712 let w = _mm256_sub_epi8(
2713 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
2714 eight,
2715 );
2716 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
2717 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(w), _mm256_sign_epi8(x, w));
2718 let d = _mm256_madd_epi16(p16, ones);
2719 let hi128 = _mm256_extracti128_si256::<1>(d);
2720 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
2721 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
2722 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
2723 acc += _mm_cvtsi128_si32(s32) as f32 * s;
2724 }
2725 acc
2726 }
2727}
2728
2729#[cfg(target_arch = "x86_64")]
2734#[target_feature(enable = "avx2")]
2735unsafe fn dot_q4t_row_1x4_avx2(
2736 bytes: &[u8],
2737 r: usize,
2738 gpr: usize,
2739 xs: [&[i8]; 4],
2740) -> [f32; 4] {
2741 unsafe {
2743 use core::arch::x86_64::*;
2744 let lomask = _mm_set1_epi8(0x0F);
2745 let eight = _mm256_set1_epi8(8);
2746 let ones = _mm256_set1_epi16(1);
2747 let mut acc = [0f32; 4];
2748 for gi in 0..gpr {
2749 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
2750 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
2751 let bb = _mm_loadu_si128(t.add(2) as *const __m128i);
2752 let lo = _mm_and_si128(bb, lomask);
2753 let hi = _mm_and_si128(_mm_srli_epi16::<4>(bb), lomask);
2754 let w = _mm256_sub_epi8(
2755 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
2756 eight,
2757 );
2758 let aw = _mm256_abs_epi8(w);
2759 for (k, xq) in xs.iter().enumerate() {
2760 let x =
2761 _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
2762 let p16 = _mm256_maddubs_epi16(aw, _mm256_sign_epi8(x, w));
2763 let d = _mm256_madd_epi16(p16, ones);
2764 let hi128 = _mm256_extracti128_si256::<1>(d);
2765 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
2766 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
2767 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
2768 acc[k] += _mm_cvtsi128_si32(s32) as f32 * s;
2769 }
2770 }
2771 acc
2772 }
2773}
2774
2775#[inline]
2777fn q4t_outlier(bytes: &[u8], r: usize, gpr: usize, j: usize) -> (f32, f32) {
2778 let gi = j / GROUP_SIZE;
2779 let k = j % GROUP_SIZE;
2780 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
2781 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
2782 let byte = tile[2 + k / 2];
2783 let nib = if k & 1 == 0 { byte & 0x0F } else { byte >> 4 };
2784 ((nib as i32 - 8) as f32, s)
2785}
2786
2787#[inline]
2790fn q4t_row_exact(bytes: &[u8], r: usize, gpr: usize, x: &[f32]) -> f32 {
2791 let mut acc = 0f32;
2792 for gi in 0..gpr {
2793 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
2794 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
2795 let xg = &x[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
2796 let mut ga = 0f32;
2797 for (k, &b) in tile[2..].iter().enumerate() {
2798 ga += ((b & 0x0F) as f32 - 8.0) * xg[k * 2]
2799 + (((b >> 4) & 0x0F) as f32 - 8.0) * xg[k * 2 + 1];
2800 }
2801 acc += ga * s;
2802 }
2803 acc
2804}
2805
2806fn q4t_matvec(bytes: &[u8], x: &[f32], rows: usize, cols: usize, out: &mut [f32], pool: Option<&Pool>) {
2808 debug_assert_eq!(out.len(), rows);
2809 let gpr = cols / GROUP_SIZE;
2810 let out_addr = SendMut(out.as_mut_ptr());
2811 if a8w8_enabled() {
2812 let act = split_act(x);
2813 let run = move |start: usize, end: usize| {
2814 for r in start..end {
2815 let mut acc = dot_q4t_row_i8(bytes, r, gpr, &act.xq) * act.sx;
2816 for &(j, xv) in &act.outliers {
2817 let (w, s) = q4t_outlier(bytes, r, gpr, j);
2818 acc += w * s * xv;
2819 }
2820 unsafe { *out_addr.at(r) = acc };
2822 }
2823 };
2824 dispatch_rows(pool, rows, &run);
2825 return;
2826 }
2827 let run = move |start: usize, end: usize| {
2828 for r in start..end {
2829 unsafe { *out_addr.at(r) = q4t_row_exact(bytes, r, gpr, x) };
2831 }
2832 };
2833 dispatch_rows(pool, rows, &run);
2834}
2835
2836#[allow(clippy::too_many_arguments)]
2838fn q4t_matvec2(
2839 bytes: &[u8],
2840 x1: &[f32],
2841 x2: &[f32],
2842 rows: usize,
2843 cols: usize,
2844 o1: &mut [f32],
2845 o2: &mut [f32],
2846 pool: Option<&Pool>,
2847) {
2848 let gpr = cols / GROUP_SIZE;
2849 let p1 = SendMut(o1.as_mut_ptr());
2850 let p2 = SendMut(o2.as_mut_ptr());
2851 if a8w8_enabled() {
2852 let a1 = split_act(x1);
2853 let a2 = split_act(x2);
2854 let run = move |start: usize, end: usize| {
2855 for r in start..end {
2856 let mut v1 = dot_q4t_row_i8(bytes, r, gpr, &a1.xq) * a1.sx;
2857 let mut v2 = dot_q4t_row_i8(bytes, r, gpr, &a2.xq) * a2.sx;
2858 for &(j, xv) in &a1.outliers {
2859 let (w, s) = q4t_outlier(bytes, r, gpr, j);
2860 v1 += w * s * xv;
2861 }
2862 for &(j, xv) in &a2.outliers {
2863 let (w, s) = q4t_outlier(bytes, r, gpr, j);
2864 v2 += w * s * xv;
2865 }
2866 unsafe {
2868 *p1.at(r) = v1;
2869 *p2.at(r) = v2;
2870 }
2871 }
2872 };
2873 dispatch_rows(pool, rows, &run);
2874 return;
2875 }
2876 let run = move |start: usize, end: usize| {
2877 for r in start..end {
2878 unsafe {
2880 *p1.at(r) = q4t_row_exact(bytes, r, gpr, x1);
2881 *p2.at(r) = q4t_row_exact(bytes, r, gpr, x2);
2882 }
2883 }
2884 };
2885 dispatch_rows(pool, rows, &run);
2886}
2887
2888#[allow(clippy::too_many_arguments)]
2890fn q4t_matmat(
2891 bytes: &[u8],
2892 xs_all: &[f32],
2893 b: usize,
2894 rows: usize,
2895 cols: usize,
2896 out: &mut [f32],
2897 pool: Option<&Pool>,
2898) {
2899 debug_assert_eq!(out.len(), b * rows);
2900 let gpr = cols / GROUP_SIZE;
2901 let out_addr = SendMut(out.as_mut_ptr());
2902 if a8w8_enabled() {
2903 let acts: Vec<SplitAct> =
2904 (0..b).map(|bi| split_act(&xs_all[bi * cols..(bi + 1) * cols])).collect();
2905 let acts = &acts;
2906 #[cfg(target_arch = "x86_64")]
2907 let blocked_ok = avx2_enabled()
2908 && std::env::var("CMF_X86_BLOCKED").map(|v| v != "0").unwrap_or(true);
2909 #[cfg(not(target_arch = "x86_64"))]
2910 let blocked_ok = false;
2911 let run = move |start: usize, end: usize| {
2912 for r in start..end {
2913 let mut bi = 0usize;
2914 #[cfg(target_arch = "x86_64")]
2915 if blocked_ok {
2916 while bi + 4 <= acts.len() {
2917 let xs = [
2918 acts[bi].xq.as_slice(),
2919 acts[bi + 1].xq.as_slice(),
2920 acts[bi + 2].xq.as_slice(),
2921 acts[bi + 3].xq.as_slice(),
2922 ];
2923 let d = unsafe { dot_q4t_row_1x4_avx2(bytes, r, gpr, xs) };
2924 for k in 0..4 {
2925 let act = &acts[bi + k];
2926 let mut acc = d[k] * act.sx;
2927 for &(j, xv) in &act.outliers {
2928 let (w, sc) = q4t_outlier(bytes, r, gpr, j);
2929 acc += w * sc * xv;
2930 }
2931 unsafe { *out_addr.at((bi + k) * rows + r) = acc };
2933 }
2934 bi += 4;
2935 }
2936 }
2937 let _ = blocked_ok;
2938 while bi < acts.len() {
2939 let act = &acts[bi];
2940 let mut acc = dot_q4t_row_i8(bytes, r, gpr, &act.xq) * act.sx;
2941 for &(j, xv) in &act.outliers {
2942 let (w, s) = q4t_outlier(bytes, r, gpr, j);
2943 acc += w * s * xv;
2944 }
2945 unsafe { *out_addr.at(bi * rows + r) = acc };
2947 bi += 1;
2948 }
2949 }
2950 };
2951 dispatch_rows(pool, rows, &run);
2952 return;
2953 }
2954 let run = move |start: usize, end: usize| {
2955 for r in start..end {
2956 for bi in 0..b {
2957 let x = &xs_all[bi * cols..(bi + 1) * cols];
2958 unsafe { *out_addr.at(bi * rows + r) = q4t_row_exact(bytes, r, gpr, x) };
2960 }
2961 }
2962 };
2963 dispatch_rows(pool, rows, &run);
2964}
2965
2966fn q1_group_sums(xq: &[i8], gpr: usize) -> Vec<i32> {
2975 (0..gpr)
2976 .map(|gi| {
2977 xq[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE]
2978 .iter()
2979 .map(|&v| v as i32)
2980 .sum()
2981 })
2982 .collect()
2983}
2984
2985#[inline]
2989#[allow(unreachable_code)]
2990#[cfg(target_arch = "x86_64")]
2995#[target_feature(enable = "avx2")]
2996unsafe fn dot_q1_row_avx2(
2997 bytes: &[u8],
2998 r: usize,
2999 gpr: usize,
3000 xq: &[i8],
3001 gsum: &[i32],
3002) -> f32 {
3003 unsafe {
3005 use core::arch::x86_64::*;
3006 let expand = _mm256_setr_epi8(
3008 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
3009 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
3010 );
3011 let bitsel = _mm256_setr_epi8(
3012 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64, -128,
3013 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64, -128,
3014 );
3015 let ones8 = _mm256_set1_epi8(1);
3016 let ones16 = _mm256_set1_epi16(1);
3017 let mut acc = 0f32;
3018 for gi in 0..gpr {
3019 let t = bytes.as_ptr().add((r * gpr + gi) * Q1_TILE);
3020 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3021 let bits = u32::from_le_bytes([*t.add(2), *t.add(3), *t.add(4), *t.add(5)]);
3022 let bc = _mm256_shuffle_epi8(_mm256_set1_epi32(bits as i32), expand);
3023 let mask = _mm256_cmpeq_epi8(_mm256_and_si256(bc, bitsel), bitsel);
3024 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
3025 let sel = _mm256_and_si256(x, mask);
3026 let p16 = _mm256_maddubs_epi16(ones8, sel);
3028 let d32 = _mm256_madd_epi16(p16, ones16);
3029 let hi128 = _mm256_extracti128_si256::<1>(d32);
3030 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d32), hi128);
3031 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
3032 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
3033 let msum = _mm_cvtsi128_si32(s32);
3034 let d = 2 * msum - gsum[gi];
3037 acc += d as f32 * s;
3038 }
3039 acc
3040 }
3041}
3042
3043#[cfg(target_arch = "x86_64")]
3046#[target_feature(enable = "avx2")]
3047unsafe fn dot_q1_row_1x4_avx2(
3048 bytes: &[u8],
3049 r: usize,
3050 gpr: usize,
3051 xs: [&[i8]; 4],
3052 gsums: [&[i32]; 4],
3053) -> [f32; 4] {
3054 unsafe {
3056 use core::arch::x86_64::*;
3057 let expand = _mm256_setr_epi8(
3058 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
3059 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
3060 );
3061 let bitsel = _mm256_setr_epi8(
3062 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64, -128,
3063 1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, 8, 16, 32, 64, -128,
3064 );
3065 let ones8 = _mm256_set1_epi8(1);
3066 let ones16 = _mm256_set1_epi16(1);
3067 let mut acc = [0f32; 4];
3068 for gi in 0..gpr {
3069 let t = bytes.as_ptr().add((r * gpr + gi) * Q1_TILE);
3070 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3071 let bits = u32::from_le_bytes([*t.add(2), *t.add(3), *t.add(4), *t.add(5)]);
3072 let bc = _mm256_shuffle_epi8(_mm256_set1_epi32(bits as i32), expand);
3073 let mask = _mm256_cmpeq_epi8(_mm256_and_si256(bc, bitsel), bitsel);
3074 for (k, xq) in xs.iter().enumerate() {
3075 let x =
3076 _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
3077 let sel = _mm256_and_si256(x, mask);
3078 let p16 = _mm256_maddubs_epi16(ones8, sel);
3079 let d32 = _mm256_madd_epi16(p16, ones16);
3080 let hi128 = _mm256_extracti128_si256::<1>(d32);
3081 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d32), hi128);
3082 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
3083 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
3084 let msum = _mm_cvtsi128_si32(s32);
3085 let d = 2 * msum - gsums[k][gi];
3086 acc[k] += d as f32 * s;
3087 }
3088 }
3089 acc
3090 }
3091}
3092
3093#[allow(unreachable_code)]
3094fn dot_q1_row_i8(bytes: &[u8], r: usize, gpr: usize, xq: &[i8], gsum: &[i32]) -> f32 {
3095 #[cfg(target_arch = "aarch64")]
3096 unsafe {
3097 return dot_q1_row_sdot(bytes, r, gpr, xq, gsum);
3098 }
3099 #[cfg(target_arch = "x86_64")]
3100 if avx2_enabled() {
3101 unsafe {
3102 return dot_q1_row_avx2(bytes, r, gpr, xq, gsum);
3103 }
3104 }
3105 let _ = gsum;
3106 let mut acc = 0f32;
3107 for gi in 0..gpr {
3108 let tile = &bytes[(r * gpr + gi) * Q1_TILE..(r * gpr + gi + 1) * Q1_TILE];
3109 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
3110 let mut d = 0i32;
3111 for (j, &b) in tile[2..].iter().enumerate() {
3112 for k in 0..8 {
3113 let w = ((b >> k) & 1) as i32 * 2 - 1;
3114 d += w * xq[gi * GROUP_SIZE + j * 8 + k] as i32;
3115 }
3116 }
3117 acc += d as f32 * s;
3118 }
3119 acc
3120}
3121
3122#[cfg(target_arch = "aarch64")]
3131#[target_feature(enable = "neon,dotprod")]
3132unsafe fn dot_q1_row_sdot(bytes: &[u8], r: usize, gpr: usize, xq: &[i8], gsum: &[i32]) -> f32 {
3133 unsafe {
3136 use core::arch::aarch64::*;
3137 use core::arch::asm;
3138 const MASKS: [u8; 16] = [1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128];
3139 let m = vld1q_u8(MASKS.as_ptr());
3140 macro_rules! tile_dot {
3142 ($t:expr, $x:expr) => {{
3143 let v0 = vcombine_u8(vdup_n_u8(*$t.add(2)), vdup_n_u8(*$t.add(3)));
3144 let v1 = vcombine_u8(vdup_n_u8(*$t.add(4)), vdup_n_u8(*$t.add(5)));
3145 let w0 = vreinterpretq_s8_u8(vtstq_u8(v0, m));
3146 let w1 = vreinterpretq_s8_u8(vtstq_u8(v1, m));
3147 let x0 = vld1q_s8($x);
3148 let x1 = vld1q_s8($x.add(16));
3149 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
3150 asm!(
3151 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
3152 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
3153 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
3154 w0 = in(vreg) w0, x0 = in(vreg) x0, w1 = in(vreg) w1, x1 = in(vreg) x1,
3155 options(pure, nomem, nostack),
3156 );
3157 vaddq_s32(a0, a1)
3158 }};
3159 }
3160 const IW00: [u8; 16] = [2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3];
3169 const IW01: [u8; 16] = [4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5];
3170 const IW10: [u8; 16] = [8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9];
3171 const IW11: [u8; 16] = [10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11];
3172 const ISC: [u8; 8] = [0, 1, 6, 7, 16, 17, 22, 23];
3173 let (iw00, iw01) = (vld1q_u8(IW00.as_ptr()), vld1q_u8(IW01.as_ptr()));
3174 let (iw10, iw11) = (vld1q_u8(IW10.as_ptr()), vld1q_u8(IW11.as_ptr()));
3175 let isc = vld1_u8(ISC.as_ptr());
3176 macro_rules! tile_dot_tbl {
3178 ($ld:expr, $i0:expr, $i1:expr, $x:expr) => {{
3179 let w0 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8($ld, $i0), m));
3180 let w1 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8($ld, $i1), m));
3181 let x0 = vld1q_s8($x);
3182 let x1 = vld1q_s8($x.add(16));
3183 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
3184 asm!(
3185 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
3186 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
3187 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
3188 w0 = in(vreg) w0, x0 = in(vreg) x0, w1 = in(vreg) w1, x1 = in(vreg) x1,
3189 options(pure, nomem, nostack),
3190 );
3191 vaddq_s32(a0, a1)
3192 }};
3193 }
3194 let base = bytes.as_ptr().add(r * gpr * Q1_TILE);
3195 let row_base = r * gpr * Q1_TILE;
3196 let abs_end = bytes.len();
3197 let xp = xq.as_ptr();
3198 let gp = gsum.as_ptr();
3199 let mut accv = vdupq_n_f32(0.0);
3200 let mut gi = 0;
3201 while gi + 4 <= gpr && row_base + (gi + 4) * Q1_TILE + 4 <= abs_end {
3204 let t0 = base.add(gi * Q1_TILE);
3205 let ld_a = vld1q_u8(t0);
3206 let ld_b = vld1q_u8(t0.add(2 * Q1_TILE));
3207 let d0 = tile_dot_tbl!(ld_a, iw00, iw01, xp.add(gi * GROUP_SIZE));
3208 let d1 = tile_dot_tbl!(ld_a, iw10, iw11, xp.add((gi + 1) * GROUP_SIZE));
3209 let d2 = tile_dot_tbl!(ld_b, iw00, iw01, xp.add((gi + 2) * GROUP_SIZE));
3210 let d3 = tile_dot_tbl!(ld_b, iw10, iw11, xp.add((gi + 3) * GROUP_SIZE));
3211 let neg = vpaddq_s32(vpaddq_s32(d0, d1), vpaddq_s32(d2, d3));
3213 let g = vld1q_s32(gp.add(gi));
3214 let dots = vnegq_s32(vaddq_s32(vshlq_n_s32::<1>(neg), g));
3215 let sc16 = vqtbl2_u8(uint8x16x2_t(ld_a, ld_b), isc);
3216 let scf: float32x4_t;
3217 asm!(
3218 "fcvtl {o:v}.4s, {i:v}.4h",
3219 o = out(vreg) scf, i = in(vreg) sc16,
3220 options(pure, nomem, nostack),
3221 );
3222 accv = vfmaq_f32(accv, vcvtq_f32_s32(dots), scf);
3223 gi += 4;
3224 }
3225 let mut acc = vaddvq_f32(accv);
3226 while gi < gpr {
3227 let t = base.add(gi * Q1_TILE);
3228 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3229 let d = vaddvq_s32(tile_dot!(t, xp.add(gi * GROUP_SIZE)));
3230 acc += (-(2 * d + *gp.add(gi))) as f32 * s;
3231 gi += 1;
3232 }
3233 acc
3234 }
3235}
3236
3237#[cfg(target_arch = "aarch64")]
3242#[target_feature(enable = "neon,dotprod")]
3243unsafe fn dot_q1_row_1x4_sdot(
3244 bytes: &[u8],
3245 r: usize,
3246 gpr: usize,
3247 xs: [&[i8]; 4],
3248 gs: [&[i32]; 4],
3249) -> [f32; 4] {
3250 unsafe {
3252 use core::arch::aarch64::*;
3253 use core::arch::asm;
3254 const MASKS: [u8; 16] = [1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128];
3255 const IW00: [u8; 16] = [2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3];
3256 const IW01: [u8; 16] = [4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5];
3257 const IW10: [u8; 16] = [8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9];
3258 const IW11: [u8; 16] = [10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11];
3259 const ISC: [u8; 8] = [0, 1, 6, 7, 16, 17, 22, 23];
3260 let m = vld1q_u8(MASKS.as_ptr());
3261 let (iw00, iw01) = (vld1q_u8(IW00.as_ptr()), vld1q_u8(IW01.as_ptr()));
3262 let (iw10, iw11) = (vld1q_u8(IW10.as_ptr()), vld1q_u8(IW11.as_ptr()));
3263 let isc = vld1_u8(ISC.as_ptr());
3264 macro_rules! sdot2 {
3265 ($w0:expr, $w1:expr, $x:expr) => {{
3266 let x0 = vld1q_s8($x);
3267 let x1 = vld1q_s8($x.add(16));
3268 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
3269 asm!(
3270 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
3271 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
3272 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
3273 w0 = in(vreg) $w0, x0 = in(vreg) x0, w1 = in(vreg) $w1, x1 = in(vreg) x1,
3274 options(pure, nomem, nostack),
3275 );
3276 vaddq_s32(a0, a1)
3277 }};
3278 }
3279 let base = bytes.as_ptr().add(r * gpr * Q1_TILE);
3280 let row_base = r * gpr * Q1_TILE;
3281 let abs_end = bytes.len();
3282 let mut accv = [vdupq_n_f32(0.0); 4];
3283 let mut gi = 0;
3284 while gi + 4 <= gpr && row_base + (gi + 4) * Q1_TILE + 4 <= abs_end {
3285 let t0 = base.add(gi * Q1_TILE);
3286 let ld_a = vld1q_u8(t0);
3287 let ld_b = vld1q_u8(t0.add(2 * Q1_TILE));
3288 let w00 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_a, iw00), m));
3290 let w01 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_a, iw01), m));
3291 let w10 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_a, iw10), m));
3292 let w11 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_a, iw11), m));
3293 let w20 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_b, iw00), m));
3294 let w21 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_b, iw01), m));
3295 let w30 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_b, iw10), m));
3296 let w31 = vreinterpretq_s8_u8(vtstq_u8(vqtbl1q_u8(ld_b, iw11), m));
3297 let sc16 = vqtbl2_u8(uint8x16x2_t(ld_a, ld_b), isc);
3298 let scf: float32x4_t;
3299 asm!(
3300 "fcvtl {o:v}.4s, {i:v}.4h",
3301 o = out(vreg) scf, i = in(vreg) sc16,
3302 options(pure, nomem, nostack),
3303 );
3304 for k in 0..4 {
3305 let xp = xs[k].as_ptr();
3306 let d0 = sdot2!(w00, w01, xp.add(gi * GROUP_SIZE));
3307 let d1 = sdot2!(w10, w11, xp.add((gi + 1) * GROUP_SIZE));
3308 let d2 = sdot2!(w20, w21, xp.add((gi + 2) * GROUP_SIZE));
3309 let d3 = sdot2!(w30, w31, xp.add((gi + 3) * GROUP_SIZE));
3310 let neg = vpaddq_s32(vpaddq_s32(d0, d1), vpaddq_s32(d2, d3));
3311 let g = vld1q_s32(gs[k].as_ptr().add(gi));
3312 let dots = vnegq_s32(vaddq_s32(vshlq_n_s32::<1>(neg), g));
3313 accv[k] = vfmaq_f32(accv[k], vcvtq_f32_s32(dots), scf);
3314 }
3315 gi += 4;
3316 }
3317 let mut acc = [
3318 vaddvq_f32(accv[0]),
3319 vaddvq_f32(accv[1]),
3320 vaddvq_f32(accv[2]),
3321 vaddvq_f32(accv[3]),
3322 ];
3323 while gi < gpr {
3324 let t = base.add(gi * Q1_TILE);
3325 let sc = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
3326 let v0 = vcombine_u8(vdup_n_u8(*t.add(2)), vdup_n_u8(*t.add(3)));
3327 let v1 = vcombine_u8(vdup_n_u8(*t.add(4)), vdup_n_u8(*t.add(5)));
3328 let w0 = vreinterpretq_s8_u8(vtstq_u8(v0, m));
3329 let w1 = vreinterpretq_s8_u8(vtstq_u8(v1, m));
3330 for k in 0..4 {
3331 let d = vaddvq_s32(sdot2!(w0, w1, xs[k].as_ptr().add(gi * GROUP_SIZE)));
3332 acc[k] += (-(2 * d + *gs[k].as_ptr().add(gi))) as f32 * sc;
3333 }
3334 gi += 1;
3335 }
3336 acc
3337 }
3338}
3339
3340#[inline]
3342fn q1_outlier(bytes: &[u8], r: usize, gpr: usize, j: usize) -> (f32, f32) {
3343 let gi = j / GROUP_SIZE;
3344 let k = j % GROUP_SIZE;
3345 let tile = &bytes[(r * gpr + gi) * Q1_TILE..(r * gpr + gi + 1) * Q1_TILE];
3346 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
3347 let bit = (tile[2 + k / 8] >> (k % 8)) & 1;
3348 ((bit as i32 * 2 - 1) as f32, s)
3349}
3350
3351#[inline]
3353fn q1_row_exact(bytes: &[u8], r: usize, gpr: usize, x: &[f32]) -> f32 {
3354 let mut acc = 0f32;
3355 for gi in 0..gpr {
3356 let tile = &bytes[(r * gpr + gi) * Q1_TILE..(r * gpr + gi + 1) * Q1_TILE];
3357 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
3358 let xg = &x[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
3359 let mut ga = 0f32;
3360 for (j, &b) in tile[2..].iter().enumerate() {
3361 for k in 0..8 {
3362 ga += (((b >> k) & 1) as f32 * 2.0 - 1.0) * xg[j * 8 + k];
3363 }
3364 }
3365 acc += ga * s;
3366 }
3367 acc
3368}
3369
3370#[allow(clippy::too_many_arguments)]
3373fn q1_range_a8w8(
3374 bytes: &[u8],
3375 gpr: usize,
3376 act: &SplitAct,
3377 gsum: &[i32],
3378 out: SendMut,
3379 start: usize,
3380 end: usize,
3381) {
3382 for r in start..end {
3383 let mut acc = dot_q1_row_i8(bytes, r, gpr, &act.xq, gsum) * act.sx;
3384 for &(j, xv) in &act.outliers {
3385 let (w, s) = q1_outlier(bytes, r, gpr, j);
3386 acc += w * s * xv;
3387 }
3388 unsafe { *out.at(r) = acc };
3390 }
3391}
3392
3393fn q1_range_f32(bytes: &[u8], gpr: usize, x: &[f32], out: SendMut, start: usize, end: usize) {
3395 for r in start..end {
3396 unsafe { *out.at(r) = q1_row_exact(bytes, r, gpr, x) };
3398 }
3399}
3400
3401fn q1t_overlay(bytes: &[u8], base_len: usize, rows: usize) -> (usize, usize, bool) {
3406 let entries = base_len + (rows + 1) * 4;
3407 (base_len, entries, entries <= bytes.len())
3408}
3409
3410#[inline]
3412fn q1t_rowptr(bytes: &[u8], rp_off: usize, r: usize) -> usize {
3413 let o = rp_off + r * 4;
3414 u32::from_le_bytes([bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]]) as usize
3415}
3416
3417const SIGN5: [[f32; 5]; 256] = {
3421 let mut lut = [[0.0f32; 5]; 256];
3422 let pow3 = [1u16, 3, 9, 27, 81];
3423 let mut byte = 0usize;
3424 while byte < 256 {
3425 let mut i = 0usize;
3426 while i < 5 {
3427 let code = (byte as u16 / pow3[i]) % 3;
3428 lut[byte][i] = if code == 1 {
3429 1.0
3430 } else if code == 2 {
3431 -1.0
3432 } else {
3433 0.0
3434 };
3435 i += 1;
3436 }
3437 byte += 1;
3438 }
3439 lut
3440};
3441
3442const SIGN5_I8: [[i8; 5]; 256] = {
3444 let mut lut = [[0i8; 5]; 256];
3445 let pow3 = [1u16, 3, 9, 27, 81];
3446 let mut byte = 0usize;
3447 while byte < 256 {
3448 let mut i = 0usize;
3449 while i < 5 {
3450 let code = (byte as u16 / pow3[i]) % 3;
3451 lut[byte][i] = if code == 1 {
3452 1
3453 } else if code == 2 {
3454 -1
3455 } else {
3456 0
3457 };
3458 i += 1;
3459 }
3460 byte += 1;
3461 }
3462 lut
3463};
3464
3465const SIGN5_U64: [u64; 256] = {
3471 let mut lut = [0u64; 256];
3472 let pow3 = [1u16, 3, 9, 27, 81];
3473 let mut byte = 0usize;
3474 while byte < 256 {
3475 let mut v = 0u64;
3476 let mut i = 0usize;
3477 while i < 5 {
3478 let code = (byte as u16 / pow3[i]) % 3;
3479 let s: u8 = if code == 1 {
3480 1
3481 } else if code == 2 {
3482 0xFF
3483 } else {
3484 0
3485 };
3486 v |= (s as u64) << (i * 8);
3487 i += 1;
3488 }
3489 lut[byte] = v;
3490 byte += 1;
3491 }
3492 lut
3493};
3494
3495#[inline]
3500fn q1t_base_weight(bytes: &[u8], r: usize, gpr: usize, j: usize) -> f32 {
3501 const TILE: usize = cortiq_core::quant::Q1T_TILE;
3502 let off = (r * gpr + j / GROUP_SIZE) * TILE;
3503 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
3504 let within = j % GROUP_SIZE;
3505 SIGN5[bytes[off + 2 + within / 5] as usize][within % 5] * s
3506}
3507
3508#[cfg(target_arch = "aarch64")]
3511#[target_feature(enable = "neon,dotprod")]
3512#[inline]
3513unsafe fn sdot32_i8(w: *const i8, x: *const i8) -> i32 {
3514 unsafe {
3516 use core::arch::aarch64::*;
3517 use core::arch::asm;
3518 let w0 = vld1q_s8(w);
3519 let w1 = vld1q_s8(w.add(16));
3520 let x0 = vld1q_s8(x);
3521 let x1 = vld1q_s8(x.add(16));
3522 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
3523 asm!(
3524 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
3525 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
3526 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
3527 w0 = in(vreg) w0, x0 = in(vreg) x0, w1 = in(vreg) w1, x1 = in(vreg) x1,
3528 options(pure, nomem, nostack),
3529 );
3530 vaddvq_s32(vaddq_s32(a0, a1))
3531 }
3532}
3533
3534#[cfg(target_arch = "x86_64")]
3537#[target_feature(enable = "avx2")]
3538#[inline]
3539unsafe fn i8dot32_avx2(w: *const i8, x: *const i8) -> i32 {
3540 unsafe {
3542 use core::arch::x86_64::*;
3543 let wv = _mm256_loadu_si256(w as *const __m256i);
3544 let xv = _mm256_loadu_si256(x as *const __m256i);
3545 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(wv), _mm256_sign_epi8(xv, wv));
3546 let d = _mm256_madd_epi16(p16, _mm256_set1_epi16(1));
3547 let hi128 = _mm256_extracti128_si256::<1>(d);
3548 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
3549 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
3550 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
3551 _mm_cvtsi128_si32(s32)
3552 }
3553}
3554
3555#[inline]
3560fn q1t_unpack_group_i8(codes: *const u8, dst: &mut [i8]) {
3561 debug_assert!(dst.len() >= 40);
3562 unsafe {
3565 let p = dst.as_mut_ptr();
3566 for bi in 0..7 {
3567 core::ptr::write_unaligned(
3568 p.add(bi * 5) as *mut u64,
3569 SIGN5_U64[*codes.add(bi) as usize],
3570 );
3571 }
3572 }
3573}
3574
3575#[inline]
3580fn q1t_i8dot32(w: *const i8, x: *const i8) -> i32 {
3581 #[cfg(target_arch = "aarch64")]
3582 unsafe {
3583 return sdot32_i8(w, x);
3584 }
3585 #[cfg(target_arch = "x86_64")]
3586 unsafe {
3587 return i8dot32_avx2(w, x);
3588 }
3589 #[allow(unreachable_code)]
3590 unsafe {
3591 let mut s = 0i32;
3592 for k in 0..GROUP_SIZE {
3593 s += *w.add(k) as i32 * *x.add(k) as i32;
3594 }
3595 s
3596 }
3597}
3598
3599#[inline]
3600unsafe fn q1t_unpack_reg_u64s(codes: *const u8) -> (u64, u64, u64, u64) {
3601 let (s0, s1, s2, s3, s4, s5, s6) = unsafe {
3602 (
3603 SIGN5_U64[*codes as usize],
3604 SIGN5_U64[*codes.add(1) as usize],
3605 SIGN5_U64[*codes.add(2) as usize],
3606 SIGN5_U64[*codes.add(3) as usize],
3607 SIGN5_U64[*codes.add(4) as usize],
3608 SIGN5_U64[*codes.add(5) as usize],
3609 SIGN5_U64[*codes.add(6) as usize],
3610 )
3611 };
3612
3613 let u0 = s0 | (s1 << 40);
3614 let u1 = (s1 >> 24) | (s2 << 16) | (s3 << 56);
3615 let u2 = (s3 >> 8) | (s4 << 32);
3616 let u3 = (s4 >> 32) | (s5 << 8) | (s6 << 48);
3617
3618 (u0, u1, u2, u3)
3619}
3620
3621#[cfg(target_arch = "aarch64")]
3625#[target_feature(enable = "neon,dotprod")]
3626unsafe fn q1t_dot_row_sdot(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
3627 use core::arch::aarch64::*;
3628 use core::arch::asm;
3629 unsafe {
3630 const TILE: usize = cortiq_core::quant::Q1T_TILE;
3631 let mut acc = 0f32;
3632 let bytes_ptr = bytes.as_ptr();
3633 let xq_ptr = xq.as_ptr();
3634 let row_off = r * gpr * TILE;
3635
3636 let gpr2 = gpr & !1;
3637 let mut gi = 0;
3638 while gi < gpr2 {
3639 let off0 = row_off + gi * TILE;
3640 let off1 = off0 + TILE;
3641 let s0 = f16_to_f32(u16::from_le_bytes([*bytes_ptr.add(off0), *bytes_ptr.add(off0 + 1)]));
3642 let s1 = f16_to_f32(u16::from_le_bytes([*bytes_ptr.add(off1), *bytes_ptr.add(off1 + 1)]));
3643
3644 let (u0_0, u1_0, u2_0, u3_0) = q1t_unpack_reg_u64s(bytes_ptr.add(off0 + 2));
3645 let (u0_1, u1_1, u2_1, u3_1) = q1t_unpack_reg_u64s(bytes_ptr.add(off1 + 2));
3646
3647 let w0_0 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u0_0), vcreate_u64(u1_0)));
3648 let w1_0 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u2_0), vcreate_u64(u3_0)));
3649 let w0_1 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u0_1), vcreate_u64(u1_1)));
3650 let w1_1 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u2_1), vcreate_u64(u3_1)));
3651
3652 let x0_0 = vld1q_s8(xq_ptr.add(gi * GROUP_SIZE));
3653 let x1_0 = vld1q_s8(xq_ptr.add(gi * GROUP_SIZE + 16));
3654 let x0_1 = vld1q_s8(xq_ptr.add((gi + 1) * GROUP_SIZE));
3655 let x1_1 = vld1q_s8(xq_ptr.add((gi + 1) * GROUP_SIZE + 16));
3656
3657 let (mut a0_0, mut a1_0) = (vdupq_n_s32(0), vdupq_n_s32(0));
3658 let (mut a0_1, mut a1_1) = (vdupq_n_s32(0), vdupq_n_s32(0));
3659 asm!(
3660 "sdot {a0_0:v}.4s, {w0_0:v}.16b, {x0_0:v}.16b",
3661 "sdot {a1_0:v}.4s, {w1_0:v}.16b, {x1_0:v}.16b",
3662 "sdot {a0_1:v}.4s, {w0_1:v}.16b, {x0_1:v}.16b",
3663 "sdot {a1_1:v}.4s, {w1_1:v}.16b, {x1_1:v}.16b",
3664 a0_0 = inout(vreg) a0_0, a1_0 = inout(vreg) a1_0,
3665 a0_1 = inout(vreg) a0_1, a1_1 = inout(vreg) a1_1,
3666 w0_0 = in(vreg) w0_0, x0_0 = in(vreg) x0_0, w1_0 = in(vreg) w1_0, x1_0 = in(vreg) x1_0,
3667 w0_1 = in(vreg) w0_1, x0_1 = in(vreg) x0_1, w1_1 = in(vreg) w1_1, x1_1 = in(vreg) x1_1,
3668 options(pure, nomem, nostack),
3669 );
3670 let d0 = vaddvq_s32(vaddq_s32(a0_0, a1_0));
3671 let d1 = vaddvq_s32(vaddq_s32(a0_1, a1_1));
3672 acc += d0 as f32 * s0 + d1 as f32 * s1;
3673 gi += 2;
3674 }
3675
3676 if gi < gpr {
3677 let off = row_off + gi * TILE;
3678 let s = f16_to_f32(u16::from_le_bytes([*bytes_ptr.add(off), *bytes_ptr.add(off + 1)]));
3679 let (u0, u1, u2, u3) = q1t_unpack_reg_u64s(bytes_ptr.add(off + 2));
3680 let w0 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u0), vcreate_u64(u1)));
3681 let w1 = vreinterpretq_s8_u64(vcombine_u64(vcreate_u64(u2), vcreate_u64(u3)));
3682 let x0 = vld1q_s8(xq_ptr.add(gi * GROUP_SIZE));
3683 let x1 = vld1q_s8(xq_ptr.add(gi * GROUP_SIZE + 16));
3684 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
3685 asm!(
3686 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
3687 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
3688 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
3689 w0 = in(vreg) w0, x0 = in(vreg) x0, w1 = in(vreg) w1, x1 = in(vreg) x1,
3690 options(pure, nomem, nostack),
3691 );
3692 let d = vaddvq_s32(vaddq_s32(a0, a1));
3693 acc += d as f32 * s;
3694 }
3695 acc
3696 }
3697}
3698
3699#[cfg(target_arch = "x86_64")]
3701#[target_feature(enable = "avx2")]
3702unsafe fn q1t_dot_row_avx2(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
3703 use core::arch::x86_64::*;
3704 unsafe {
3705 const TILE: usize = cortiq_core::quant::Q1T_TILE;
3706 let mut acc = 0f32;
3707 let bytes_ptr = bytes.as_ptr();
3708 let xq_ptr = xq.as_ptr();
3709 let row_off = r * gpr * TILE;
3710
3711 let ones = _mm256_set1_epi16(1);
3712 for gi in 0..gpr {
3713 let off = row_off + gi * TILE;
3714 let s = f16_to_f32(u16::from_le_bytes([*bytes_ptr.add(off), *bytes_ptr.add(off + 1)]));
3715 let (u0, u1, u2, u3) = q1t_unpack_reg_u64s(bytes_ptr.add(off + 2));
3716 let wv = _mm256_set_epi64x(u3 as i64, u2 as i64, u1 as i64, u0 as i64);
3717 let xv = _mm256_loadu_si256(xq_ptr.add(gi * GROUP_SIZE) as *const __m256i);
3718 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(wv), _mm256_sign_epi8(xv, wv));
3719 let d256 = _mm256_madd_epi16(p16, ones);
3720 let d128 = _mm_add_epi32(_mm256_castsi256_si128(d256), _mm256_extracti128_si256(d256, 1));
3721 let d64 = _mm_add_epi32(d128, _mm_shuffle_epi32(d128, 0xee));
3722 let d32 = _mm_cvtsi128_si32(_mm_add_epi32(d64, _mm_shuffle_epi32(d64, 0x55)));
3723 acc += d32 as f32 * s;
3724 }
3725 acc
3726 }
3727}
3728
3729#[inline]
3733fn q1t_dot_row_i8(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
3734 #[cfg(target_arch = "aarch64")]
3735 unsafe {
3736 return q1t_dot_row_sdot(bytes, r, gpr, xq);
3737 }
3738 #[cfg(target_arch = "x86_64")]
3739 unsafe {
3740 return q1t_dot_row_avx2(bytes, r, gpr, xq);
3741 }
3742 #[allow(unreachable_code)]
3743 {
3744 const TILE: usize = cortiq_core::quant::Q1T_TILE;
3745 let mut acc = 0f32;
3746 let mut sg = [0i8; GROUP_SIZE + 8]; for gi in 0..gpr {
3748 let off = (r * gpr + gi) * TILE;
3749 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
3750 q1t_unpack_group_i8(bytes.as_ptr().wrapping_add(off + 2), &mut sg);
3751 let mut d = 0i32;
3752 for k in 0..GROUP_SIZE {
3753 d += sg[k] as i32 * xq[gi * GROUP_SIZE + k] as i32;
3754 }
3755 acc += d as f32 * s;
3756 }
3757 acc
3758 }
3759}
3760
3761fn q1t_row_outlier_correction(
3768 bytes: &[u8],
3769 r: usize,
3770 rp_off: usize,
3771 entries_off: usize,
3772 has_ov: bool,
3773 x: &[f32],
3774) -> f32 {
3775 if !has_ov {
3776 return 0.0;
3777 }
3778 let (c0, c1) = (q1t_rowptr(bytes, rp_off, r), q1t_rowptr(bytes, rp_off, r + 1));
3779 let mut corr = 0f32;
3780 for p in c0..c1 {
3781 let e = entries_off + p * 4;
3782 let col = u16::from_le_bytes([bytes[e], bytes[e + 1]]) as usize;
3783 let val = f16_to_f32(u16::from_le_bytes([bytes[e + 2], bytes[e + 3]]));
3784 corr += val * x[col];
3785 }
3786 corr
3787}
3788
3789fn q1t_dequant_row(
3793 bytes: &[u8],
3794 r: usize,
3795 gpr: usize,
3796 rp_off: usize,
3797 entries_off: usize,
3798 has_ov: bool,
3799 buf: &mut [f32],
3800) {
3801 const TILE: usize = cortiq_core::quant::Q1T_TILE;
3802 for g in 0..gpr {
3803 let off = (r * gpr + g) * TILE;
3804 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
3805 let codes = &bytes[off + 2..off + TILE];
3806 let bc = g * GROUP_SIZE;
3807 for bi in 0..6 {
3809 let lut = &SIGN5[codes[bi] as usize];
3810 let d = &mut buf[bc + bi * 5..bc + bi * 5 + 5];
3811 for i in 0..5 {
3812 d[i] = lut[i] * s;
3813 }
3814 }
3815 let lut = &SIGN5[codes[6] as usize];
3816 buf[bc + 30] = lut[0] * s;
3817 buf[bc + 31] = lut[1] * s;
3818 }
3819 if !has_ov {
3820 return;
3821 }
3822 let (c0, c1) = (q1t_rowptr(bytes, rp_off, r), q1t_rowptr(bytes, rp_off, r + 1));
3823 for p in c0..c1 {
3824 let e = entries_off + p * 4;
3825 let col = u16::from_le_bytes([bytes[e], bytes[e + 1]]) as usize;
3826 buf[col] = f16_to_f32(u16::from_le_bytes([bytes[e + 2], bytes[e + 3]]));
3827 }
3828}
3829
3830fn q1t_add_overlay(bytes: &[u8], x: &[f32], rows: usize, cols: usize, out: &mut [f32], pool: Option<&Pool>) {
3834 const TILE: usize = cortiq_core::quant::Q1T_TILE;
3835 let gpr = cols / GROUP_SIZE;
3836 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
3837 if !has_ov {
3838 return;
3839 }
3840 let out_addr = SendMut(out.as_mut_ptr());
3841 let run = move |start: usize, end: usize| {
3842 for r in start..end {
3843 let corr = q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x);
3844 unsafe { *out_addr.at(r) += corr };
3846 }
3847 };
3848 dispatch_rows(pool, rows, &run);
3849}
3850
3851#[allow(clippy::too_many_arguments)]
3854fn q1t_range_a8w8(
3855 bytes: &[u8],
3856 gpr: usize,
3857 rp_off: usize,
3858 ent_off: usize,
3859 has_ov: bool,
3860 act: &SplitAct,
3861 x: &[f32],
3862 out: SendMut,
3863 start: usize,
3864 end: usize,
3865) {
3866 for r in start..end {
3867 let mut acc = q1t_dot_row_i8(bytes, r, gpr, &act.xq) * act.sx;
3868 for &(j, xv) in &act.outliers {
3869 acc += q1t_base_weight(bytes, r, gpr, j) * xv;
3870 }
3871 acc += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x);
3872 unsafe { *out.at(r) = acc };
3874 }
3875}
3876
3877#[allow(clippy::too_many_arguments)]
3880fn q1t_range_f32_batch(
3881 bytes: &[u8],
3882 gpr: usize,
3883 rp_off: usize,
3884 ent_off: usize,
3885 has_ov: bool,
3886 x: &[f32],
3887 out: SendMut,
3888 start: usize,
3889 end: usize,
3890) {
3891 const TILE: usize = cortiq_core::quant::Q1T_TILE;
3892 let mut sg = [0f32; GROUP_SIZE];
3893 for r in start..end {
3894 let mut acc = 0f32;
3895 for g in 0..gpr {
3896 let off = (r * gpr + g) * TILE;
3897 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
3898 let codes = &bytes[off + 2..off + TILE];
3899 let xg = &x[g * GROUP_SIZE..g * GROUP_SIZE + GROUP_SIZE];
3900 for bi in 0..6 {
3901 sg[bi * 5..bi * 5 + 5].copy_from_slice(&SIGN5[codes[bi] as usize]);
3902 }
3903 let lut = &SIGN5[codes[6] as usize];
3904 sg[30] = lut[0];
3905 sg[31] = lut[1];
3906 let mut gsum = 0f32;
3907 for k in 0..GROUP_SIZE {
3908 gsum += sg[k] * xg[k];
3909 }
3910 acc += s * gsum;
3911 }
3912 acc += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x);
3913 unsafe { *out.at(r) = acc };
3915 }
3916}
3917
3918fn q1t_matvec(bytes: &[u8], x: &[f32], rows: usize, cols: usize, out: &mut [f32], pool: Option<&Pool>) {
3922 debug_assert_eq!(out.len(), rows);
3923 const TILE: usize = cortiq_core::quant::Q1T_TILE;
3924 let gpr = cols / GROUP_SIZE;
3925 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
3926 let out_addr = SendMut(out.as_mut_ptr());
3927 if a8w8_enabled() {
3931 let act = split_act(x);
3932 let act = &act;
3933 let run = move |start: usize, end: usize| {
3934 for r in start..end {
3935 let mut acc = q1t_dot_row_i8(bytes, r, gpr, &act.xq) * act.sx;
3936 for &(j, xv) in &act.outliers {
3937 acc += q1t_base_weight(bytes, r, gpr, j) * xv;
3938 }
3939 acc += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x);
3940 unsafe { *out_addr.at(r) = acc };
3942 }
3943 };
3944 dispatch_rows(pool, rows, &run);
3945 return;
3946 }
3947 let run = move |start: usize, end: usize| {
3948 let mut sg = [0f32; GROUP_SIZE];
3952 for r in start..end {
3953 let mut acc = 0f32;
3954 for g in 0..gpr {
3955 let off = (r * gpr + g) * TILE;
3956 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
3957 let codes = &bytes[off + 2..off + TILE];
3958 let xg = &x[g * GROUP_SIZE..g * GROUP_SIZE + GROUP_SIZE];
3959 for bi in 0..6 {
3960 sg[bi * 5..bi * 5 + 5].copy_from_slice(&SIGN5[codes[bi] as usize]);
3961 }
3962 let lut = &SIGN5[codes[6] as usize];
3963 sg[30] = lut[0];
3964 sg[31] = lut[1];
3965 let mut gsum = 0f32;
3966 for k in 0..GROUP_SIZE {
3967 gsum += sg[k] * xg[k];
3968 }
3969 acc += s * gsum;
3970 }
3971 acc += q1t_row_outlier_correction(bytes, r, rp_off, ent_off, has_ov, x);
3972 unsafe { *out_addr.at(r) = acc };
3973 }
3974 };
3975 dispatch_rows(pool, rows, &run);
3976}
3977
3978fn q1t_matmat(
3981 bytes: &[u8],
3982 xs: &[f32],
3983 b: usize,
3984 rows: usize,
3985 cols: usize,
3986 out: &mut [f32],
3987 pool: Option<&Pool>,
3988) {
3989 debug_assert_eq!(out.len(), b * rows);
3990 const TILE: usize = cortiq_core::quant::Q1T_TILE;
3991 let gpr = cols / GROUP_SIZE;
3992 let (rp_off, ent_off, has_ov) = q1t_overlay(bytes, rows * gpr * TILE, rows);
3993 let out_addr = SendMut(out.as_mut_ptr());
3994 if a8w8_enabled() {
3998 let acts: Vec<SplitAct> = (0..b)
3999 .map(|bi| split_act(&xs[bi * cols..(bi + 1) * cols]))
4000 .collect();
4001 let acts = &acts;
4002 let run = move |start: usize, end: usize| {
4003 let mut sg = vec![0i8; cols + 8]; let mut sc = vec![0f32; gpr]; let mut accs = vec![0f32; b]; for r in start..end {
4007 for g in 0..gpr {
4008 let off = (r * gpr + g) * TILE;
4009 sc[g] = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
4010 q1t_unpack_group_i8(bytes.as_ptr().wrapping_add(off + 2), &mut sg[g * GROUP_SIZE..]);
4011 }
4012 for bi in 0..b {
4013 let act = &acts[bi];
4014 let mut isum = 0f32;
4015 for g in 0..gpr {
4016 let d = q1t_i8dot32(
4017 sg.as_ptr().wrapping_add(g * GROUP_SIZE),
4018 act.xq.as_ptr().wrapping_add(g * GROUP_SIZE),
4019 );
4020 isum += d as f32 * sc[g];
4021 }
4022 let mut acc = isum * act.sx;
4023 for &(j, xv) in &act.outliers {
4024 acc += q1t_base_weight(bytes, r, gpr, j) * xv;
4025 }
4026 accs[bi] = acc;
4027 }
4028 if has_ov {
4032 let (c0, c1) =
4033 (q1t_rowptr(bytes, rp_off, r), q1t_rowptr(bytes, rp_off, r + 1));
4034 for p in c0..c1 {
4035 let e = ent_off + p * 4;
4036 let col = u16::from_le_bytes([bytes[e], bytes[e + 1]]) as usize;
4037 let val = f16_to_f32(u16::from_le_bytes([bytes[e + 2], bytes[e + 3]]));
4038 for bi in 0..b {
4039 accs[bi] += val * xs[bi * cols + col];
4040 }
4041 }
4042 }
4043 for bi in 0..b {
4044 unsafe { *out_addr.at(bi * rows + r) = accs[bi] };
4045 }
4046 }
4047 };
4048 dispatch_rows(pool, rows, &run);
4049 return;
4050 }
4051 let run = move |start: usize, end: usize| {
4052 let mut buf = vec![0f32; cols];
4053 for r in start..end {
4054 q1t_dequant_row(bytes, r, gpr, rp_off, ent_off, has_ov, &mut buf);
4055 for bi in 0..b {
4056 let xr = &xs[bi * cols..(bi + 1) * cols];
4057 let mut acc = 0f32;
4058 for j in 0..cols {
4059 acc += buf[j] * xr[j];
4060 }
4061 unsafe { *out_addr.at(bi * rows + r) = acc };
4062 }
4063 }
4064 };
4065 dispatch_rows(pool, rows, &run);
4066}
4067
4068fn q1_matvec(bytes: &[u8], x: &[f32], rows: usize, cols: usize, out: &mut [f32], pool: Option<&Pool>) {
4069 debug_assert_eq!(out.len(), rows);
4070 let gpr = cols / GROUP_SIZE;
4071 let out_addr = SendMut(out.as_mut_ptr());
4072 if a8w8_enabled() {
4073 let act = split_act(x);
4074 let gsum = q1_group_sums(&act.xq, gpr);
4075 let (act, gsum) = (&act, &gsum);
4076 let run =
4077 move |start: usize, end: usize| q1_range_a8w8(bytes, gpr, act, gsum, out_addr, start, end);
4078 dispatch_rows(pool, rows, &run);
4079 return;
4080 }
4081 let run = move |start: usize, end: usize| q1_range_f32(bytes, gpr, x, out_addr, start, end);
4082 dispatch_rows(pool, rows, &run);
4083}
4084
4085#[allow(clippy::too_many_arguments)]
4087fn q1_matvec2(
4088 bytes: &[u8],
4089 x1: &[f32],
4090 x2: &[f32],
4091 rows: usize,
4092 cols: usize,
4093 o1: &mut [f32],
4094 o2: &mut [f32],
4095 pool: Option<&Pool>,
4096) {
4097 let gpr = cols / GROUP_SIZE;
4098 let p1 = SendMut(o1.as_mut_ptr());
4099 let p2 = SendMut(o2.as_mut_ptr());
4100 if a8w8_enabled() {
4101 let a1 = split_act(x1);
4102 let a2 = split_act(x2);
4103 let g1 = q1_group_sums(&a1.xq, gpr);
4104 let g2 = q1_group_sums(&a2.xq, gpr);
4105 let (a1, a2, g1, g2) = (&a1, &a2, &g1, &g2);
4106 let run = move |start: usize, end: usize| {
4107 for r in start..end {
4108 let mut v1 = dot_q1_row_i8(bytes, r, gpr, &a1.xq, g1) * a1.sx;
4109 let mut v2 = dot_q1_row_i8(bytes, r, gpr, &a2.xq, g2) * a2.sx;
4110 for &(j, xv) in &a1.outliers {
4111 let (w, s) = q1_outlier(bytes, r, gpr, j);
4112 v1 += w * s * xv;
4113 }
4114 for &(j, xv) in &a2.outliers {
4115 let (w, s) = q1_outlier(bytes, r, gpr, j);
4116 v2 += w * s * xv;
4117 }
4118 unsafe {
4120 *p1.at(r) = v1;
4121 *p2.at(r) = v2;
4122 }
4123 }
4124 };
4125 dispatch_rows(pool, rows, &run);
4126 return;
4127 }
4128 let run = move |start: usize, end: usize| {
4129 for r in start..end {
4130 unsafe {
4132 *p1.at(r) = q1_row_exact(bytes, r, gpr, x1);
4133 *p2.at(r) = q1_row_exact(bytes, r, gpr, x2);
4134 }
4135 }
4136 };
4137 dispatch_rows(pool, rows, &run);
4138}
4139
4140#[allow(clippy::too_many_arguments)]
4142fn q1_matmat(
4143 bytes: &[u8],
4144 xs_all: &[f32],
4145 b: usize,
4146 rows: usize,
4147 cols: usize,
4148 out: &mut [f32],
4149 pool: Option<&Pool>,
4150) {
4151 debug_assert_eq!(out.len(), b * rows);
4152 let gpr = cols / GROUP_SIZE;
4153 let out_addr = SendMut(out.as_mut_ptr());
4154 if a8w8_enabled() {
4155 let acts: Vec<(SplitAct, Vec<i32>)> = (0..b)
4156 .map(|bi| {
4157 let act = split_act(&xs_all[bi * cols..(bi + 1) * cols]);
4158 let gsum = q1_group_sums(&act.xq, gpr);
4159 (act, gsum)
4160 })
4161 .collect();
4162 let acts = &acts;
4163 #[cfg(target_arch = "x86_64")]
4164 let blocked_ok = avx2_enabled()
4165 && std::env::var("CMF_X86_BLOCKED").map(|v| v != "0").unwrap_or(true);
4166 #[cfg(target_arch = "aarch64")]
4167 let blocked_ok = sdot_enabled()
4168 && std::env::var("CMF_X86_BLOCKED").map(|v| v != "0").unwrap_or(true);
4169 let run = move |start: usize, end: usize| {
4170 for r in start..end {
4171 let mut bi = 0usize;
4172 #[cfg(target_arch = "aarch64")]
4175 if blocked_ok {
4176 while bi + 4 <= acts.len() {
4177 let xs = [
4178 acts[bi].0.xq.as_slice(),
4179 acts[bi + 1].0.xq.as_slice(),
4180 acts[bi + 2].0.xq.as_slice(),
4181 acts[bi + 3].0.xq.as_slice(),
4182 ];
4183 let gs = [
4184 acts[bi].1.as_slice(),
4185 acts[bi + 1].1.as_slice(),
4186 acts[bi + 2].1.as_slice(),
4187 acts[bi + 3].1.as_slice(),
4188 ];
4189 let d = unsafe { dot_q1_row_1x4_sdot(bytes, r, gpr, xs, gs) };
4190 for k in 0..4 {
4191 let (act, _) = &acts[bi + k];
4192 let mut acc = d[k] * act.sx;
4193 for &(j, xv) in &act.outliers {
4194 let (w, sc) = q1_outlier(bytes, r, gpr, j);
4195 acc += w * sc * xv;
4196 }
4197 unsafe { *out_addr.at((bi + k) * rows + r) = acc };
4199 }
4200 bi += 4;
4201 }
4202 }
4203 #[cfg(target_arch = "x86_64")]
4204 if blocked_ok {
4205 while bi + 4 <= acts.len() {
4206 let xs = [
4207 acts[bi].0.xq.as_slice(),
4208 acts[bi + 1].0.xq.as_slice(),
4209 acts[bi + 2].0.xq.as_slice(),
4210 acts[bi + 3].0.xq.as_slice(),
4211 ];
4212 let gs = [
4213 acts[bi].1.as_slice(),
4214 acts[bi + 1].1.as_slice(),
4215 acts[bi + 2].1.as_slice(),
4216 acts[bi + 3].1.as_slice(),
4217 ];
4218 let d = unsafe { dot_q1_row_1x4_avx2(bytes, r, gpr, xs, gs) };
4219 for k in 0..4 {
4220 let (act, _) = &acts[bi + k];
4221 let mut acc = d[k] * act.sx;
4222 for &(j, xv) in &act.outliers {
4223 let (w, sc) = q1_outlier(bytes, r, gpr, j);
4224 acc += w * sc * xv;
4225 }
4226 unsafe { *out_addr.at((bi + k) * rows + r) = acc };
4228 }
4229 bi += 4;
4230 }
4231 }
4232 while bi < acts.len() {
4233 let (act, gsum) = &acts[bi];
4234 let mut acc = dot_q1_row_i8(bytes, r, gpr, &act.xq, gsum) * act.sx;
4235 for &(j, xv) in &act.outliers {
4236 let (w, s) = q1_outlier(bytes, r, gpr, j);
4237 acc += w * s * xv;
4238 }
4239 unsafe { *out_addr.at(bi * rows + r) = acc };
4241 bi += 1;
4242 }
4243 }
4244 };
4245 dispatch_rows(pool, rows, &run);
4246 return;
4247 }
4248 let run = move |start: usize, end: usize| {
4249 for r in start..end {
4250 for bi in 0..b {
4251 let x = &xs_all[bi * cols..(bi + 1) * cols];
4252 unsafe { *out_addr.at(bi * rows + r) = q1_row_exact(bytes, r, gpr, x) };
4254 }
4255 }
4256 };
4257 dispatch_rows(pool, rows, &run);
4258}
4259
4260fn q4matvec(bytes: &[u8], x: &[f32], rows: usize, cols: usize, out: &mut [f32], pool: Option<&Pool>) {
4266 debug_assert_eq!(out.len(), rows);
4267 let (packed, scales) = q4_split(bytes, rows, cols);
4268 let gpr = cols / GROUP_SIZE;
4269 let out_addr = SendMut(out.as_mut_ptr());
4270
4271 if a8w8_enabled() {
4272 let act = split_act(x);
4273 let run = move |start: usize, end: usize| {
4274 q4_range_a8w8(packed, scales, gpr, cols, &act, out_addr, start, end)
4275 };
4276 dispatch_rows(pool, rows, &run);
4277 return;
4278 }
4279
4280 let run = move |start: usize, end: usize| {
4281 q4_range_f32(packed, scales, gpr, x, out_addr, start, end)
4282 };
4283 dispatch_rows(pool, rows, &run);
4284}
4285
4286#[inline]
4289#[allow(unreachable_code)]
4290#[cfg(target_arch = "x86_64")]
4295#[target_feature(enable = "avx2")]
4296unsafe fn dot_q4b_row_1x4_avx2(
4297 buf: &[u8],
4298 scales: &[u8],
4299 g0: usize,
4300 gpr: usize,
4301 xs: [&[i8]; 4],
4302) -> [f32; 4] {
4303 unsafe {
4305 use core::arch::x86_64::*;
4306 let ones = _mm256_set1_epi16(1);
4307 let mut acc = [0f32; 4];
4308 for gi in 0..gpr {
4309 let s = f16_to_f32(u16::from_le_bytes([
4310 scales[(g0 + gi) * 2],
4311 scales[(g0 + gi) * 2 + 1],
4312 ]));
4313 let w = _mm256_loadu_si256(buf.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
4314 let aw = _mm256_abs_epi8(w);
4315 for (k, xq) in xs.iter().enumerate() {
4316 let x =
4317 _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
4318 let p16 = _mm256_maddubs_epi16(aw, _mm256_sign_epi8(x, w));
4319 let d = _mm256_madd_epi16(p16, ones);
4320 let hi128 = _mm256_extracti128_si256::<1>(d);
4321 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
4322 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
4323 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
4324 acc[k] += _mm_cvtsi128_si32(s32) as f32 * s;
4325 }
4326 }
4327 acc
4328 }
4329}
4330
4331#[cfg(target_arch = "x86_64")]
4337#[target_feature(enable = "avx2")]
4338unsafe fn dot_q4b_row_1x4_sx_avx2(
4339 buf: &[u8],
4340 scales: &[u8],
4341 g0: usize,
4342 gpr: usize,
4343 xs: [&[i8]; 4],
4344 sxs: [f32; 4],
4345) -> [f32; 4] {
4346 unsafe {
4348 use core::arch::x86_64::*;
4349 let ones = _mm256_set1_epi16(1);
4350 let mut acc = [0f32; 4];
4351 for gi in 0..gpr {
4352 let s = f16_to_f32(u16::from_le_bytes([
4353 scales[(g0 + gi) * 2],
4354 scales[(g0 + gi) * 2 + 1],
4355 ]));
4356 let w = _mm256_loadu_si256(buf.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
4357 let aw = _mm256_abs_epi8(w);
4358 for (k, xq) in xs.iter().enumerate() {
4359 let x =
4360 _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
4361 let p16 = _mm256_maddubs_epi16(aw, _mm256_sign_epi8(x, w));
4362 let d = _mm256_madd_epi16(p16, ones);
4363 let hi128 = _mm256_extracti128_si256::<1>(d);
4364 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
4365 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
4366 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
4367 acc[k] += (_mm_cvtsi128_si32(s32) as f32 * sxs[k]) * s;
4368 }
4369 }
4370 acc
4371 }
4372}
4373
4374#[allow(unreachable_code)]
4375fn dot_q4_row_i8(packed: &[u8], scales: &[u8], g0: usize, gpr: usize, xq: &[i8]) -> f32 {
4376 #[cfg(target_arch = "aarch64")]
4377 unsafe {
4378 return dot_q4_row_sdot(packed, scales, g0, gpr, xq);
4379 }
4380 #[cfg(target_arch = "x86_64")]
4381 unsafe {
4382 return dot_q4_row_avx2(packed, scales, g0, gpr, xq);
4383 }
4384 let mut acc = 0f32;
4385 for gi in 0..gpr {
4386 let g = g0 + gi;
4387 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
4388 let mut d = 0i32;
4389 for (k, &b) in packed[g * 16..(g + 1) * 16].iter().enumerate() {
4390 d += ((b & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2] as i32
4391 + (((b >> 4) & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2 + 1] as i32;
4392 }
4393 acc += d as f32 * s;
4394 }
4395 acc
4396}
4397
4398#[inline]
4400#[allow(unreachable_code)]
4401fn dot_q4_row_i8_2(
4402 packed: &[u8],
4403 scales: &[u8],
4404 g0: usize,
4405 gpr: usize,
4406 xq1: &[i8],
4407 xq2: &[i8],
4408) -> (f32, f32) {
4409 #[cfg(target_arch = "aarch64")]
4410 unsafe {
4411 return dot_q4_row_sdot2(packed, scales, g0, gpr, xq1, xq2);
4412 }
4413 #[cfg(target_arch = "x86_64")]
4414 unsafe {
4415 return dot_q4_row_avx2_2(packed, scales, g0, gpr, xq1, xq2);
4416 }
4417 (
4418 dot_q4_row_i8(packed, scales, g0, gpr, xq1),
4419 dot_q4_row_i8(packed, scales, g0, gpr, xq2),
4420 )
4421}
4422
4423#[allow(clippy::too_many_arguments)]
4426fn q4_range_a8w8(
4427 packed: &[u8],
4428 scales: &[u8],
4429 gpr: usize,
4430 cols: usize,
4431 act: &SplitAct,
4432 out: SendMut,
4433 start: usize,
4434 end: usize,
4435) {
4436 for r in start..end {
4437 let mut acc = dot_q4_row_i8(packed, scales, r * gpr, gpr, &act.xq) * act.sx;
4438 for &(j, xv) in &act.outliers {
4440 let flat = r * cols + j;
4441 let byte = packed[flat / 2];
4442 let nib = if flat & 1 == 0 { byte & 0x0F } else { byte >> 4 };
4443 let s = f16_to_f32(u16::from_le_bytes([
4444 scales[(flat / GROUP_SIZE) * 2],
4445 scales[(flat / GROUP_SIZE) * 2 + 1],
4446 ]));
4447 acc += ((nib as i32 - 8) as f32) * s * xv;
4448 }
4449 unsafe { *out.at(r) = acc };
4451 }
4452}
4453
4454#[allow(clippy::too_many_arguments)]
4457fn q4_range2_a8w8(
4458 packed: &[u8],
4459 scales: &[u8],
4460 gpr: usize,
4461 cols: usize,
4462 a1: &SplitAct,
4463 a2: &SplitAct,
4464 p1: SendMut,
4465 p2: SendMut,
4466 start: usize,
4467 end: usize,
4468) {
4469 for r in start..end {
4470 let (s1, s2) = dot_q4_row_i8_2(packed, scales, r * gpr, gpr, &a1.xq, &a2.xq);
4471 let mut acc1 = s1 * a1.sx;
4472 let mut acc2 = s2 * a2.sx;
4473 let fix = |outliers: &[(usize, f32)], acc: &mut f32| {
4475 for &(j, xv) in outliers {
4476 let flat = r * cols + j;
4477 let byte = packed[flat / 2];
4478 let nib = if flat & 1 == 0 { byte & 0x0F } else { byte >> 4 };
4479 let s = f16_to_f32(u16::from_le_bytes([
4480 scales[(flat / GROUP_SIZE) * 2],
4481 scales[(flat / GROUP_SIZE) * 2 + 1],
4482 ]));
4483 *acc += ((nib as i32 - 8) as f32) * s * xv;
4484 }
4485 };
4486 fix(&a1.outliers, &mut acc1);
4487 fix(&a2.outliers, &mut acc2);
4488 unsafe {
4490 *p1.at(r) = acc1;
4491 *p2.at(r) = acc2;
4492 }
4493 }
4494}
4495
4496fn q4_range_f32(
4498 packed: &[u8],
4499 scales: &[u8],
4500 gpr: usize,
4501 x: &[f32],
4502 out: SendMut,
4503 start: usize,
4504 end: usize,
4505) {
4506 for r in start..end {
4507 let mut acc = 0f32;
4508 for gi in 0..gpr {
4509 let g = r * gpr + gi;
4510 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
4511 let pk = &packed[g * 16..(g + 1) * 16];
4512 let xg = &x[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
4513 let mut ga = 0f32;
4514 for (k, &b) in pk.iter().enumerate() {
4515 ga += ((b & 0x0F) as f32 - 8.0) * xg[k * 2]
4516 + (((b >> 4) & 0x0F) as f32 - 8.0) * xg[k * 2 + 1];
4517 }
4518 acc += ga * s;
4519 }
4520 unsafe { *out.at(r) = acc };
4522 }
4523}
4524
4525#[allow(clippy::too_many_arguments)]
4529fn q4matvec2(
4530 bytes: &[u8],
4531 x1: &[f32],
4532 x2: &[f32],
4533 rows: usize,
4534 cols: usize,
4535 o1: &mut [f32],
4536 o2: &mut [f32],
4537 pool: Option<&Pool>,
4538) {
4539 debug_assert_eq!(o1.len(), rows);
4540 debug_assert_eq!(o2.len(), rows);
4541 let (packed, scales) = q4_split(bytes, rows, cols);
4542 let gpr = cols / GROUP_SIZE;
4543
4544 if a8w8_enabled() {
4545 let a1 = split_act(x1);
4546 let a2 = split_act(x2);
4547 let p1 = SendMut(o1.as_mut_ptr());
4548 let p2 = SendMut(o2.as_mut_ptr());
4549 let run = move |start: usize, end: usize| {
4550 q4_range2_a8w8(packed, scales, gpr, cols, &a1, &a2, p1, p2, start, end)
4551 };
4552 dispatch_rows(pool, rows, &run);
4553 return;
4554 }
4555
4556 let p1 = SendMut(o1.as_mut_ptr());
4557 let p2 = SendMut(o2.as_mut_ptr());
4558 let run = move |start: usize, end: usize| {
4559 q4_range2_f32(packed, scales, gpr, x1, x2, p1, p2, start, end)
4560 };
4561 dispatch_rows(pool, rows, &run);
4562}
4563
4564#[allow(clippy::too_many_arguments)]
4566fn q4_range2_f32(
4567 packed: &[u8],
4568 scales: &[u8],
4569 gpr: usize,
4570 x1: &[f32],
4571 x2: &[f32],
4572 p1: SendMut,
4573 p2: SendMut,
4574 start: usize,
4575 end: usize,
4576) {
4577 for r in start..end {
4578 let (mut acc1, mut acc2) = (0f32, 0f32);
4579 for gi in 0..gpr {
4580 let g = r * gpr + gi;
4581 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
4582 let pk = &packed[g * 16..(g + 1) * 16];
4583 let x1g = &x1[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
4584 let x2g = &x2[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
4585 let (mut g1, mut g2) = (0f32, 0f32);
4586 for (k, &b) in pk.iter().enumerate() {
4587 let wl = (b & 0x0F) as f32 - 8.0;
4588 let wh = ((b >> 4) & 0x0F) as f32 - 8.0;
4589 g1 += wl * x1g[k * 2] + wh * x1g[k * 2 + 1];
4590 g2 += wl * x2g[k * 2] + wh * x2g[k * 2 + 1];
4591 }
4592 acc1 += g1 * s;
4593 acc2 += g2 * s;
4594 }
4595 unsafe {
4597 *p1.at(r) = acc1;
4598 *p2.at(r) = acc2;
4599 }
4600 }
4601}
4602
4603thread_local! {
4604 static ROW_I8: std::cell::RefCell<Vec<u8>> = const { std::cell::RefCell::new(Vec::new()) };
4607 static ROW_F32: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
4608}
4609
4610#[allow(clippy::too_many_arguments)]
4616fn q4matmat(
4617 bytes: &[u8],
4618 xs_all: &[f32],
4619 b: usize,
4620 rows: usize,
4621 cols: usize,
4622 out: &mut [f32],
4623 pool: Option<&Pool>,
4624) {
4625 debug_assert_eq!(xs_all.len(), b * cols);
4626 debug_assert_eq!(out.len(), b * rows);
4627 let (packed, scales) = q4_split(bytes, rows, cols);
4628 let gpr = cols / GROUP_SIZE;
4629 let gscale = |g: usize| f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
4630
4631 if a8w8_enabled() {
4632 let acts: Vec<SplitAct> =
4633 (0..b).map(|bi| split_act(&xs_all[bi * cols..(bi + 1) * cols])).collect();
4634 let acts = &acts;
4635 let out_addr = SendMut(out.as_mut_ptr());
4636 let run = move |start: usize, end: usize| {
4637 ROW_I8.with(|rb| {
4638 let mut buf = rb.borrow_mut();
4639 buf.resize(cols, 0);
4640 for r in start..end {
4641 for gi in 0..gpr {
4645 let g = r * gpr + gi;
4646 for (k, &bt) in packed[g * 16..(g + 1) * 16].iter().enumerate() {
4647 buf[gi * GROUP_SIZE + k * 2] = ((bt & 0x0F) as i32 - 8) as i8 as u8;
4648 buf[gi * GROUP_SIZE + k * 2 + 1] =
4649 (((bt >> 4) & 0x0F) as i32 - 8) as i8 as u8;
4650 }
4651 }
4652 let mut bi = 0usize;
4653 #[cfg(target_arch = "x86_64")]
4654 if avx2_enabled()
4655 && std::env::var("CMF_X86_BLOCKED").map(|v| v != "0").unwrap_or(true)
4656 {
4657 while bi + 4 <= acts.len() {
4658 let xs = [
4659 acts[bi].xq.as_slice(),
4660 acts[bi + 1].xq.as_slice(),
4661 acts[bi + 2].xq.as_slice(),
4662 acts[bi + 3].xq.as_slice(),
4663 ];
4664 let d = unsafe {
4665 dot_q4b_row_1x4_avx2(&buf, scales, r * gpr, gpr, xs)
4666 };
4667 for k in 0..4 {
4668 let act = &acts[bi + k];
4669 let mut acc = d[k] * act.sx;
4670 for &(j, xv) in &act.outliers {
4671 acc += (buf[j] as i8) as f32
4672 * gscale((r * cols + j) / GROUP_SIZE)
4673 * xv;
4674 }
4675 unsafe { *out_addr.at((bi + k) * rows + r) = acc };
4677 }
4678 bi += 4;
4679 }
4680 }
4681 while bi < acts.len() {
4682 let act = &acts[bi];
4683 let mut acc = 0f32;
4684 for gi in 0..gpr {
4685 let d = dot_i8_i8(
4686 &buf[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE],
4687 &act.xq[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE],
4688 );
4689 acc += d as f32 * gscale(r * gpr + gi);
4690 }
4691 acc *= act.sx;
4692 for &(j, xv) in &act.outliers {
4694 acc += (buf[j] as i8) as f32 * gscale((r * cols + j) / GROUP_SIZE) * xv;
4695 }
4696 unsafe { *out_addr.at(bi * rows + r) = acc };
4698 bi += 1;
4699 }
4700 }
4701 })
4702 };
4703 dispatch_rows(pool, rows, &run);
4704 return;
4705 }
4706
4707 let out_addr = SendMut(out.as_mut_ptr());
4708 let run = move |start: usize, end: usize| {
4709 ROW_F32.with(|rb| {
4710 let mut buf = rb.borrow_mut();
4711 buf.resize(cols, 0.0);
4712 for r in start..end {
4713 for gi in 0..gpr {
4716 let g = r * gpr + gi;
4717 for (k, &bt) in packed[g * 16..(g + 1) * 16].iter().enumerate() {
4718 buf[gi * GROUP_SIZE + k * 2] = (bt & 0x0F) as f32 - 8.0;
4719 buf[gi * GROUP_SIZE + k * 2 + 1] = ((bt >> 4) & 0x0F) as f32 - 8.0;
4720 }
4721 }
4722 for bi in 0..b {
4723 let x = &xs_all[bi * cols..(bi + 1) * cols];
4724 let mut acc = 0f32;
4725 for gi in 0..gpr {
4726 let mut ga = 0f32;
4727 for k in 0..GROUP_SIZE / 2 {
4732 let e = gi * GROUP_SIZE + k * 2;
4733 ga += buf[e] * x[e] + buf[e + 1] * x[e + 1];
4734 }
4735 acc += ga * gscale(r * gpr + gi);
4736 }
4737 unsafe { *out_addr.at(bi * rows + r) = acc };
4739 }
4740 }
4741 })
4742 };
4743 dispatch_rows(pool, rows, &run);
4744}
4745
4746#[allow(clippy::too_many_arguments)]
4751fn vbitmatmat(
4752 bytes: &[u8],
4753 offsets: &[usize],
4754 xs_all: &[f32],
4755 b: usize,
4756 rows: usize,
4757 cols: usize,
4758 out: &mut [f32],
4759 pool: Option<&Pool>,
4760) {
4761 debug_assert_eq!(xs_all.len(), b * cols);
4762 debug_assert_eq!(out.len(), b * rows);
4763 debug_assert_eq!(offsets.len(), rows + 1);
4764 let ng = cols / GROUP_SIZE;
4765 let bits = &bytes[..rows];
4766 let sc_off = rows;
4767 let gscale = |r: usize, g: usize| {
4768 let so = (r * ng + g) * 2;
4769 f16_to_f32(u16::from_le_bytes([bytes[sc_off + so], bytes[sc_off + so + 1]]))
4770 };
4771
4772 let decode_f32 = |r: usize, dst: &mut [f32]| {
4774 let bw = bits[r] as usize;
4775 let l = ((1i32 << (bw - 1)) - 1) as f32;
4776 let data = &bytes[offsets[r]..offsets[r + 1]];
4777 let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
4778 for d in dst.iter_mut() {
4779 while nbits < bw {
4780 acc = (acc << 8) | data[idx] as u64;
4781 idx += 1;
4782 nbits += 8;
4783 }
4784 let u = ((acc >> (nbits - bw)) & ((1u64 << bw) - 1)) as f32;
4785 nbits -= bw;
4786 *d = u - l;
4787 }
4788 };
4789
4790 if a8w8_enabled() {
4791 let acts: Vec<SplitAct> =
4792 (0..b).map(|bi| split_act(&xs_all[bi * cols..(bi + 1) * cols])).collect();
4793 let acts = &acts;
4794 let out_addr = SendMut(out.as_mut_ptr());
4795 let run = move |start: usize, end: usize| {
4796 for r in start..end {
4797 let bw = bits[r] as usize;
4798 if bw == 8 {
4799 ROW_F32.with(|rb| {
4802 let mut buf = rb.borrow_mut();
4803 buf.resize(cols, 0.0);
4804 decode_f32(r, &mut buf);
4805 for bi in 0..b {
4806 let x = &xs_all[bi * cols..(bi + 1) * cols];
4807 let mut dot = 0f32;
4808 for g in 0..ng {
4809 let mut gd = 0f32;
4810 for k in 0..GROUP_SIZE {
4811 gd += buf[g * GROUP_SIZE + k] * x[g * GROUP_SIZE + k];
4812 }
4813 dot += gd * gscale(r, g);
4814 }
4815 unsafe { *out_addr.at(bi * rows + r) = dot };
4817 }
4818 });
4819 continue;
4820 }
4821 let l = (1i32 << (bw - 1)) - 1;
4822 let data = &bytes[offsets[r]..offsets[r + 1]];
4823 ROW_I8.with(|rb| {
4824 let mut buf = rb.borrow_mut();
4825 buf.resize(cols, 0);
4826 #[inline(always)]
4827 fn fill<const B: usize>(data: &[u8], l: i32, buf: &mut [u8]) {
4828 for (blk, chunk) in buf.chunks_exact_mut(8).enumerate() {
4829 let u = unpack8::<B>(&data[blk * B..]);
4830 for k in 0..8 {
4831 chunk[k] = (u[k] - l) as i8 as u8;
4832 }
4833 }
4834 }
4835 match bw {
4836 3 => fill::<3>(data, l, &mut buf),
4837 4 => vbit_fill4(data, &mut buf),
4838 5 => fill::<5>(data, l, &mut buf),
4839 6 => fill::<6>(data, l, &mut buf),
4840 _ => unreachable!("vbit bit-width {bw} (validated at load)"),
4841 }
4842 let mut bi = 0usize;
4843 #[cfg(target_arch = "x86_64")]
4847 if avx2_enabled()
4848 && std::env::var("CMF_X86_BLOCKED").map(|v| v != "0").unwrap_or(true)
4849 {
4850 while bi + 4 <= acts.len() {
4851 let xs = [
4852 acts[bi].xq.as_slice(),
4853 acts[bi + 1].xq.as_slice(),
4854 acts[bi + 2].xq.as_slice(),
4855 acts[bi + 3].xq.as_slice(),
4856 ];
4857 let sxs = [
4858 acts[bi].sx,
4859 acts[bi + 1].sx,
4860 acts[bi + 2].sx,
4861 acts[bi + 3].sx,
4862 ];
4863 let d = unsafe {
4864 dot_q4b_row_1x4_sx_avx2(&buf, &bytes[sc_off..], r * ng, ng, xs, sxs)
4865 };
4866 for k in 0..4 {
4867 let act = &acts[bi + k];
4868 let mut dot = d[k];
4869 for &(j, xv) in &act.outliers {
4870 dot +=
4871 (buf[j] as i8) as f32 * gscale(r, j / GROUP_SIZE) * xv;
4872 }
4873 unsafe { *out_addr.at((bi + k) * rows + r) = dot };
4875 }
4876 bi += 4;
4877 }
4878 }
4879 while bi < acts.len() {
4880 let act = &acts[bi];
4881 let mut dot = 0f32;
4882 for g in 0..ng {
4883 let d = dot_i8_i8(
4884 &buf[g * GROUP_SIZE..(g + 1) * GROUP_SIZE],
4885 &act.xq[g * GROUP_SIZE..(g + 1) * GROUP_SIZE],
4886 ) as f32
4887 * act.sx;
4888 dot += d * gscale(r, g);
4889 }
4890 for &(j, xv) in &act.outliers {
4891 dot += (buf[j] as i8) as f32 * gscale(r, j / GROUP_SIZE) * xv;
4892 }
4893 unsafe { *out_addr.at(bi * rows + r) = dot };
4895 bi += 1;
4896 }
4897 });
4898 }
4899 };
4900 dispatch_rows(pool, rows, &run);
4901 return;
4902 }
4903
4904 let out_addr = SendMut(out.as_mut_ptr());
4905 let run = move |start: usize, end: usize| {
4906 ROW_F32.with(|rb| {
4907 let mut buf = rb.borrow_mut();
4908 buf.resize(cols, 0.0);
4909 for r in start..end {
4910 decode_f32(r, &mut buf);
4911 for bi in 0..b {
4912 let x = &xs_all[bi * cols..(bi + 1) * cols];
4913 let mut dot = 0f32;
4914 for g in 0..ng {
4915 let mut gd = 0f32;
4916 for k in 0..GROUP_SIZE {
4917 gd += buf[g * GROUP_SIZE + k] * x[g * GROUP_SIZE + k];
4918 }
4919 dot += gd * gscale(r, g);
4920 }
4921 unsafe { *out_addr.at(bi * rows + r) = dot };
4923 }
4924 }
4925 })
4926 };
4927 dispatch_rows(pool, rows, &run);
4928}
4929
4930pub(crate) fn gpu_batch_job<'a>(
4934 t: &'a QTensor,
4935 x: &[f32],
4936) -> Option<(std::sync::Arc<CmfModel>, crate::gpu::BatchJob<'a>)> {
4937 match t {
4938 QTensor::Mapped {
4939 model,
4940 idx,
4941 dtype: dt @ (TensorDtype::Q8Row | TensorDtype::Q8_2f),
4942 rows,
4943 cols,
4944 row_scale,
4945 col_field,
4946 ..
4947 } => Some((
4948 model.clone(),
4949 crate::gpu::BatchJob {
4950 idx: *idx,
4951 rows: *rows,
4952 cols: *cols,
4953 row_scale,
4954 xs: prescale(x, col_field, *dt).into_owned(),
4955 q1: false,
4956 },
4957 )),
4958 QTensor::Mapped {
4960 model,
4961 idx,
4962 dtype: TensorDtype::Q1,
4963 rows,
4964 cols,
4965 ..
4966 } => Some((
4967 model.clone(),
4968 crate::gpu::BatchJob {
4969 idx: *idx,
4970 rows: *rows,
4971 cols: *cols,
4972 row_scale: &[],
4973 xs: x.to_vec(),
4974 q1: true,
4975 },
4976 )),
4977 _ => None,
4978 }
4979}
4980
4981thread_local! {
4982 static PRESCALE_BUF1: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
4983 static PRESCALE_BUF2: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
4984}
4985
4986pub(crate) fn prescale<'a>(
4987 x: &'a [f32],
4988 col_field: &[f32],
4989 dtype: TensorDtype,
4990) -> std::borrow::Cow<'a, [f32]> {
4991 if dtype == TensorDtype::Q8_2f {
4992 x.iter().zip(col_field).map(|(a, c)| a * c).collect()
4993 } else {
4994 std::borrow::Cow::Borrowed(x)
4995 }
4996}
4997
4998pub(crate) fn prescale_with<R, F: FnOnce(&[f32]) -> R>(
5001 x: &[f32],
5002 col_field: &[f32],
5003 dtype: TensorDtype,
5004 buf_id: u8,
5005 f: F,
5006) -> R {
5007 if dtype == TensorDtype::Q8_2f {
5008 if buf_id == 1 {
5009 PRESCALE_BUF1.with(|b| {
5010 let mut buf = b.borrow_mut();
5011 buf.clear();
5012 buf.extend(x.iter().zip(col_field).map(|(a, c)| a * c));
5013 f(&buf)
5014 })
5015 } else {
5016 PRESCALE_BUF2.with(|b| {
5017 let mut buf = b.borrow_mut();
5018 buf.clear();
5019 buf.extend(x.iter().zip(col_field).map(|(a, c)| a * c));
5020 f(&buf)
5021 })
5022 }
5023 } else {
5024 f(x)
5025 }
5026}
5027
5028#[cfg(target_arch = "x86_64")]
5033pub(crate) fn avx2_enabled() -> bool {
5034 use std::sync::OnceLock;
5035 static ON: OnceLock<bool> = OnceLock::new();
5036 *ON.get_or_init(|| {
5037 std::env::var("CMF_AVX2").map(|v| v != "0").unwrap_or(true)
5038 && std::arch::is_x86_feature_detected!("avx2")
5039 && std::arch::is_x86_feature_detected!("fma")
5040 })
5041}
5042
5043#[cfg(target_arch = "x86_64")]
5048fn avx2_a8w8_enabled() -> bool {
5049 use std::sync::OnceLock;
5050 static ON: OnceLock<bool> = OnceLock::new();
5051 *ON.get_or_init(|| {
5052 avx2_enabled() && std::env::var("CMF_SDOT").map(|v| v != "0").unwrap_or(true)
5053 })
5054}
5055
5056#[inline]
5060pub(crate) fn a8w8_enabled() -> bool {
5061 #[cfg(target_arch = "aarch64")]
5062 {
5063 sdot_enabled()
5064 }
5065 #[cfg(target_arch = "x86_64")]
5066 {
5067 avx2_a8w8_enabled()
5068 }
5069 #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
5070 {
5071 false
5072 }
5073}
5074
5075#[inline]
5078#[allow(unreachable_code)]
5079fn dot_i8_i8(w: &[u8], xq: &[i8]) -> i32 {
5080 #[cfg(target_arch = "aarch64")]
5081 unsafe {
5082 return dot_i8_sdot(w, xq);
5083 }
5084 #[cfg(target_arch = "x86_64")]
5085 unsafe {
5086 if avx512vnni_enabled() {
5087 return dot_i8_i8_vnni(w, xq);
5088 }
5089 return dot_i8_i8_avx2(w, xq);
5090 }
5091 w.iter().zip(xq).map(|(&a, &b)| (a as i8) as i32 * b as i32).sum()
5092}
5093
5094#[cfg(target_arch = "x86_64")]
5098fn avx512vnni_enabled() -> bool {
5099 use std::sync::OnceLock;
5100 static ON: OnceLock<bool> = OnceLock::new();
5101 *ON.get_or_init(|| {
5102 std::env::var("CMF_AVX512").map(|v| v != "0").unwrap_or(true)
5103 && std::arch::is_x86_feature_detected!("avx512f")
5104 && std::arch::is_x86_feature_detected!("avx512bw")
5105 && std::arch::is_x86_feature_detected!("avx512vl")
5106 && std::arch::is_x86_feature_detected!("avx512vnni")
5107 })
5108}
5109
5110#[cfg(target_arch = "x86_64")]
5115#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
5116unsafe fn dot_i8_i8_vnni(w: &[u8], xq: &[i8]) -> i32 {
5117 unsafe {
5119 use core::arch::x86_64::*;
5120 let n = w.len();
5121 let mut j = 0usize;
5122 let mut total: i32;
5123 {
5128 #[inline(always)]
5129 unsafe fn step(
5130 w: *const u8,
5131 x: *const i8,
5132 acc: core::arch::x86_64::__m512i,
5133 ) -> core::arch::x86_64::__m512i {
5134 unsafe {
5135 use core::arch::x86_64::*;
5136 let wv = _mm512_loadu_si512(w as *const _);
5137 let xv = _mm512_loadu_si512(x as *const _);
5138 let aw = _mm512_abs_epi8(wv);
5139 let neg = _mm512_movepi8_mask(wv);
5140 let sx = _mm512_mask_sub_epi8(xv, neg, _mm512_setzero_si512(), xv);
5141 _mm512_dpbusd_epi32(acc, aw, sx)
5142 }
5143 }
5144 let (mut a0, mut a1, mut a2, mut a3) = (
5145 _mm512_setzero_si512(),
5146 _mm512_setzero_si512(),
5147 _mm512_setzero_si512(),
5148 _mm512_setzero_si512(),
5149 );
5150 while j + 256 <= n {
5151 a0 = step(w.as_ptr().add(j), xq.as_ptr().add(j), a0);
5152 a1 = step(w.as_ptr().add(j + 64), xq.as_ptr().add(j + 64), a1);
5153 a2 = step(w.as_ptr().add(j + 128), xq.as_ptr().add(j + 128), a2);
5154 a3 = step(w.as_ptr().add(j + 192), xq.as_ptr().add(j + 192), a3);
5155 j += 256;
5156 }
5157 while j + 64 <= n {
5158 a0 = step(w.as_ptr().add(j), xq.as_ptr().add(j), a0);
5159 j += 64;
5160 }
5161 let s01 = _mm512_add_epi32(a0, a1);
5162 let s23 = _mm512_add_epi32(a2, a3);
5163 total = _mm512_reduce_add_epi32(_mm512_add_epi32(s01, s23));
5164 }
5165 if j + 32 <= n {
5167 let wv = _mm256_loadu_si256(w.as_ptr().add(j) as *const __m256i);
5168 let xv = _mm256_loadu_si256(xq.as_ptr().add(j) as *const __m256i);
5169 let d = _mm256_dpbusd_epi32(
5170 _mm256_setzero_si256(),
5171 _mm256_abs_epi8(wv),
5172 _mm256_sign_epi8(xv, wv),
5173 );
5174 let hi128 = _mm256_extracti128_si256::<1>(d);
5175 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
5176 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
5177 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
5178 total += _mm_cvtsi128_si32(s32);
5179 j += 32;
5180 }
5181 while j < n {
5182 total += (w[j] as i8) as i32 * xq[j] as i32;
5183 j += 1;
5184 }
5185 total
5186 }
5187}
5188
5189#[cfg(target_arch = "x86_64")]
5191#[target_feature(enable = "avx2,fma")]
5192unsafe fn dot_i8_f32_avx2(w: &[u8], x: &[f32]) -> f32 {
5193 unsafe {
5195 use core::arch::x86_64::*;
5196 let n = x.len();
5197 let wp = w.as_ptr();
5198 let xp = x.as_ptr();
5199 let (mut a0, mut a1) = (_mm256_setzero_ps(), _mm256_setzero_ps());
5200 let mut j = 0usize;
5201 while j + 16 <= n {
5202 let wb = _mm_loadu_si128(wp.add(j) as *const __m128i);
5203 let lo = _mm256_cvtepi8_epi32(wb);
5204 let hi = _mm256_cvtepi8_epi32(_mm_srli_si128::<8>(wb));
5205 a0 = _mm256_fmadd_ps(_mm256_cvtepi32_ps(lo), _mm256_loadu_ps(xp.add(j)), a0);
5206 a1 = _mm256_fmadd_ps(_mm256_cvtepi32_ps(hi), _mm256_loadu_ps(xp.add(j + 8)), a1);
5207 j += 16;
5208 }
5209 let acc = _mm256_add_ps(a0, a1);
5210 let hi128 = _mm256_extractf128_ps::<1>(acc);
5211 let s128 = _mm_add_ps(_mm256_castps256_ps128(acc), hi128);
5212 let s64 = _mm_add_ps(s128, _mm_movehl_ps(s128, s128));
5213 let s32 = _mm_add_ss(s64, _mm_shuffle_ps::<1>(s64, s64));
5214 let mut sum = _mm_cvtss_f32(s32);
5215 while j < n {
5216 sum += (*wp.add(j) as i8) as f32 * *xp.add(j);
5217 j += 1;
5218 }
5219 sum
5220 }
5221}
5222
5223#[cfg(target_arch = "x86_64")]
5228#[target_feature(enable = "avx2")]
5229unsafe fn dot_i8_i8_avx2(w: &[u8], xq: &[i8]) -> i32 {
5230 unsafe {
5232 use core::arch::x86_64::*;
5233 let n = w.len();
5234 let ones = _mm256_set1_epi16(1);
5235 let mut acc = _mm256_setzero_si256();
5236 let mut j = 0usize;
5237 while j + 32 <= n {
5238 let wv = _mm256_loadu_si256(w.as_ptr().add(j) as *const __m256i);
5239 let xv = _mm256_loadu_si256(xq.as_ptr().add(j) as *const __m256i);
5240 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(wv), _mm256_sign_epi8(xv, wv));
5241 acc = _mm256_add_epi32(acc, _mm256_madd_epi16(p16, ones));
5242 j += 32;
5243 }
5244 let hi128 = _mm256_extracti128_si256::<1>(acc);
5245 let s128 = _mm_add_epi32(_mm256_castsi256_si128(acc), hi128);
5246 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
5247 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
5248 let mut s = _mm_cvtsi128_si32(s32);
5249 while j < n {
5250 s += (w[j] as i8) as i32 * xq[j] as i32;
5251 j += 1;
5252 }
5253 s
5254 }
5255}
5256
5257#[cfg(target_arch = "aarch64")]
5261#[target_feature(enable = "neon,i8mm")]
5262unsafe fn dot_i8_smmla_2x4(w0: &[u8], w1: &[u8], xs: [&[i8]; 4]) -> [[i32; 4]; 2] {
5263 unsafe {
5265 use core::arch::aarch64::*;
5266 use core::arch::asm;
5267 let n = w0.len();
5268 let w0p = w0.as_ptr() as *const i8;
5269 let w1p = w1.as_ptr() as *const i8;
5270 let mut acc01 = vdupq_n_s32(0);
5273 let mut acc23 = vdupq_n_s32(0);
5274 let mut i = 0usize;
5275 while i + 8 <= n {
5276 let wa = vcombine_s8(vld1_s8(w0p.add(i)), vld1_s8(w1p.add(i)));
5277 let xb01 =
5278 vcombine_s8(vld1_s8(xs[0].as_ptr().add(i)), vld1_s8(xs[1].as_ptr().add(i)));
5279 let xb23 =
5280 vcombine_s8(vld1_s8(xs[2].as_ptr().add(i)), vld1_s8(xs[3].as_ptr().add(i)));
5281 asm!(
5282 "smmla {a01:v}.4s, {w:v}.16b, {x01:v}.16b",
5283 "smmla {a23:v}.4s, {w:v}.16b, {x23:v}.16b",
5284 a01 = inout(vreg) acc01, a23 = inout(vreg) acc23,
5285 w = in(vreg) wa, x01 = in(vreg) xb01, x23 = in(vreg) xb23,
5286 options(pure, nomem, nostack),
5287 );
5288 i += 8;
5289 }
5290 let mut out = [[0i32; 4]; 2];
5291 let a01: [i32; 4] = core::mem::transmute(acc01);
5292 let a23: [i32; 4] = core::mem::transmute(acc23);
5293 out[0][0] = a01[0];
5294 out[0][1] = a01[1];
5295 out[1][0] = a01[2];
5296 out[1][1] = a01[3];
5297 out[0][2] = a23[0];
5298 out[0][3] = a23[1];
5299 out[1][2] = a23[2];
5300 out[1][3] = a23[3];
5301 if i < n {
5302 for (k, x) in xs.iter().enumerate() {
5303 for j in i..n {
5304 out[0][k] += (w0[j] as i8) as i32 * x[j] as i32;
5305 out[1][k] += (w1[j] as i8) as i32 * x[j] as i32;
5306 }
5307 }
5308 }
5309 out
5310 }
5311}
5312
5313#[cfg(target_arch = "aarch64")]
5317#[target_feature(enable = "neon,dotprod")]
5318unsafe fn dot_i8_sdot_2x4(w0: &[u8], w1: &[u8], xs: [&[i8]; 4]) -> [[i32; 4]; 2] {
5319 unsafe {
5321 use core::arch::aarch64::*;
5322 use core::arch::asm;
5323 let n = w0.len();
5324 let w0p = w0.as_ptr() as *const i8;
5325 let w1p = w1.as_ptr() as *const i8;
5326 let mut acc = [[vdupq_n_s32(0); 4]; 2];
5327 let mut i = 0usize;
5328 while i + 16 <= n {
5329 let wv0 = vld1q_s8(w0p.add(i));
5330 let wv1 = vld1q_s8(w1p.add(i));
5331 for (k, x) in xs.iter().enumerate() {
5332 let xv = vld1q_s8(x.as_ptr().add(i));
5333 let (mut a0, mut a1) = (acc[0][k], acc[1][k]);
5334 asm!(
5335 "sdot {a0:v}.4s, {w0:v}.16b, {x:v}.16b",
5336 "sdot {a1:v}.4s, {w1:v}.16b, {x:v}.16b",
5337 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
5338 w0 = in(vreg) wv0, w1 = in(vreg) wv1, x = in(vreg) xv,
5339 options(pure, nomem, nostack),
5340 );
5341 acc[0][k] = a0;
5342 acc[1][k] = a1;
5343 }
5344 i += 16;
5345 }
5346 let mut out = [[0i32; 4]; 2];
5347 for r in 0..2 {
5348 for k in 0..4 {
5349 out[r][k] = vaddvq_s32(acc[r][k]);
5350 }
5351 }
5352 if i < n {
5353 for (k, x) in xs.iter().enumerate() {
5354 for j in i..n {
5355 out[0][k] += (w0[j] as i8) as i32 * x[j] as i32;
5356 out[1][k] += (w1[j] as i8) as i32 * x[j] as i32;
5357 }
5358 }
5359 }
5360 out
5361 }
5362}
5363
5364#[cfg(target_arch = "x86_64")]
5370#[target_feature(enable = "avx2")]
5371unsafe fn dot_i8_i8_avx2_2x4(w0: &[u8], w1: &[u8], xs: [&[i8]; 4]) -> [[i32; 4]; 2] {
5372 unsafe {
5374 use core::arch::x86_64::*;
5375 let n = w0.len();
5376 let ones = _mm256_set1_epi16(1);
5377 let mut acc = [[_mm256_setzero_si256(); 4]; 2];
5378 let mut j = 0usize;
5379 while j + 32 <= n {
5380 let wv0 = _mm256_loadu_si256(w0.as_ptr().add(j) as *const __m256i);
5381 let wv1 = _mm256_loadu_si256(w1.as_ptr().add(j) as *const __m256i);
5382 let aw0 = _mm256_abs_epi8(wv0);
5383 let aw1 = _mm256_abs_epi8(wv1);
5384 for (k, x) in xs.iter().enumerate() {
5385 let xv = _mm256_loadu_si256(x.as_ptr().add(j) as *const __m256i);
5386 let p0 = _mm256_maddubs_epi16(aw0, _mm256_sign_epi8(xv, wv0));
5387 acc[0][k] = _mm256_add_epi32(acc[0][k], _mm256_madd_epi16(p0, ones));
5388 let p1 = _mm256_maddubs_epi16(aw1, _mm256_sign_epi8(xv, wv1));
5389 acc[1][k] = _mm256_add_epi32(acc[1][k], _mm256_madd_epi16(p1, ones));
5390 }
5391 j += 32;
5392 }
5393 let mut out = [[0i32; 4]; 2];
5394 for r in 0..2 {
5395 for k in 0..4 {
5396 let a = acc[r][k];
5397 let hi128 = _mm256_extracti128_si256::<1>(a);
5398 let s128 = _mm_add_epi32(_mm256_castsi256_si128(a), hi128);
5399 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
5400 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
5401 out[r][k] = _mm_cvtsi128_si32(s32);
5402 }
5403 }
5404 if j < n {
5405 for (k, x) in xs.iter().enumerate() {
5406 for i in j..n {
5407 out[0][k] += (w0[i] as i8) as i32 * x[i] as i32;
5408 out[1][k] += (w1[i] as i8) as i32 * x[i] as i32;
5409 }
5410 }
5411 }
5412 out
5413 }
5414}
5415
5416#[cfg(target_arch = "x86_64")]
5421#[inline]
5422fn row_dot_avx2(row: &[u8], act: &SplitAct) -> f32 {
5423 let dot = if avx512vnni_enabled() && row.len() >= 64 {
5424 (unsafe { dot_u8p128_i8_vnni(row, &act.xq) }) - 128 * act.xsum
5425 } else {
5426 unsafe { dot_i8_i8_avx2(row, &act.xq) }
5427 };
5428 let mut acc = dot as f32 * act.sx;
5429 for &(j, xv) in &act.outliers {
5430 acc += (row[j] as i8) as f32 * xv;
5431 }
5432 acc
5433}
5434
5435#[cfg(target_arch = "x86_64")]
5439#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
5440unsafe fn dot_u8p128_i8_vnni(w: &[u8], xq: &[i8]) -> i32 {
5441 unsafe {
5443 use core::arch::x86_64::*;
5444 let n = w.len();
5445 let flip = _mm512_set1_epi8(-128); #[inline(always)]
5447 unsafe fn step(
5448 w: *const u8,
5449 x: *const i8,
5450 flip: core::arch::x86_64::__m512i,
5451 acc: core::arch::x86_64::__m512i,
5452 ) -> core::arch::x86_64::__m512i {
5453 unsafe {
5454 use core::arch::x86_64::*;
5455 let wv = _mm512_xor_si512(_mm512_loadu_si512(w as *const _), flip);
5456 _mm512_dpbusd_epi32(acc, wv, _mm512_loadu_si512(x as *const _))
5457 }
5458 }
5459 let (mut a0, mut a1, mut a2, mut a3) = (
5460 _mm512_setzero_si512(),
5461 _mm512_setzero_si512(),
5462 _mm512_setzero_si512(),
5463 _mm512_setzero_si512(),
5464 );
5465 let mut j = 0usize;
5466 while j + 256 <= n {
5467 a0 = step(w.as_ptr().add(j), xq.as_ptr().add(j), flip, a0);
5468 a1 = step(w.as_ptr().add(j + 64), xq.as_ptr().add(j + 64), flip, a1);
5469 a2 = step(w.as_ptr().add(j + 128), xq.as_ptr().add(j + 128), flip, a2);
5470 a3 = step(w.as_ptr().add(j + 192), xq.as_ptr().add(j + 192), flip, a3);
5471 j += 256;
5472 }
5473 while j + 64 <= n {
5474 a0 = step(w.as_ptr().add(j), xq.as_ptr().add(j), flip, a0);
5475 j += 64;
5476 }
5477 let mut total = _mm512_reduce_add_epi32(_mm512_add_epi32(
5478 _mm512_add_epi32(a0, a1),
5479 _mm512_add_epi32(a2, a3),
5480 ));
5481 while j < n {
5483 total += ((w[j] ^ 0x80) as i32) * xq[j] as i32;
5484 j += 1;
5485 }
5486 total
5487 }
5488}
5489
5490#[cfg(target_arch = "x86_64")]
5496#[target_feature(enable = "avx2")]
5497unsafe fn dot_q4_row_avx2(packed: &[u8], scales: &[u8], g0: usize, gpr: usize, xq: &[i8]) -> f32 {
5498 unsafe {
5501 use core::arch::x86_64::*;
5502 let lomask = _mm_set1_epi8(0x0F);
5503 let eight = _mm256_set1_epi8(8);
5504 let ones = _mm256_set1_epi16(1);
5505 let mut acc = 0f32;
5506 for gi in 0..gpr {
5507 let g = g0 + gi;
5508 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
5509 let b = _mm_loadu_si128(packed.as_ptr().add(g * 16) as *const __m128i);
5510 let lo = _mm_and_si128(b, lomask);
5511 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), lomask);
5512 let w = _mm256_sub_epi8(
5513 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
5514 eight,
5515 );
5516 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
5517 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(w), _mm256_sign_epi8(x, w));
5518 let d = _mm256_madd_epi16(p16, ones);
5519 let hi128 = _mm256_extracti128_si256::<1>(d);
5520 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
5521 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
5522 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
5523 acc += _mm_cvtsi128_si32(s32) as f32 * s;
5524 }
5525 acc
5526 }
5527}
5528
5529#[cfg(target_arch = "x86_64")]
5532#[target_feature(enable = "avx2")]
5533unsafe fn dot_q4_row_avx2_2(
5534 packed: &[u8],
5535 scales: &[u8],
5536 g0: usize,
5537 gpr: usize,
5538 xq1: &[i8],
5539 xq2: &[i8],
5540) -> (f32, f32) {
5541 unsafe {
5543 use core::arch::x86_64::*;
5544 let lomask = _mm_set1_epi8(0x0F);
5545 let eight = _mm256_set1_epi8(8);
5546 let ones = _mm256_set1_epi16(1);
5547 let (mut acc1, mut acc2) = (0f32, 0f32);
5548 #[inline(always)]
5549 unsafe fn hsum(d: core::arch::x86_64::__m256i) -> i32 {
5550 unsafe {
5551 use core::arch::x86_64::*;
5552 let hi128 = _mm256_extracti128_si256::<1>(d);
5553 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
5554 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
5555 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
5556 _mm_cvtsi128_si32(s32)
5557 }
5558 }
5559 for gi in 0..gpr {
5560 let g = g0 + gi;
5561 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
5562 let b = _mm_loadu_si128(packed.as_ptr().add(g * 16) as *const __m128i);
5563 let lo = _mm_and_si128(b, lomask);
5564 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), lomask);
5565 let w = _mm256_sub_epi8(
5566 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
5567 eight,
5568 );
5569 let aw = _mm256_abs_epi8(w);
5570 let x1 = _mm256_loadu_si256(xq1.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
5571 let x2 = _mm256_loadu_si256(xq2.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
5572 let d1 = _mm256_madd_epi16(_mm256_maddubs_epi16(aw, _mm256_sign_epi8(x1, w)), ones);
5573 let d2 = _mm256_madd_epi16(_mm256_maddubs_epi16(aw, _mm256_sign_epi8(x2, w)), ones);
5574 acc1 += hsum(d1) as f32 * s;
5575 acc2 += hsum(d2) as f32 * s;
5576 }
5577 (acc1, acc2)
5578 }
5579}
5580
5581#[cfg(target_arch = "x86_64")]
5583fn q8_range_avx2(
5584 q: &[u8],
5585 row_scale: &[f32],
5586 act: &SplitAct,
5587 cols: usize,
5588 out_addr: SendMut,
5589 start: usize,
5590 end: usize,
5591) {
5592 for o in start..end {
5593 let v = row_dot_avx2(&q[o * cols..(o + 1) * cols], act) * row_scale[o];
5594 unsafe { *out_addr.at(o) = v };
5596 }
5597}
5598
5599#[cfg(target_arch = "x86_64")]
5601#[allow(clippy::too_many_arguments)]
5602fn q8_range2_avx2(
5603 q: &[u8],
5604 row_scale: &[f32],
5605 a1: &SplitAct,
5606 a2: &SplitAct,
5607 cols: usize,
5608 p1: SendMut,
5609 p2: SendMut,
5610 start: usize,
5611 end: usize,
5612) {
5613 for o in start..end {
5614 let row = &q[o * cols..(o + 1) * cols];
5615 unsafe {
5617 *p1.at(o) = row_dot_avx2(row, a1) * row_scale[o];
5618 *p2.at(o) = row_dot_avx2(row, a2) * row_scale[o];
5619 }
5620 }
5621}
5622
5623#[cfg(target_arch = "aarch64")]
5634fn i8mm_enabled() -> bool {
5635 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5636 *ON.get_or_init(|| {
5637 std::env::var("CMF_I8MM").map(|v| v == "1").unwrap_or(false)
5638 && std::arch::is_aarch64_feature_detected!("i8mm")
5639 })
5640}
5641
5642#[cfg_attr(not(target_arch = "aarch64"), allow(dead_code))]
5646fn sdot_enabled() -> bool {
5647 use std::sync::OnceLock;
5648 static ON: OnceLock<bool> = OnceLock::new();
5649 *ON.get_or_init(|| {
5650 let want = std::env::var("CMF_SDOT").map(|v| v != "0").unwrap_or(true);
5651 if !want { return false; }
5652
5653 #[cfg(target_arch = "aarch64")]
5654 {
5655 if std::arch::is_aarch64_feature_detected!("dotprod") {
5656 return true;
5657 }
5658 #[cfg(target_os = "android")]
5659 {
5660 if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") {
5661 if cpuinfo.lines().any(|l| (l.starts_with("Features") || l.starts_with("features")) && l.contains("asimddp")) {
5662 return true;
5663 }
5664 }
5665 }
5666 false
5667 }
5668 #[cfg(not(target_arch = "aarch64"))]
5669 {
5670 false
5671 }
5672 })
5673}
5674
5675struct SplitAct {
5680 xq: Vec<i8>,
5681 sx: f32,
5682 outliers: Vec<(usize, f32)>,
5683 #[cfg_attr(not(target_arch = "x86_64"), allow(dead_code))]
5686 xsum: i32,
5687}
5688
5689thread_local! {
5690 static XQ_FREE: std::cell::RefCell<Vec<Vec<i8>>> =
5693 const { std::cell::RefCell::new(Vec::new()) };
5694}
5695
5696impl Drop for SplitAct {
5697 fn drop(&mut self) {
5698 let buf = std::mem::take(&mut self.xq);
5699 if buf.capacity() > 0 {
5700 XQ_FREE.with(|f| {
5701 let mut f = f.borrow_mut();
5702 if f.len() < 16 {
5703 f.push(buf);
5704 }
5705 });
5706 }
5707 }
5708}
5709
5710fn split_act(x: &[f32]) -> SplitAct {
5711 let n = x.len();
5712 let rms = (x.iter().map(|&v| (v * v) as f64).sum::<f64>() / n.max(1) as f64).sqrt() as f32;
5713 let thr = 8.0 * rms;
5714 let mut outliers: Vec<(usize, f32)> = Vec::new();
5718 let mut amax = 0f32;
5719 for (j, &v) in x.iter().enumerate() {
5720 let a = v.abs();
5721 if a > thr {
5722 outliers.push((j, v));
5723 } else if a > amax {
5724 amax = a;
5725 }
5726 }
5727 let sx = if amax > 0.0 { amax / 127.0 } else { 1.0 };
5728 let inv = 1.0 / sx;
5729 let mut xq = XQ_FREE.with(|f| f.borrow_mut().pop()).unwrap_or_default();
5730 xq.clear();
5731 xq.reserve(n);
5732 if outliers.is_empty() {
5733 xq.extend(x.iter().map(|&v| (v * inv).round().clamp(-127.0, 127.0) as i8));
5734 } else {
5735 xq.extend(x.iter().map(|&v| {
5737 if v.abs() > thr {
5738 0
5739 } else {
5740 (v * inv).round().clamp(-127.0, 127.0) as i8
5741 }
5742 }));
5743 }
5744 let xsum = xq.iter().map(|&v| v as i32).sum();
5745 SplitAct { xq, sx, outliers, xsum }
5746}
5747
5748fn split_act_q8_2f(x: &[f32], col: &[f32]) -> SplitAct {
5749 let n = x.len();
5750 let rms = (x.iter().zip(col).map(|(&a, &c)| { let v = a * c; (v * v) as f64 })
5751 .sum::<f64>() / n.max(1) as f64).sqrt() as f32;
5752 let thr = 8.0 * rms;
5753
5754 let mut outliers = Vec::new();
5755 let mut amax = 0f32;
5756 for (j, (&a, &c)) in x.iter().zip(col).enumerate() {
5757 let v = a * c;
5758 let s = v.abs();
5759 if s > thr {
5760 outliers.push((j, v));
5761 } else if s > amax {
5762 amax = s;
5763 }
5764 }
5765
5766 let sx = if amax > 0.0 { amax / 127.0 } else { 1.0 };
5767 let inv = 1.0 / sx;
5768 let mut xq = XQ_FREE.with(|f| f.borrow_mut().pop()).unwrap_or_default();
5769 xq.clear();
5770 xq.reserve(n);
5771 if outliers.is_empty() {
5772 xq.extend(x.iter().zip(col).map(|(&a, &c)| ((a * c) * inv).round().clamp(-127.0, 127.0) as i8));
5773 } else {
5774 xq.extend(x.iter().zip(col).map(|(&a, &c)| {
5775 let v = a * c;
5776 if v.abs() > thr { 0 } else { (v * inv).round().clamp(-127.0, 127.0) as i8 }
5777 }));
5778 }
5779 let xsum = xq.iter().map(|&v| v as i32).sum();
5780 SplitAct { xq, sx, outliers, xsum }
5781}
5782
5783#[cfg(target_arch = "aarch64")]
5786#[target_feature(enable = "neon,dotprod")]
5787unsafe fn dot_i8_sdot(w: &[u8], xq: &[i8]) -> i32 {
5788 unsafe {
5790 use core::arch::aarch64::*;
5791 use core::arch::asm;
5792 let wp = w.as_ptr() as *const i8;
5793 let n = w.len();
5794 let (mut a0, mut a1, mut a2, mut a3) =
5795 (vdupq_n_s32(0), vdupq_n_s32(0), vdupq_n_s32(0), vdupq_n_s32(0));
5796 let mut i = 0;
5797 while i + 64 <= n {
5798 let (w0, x0) = (vld1q_s8(wp.add(i)), vld1q_s8(xq.as_ptr().add(i)));
5799 let (w1, x1) = (vld1q_s8(wp.add(i + 16)), vld1q_s8(xq.as_ptr().add(i + 16)));
5800 let (w2, x2) = (vld1q_s8(wp.add(i + 32)), vld1q_s8(xq.as_ptr().add(i + 32)));
5801 let (w3, x3) = (vld1q_s8(wp.add(i + 48)), vld1q_s8(xq.as_ptr().add(i + 48)));
5802 asm!(
5803 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
5804 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
5805 "sdot {a2:v}.4s, {w2:v}.16b, {x2:v}.16b",
5806 "sdot {a3:v}.4s, {w3:v}.16b, {x3:v}.16b",
5807 a0 = inout(vreg) a0, a1 = inout(vreg) a1, a2 = inout(vreg) a2, a3 = inout(vreg) a3,
5808 w0 = in(vreg) w0, x0 = in(vreg) x0, w1 = in(vreg) w1, x1 = in(vreg) x1,
5809 w2 = in(vreg) w2, x2 = in(vreg) x2, w3 = in(vreg) w3, x3 = in(vreg) x3,
5810 options(pure, nomem, nostack),
5811 );
5812 i += 64;
5813 }
5814 while i + 16 <= n {
5815 let (wv, xv) = (vld1q_s8(wp.add(i)), vld1q_s8(xq.as_ptr().add(i)));
5816 asm!("sdot {a:v}.4s, {w:v}.16b, {x:v}.16b",
5817 a = inout(vreg) a0, w = in(vreg) wv, x = in(vreg) xv, options(pure, nomem, nostack));
5818 i += 16;
5819 }
5820 let mut s = vaddvq_s32(vaddq_s32(vaddq_s32(a0, a1), vaddq_s32(a2, a3)));
5821 while i < n {
5822 s += (*wp.add(i)) as i32 * xq[i] as i32;
5823 i += 1;
5824 }
5825 s
5826}
5827}
5828
5829#[cfg(target_arch = "aarch64")]
5833#[target_feature(enable = "neon,dotprod")]
5834unsafe fn dot_i8_sdot_4rows(w0: &[u8], w1: &[u8], w2: &[u8], w3: &[u8], xq: &[i8]) -> [i32; 4] {
5835 unsafe {
5837 use core::arch::aarch64::*;
5838 use core::arch::asm;
5839 let n = xq.len();
5840 let px = xq.as_ptr();
5841 let (p0, p1, p2, p3) = (
5842 w0.as_ptr() as *const i8,
5843 w1.as_ptr() as *const i8,
5844 w2.as_ptr() as *const i8,
5845 w3.as_ptr() as *const i8,
5846 );
5847 let (mut a0, mut a1, mut a2, mut a3) =
5848 (vdupq_n_s32(0), vdupq_n_s32(0), vdupq_n_s32(0), vdupq_n_s32(0));
5849 let mut i = 0;
5850 while i + 16 <= n {
5851 let x = vld1q_s8(px.add(i));
5852 let v0 = vld1q_s8(p0.add(i));
5853 let v1 = vld1q_s8(p1.add(i));
5854 let v2 = vld1q_s8(p2.add(i));
5855 let v3 = vld1q_s8(p3.add(i));
5856 asm!(
5857 "sdot {a0:v}.4s, {v0:v}.16b, {x:v}.16b",
5858 "sdot {a1:v}.4s, {v1:v}.16b, {x:v}.16b",
5859 "sdot {a2:v}.4s, {v2:v}.16b, {x:v}.16b",
5860 "sdot {a3:v}.4s, {v3:v}.16b, {x:v}.16b",
5861 a0 = inout(vreg) a0, a1 = inout(vreg) a1, a2 = inout(vreg) a2, a3 = inout(vreg) a3,
5862 v0 = in(vreg) v0, v1 = in(vreg) v1, v2 = in(vreg) v2, v3 = in(vreg) v3, x = in(vreg) x,
5863 options(pure, nomem, nostack),
5864 );
5865 i += 16;
5866 }
5867 let mut r = [vaddvq_s32(a0), vaddvq_s32(a1), vaddvq_s32(a2), vaddvq_s32(a3)];
5868 while i < n {
5869 let xi = *px.add(i) as i32;
5870 r[0] += (*p0.add(i)) as i32 * xi;
5871 r[1] += (*p1.add(i)) as i32 * xi;
5872 r[2] += (*p2.add(i)) as i32 * xi;
5873 r[3] += (*p3.add(i)) as i32 * xi;
5874 i += 1;
5875 }
5876 r
5877}
5878}
5879
5880#[cfg(target_arch = "aarch64")]
5887#[target_feature(enable = "neon,dotprod")]
5888unsafe fn dot_i8_sdot_4rows_il(g: &[u8], xq: &[i8]) -> [i32; 4] {
5889 unsafe {
5892 use core::arch::aarch64::*;
5893 use core::arch::asm;
5894 let n = xq.len();
5895 let px = xq.as_ptr();
5896 let pg = g.as_ptr() as *const i8;
5897 let (mut a0, mut a1, mut a2, mut a3) =
5898 (vdupq_n_s32(0), vdupq_n_s32(0), vdupq_n_s32(0), vdupq_n_s32(0));
5899 let mut i = 0;
5900 while i + 16 <= n {
5901 let x = vld1q_s8(px.add(i));
5902 let base = pg.add(4 * i);
5903 let v0 = vld1q_s8(base);
5904 let v1 = vld1q_s8(base.add(16));
5905 let v2 = vld1q_s8(base.add(32));
5906 let v3 = vld1q_s8(base.add(48));
5907 asm!(
5908 "sdot {a0:v}.4s, {v0:v}.16b, {x:v}.16b",
5909 "sdot {a1:v}.4s, {v1:v}.16b, {x:v}.16b",
5910 "sdot {a2:v}.4s, {v2:v}.16b, {x:v}.16b",
5911 "sdot {a3:v}.4s, {v3:v}.16b, {x:v}.16b",
5912 a0 = inout(vreg) a0, a1 = inout(vreg) a1, a2 = inout(vreg) a2, a3 = inout(vreg) a3,
5913 v0 = in(vreg) v0, v1 = in(vreg) v1, v2 = in(vreg) v2, v3 = in(vreg) v3, x = in(vreg) x,
5914 options(pure, nomem, nostack),
5915 );
5916 i += 16;
5917 }
5918 [vaddvq_s32(a0), vaddvq_s32(a1), vaddvq_s32(a2), vaddvq_s32(a3)]
5919 }
5920}
5921
5922#[cfg(target_arch = "aarch64")]
5928fn q8_range_sdot(
5929 q: &[u8],
5930 rep: &[u8],
5931 row_scale: &[f32],
5932 act: &SplitAct,
5933 cols: usize,
5934 out_addr: SendMut,
5935 start: usize,
5936 end: usize,
5937) {
5938 let mut o = start;
5939 if !rep.is_empty() {
5942 while o < end && o % 4 != 0 {
5943 let v = row_dot_sdot(&q[o * cols..(o + 1) * cols], act) * row_scale[o];
5944 unsafe { *out_addr.at(o) = v };
5945 o += 1;
5946 }
5947 }
5948 while o + 4 <= end {
5949 let r = if rep.is_empty() {
5950 unsafe {
5951 dot_i8_sdot_4rows(
5952 &q[o * cols..(o + 1) * cols],
5953 &q[(o + 1) * cols..(o + 2) * cols],
5954 &q[(o + 2) * cols..(o + 3) * cols],
5955 &q[(o + 3) * cols..(o + 4) * cols],
5956 &act.xq,
5957 )
5958 }
5959 } else {
5960 unsafe { dot_i8_sdot_4rows_il(&rep[o * cols..(o + 4) * cols], &act.xq) }
5961 };
5962 for k in 0..4 {
5963 let mut acc = r[k] as f32 * act.sx;
5964 for &(j, xv) in &act.outliers {
5965 acc += (q[(o + k) * cols + j] as i8) as f32 * xv;
5966 }
5967 unsafe { *out_addr.at(o + k) = acc * row_scale[o + k] };
5969 }
5970 o += 4;
5971 }
5972 while o < end {
5973 let v = row_dot_sdot(&q[o * cols..(o + 1) * cols], act) * row_scale[o];
5974 unsafe { *out_addr.at(o) = v };
5975 o += 1;
5976 }
5977}
5978
5979#[cfg(target_arch = "aarch64")]
5982#[allow(clippy::too_many_arguments)]
5983fn q8_range2_sdot(
5984 q: &[u8],
5985 row_scale: &[f32],
5986 a1: &SplitAct,
5987 a2: &SplitAct,
5988 cols: usize,
5989 p1: SendMut,
5990 p2: SendMut,
5991 start: usize,
5992 end: usize,
5993) {
5994 for o in start..end {
5995 let row = &q[o * cols..(o + 1) * cols];
5996 unsafe {
5998 *p1.at(o) = row_dot_sdot(row, a1) * row_scale[o];
5999 *p2.at(o) = row_dot_sdot(row, a2) * row_scale[o];
6000 }
6001 }
6002}
6003
6004#[allow(clippy::too_many_arguments)]
6006fn q8_range2_f32(
6007 q: &[u8],
6008 row_scale: &[f32],
6009 x1: &[f32],
6010 x2: &[f32],
6011 cols: usize,
6012 p1: SendMut,
6013 p2: SendMut,
6014 start: usize,
6015 end: usize,
6016) {
6017 for o in start..end {
6018 let row = &q[o * cols..(o + 1) * cols];
6019 unsafe {
6021 *p1.at(o) = dot_i8_f32(row, x1) * row_scale[o];
6022 *p2.at(o) = dot_i8_f32(row, x2) * row_scale[o];
6023 }
6024 }
6025}
6026
6027fn q8_range_f32(
6029 q: &[u8],
6030 row_scale: &[f32],
6031 xs: &[f32],
6032 cols: usize,
6033 out_addr: SendMut,
6034 start: usize,
6035 end: usize,
6036) {
6037 for o in start..end {
6038 let v = dot_i8_f32(&q[o * cols..(o + 1) * cols], xs) * row_scale[o];
6039 unsafe { *out_addr.at(o) = v };
6041 }
6042}
6043
6044#[cfg(target_arch = "aarch64")]
6047#[inline]
6048fn row_dot_sdot(row: &[u8], act: &SplitAct) -> f32 {
6049 let mut acc = unsafe { dot_i8_sdot(row, &act.xq) } as f32 * act.sx;
6050 for &(j, xv) in &act.outliers {
6051 acc += (row[j] as i8) as f32 * xv;
6052 }
6053 acc
6054}
6055
6056#[cfg(target_arch = "aarch64")]
6064#[target_feature(enable = "neon,dotprod")]
6065unsafe fn dot_q4_row_sdot(packed: &[u8], scales: &[u8], g0: usize, gpr: usize, xq: &[i8]) -> f32 {
6066 unsafe {
6069 use core::arch::aarch64::*;
6070 use core::arch::asm;
6071 let lomask = vdupq_n_u8(0x0F);
6072 let eight = vdupq_n_s8(8);
6073 let mut acc = 0f32;
6074 for gi in 0..gpr {
6075 let g = g0 + gi;
6076 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
6077 let b = vld1q_u8(packed.as_ptr().add(g * 16));
6078 let lo = vandq_u8(b, lomask);
6079 let hi = vshrq_n_u8::<4>(b);
6080 let e0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(lo, hi)), eight);
6081 let e1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(lo, hi)), eight);
6082 let x0 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE));
6083 let x1 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE + 16));
6084 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
6085 asm!(
6086 "sdot {a0:v}.4s, {e0:v}.16b, {x0:v}.16b",
6087 "sdot {a1:v}.4s, {e1:v}.16b, {x1:v}.16b",
6088 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
6089 e0 = in(vreg) e0, x0 = in(vreg) x0, e1 = in(vreg) e1, x1 = in(vreg) x1,
6090 options(pure, nomem, nostack),
6091 );
6092 acc += vaddvq_s32(vaddq_s32(a0, a1)) as f32 * s;
6093 }
6094 acc
6095 }
6096}
6097
6098#[cfg(target_arch = "aarch64")]
6103#[target_feature(enable = "neon,dotprod")]
6104unsafe fn dot_q4_row_sdot2(
6105 packed: &[u8],
6106 scales: &[u8],
6107 g0: usize,
6108 gpr: usize,
6109 xq1: &[i8],
6110 xq2: &[i8],
6111) -> (f32, f32) {
6112 unsafe {
6115 use core::arch::aarch64::*;
6116 use core::arch::asm;
6117 let lomask = vdupq_n_u8(0x0F);
6118 let eight = vdupq_n_s8(8);
6119 let (mut acc1, mut acc2) = (0f32, 0f32);
6120 for gi in 0..gpr {
6121 let g = g0 + gi;
6122 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
6123 let b = vld1q_u8(packed.as_ptr().add(g * 16));
6124 let lo = vandq_u8(b, lomask);
6125 let hi = vshrq_n_u8::<4>(b);
6126 let e0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(lo, hi)), eight);
6127 let e1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(lo, hi)), eight);
6128 let x10 = vld1q_s8(xq1.as_ptr().add(gi * GROUP_SIZE));
6129 let x11 = vld1q_s8(xq1.as_ptr().add(gi * GROUP_SIZE + 16));
6130 let x20 = vld1q_s8(xq2.as_ptr().add(gi * GROUP_SIZE));
6131 let x21 = vld1q_s8(xq2.as_ptr().add(gi * GROUP_SIZE + 16));
6132 let (mut a0, mut a1, mut b0, mut b1) =
6133 (vdupq_n_s32(0), vdupq_n_s32(0), vdupq_n_s32(0), vdupq_n_s32(0));
6134 asm!(
6135 "sdot {a0:v}.4s, {e0:v}.16b, {x10:v}.16b",
6136 "sdot {a1:v}.4s, {e1:v}.16b, {x11:v}.16b",
6137 "sdot {b0:v}.4s, {e0:v}.16b, {x20:v}.16b",
6138 "sdot {b1:v}.4s, {e1:v}.16b, {x21:v}.16b",
6139 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
6140 b0 = inout(vreg) b0, b1 = inout(vreg) b1,
6141 e0 = in(vreg) e0, e1 = in(vreg) e1,
6142 x10 = in(vreg) x10, x11 = in(vreg) x11,
6143 x20 = in(vreg) x20, x21 = in(vreg) x21,
6144 options(pure, nomem, nostack),
6145 );
6146 acc1 += vaddvq_s32(vaddq_s32(a0, a1)) as f32 * s;
6147 acc2 += vaddvq_s32(vaddq_s32(b0, b1)) as f32 * s;
6148 }
6149 (acc1, acc2)
6150 }
6151}
6152
6153#[inline]
6158pub(crate) fn axpy_i8_f32(acc: &mut [f32], row: &[i8], w: f32) {
6159 #[cfg(target_arch = "aarch64")]
6160 unsafe {
6161 return axpy_i8_f32_neon(acc, row, w);
6162 }
6163 #[cfg(target_arch = "x86_64")]
6164 if avx2_enabled() {
6165 return unsafe { axpy_i8_f32_avx2(acc, row, w) };
6166 }
6167 #[allow(unreachable_code)]
6168 {
6169 for (a, &b) in acc.iter_mut().zip(row) {
6170 *a += w * b as f32;
6171 }
6172 }
6173}
6174
6175#[cfg(target_arch = "x86_64")]
6177#[target_feature(enable = "avx2,fma")]
6178unsafe fn axpy_i8_f32_avx2(acc: &mut [f32], row: &[i8], w: f32) {
6179 unsafe {
6181 use core::arch::x86_64::*;
6182 let n = acc.len().min(row.len());
6183 let ap = acc.as_mut_ptr();
6184 let rp = row.as_ptr();
6185 let wv = _mm256_set1_ps(w);
6186 let mut j = 0usize;
6187 while j + 16 <= n {
6188 let rb = _mm_loadu_si128(rp.add(j) as *const __m128i);
6189 let lo = _mm256_cvtepi32_ps(_mm256_cvtepi8_epi32(rb));
6190 let hi = _mm256_cvtepi32_ps(_mm256_cvtepi8_epi32(_mm_srli_si128::<8>(rb)));
6191 let v0 = _mm256_fmadd_ps(wv, lo, _mm256_loadu_ps(ap.add(j)));
6192 let v1 = _mm256_fmadd_ps(wv, hi, _mm256_loadu_ps(ap.add(j + 8)));
6193 _mm256_storeu_ps(ap.add(j), v0);
6194 _mm256_storeu_ps(ap.add(j + 8), v1);
6195 j += 16;
6196 }
6197 while j < n {
6198 *ap.add(j) += w * (*rp.add(j)) as f32;
6199 j += 1;
6200 }
6201 }
6202}
6203
6204#[cfg(target_arch = "aarch64")]
6205#[target_feature(enable = "neon")]
6206unsafe fn axpy_i8_f32_neon(acc: &mut [f32], row: &[i8], w: f32) {
6207 unsafe {
6209 use core::arch::aarch64::*;
6210 let n = acc.len().min(row.len());
6211 let ap = acc.as_mut_ptr();
6212 let rp = row.as_ptr();
6213 let wv = vdupq_n_f32(w);
6214 let mut j = 0usize;
6215 while j + 16 <= n {
6216 let rb = vld1q_s8(rp.add(j));
6217 let lo = vmovl_s8(vget_low_s8(rb));
6218 let hi = vmovl_s8(vget_high_s8(rb));
6219 for (off, half) in [(0, lo), (8, hi)] {
6220 let f0 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(half)));
6221 let f1 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(half)));
6222 let o = j + off;
6223 vst1q_f32(ap.add(o), vfmaq_f32(vld1q_f32(ap.add(o)), wv, f0));
6224 vst1q_f32(ap.add(o + 4), vfmaq_f32(vld1q_f32(ap.add(o + 4)), wv, f1));
6225 }
6226 j += 16;
6227 }
6228 while j < n {
6229 *ap.add(j) += w * (*rp.add(j)) as f32;
6230 j += 1;
6231 }
6232}
6233}
6234
6235#[inline]
6238pub(crate) fn dot_i8_f32(w: &[u8], x: &[f32]) -> f32 {
6239 #[cfg(target_arch = "aarch64")]
6240 unsafe {
6241 return dot_i8_f32_neon(w, x);
6242 }
6243 #[cfg(target_arch = "x86_64")]
6244 if avx2_enabled() {
6245 return unsafe { dot_i8_f32_avx2(w, x) };
6246 }
6247 #[allow(unreachable_code)]
6248 {
6249 let mut sum = 0.0f32;
6250 for (j, &b) in w.iter().enumerate() {
6251 sum += (b as i8) as f32 * x[j];
6252 }
6253 sum
6254 }
6255}
6256
6257#[inline]
6261fn dot_i8_col_f32(w: &[u8], x: &[f32], col: &[f32]) -> f32 {
6262 #[cfg(target_arch = "aarch64")]
6263 unsafe {
6264 return dot_i8_col_f32_neon(w, x, col);
6265 }
6266 #[allow(unreachable_code)]
6267 {
6268 let mut sum = 0.0f32;
6269 for (j, &b) in w.iter().enumerate() {
6270 sum += (b as i8) as f32 * x[j] * col[j];
6271 }
6272 sum
6273 }
6274}
6275
6276#[cfg(target_arch = "aarch64")]
6277#[target_feature(enable = "neon")]
6278unsafe fn dot_i8_col_f32_neon(w: &[u8], x: &[f32], col: &[f32]) -> f32 {
6279 unsafe {
6281 use core::arch::aarch64::*;
6282 let n = x.len();
6283 let wp = w.as_ptr() as *const i8;
6284 let xp = x.as_ptr();
6285 let cp = col.as_ptr();
6286 let (mut a0, mut a1, mut a2, mut a3) =
6287 (vdupq_n_f32(0.0), vdupq_n_f32(0.0), vdupq_n_f32(0.0), vdupq_n_f32(0.0));
6288 let mut j = 0usize;
6289 while j + 16 <= n {
6290 let wb = vld1q_s8(wp.add(j));
6291 let lo = vmovl_s8(vget_low_s8(wb));
6292 let hi = vmovl_s8(vget_high_s8(wb));
6293 let w0 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(lo)));
6294 let w1 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(lo)));
6295 let w2 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(hi)));
6296 let w3 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(hi)));
6297 a0 = vfmaq_f32(a0, w0, vmulq_f32(vld1q_f32(xp.add(j)), vld1q_f32(cp.add(j))));
6298 a1 = vfmaq_f32(a1, w1, vmulq_f32(vld1q_f32(xp.add(j + 4)), vld1q_f32(cp.add(j + 4))));
6299 a2 = vfmaq_f32(a2, w2, vmulq_f32(vld1q_f32(xp.add(j + 8)), vld1q_f32(cp.add(j + 8))));
6300 a3 = vfmaq_f32(a3, w3, vmulq_f32(vld1q_f32(xp.add(j + 12)), vld1q_f32(cp.add(j + 12))));
6301 j += 16;
6302 }
6303 let mut sum = vaddvq_f32(vaddq_f32(vaddq_f32(a0, a1), vaddq_f32(a2, a3)));
6304 while j < n {
6305 sum += (*wp.add(j)) as f32 * *xp.add(j) * *cp.add(j);
6306 j += 1;
6307 }
6308 sum
6309}
6310}
6311
6312#[cfg(target_arch = "aarch64")]
6313#[target_feature(enable = "neon")]
6314unsafe fn dot_i8_f32_neon(w: &[u8], x: &[f32]) -> f32 {
6315 unsafe {
6317 use core::arch::aarch64::*;
6318 let n = x.len();
6319 let wp = w.as_ptr() as *const i8;
6320 let xp = x.as_ptr();
6321 let (mut a0, mut a1, mut a2, mut a3) =
6322 (vdupq_n_f32(0.0), vdupq_n_f32(0.0), vdupq_n_f32(0.0), vdupq_n_f32(0.0));
6323 let mut j = 0usize;
6324 while j + 16 <= n {
6325 let wb = vld1q_s8(wp.add(j));
6326 let lo = vmovl_s8(vget_low_s8(wb));
6327 let hi = vmovl_s8(vget_high_s8(wb));
6328 let w0 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(lo)));
6329 let w1 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(lo)));
6330 let w2 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(hi)));
6331 let w3 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(hi)));
6332 a0 = vfmaq_f32(a0, w0, vld1q_f32(xp.add(j)));
6333 a1 = vfmaq_f32(a1, w1, vld1q_f32(xp.add(j + 4)));
6334 a2 = vfmaq_f32(a2, w2, vld1q_f32(xp.add(j + 8)));
6335 a3 = vfmaq_f32(a3, w3, vld1q_f32(xp.add(j + 12)));
6336 j += 16;
6337 }
6338 let mut sum = vaddvq_f32(vaddq_f32(vaddq_f32(a0, a1), vaddq_f32(a2, a3)));
6339 while j < n {
6340 sum += (*wp.add(j)) as f32 * *xp.add(j);
6341 j += 1;
6342 }
6343 sum
6344}
6345}
6346
6347#[allow(clippy::too_many_arguments)]
6348fn qmatvec(
6349 q: &[u8],
6350 rep: &[u8],
6351 row_scale: &[f32],
6352 x: &[f32],
6353 col_field: &[f32],
6354 dtype: TensorDtype,
6355 rows: usize,
6356 cols: usize,
6357 out: &mut [f32],
6358 pool: Option<&Pool>,
6359) {
6360 debug_assert_eq!(out.len(), rows);
6361 #[cfg(not(target_arch = "aarch64"))]
6362 let _ = rep;
6363
6364 #[cfg(target_arch = "aarch64")]
6365 if sdot_enabled() {
6366 let act = if dtype == TensorDtype::Q8_2f {
6367 split_act_q8_2f(x, col_field)
6368 } else {
6369 split_act(x)
6370 };
6371 let out_addr = SendMut(out.as_mut_ptr());
6372 let run_range = |start: usize, end: usize| {
6373 q8_range_sdot(q, rep, row_scale, &act, cols, out_addr, start, end)
6374 };
6375 match pool {
6376 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
6377 _ => run_range(0, rows),
6378 }
6379 return;
6380 }
6381 #[cfg(target_arch = "x86_64")]
6384 if avx2_a8w8_enabled() {
6385 let act = if dtype == TensorDtype::Q8_2f {
6386 split_act_q8_2f(x, col_field)
6387 } else {
6388 split_act(x)
6389 };
6390 let out_addr = SendMut(out.as_mut_ptr());
6391 let run_range =
6392 |start: usize, end: usize| q8_range_avx2(q, row_scale, &act, cols, out_addr, start, end);
6393 match pool {
6394 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
6395 _ => run_range(0, rows),
6396 }
6397 return;
6398 }
6399
6400 prescale_with(x, col_field, dtype, 1, |xs| {
6401 let out_addr = SendMut(out.as_mut_ptr());
6402 let run_range = move |start: usize, end: usize| {
6403 for o in start..end {
6404 let v = dot_i8_f32(&q[o * cols..(o + 1) * cols], xs) * row_scale[o];
6405 unsafe { *out_addr.at(o) = v };
6407 }
6408 };
6409 match pool {
6410 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
6411 _ => run_range(0, rows),
6412 }
6413 });
6414}
6415
6416#[allow(clippy::too_many_arguments)]
6417fn qmatvec2(
6418 q: &[u8],
6419 row_scale: &[f32],
6420 x1: &[f32],
6421 x2: &[f32],
6422 col_field: &[f32],
6423 dtype: TensorDtype,
6424 rows: usize,
6425 cols: usize,
6426 o1: &mut [f32],
6427 o2: &mut [f32],
6428 pool: Option<&Pool>,
6429) {
6430 #[cfg(target_arch = "aarch64")]
6431 if sdot_enabled() {
6432 let a1s = if dtype == TensorDtype::Q8_2f { split_act_q8_2f(x1, col_field) } else { split_act(x1) };
6433 let a2s = if dtype == TensorDtype::Q8_2f { split_act_q8_2f(x2, col_field) } else { split_act(x2) };
6434 let p1 = SendMut(o1.as_mut_ptr());
6435 let p2 = SendMut(o2.as_mut_ptr());
6436 let run_range = |start: usize, end: usize| {
6437 q8_range2_sdot(q, row_scale, &a1s, &a2s, cols, p1, p2, start, end)
6438 };
6439 match pool {
6440 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
6441 _ => run_range(0, rows),
6442 }
6443 return;
6444 }
6445 #[cfg(target_arch = "x86_64")]
6446 if avx2_a8w8_enabled() {
6447 let a1s = if dtype == TensorDtype::Q8_2f { split_act_q8_2f(x1, col_field) } else { split_act(x1) };
6448 let a2s = if dtype == TensorDtype::Q8_2f { split_act_q8_2f(x2, col_field) } else { split_act(x2) };
6449 let p1 = SendMut(o1.as_mut_ptr());
6450 let p2 = SendMut(o2.as_mut_ptr());
6451 let run_range = |start: usize, end: usize| {
6452 q8_range2_avx2(q, row_scale, &a1s, &a2s, cols, p1, p2, start, end)
6453 };
6454 match pool {
6455 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
6456 _ => run_range(0, rows),
6457 }
6458 return;
6459 }
6460
6461 prescale_with(x1, col_field, dtype, 1, |x1s| {
6462 prescale_with(x2, col_field, dtype, 2, |x2s| {
6463 let p1 = SendMut(o1.as_mut_ptr());
6464 let p2 = SendMut(o2.as_mut_ptr());
6465 let run_range = move |start: usize, end: usize| {
6466 for o in start..end {
6467 let row = &q[o * cols..(o + 1) * cols];
6468 let s1 = dot_i8_f32(row, x1s) * row_scale[o];
6469 let s2 = dot_i8_f32(row, x2s) * row_scale[o];
6470 unsafe {
6472 *p1.at(o) = s1;
6473 *p2.at(o) = s2;
6474 }
6475 }
6476 };
6477 match pool {
6478 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
6479 _ => run_range(0, rows),
6480 }
6481 });
6482 });
6483}
6484
6485#[derive(Clone, Copy)]
6486struct SendMut(*mut f32);
6487unsafe impl Send for SendMut {}
6488unsafe impl Sync for SendMut {}
6489
6490impl SendMut {
6491 #[inline]
6492 fn at(self, i: usize) -> *mut f32 {
6493 unsafe { self.0.add(i) }
6494 }
6495}
6496
6497#[cfg(test)]
6498mod tests {
6499 use super::*;
6500
6501 #[test]
6502 fn f32_matvec_matches_matvec_rows_bitexact() {
6503 let (rows, cols) = (300, 40);
6504 let w: Vec<f32> = (0..rows * cols).map(|i| (i as f32 * 0.017).sin()).collect();
6505 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.05).cos()).collect();
6506 let qt = QTensor::from_f32(w.clone(), rows, cols);
6507
6508 let mut a = vec![0.0f32; rows];
6509 matvec_rows(None, &w, &x, &mut a);
6510 let mut b = vec![0.0f32; rows];
6511 qt.matvec(&x, &mut b, None);
6512 assert_eq!(a, b);
6513 }
6514
6515 #[test]
6516 fn sdot_kernel_exact_on_grid() {
6517 eprintln!("sdot_enabled = {}", sdot_enabled());
6522 let (rows, cols) = (9, 80); let w: Vec<u8> = (0..rows * cols)
6524 .map(|i| (((i * 37) % 251) as i32 - 125) as i8 as u8)
6525 .collect();
6526 let scales: Vec<f32> = (0..rows).map(|o| 0.005 + o as f32 * 0.001).collect();
6527 let x: Vec<f32> = (0..cols)
6528 .map(|i| match i % 3 {
6529 0 => 1.0,
6530 1 => -1.0,
6531 _ => 0.0,
6532 })
6533 .collect();
6534 let mut a = vec![0.0f32; rows];
6535 qmatvec(&w, &[], &scales, &x, &[], TensorDtype::Q8Row, rows, cols, &mut a, None);
6536 for o in 0..rows {
6537 let mut acc = 0.0f32;
6538 for j in 0..cols {
6539 acc += (w[o * cols + j] as i8) as f32 * x[j];
6540 }
6541 let expect = acc * scales[o];
6542 assert!(
6543 (a[o] - expect).abs() < 1e-3 * expect.abs().max(1e-3),
6544 "row {o}: {} vs {expect}",
6545 a[o]
6546 );
6547 }
6548 }
6549
6550 #[test]
6551 fn q1_tbl_fast_path_matches_reference() {
6552 let (rows, cols) = (5, 256);
6557 let gpr = cols / GROUP_SIZE;
6558 let mut bytes = Vec::new();
6559 for t in 0..rows * gpr {
6560 let s = 0.007 + (t % 11) as f32 * 0.004;
6561 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
6562 for j in 0..4 {
6563 bytes.push(((t * 53 + j * 89 + 7) % 249) as u8);
6564 }
6565 }
6566 let x: Vec<f32> = (0..cols)
6567 .map(|i| if (i * 5) % 7 < 3 { 1.0 } else { -1.0 })
6568 .collect();
6569 let mut w = vec![0.0f32; rows * cols];
6570 cortiq_core::quant::dequant_q1(&bytes, &mut w);
6571 let mut got = vec![0.0f32; rows];
6572 q1_matvec(&bytes, &x, rows, cols, &mut got, None);
6573 for o in 0..rows {
6574 let expect: f32 = (0..cols).map(|j| w[o * cols + j] * x[j]).sum();
6575 assert!(
6576 (got[o] - expect).abs() < 1e-3 * expect.abs().max(1e-3),
6577 "row {o}: {} vs {expect}",
6578 got[o]
6579 );
6580 }
6581 let b = 5usize;
6584 let mut xs_all = Vec::new();
6585 for bi in 0..b {
6586 xs_all.extend(x.iter().map(|v| if bi % 2 == 0 { *v } else { -*v }));
6587 }
6588 let mut mm = vec![0.0f32; b * rows];
6589 q1_matmat(&bytes, &xs_all, b, rows, cols, &mut mm, None);
6590 for bi in 0..b {
6591 let mut single = vec![0.0f32; rows];
6592 q1_matvec(&bytes, &xs_all[bi * cols..(bi + 1) * cols], rows, cols, &mut single, None);
6593 assert_eq!(&mm[bi * rows..(bi + 1) * rows], &single[..], "stream {bi}");
6594 }
6595 }
6596
6597 #[test]
6598 fn q1_kernels_match_exact_reference() {
6599 let (rows, cols) = (7, 96);
6601 let gpr = cols / GROUP_SIZE;
6602 let mut bytes = Vec::new();
6603 for t in 0..rows * gpr {
6604 let s = 0.01 + (t % 13) as f32 * 0.003;
6605 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
6606 for j in 0..4 {
6607 bytes.push(((t * 31 + j * 97) % 251) as u8);
6608 }
6609 }
6610 let x: Vec<f32> = (0..cols)
6612 .map(|i| if i % 3 == 0 { 1.0 } else { -1.0 })
6613 .collect();
6614 let mut w = vec![0.0f32; rows * cols];
6616 cortiq_core::quant::dequant_q1(&bytes, &mut w);
6617 let mut expect = vec![0.0f32; rows];
6618 for o in 0..rows {
6619 expect[o] = (0..cols).map(|j| w[o * cols + j] * x[j]).sum();
6620 }
6621 let mut got = vec![0.0f32; rows];
6622 q1_matvec(&bytes, &x, rows, cols, &mut got, None);
6623 for o in 0..rows {
6624 assert!(
6625 (got[o] - expect[o]).abs() < 1e-3 * expect[o].abs().max(1e-3),
6626 "row {o}: {} vs {}",
6627 got[o],
6628 expect[o]
6629 );
6630 }
6631 let x2: Vec<f32> = x.iter().map(|v| -v).collect();
6633 let (mut a1, mut a2) = (vec![0.0f32; rows], vec![0.0f32; rows]);
6634 q1_matvec2(&bytes, &x, &x2, rows, cols, &mut a1, &mut a2, None);
6635 assert_eq!(a1, got);
6636 let mut xs = x.clone();
6637 xs.extend_from_slice(&x2);
6638 let mut mm = vec![0.0f32; 2 * rows];
6639 q1_matmat(&bytes, &xs, 2, rows, cols, &mut mm, None);
6640 assert_eq!(&mm[..rows], got.as_slice());
6641 assert_eq!(&mm[rows..], a2.as_slice());
6642 }
6643
6644 #[test]
6645 fn repack_is_bit_identical() {
6646 let (rows, cols) = (267, 96); let w: Vec<u8> = (0..rows * cols)
6652 .map(|i| (((i * 89) % 253) as i32 - 126) as i8 as u8)
6653 .collect();
6654 let scales: Vec<f32> = (0..rows).map(|o| 0.003 + o as f32 * 0.0007).collect();
6655 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.37).sin() * 2.0).collect();
6656 let rep = q8_repack_layout(&w, rows, cols);
6657 for g in 0..rows / 4 {
6659 for c in 0..cols / 16 {
6660 for lane in 0..4 {
6661 assert_eq!(
6662 &rep[g * 4 * cols + c * 64 + lane * 16..g * 4 * cols + c * 64 + lane * 16 + 16],
6663 &w[(g * 4 + lane) * cols + c * 16..(g * 4 + lane) * cols + c * 16 + 16],
6664 );
6665 }
6666 }
6667 }
6668 let mut a = vec![0.0f32; rows];
6669 qmatvec(&w, &[], &scales, &x, &[], TensorDtype::Q8Row, rows, cols, &mut a, None);
6670 let mut b = vec![0.0f32; rows];
6671 qmatvec(&w, &rep, &scales, &x, &[], TensorDtype::Q8Row, rows, cols, &mut b, None);
6672 assert_eq!(a, b, "full-range repack output diverged");
6673
6674 #[cfg(target_arch = "aarch64")]
6675 if sdot_enabled() {
6676 let act = split_act(&x);
6678 let mut c1 = vec![0.0f32; rows];
6679 let mut c2 = vec![0.0f32; rows];
6680 q8_range_sdot(&w, &[], &scales, &act, cols, SendMut(c1.as_mut_ptr()), 3, rows - 2);
6681 q8_range_sdot(&w, &rep, &scales, &act, cols, SendMut(c2.as_mut_ptr()), 3, rows - 2);
6682 assert_eq!(c1, c2, "unaligned-range repack output diverged");
6683 }
6684 }
6685
6686 #[test]
6687 fn sdot_a8w8_noise_is_bounded() {
6688 let (rows, cols) = (16, 512);
6692 let w: Vec<u8> = (0..rows * cols)
6693 .map(|i| (((i * 37) % 251) as i32 - 125) as i8 as u8)
6694 .collect();
6695 let scales = vec![0.01f32; rows];
6696 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.21).sin()).collect();
6697 let mut a = vec![0.0f32; rows];
6698 qmatvec(&w, &[], &scales, &x, &[], TensorDtype::Q8Row, rows, cols, &mut a, None);
6699 let (mut num, mut den) = (0f64, 0f64);
6700 for o in 0..rows {
6701 let mut acc = 0.0f32;
6702 for j in 0..cols {
6703 acc += (w[o * cols + j] as i8) as f32 * x[j];
6704 }
6705 let expect = acc * scales[o];
6706 num += ((a[o] - expect) as f64).powi(2);
6707 den += (expect as f64).powi(2);
6708 }
6709 let rel = (num / den.max(1e-12)).sqrt();
6710 assert!(rel < 0.05, "A8W8 relative L2 error too high: {rel}");
6711 }
6712
6713 #[test]
6714 fn i8_dot_neon_matches_scalar() {
6715 let n = 100;
6716 let w: Vec<u8> = (0..n).map(|i| ((i * 37 + 11) % 251) as u8).collect();
6717 let x: Vec<f32> = (0..n).map(|i| (i as f32 * 0.13).sin()).collect();
6718 let mut scalar = 0.0f32;
6719 for j in 0..n {
6720 scalar += (w[j] as i8) as f32 * x[j];
6721 }
6722 let fast = dot_i8_f32(&w, &x);
6723 assert!((scalar - fast).abs() < 1e-3 * scalar.abs().max(1.0));
6724 }
6725
6726 #[test]
6728 fn vbitmatvec_matches_full_dequant() {
6729 let (rows, cols) = (6, 64);
6730 let ng = cols / GROUP_SIZE;
6731 let bits: Vec<u8> = vec![3, 4, 5, 6, 8, 4];
6733 let mut bytes = bits.clone();
6734 for g in 0..rows * ng {
6735 let s = 0.02 + 0.001 * g as f32;
6736 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
6737 }
6738 for r in 0..rows {
6739 let b = bits[r] as usize;
6740 let (mut acc, mut nb) = (0u64, 0usize);
6741 let mut rowbytes = Vec::new();
6742 for i in 0..cols {
6743 let v = ((i * 7 + r * 13) % (1 << b)) as u64;
6744 acc = (acc << b) | v;
6745 nb += b;
6746 while nb >= 8 {
6747 nb -= 8;
6748 rowbytes.push(((acc >> nb) & 0xFF) as u8);
6749 }
6750 }
6751 if nb > 0 {
6752 rowbytes.push(((acc << (8 - nb)) & 0xFF) as u8);
6753 }
6754 bytes.extend_from_slice(&rowbytes);
6755 }
6756 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.19).sin()).collect();
6757
6758 let mut reference = vec![0f32; rows * cols];
6759 cortiq_core::quant::dequant_vbit(&bytes, rows, cols, &mut reference).unwrap();
6760 let mut expect = vec![0f32; rows];
6761 for r in 0..rows {
6762 expect[r] = reference[r * cols..(r + 1) * cols]
6763 .iter()
6764 .zip(&x)
6765 .map(|(w, xv)| w * xv)
6766 .sum();
6767 }
6768 let mut got = vec![0f32; rows];
6769 let offsets = vbit_row_offsets(&bytes, rows, cols);
6770 vbitmatvec(&bytes, &offsets, &x, rows, cols, &mut got, None);
6771 let tol = if a8w8_enabled() { 6e-2 } else { 1e-4 };
6775 let scale = expect.iter().fold(0f32, |m, v| m.max(v.abs())).max(1e-6);
6776 for r in 0..rows {
6777 assert!(
6778 (got[r] - expect[r]).abs() < tol * scale,
6779 "row {r}: {} vs {}",
6780 got[r],
6781 expect[r]
6782 );
6783 }
6784 }
6785
6786 #[test]
6791 #[cfg(target_arch = "x86_64")]
6792 fn vbit_matmat_blocked_matches_per_row() {
6793 let (rows, cols, b) = (64usize, 128usize, 9usize);
6794 let ng = cols / GROUP_SIZE;
6795 let bits: Vec<u8> = (0..rows).map(|r| [3u8, 4, 5, 6][r % 4]).collect();
6796 let mut bytes = bits.clone();
6797 for g in 0..rows * ng {
6798 let sc = 0.02 + 0.0005 * g as f32;
6799 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(sc).to_le_bytes());
6800 }
6801 for r in 0..rows {
6802 let bw = bits[r] as usize;
6803 let (mut acc, mut nb) = (0u64, 0usize);
6804 let mut rowbytes = Vec::new();
6805 for i in 0..cols {
6806 let v = ((i * 7 + r * 13) % (1 << bw)) as u64;
6807 acc = (acc << bw) | v;
6808 nb += bw;
6809 while nb >= 8 {
6810 nb -= 8;
6811 rowbytes.push(((acc >> nb) & 0xFF) as u8);
6812 }
6813 }
6814 if nb > 0 {
6815 rowbytes.push(((acc << (8 - nb)) & 0xFF) as u8);
6816 }
6817 bytes.extend_from_slice(&rowbytes);
6818 }
6819 let x: Vec<f32> =
6820 (0..b * cols).map(|i| ((i * 13 + 7) % 97) as f32 / 97.0 - 0.5).collect();
6821 let offsets = vbit_row_offsets(&bytes, rows, cols);
6822 let mut y_a = vec![0f32; b * rows];
6823 let mut y_b = vec![0f32; b * rows];
6824 unsafe { std::env::set_var("CMF_X86_BLOCKED", "1") };
6825 vbitmatmat(&bytes, &offsets, &x, b, rows, cols, &mut y_a, None);
6826 unsafe { std::env::set_var("CMF_X86_BLOCKED", "0") };
6827 vbitmatmat(&bytes, &offsets, &x, b, rows, cols, &mut y_b, None);
6828 unsafe { std::env::remove_var("CMF_X86_BLOCKED") };
6829 let max_d =
6830 y_a.iter().zip(&y_b).map(|(p, q)| (p - q).abs()).fold(0.0f32, f32::max);
6831 assert!(max_d < 1e-4, "vbit blocked ≠ per-row: max|Δ| = {max_d}");
6832 }
6833
6834 #[test]
6835 fn q4matvec_matches_full_dequant() {
6836 let (rows, cols) = (8, 64);
6837 let groups = rows * cols / GROUP_SIZE;
6838 let mut bytes = Vec::with_capacity(groups * 16 + groups * 2);
6840 for i in 0..groups * 16 {
6841 bytes.push((((i * 7 + 3) % 256) & 0xFF) as u8);
6842 }
6843 for g in 0..groups {
6844 let s = 0.01 + 0.003 * g as f32;
6845 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
6846 }
6847 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.17).sin()).collect();
6848
6849 let mut reference = vec![0.0f32; rows * cols];
6850 cortiq_core::quant::dequant_q4_block(&bytes, &mut reference);
6851 let mut expect = vec![0.0f32; rows];
6852 for r in 0..rows {
6853 expect[r] = reference[r * cols..(r + 1) * cols]
6854 .iter()
6855 .zip(&x)
6856 .map(|(w, xv)| w * xv)
6857 .sum();
6858 }
6859
6860 let mut got = vec![0.0f32; rows];
6861 q4matvec(&bytes, &x, rows, cols, &mut got, None);
6862 let tol = if a8w8_enabled() { 6e-2 } else { 1e-4 };
6866 let scale = expect.iter().fold(0f32, |m, v| m.max(v.abs())).max(1.0);
6867 for r in 0..rows {
6868 assert!(
6869 (got[r] - expect[r]).abs() < tol * scale,
6870 "row {r}: {} vs {}",
6871 got[r],
6872 expect[r]
6873 );
6874 }
6875 }
6876
6877 #[test]
6880 fn vbitmatvec2_equals_two_singles() {
6881 let (rows, cols) = (6, 64);
6882 let ng = cols / GROUP_SIZE;
6883 let bits: Vec<u8> = vec![3, 4, 5, 6, 8, 4];
6884 let mut bytes = bits.clone();
6885 for g in 0..rows * ng {
6886 let s = 0.02 + 0.001 * g as f32;
6887 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
6888 }
6889 for r in 0..rows {
6890 let b = bits[r] as usize;
6891 let (mut acc, mut nb) = (0u64, 0usize);
6892 let mut rowbytes = Vec::new();
6893 for i in 0..cols {
6894 let v = ((i * 7 + r * 13) % (1 << b)) as u64;
6895 acc = (acc << b) | v;
6896 nb += b;
6897 while nb >= 8 {
6898 nb -= 8;
6899 rowbytes.push(((acc >> nb) & 0xFF) as u8);
6900 }
6901 }
6902 if nb > 0 {
6903 rowbytes.push(((acc << (8 - nb)) & 0xFF) as u8);
6904 }
6905 bytes.extend_from_slice(&rowbytes);
6906 }
6907 let x1: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.19).sin()).collect();
6908 let x2: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.11).cos()).collect();
6909 let offsets = vbit_row_offsets(&bytes, rows, cols);
6910
6911 let (mut a1, mut a2) = (vec![0f32; rows], vec![0f32; rows]);
6912 vbitmatvec(&bytes, &offsets, &x1, rows, cols, &mut a1, None);
6913 vbitmatvec(&bytes, &offsets, &x2, rows, cols, &mut a2, None);
6914 let (mut b1, mut b2) = (vec![0f32; rows], vec![0f32; rows]);
6915 vbitmatvec2(&bytes, &offsets, &x1, &x2, rows, cols, &mut b1, &mut b2, None);
6916 assert_eq!(a1, b1, "fused vbit lane 1 must be bit-identical");
6917 assert_eq!(a2, b2, "fused vbit lane 2 must be bit-identical");
6918 }
6919
6920 #[test]
6922 fn q4matvec2_equals_two_singles() {
6923 let (rows, cols) = (8, 128);
6924 let groups = rows * cols / GROUP_SIZE;
6925 let mut bytes = Vec::with_capacity(groups * 16 + groups * 2);
6926 for i in 0..groups * 16 {
6927 bytes.push((((i * 7 + 3) % 256) & 0xFF) as u8);
6928 }
6929 for g in 0..groups {
6930 let s = 0.01 + 0.003 * g as f32;
6931 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
6932 }
6933 let mut x1: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.17).sin()).collect();
6936 x1[9] = 250.0;
6937 let x2: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.23).cos()).collect();
6938
6939 let (mut a1, mut a2) = (vec![0f32; rows], vec![0f32; rows]);
6940 q4matvec(&bytes, &x1, rows, cols, &mut a1, None);
6941 q4matvec(&bytes, &x2, rows, cols, &mut a2, None);
6942 let (mut b1, mut b2) = (vec![0f32; rows], vec![0f32; rows]);
6943 q4matvec2(&bytes, &x1, &x2, rows, cols, &mut b1, &mut b2, None);
6944 assert_eq!(a1, b1, "fused q4 lane 1 must be bit-identical");
6945 assert_eq!(a2, b2, "fused q4 lane 2 must be bit-identical");
6946 }
6947
6948 #[test]
6951 fn matvec_many_equals_separate_matvecs() {
6952 use crate::pool::Pool;
6953 let (r1, r2, cols) = (300, 200, 64);
6954 let mk = |salt: usize, rows: usize| {
6955 QTensor::from_f32(
6956 (0..rows * cols).map(|i| ((i * 7 + salt) % 97) as f32 / 97.0 - 0.5).collect(),
6957 rows,
6958 cols,
6959 )
6960 };
6961 let (a, b) = (mk(1, r1), mk(5, r2));
6962 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.11).sin()).collect();
6963 let pool = Pool::new(3);
6964
6965 let (mut ea, mut eb) = (vec![0f32; r1], vec![0f32; r2]);
6966 a.matvec(&x, &mut ea, Some(&pool));
6967 b.matvec(&x, &mut eb, Some(&pool));
6968 let (mut ga, mut gb) = (vec![0f32; r1], vec![0f32; r2]);
6969 QTensor::matvec_many([&a, &b], &x, [&mut ga, &mut gb], Some(&pool));
6970 assert_eq!(ea, ga, "fused multi-matrix lane 1 must be bit-identical");
6971 assert_eq!(eb, gb, "fused multi-matrix lane 2 must be bit-identical");
6972 }
6973
6974 #[test]
6977 fn batched_matmat_equals_per_position_matvec() {
6978 let (rows, cols, b) = (8, 64, 5);
6979 let groups = rows * cols / GROUP_SIZE;
6981 let mut q4 = Vec::new();
6982 for i in 0..groups * 16 {
6983 q4.push((((i * 7 + 3) % 256) & 0xFF) as u8);
6984 }
6985 for g in 0..groups {
6986 q4.extend_from_slice(
6987 &cortiq_core::quant::f32_to_f16(0.01 + 0.003 * g as f32).to_le_bytes(),
6988 );
6989 }
6990 let ng = cols / GROUP_SIZE;
6992 let bits: Vec<u8> = vec![3, 4, 5, 6, 8, 4, 5, 3];
6993 let mut vb = bits.clone();
6994 for g in 0..rows * ng {
6995 vb.extend_from_slice(
6996 &cortiq_core::quant::f32_to_f16(0.02 + 0.001 * g as f32).to_le_bytes(),
6997 );
6998 }
6999 for r in 0..rows {
7000 let bw = bits[r] as usize;
7001 let (mut acc, mut nb) = (0u64, 0usize);
7002 let mut rowbytes = Vec::new();
7003 for i in 0..cols {
7004 let v = ((i * 7 + r * 13) % (1 << bw)) as u64;
7005 acc = (acc << bw) | v;
7006 nb += bw;
7007 while nb >= 8 {
7008 nb -= 8;
7009 rowbytes.push(((acc >> nb) & 0xFF) as u8);
7010 }
7011 }
7012 if nb > 0 {
7013 rowbytes.push(((acc << (8 - nb)) & 0xFF) as u8);
7014 }
7015 vb.extend_from_slice(&rowbytes);
7016 }
7017 let offsets = vbit_row_offsets(&vb, rows, cols);
7018
7019 let xs: Vec<f32> = (0..b * cols).map(|i| (i as f32 * 0.13).sin()).collect();
7020
7021 let mut got = vec![0f32; b * rows];
7023 q4matmat(&q4, &xs, b, rows, cols, &mut got, None);
7024 for bi in 0..b {
7025 let mut expect = vec![0f32; rows];
7026 q4matvec(&q4, &xs[bi * cols..(bi + 1) * cols], rows, cols, &mut expect, None);
7027 assert_eq!(&got[bi * rows..(bi + 1) * rows], &expect[..], "q4 batch pos {bi}");
7028 }
7029
7030 let mut got = vec![0f32; b * rows];
7032 vbitmatmat(&vb, &offsets, &xs, b, rows, cols, &mut got, None);
7033 for bi in 0..b {
7034 let mut expect = vec![0f32; rows];
7035 vbitmatvec(
7036 &vb, &offsets, &xs[bi * cols..(bi + 1) * cols], rows, cols, &mut expect, None,
7037 );
7038 assert_eq!(&got[bi * rows..(bi + 1) * rows], &expect[..], "vbit batch pos {bi}");
7039 }
7040 }
7041
7042 #[test]
7046 fn q4_tiled_matches_q4_block_bitexact() {
7047 let (rows, cols, b) = (8usize, 128usize, 3usize);
7048 let groups = rows * cols / GROUP_SIZE;
7049 let mut split = Vec::with_capacity(groups * 18);
7050 for i in 0..groups * 16 {
7051 split.push((((i * 7 + 3) % 256) & 0xFF) as u8);
7052 }
7053 for g in 0..groups {
7054 split.extend_from_slice(
7055 &cortiq_core::quant::f32_to_f16(0.01 + 0.003 * g as f32).to_le_bytes(),
7056 );
7057 }
7058 let (packed, scales) = split.split_at(groups * 16);
7060 let mut tiled = Vec::with_capacity(groups * Q4_TILE);
7061 for g in 0..groups {
7062 tiled.extend_from_slice(&scales[g * 2..g * 2 + 2]);
7063 tiled.extend_from_slice(&packed[g * 16..(g + 1) * 16]);
7064 }
7065
7066 let mut x1: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.17).sin()).collect();
7067 x1[9] = 250.0; let x2: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.23).cos()).collect();
7069
7070 let (mut a, mut t) = (vec![0f32; rows], vec![0f32; rows]);
7071 q4matvec(&split, &x1, rows, cols, &mut a, None);
7072 q4t_matvec(&tiled, &x1, rows, cols, &mut t, None);
7073 assert_eq!(a, t, "q4t matvec must match q4 bit-for-bit");
7074
7075 let (mut a1, mut a2) = (vec![0f32; rows], vec![0f32; rows]);
7076 let (mut t1, mut t2) = (vec![0f32; rows], vec![0f32; rows]);
7077 q4matvec2(&split, &x1, &x2, rows, cols, &mut a1, &mut a2, None);
7078 q4t_matvec2(&tiled, &x1, &x2, rows, cols, &mut t1, &mut t2, None);
7079 assert_eq!(a1, t1);
7080 assert_eq!(a2, t2);
7081
7082 let xs: Vec<f32> = (0..b * cols).map(|i| (i as f32 * 0.13).sin()).collect();
7083 let (mut am, mut tm) = (vec![0f32; b * rows], vec![0f32; b * rows]);
7084 q4matmat(&split, &xs, b, rows, cols, &mut am, None);
7085 q4t_matmat(&tiled, &xs, b, rows, cols, &mut tm, None);
7086 assert_eq!(am, tm, "q4t matmat must match q4 bit-for-bit");
7087 }
7088
7089 #[test]
7096 fn q4matvec_sdot_outlier_exact() {
7097 let (rows, cols) = (4, 128);
7098 let groups = rows * cols / GROUP_SIZE;
7099 let mut bytes = Vec::with_capacity(groups * 16 + groups * 2);
7100 for i in 0..groups * 16 {
7101 bytes.push(((i * 11 + 5) % 256) as u8);
7102 }
7103 for g in 0..groups {
7104 let s = 0.02 + 0.002 * g as f32;
7105 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
7106 }
7107 let mut x: Vec<f32> = (0..cols)
7108 .map(|i| match i % 3 {
7109 0 => 1.0,
7110 1 => -1.0,
7111 _ => 0.0,
7112 })
7113 .collect();
7114 x[17] = 300.0; let mut reference = vec![0.0f32; rows * cols];
7117 cortiq_core::quant::dequant_q4_block(&bytes, &mut reference);
7118 let mut expect = vec![0.0f32; rows];
7119 for r in 0..rows {
7120 expect[r] = reference[r * cols..(r + 1) * cols]
7121 .iter()
7122 .zip(&x)
7123 .map(|(w, xv)| w * xv)
7124 .sum();
7125 }
7126 let mut got = vec![0.0f32; rows];
7127 q4matvec(&bytes, &x, rows, cols, &mut got, None);
7128 let scale = expect.iter().fold(0f32, |m, v| m.max(v.abs())).max(1.0);
7129 for r in 0..rows {
7130 assert!(
7131 (got[r] - expect[r]).abs() < 2e-3 * scale,
7132 "row {r}: {} vs {} (outlier term must be exact)",
7133 got[r],
7134 expect[r]
7135 );
7136 }
7137 }
7138
7139 #[test]
7143 fn q1t_matvec_matches_reference() {
7144 use cortiq_core::quant::{dequant_q1t, f32_to_f16};
7145 let (rows, cols) = (3usize, 64usize); let gpr = cols / GROUP_SIZE;
7147 let scales = [0.5f32, 0.3, 0.7, 0.2, 0.6, 0.15];
7148 let outliers: [(u32, f32); 3] = [(5, 9.0), (70, -4.5), (150, 3.25)];
7150 let is_out = |flat: usize| outliers.iter().any(|&(i, _)| i as usize == flat);
7151 let mut bytes = Vec::new();
7152 for r in 0..rows {
7153 for g in 0..gpr {
7154 bytes.extend_from_slice(&f32_to_f16(scales[r * gpr + g]).to_le_bytes());
7155 let mut c = [0u8; 7];
7156 for k in 0..GROUP_SIZE {
7157 let code = if is_out(r * cols + g * GROUP_SIZE + k) {
7159 0
7160 } else {
7161 ((k + r * 3 + g) % 3) as u8 };
7163 cortiq_core::quant::q1t_pack(&mut c, k, code);
7164 }
7165 bytes.extend_from_slice(&c);
7166 }
7167 }
7168 let mut row_ptr = vec![0u32; rows + 1];
7171 for &(idx, _) in &outliers {
7172 row_ptr[idx as usize / cols + 1] += 1;
7173 }
7174 for r in 0..rows {
7175 row_ptr[r + 1] += row_ptr[r];
7176 }
7177 for &p in &row_ptr {
7178 bytes.extend_from_slice(&p.to_le_bytes());
7179 }
7180 for &(idx, v) in &outliers {
7181 bytes.extend_from_slice(&((idx as usize % cols) as u16).to_le_bytes());
7182 bytes.extend_from_slice(&f32_to_f16(v).to_le_bytes());
7183 }
7184
7185 let mut refw = vec![0f32; rows * cols];
7186 dequant_q1t(&bytes, rows, cols, &mut refw);
7187 let x: Vec<f32> = (0..cols).map(|j| if j % 3 == 0 { 1.0 } else { -1.0 }).collect();
7190 let mut expect = vec![0f32; rows];
7191 for r in 0..rows {
7192 let mut a = 0.0f32;
7193 for j in 0..cols {
7194 a += refw[r * cols + j] * x[j];
7195 }
7196 expect[r] = a;
7197 }
7198 let tol = |e: f32| 1e-3 * e.abs().max(1e-3);
7199 let mut got = vec![0f32; rows];
7200 q1t_matvec(&bytes, &x, rows, cols, &mut got, None);
7201 for r in 0..rows {
7202 assert!((got[r] - expect[r]).abs() < tol(expect[r]), "row {r}: {} vs {}", got[r], expect[r]);
7203 }
7204 let x2: Vec<f32> = x.iter().chain(x.iter().map(|v| v)).copied().collect();
7206 let mut gm = vec![0f32; 2 * rows];
7207 q1t_matmat(&bytes, &x2, 2, rows, cols, &mut gm, None);
7208 for r in 0..rows {
7209 assert!((gm[r] - expect[r]).abs() < tol(expect[r]));
7210 assert!((gm[rows + r] - expect[r]).abs() < tol(expect[r]));
7211 }
7212 let (mut p1, mut p2) = (vec![0f32; rows], vec![0f32; rows]);
7217 q1t_matvec(&bytes, &x, rows, cols, &mut p1, None);
7218 q1t_matvec(&bytes, &x, rows, cols, &mut p2, None);
7219 for r in 0..rows {
7220 assert!((p1[r] - expect[r]).abs() < tol(expect[r]) && (p2[r] - expect[r]).abs() < tol(expect[r]));
7221 }
7222 }
7223
7224 #[test]
7228 #[ignore]
7229 fn q1t_matvec_speed() {
7230 use cortiq_core::quant::{f32_to_f16, q1t_code, q1t_pack, Q1T_TILE};
7231 use std::time::Instant;
7232 let (rows, cols) = (8192usize, 4096usize); let gpr = cols / GROUP_SIZE;
7234 let mut bytes = Vec::with_capacity(rows * gpr * Q1T_TILE + 16);
7235 for r in 0..rows {
7236 for g in 0..gpr {
7237 let s = 0.1 + ((r + g) % 7) as f32 * 0.01;
7238 bytes.extend_from_slice(&f32_to_f16(s).to_le_bytes());
7239 let mut c = [0u8; 7];
7240 for k in 0..GROUP_SIZE {
7241 q1t_pack(&mut c, k, ((k * 7 + r + g) % 3) as u8);
7242 }
7243 bytes.extend_from_slice(&c);
7244 }
7245 }
7246 let (n, stride) = (rows * cols, 40usize); let mut row_ptr = vec![0u32; rows + 1];
7248 let mut idx = 0usize;
7249 while idx < n {
7250 row_ptr[idx / cols + 1] += 1;
7251 idx += stride;
7252 }
7253 for r in 0..rows {
7254 row_ptr[r + 1] += row_ptr[r];
7255 }
7256 for &p in &row_ptr {
7257 bytes.extend_from_slice(&p.to_le_bytes());
7258 }
7259 let mut idx = 0usize;
7260 while idx < n {
7261 bytes.extend_from_slice(&((idx % cols) as u16).to_le_bytes());
7262 bytes.extend_from_slice(&f32_to_f16((idx % 13) as f32 * 0.1 - 0.6).to_le_bytes());
7263 idx += stride;
7264 }
7265 let x: Vec<f32> = (0..cols).map(|j| if j % 3 == 0 { 1.0 } else { -1.0 }).collect();
7268 let (rp_off, ent_off, has_ov) = q1t_overlay(&bytes, rows * gpr * Q1T_TILE, rows);
7269
7270 let slow = |out: &mut [f32]| {
7272 let mut buf = vec![0f32; cols];
7273 for r in 0..rows {
7274 for g in 0..gpr {
7275 let off = (r * gpr + g) * Q1T_TILE;
7276 let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
7277 let codes = &bytes[off + 2..off + Q1T_TILE];
7278 for k in 0..GROUP_SIZE {
7279 buf[g * GROUP_SIZE + k] = match q1t_code(codes, k) {
7280 1 => s,
7281 2 => -s,
7282 _ => 0.0,
7283 };
7284 }
7285 }
7286 out[r] = q1t_row_outlier_correction(&bytes, r, rp_off, ent_off, has_ov, &x)
7287 + (0..cols).map(|j| buf[j] * x[j]).sum::<f32>();
7288 }
7289 };
7290 let iters = 5;
7291 let mut a = vec![0f32; rows];
7292 slow(&mut a); let t = Instant::now();
7294 for _ in 0..iters {
7295 slow(&mut a);
7296 }
7297 let slow_ms = t.elapsed().as_secs_f64() * 1e3 / iters as f64;
7298
7299 let mut b = vec![0f32; rows];
7300 q1t_matvec(&bytes, &x, rows, cols, &mut b, None); let t = Instant::now();
7302 for _ in 0..iters {
7303 q1t_matvec(&bytes, &x, rows, cols, &mut b, None);
7304 }
7305 let fast_ms = t.elapsed().as_secs_f64() * 1e3 / iters as f64;
7306
7307 for r in 0..rows {
7308 assert!((a[r] - b[r]).abs() < 1e-2, "mismatch row {r}");
7309 }
7310 println!(
7311 "q1t matvec {rows}x{cols} (1 thread): div-decode {slow_ms:.2} ms fused-LUT {fast_ms:.2} ms => {:.2}x",
7312 slow_ms / fast_ms
7313 );
7314 }
7315}