1use crate::pool::{matvec_rows, matvec_rows2, Pool};
15use cortiq_core::quant::{f16_to_f32, GROUP_SIZE, 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 },
41}
42
43fn vbit_row_offsets(bytes: &[u8], rows: usize, cols: usize) -> Vec<usize> {
46 let ng = cols / GROUP_SIZE;
47 let bits = &bytes[..rows];
48 let mut offsets = Vec::with_capacity(rows + 1);
49 let mut off = rows + rows * ng * 2;
50 for r in 0..rows {
51 offsets.push(off);
52 off += (cols * bits[r] as usize + 7) / 8;
53 }
54 offsets.push(off);
55 offsets
56}
57
58impl QTensor {
59 pub fn from_f32(data: Vec<f32>, rows: usize, cols: usize) -> Self {
60 debug_assert_eq!(data.len(), rows * cols);
61 Self::F32 { data, rows, cols }
62 }
63
64 pub fn from_model(model: &Arc<CmfModel>, name: &str) -> Result<Self, String> {
67 let idx = model
70 .tensor_index(name)
71 .ok_or_else(|| format!("tensor '{name}' not found in CMF directory"))?;
72 let entry = &model.tensors[idx];
73 if entry.shape.len() != 2 {
74 return Err(format!("QTensor::from_model needs 2-D, got '{name}'"));
75 }
76 let (rows, cols) = (entry.shape[0], entry.shape[1]);
77 let bytes = model.entry_bytes(entry);
78
79 match entry.dtype {
80 TensorDtype::Q8Row | TensorDtype::Q8_2f => {
81 let n = rows * cols;
82 let scales_off = n;
83 let row_scale: Vec<f32> = (0..rows)
84 .map(|o| {
85 f16_to_f32(u16::from_le_bytes([
86 bytes[scales_off + o * 2],
87 bytes[scales_off + o * 2 + 1],
88 ]))
89 })
90 .collect();
91 let col_field: Vec<f32> = if entry.dtype == TensorDtype::Q8_2f {
92 let col_off = n + rows * 2;
93 (0..cols)
94 .map(|i| {
95 f16_to_f32(u16::from_le_bytes([
96 bytes[col_off + i * 2],
97 bytes[col_off + i * 2 + 1],
98 ]))
99 })
100 .collect()
101 } else {
102 Vec::new()
103 };
104 Ok(Self::Mapped {
105 model: model.clone(),
106 idx,
107 dtype: entry.dtype,
108 rows,
109 cols,
110 row_scale,
111 col_field,
112 vbit_offsets: Vec::new(),
113 })
114 }
115 TensorDtype::Vbit if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
117 model: model.clone(),
118 idx,
119 dtype: entry.dtype,
120 rows,
121 cols,
122 row_scale: Vec::new(),
123 col_field: Vec::new(),
124 vbit_offsets: vbit_row_offsets(bytes, rows, cols),
125 }),
126 TensorDtype::VbitRo if cols % GROUP_SIZE == 0 => {
130 let (_, off_off, packed_off) =
131 cortiq_core::quant::vbit_ro_sections(rows, cols);
132 let offsets: Vec<usize> = (0..=rows)
133 .map(|r| {
134 packed_off + cortiq_core::quant::vbit_ro_offset(bytes, off_off, r)
135 })
136 .collect();
137 Ok(Self::Mapped {
138 model: model.clone(),
139 idx,
140 dtype: entry.dtype,
141 rows,
142 cols,
143 row_scale: Vec::new(),
144 col_field: Vec::new(),
145 vbit_offsets: offsets,
146 })
147 }
148 TensorDtype::Q4Tiled if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
154 model: model.clone(),
155 idx,
156 dtype: entry.dtype,
157 rows,
158 cols,
159 row_scale: Vec::new(),
160 col_field: Vec::new(),
161 vbit_offsets: Vec::new(),
162 }),
163 TensorDtype::Q4Block if cols % GROUP_SIZE == 0 => Ok(Self::Mapped {
164 model: model.clone(),
165 idx,
166 dtype: entry.dtype,
167 rows,
168 cols,
169 row_scale: Vec::new(),
170 col_field: Vec::new(),
171 vbit_offsets: Vec::new(),
172 }),
173 _ => {
175 let mut data = vec![0.0f32; rows * cols];
176 cortiq_core::quant::dequant_tensor(entry, bytes, &mut data)?;
177 Ok(Self::from_f32(data, rows, cols))
178 }
179 }
180 }
181
182 pub fn rows(&self) -> usize {
183 match self {
184 Self::F32 { rows, .. } | Self::Mapped { rows, .. } => *rows,
185 }
186 }
187
188 pub fn cols(&self) -> usize {
189 match self {
190 Self::F32 { cols, .. } | Self::Mapped { cols, .. } => *cols,
191 }
192 }
193
194 pub fn as_f32(&self) -> Option<&[f32]> {
197 match self {
198 Self::F32 { data, .. } => Some(data),
199 Self::Mapped { .. } => None,
200 }
201 }
202
203 fn quant_bytes(&self) -> &[u8] {
204 match self {
205 Self::Mapped { model, idx, .. } => model.entry_bytes(&model.tensors[*idx]),
206 Self::F32 { .. } => unreachable!("quant_bytes on F32"),
207 }
208 }
209
210 pub fn row_f32(&self, r: usize, dst: &mut [f32]) {
212 let cols = self.cols();
213 debug_assert_eq!(dst.len(), cols);
214 match self {
215 Self::F32 { data, .. } => dst.copy_from_slice(&data[r * cols..(r + 1) * cols]),
216 Self::Mapped {
217 dtype,
218 row_scale,
219 col_field,
220 vbit_offsets,
221 ..
222 } => {
223 if *dtype == TensorDtype::Q4Tiled {
224 let bytes = self.quant_bytes();
225 let gpr = cols / GROUP_SIZE;
226 for gi in 0..gpr {
227 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
228 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
229 for (k, &b) in tile[2..].iter().enumerate() {
230 dst[gi * GROUP_SIZE + k * 2] = ((b & 0x0F) as f32 - 8.0) * s;
231 dst[gi * GROUP_SIZE + k * 2 + 1] = (((b >> 4) & 0x0F) as f32 - 8.0) * s;
232 }
233 }
234 return;
235 }
236 if *dtype == TensorDtype::Q4Block {
237 let (packed, scales) = q4_split(self.quant_bytes(), self.rows(), cols);
238 let gpr = cols / GROUP_SIZE;
239 for gi in 0..gpr {
240 let g = r * gpr + gi;
241 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
242 for (k, &b) in packed[g * 16..(g + 1) * 16].iter().enumerate() {
243 dst[gi * GROUP_SIZE + k * 2] = ((b & 0x0F) as f32 - 8.0) * s;
244 dst[gi * GROUP_SIZE + k * 2 + 1] = (((b >> 4) & 0x0F) as f32 - 8.0) * s;
245 }
246 }
247 return;
248 }
249 if matches!(dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
250 let bytes = self.quant_bytes();
251 let rows = self.rows();
252 let ng = cols / GROUP_SIZE;
253 let bits = &bytes[..rows];
254 let sc_off = rows;
255 let off = vbit_offsets[r];
258 let b = bits[r] as usize;
259 let l = ((1usize << (b - 1)) - 1) as f32;
260 let data = &bytes[off..];
261 let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
262 for (i, d) in dst.iter_mut().enumerate() {
263 while nbits < b {
264 acc = (acc << 8) | data[idx] as u64;
265 idx += 1;
266 nbits += 8;
267 }
268 let u = ((acc >> (nbits - b)) & ((1u64 << b) - 1)) as f32;
269 nbits -= b;
270 let so = (r * ng + i / GROUP_SIZE) * 2;
271 let sv = f16_to_f32(u16::from_le_bytes([
272 bytes[sc_off + so],
273 bytes[sc_off + so + 1],
274 ]));
275 *d = (u - l) * sv;
276 }
277 return;
278 }
279 let q = &self.quant_bytes()[r * cols..(r + 1) * cols];
280 let s = row_scale[r];
281 match dtype {
282 TensorDtype::Q8Row => {
283 for (d, &b) in dst.iter_mut().zip(q) {
284 *d = (b as i8) as f32 * s;
285 }
286 }
287 TensorDtype::Q8_2f => {
288 for (i, (d, &b)) in dst.iter_mut().zip(q).enumerate() {
289 *d = (b as i8) as f32 * s * col_field[i];
290 }
291 }
292 _ => unreachable!(),
293 }
294 }
295 }
296 }
297
298 pub fn sparse_col_ok(&self) -> bool {
303 match self {
304 Self::F32 { .. } => true,
305 Self::Mapped { dtype, .. } => {
306 matches!(dtype, TensorDtype::Q8Row | TensorDtype::Q8_2f)
307 }
308 }
309 }
310
311 pub fn add_col_scaled(&self, c: usize, w: f32, out: &mut [f32]) {
315 let inter = self.cols();
316 let hidden = self.rows();
317 debug_assert_eq!(out.len(), hidden);
318 match self {
319 Self::F32 { data, .. } => {
320 for (k, o) in out.iter_mut().enumerate() {
321 *o += w * data[k * inter + c];
322 }
323 }
324 Self::Mapped {
325 dtype, row_scale, col_field, ..
326 } => {
327 let q = self.quant_bytes();
328 let colf = if *dtype == TensorDtype::Q8_2f {
329 col_field[c]
330 } else {
331 1.0
332 };
333 let wc = w * colf;
334 for (k, o) in out.iter_mut().enumerate() {
335 let b = q[k * inter + c] as i8 as f32;
336 *o += wc * b * row_scale[k];
337 }
338 }
339 }
340 }
341
342 pub fn row_dot(&self, r: usize, x: &[f32], scratch: &mut [f32]) -> f32 {
346 let cols = self.cols();
347 match self {
348 Self::F32 { data, .. } => {
349 let row = &data[r * cols..(r + 1) * cols];
350 row.iter().zip(x).map(|(w, v)| w * v).sum()
351 }
352 Self::Mapped { dtype, row_scale, col_field, .. } => match dtype {
353 TensorDtype::Q8Row => {
354 let q = &self.quant_bytes()[r * cols..(r + 1) * cols];
355 dot_i8_f32(q, x) * row_scale[r]
356 }
357 TensorDtype::Q8_2f => {
358 let q = &self.quant_bytes()[r * cols..(r + 1) * cols];
359 dot_i8_col_f32(q, x, col_field) * row_scale[r]
360 }
361 _ => {
362 self.row_f32(r, scratch);
363 scratch.iter().zip(x).map(|(w, v)| w * v).sum()
364 }
365 },
366 }
367 }
368
369 pub fn matvec(&self, x: &[f32], out: &mut [f32], pool: Option<&Pool>) {
372 match self {
373 Self::F32 { data, .. } => matvec_rows(pool, data, x, out),
374 Self::Mapped {
375 model,
376 idx,
377 dtype,
378 rows,
379 cols,
380 row_scale,
381 col_field,
382 vbit_offsets,
383 } => {
384 let _ = (model, idx);
385 if *dtype == TensorDtype::Q4Block {
386 q4matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
387 return;
388 }
389 if *dtype == TensorDtype::Q4Tiled {
390 q4t_matvec(self.quant_bytes(), x, *rows, *cols, out, pool);
391 return;
392 }
393 if matches!(dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
394 vbitmatvec(self.quant_bytes(), vbit_offsets, x, *rows, *cols, out, pool);
395 return;
396 }
397 let xs = prescale(x, col_field, *dtype);
398 if *rows >= crate::gpu::min_rows()
403 && matches!(dtype, TensorDtype::Q8Row | TensorDtype::Q8_2f)
404 && std::env::var("CMF_GPU_LMHEAD").map(|v| v != "0").unwrap_or(true)
405 && crate::gpu::enabled_here()
406 {
407 let t0 = std::time::Instant::now();
410 match crate::gpu::probe_arm(crate::gpu::OpClass::Matvec) {
411 crate::gpu::ProbeArm::Gpu => {}
412 crate::gpu::ProbeArm::CpuTimed => {
413 qmatvec(self.quant_bytes(), row_scale, &xs, *rows, *cols, out, pool);
414 crate::gpu::probe_record(
415 crate::gpu::OpClass::Matvec,
416 false,
417 t0.elapsed(),
418 );
419 return;
420 }
421 crate::gpu::ProbeArm::Cpu => {
422 qmatvec(self.quant_bytes(), row_scale, &xs, *rows, *cols, out, pool);
423 return;
424 }
425 }
426 let frac = std::env::var("CMF_GPU_SPLIT")
427 .ok()
428 .and_then(|v| v.parse::<f32>().ok())
429 .unwrap_or(0.5)
430 .clamp(0.0, 1.0);
431 let cpu_rows = ((*rows as f32) * (1.0 - frac)) as usize;
432 let (out_cpu, out_gpu) = out.split_at_mut(cpu_rows);
433 let bytes = self.quant_bytes();
434 let ok = std::thread::scope(|sc| {
435 let g = sc.spawn(|| {
436 crate::gpu::q8_matvec_range(
437 model,
438 *idx,
439 cpu_rows,
440 &row_scale[cpu_rows..],
441 &xs,
442 *rows - cpu_rows,
443 *cols,
444 out_gpu,
445 )
446 });
447 if cpu_rows > 0 {
448 qmatvec(
449 &bytes[..cpu_rows * *cols],
450 &row_scale[..cpu_rows],
451 &xs,
452 cpu_rows,
453 *cols,
454 out_cpu,
455 pool,
456 );
457 }
458 g.join().unwrap_or(false)
459 });
460 if ok {
461 crate::gpu::probe_record(crate::gpu::OpClass::Matvec, true, t0.elapsed());
462 return;
463 }
464 qmatvec(
466 &bytes[cpu_rows * *cols..(*rows) * *cols],
467 &row_scale[cpu_rows..],
468 &xs,
469 *rows - cpu_rows,
470 *cols,
471 out_gpu,
472 pool,
473 );
474 return;
475 }
476 qmatvec(self.quant_bytes(), row_scale, &xs, *rows, *cols, out, pool);
477 }
478 }
479 }
480
481 pub fn matvec2(&self, x1: &[f32], x2: &[f32], o1: &mut [f32], o2: &mut [f32], pool: Option<&Pool>) {
483 match self {
484 Self::F32 { data, .. } => matvec_rows2(pool, data, x1, x2, o1, o2),
485 Self::Mapped {
486 dtype,
487 rows,
488 cols,
489 row_scale,
490 col_field,
491 vbit_offsets,
492 ..
493 } => {
494 if *dtype == TensorDtype::Q4Block {
495 q4matvec2(self.quant_bytes(), x1, x2, *rows, *cols, o1, o2, pool);
496 return;
497 }
498 if *dtype == TensorDtype::Q4Tiled {
499 q4t_matvec2(self.quant_bytes(), x1, x2, *rows, *cols, o1, o2, pool);
500 return;
501 }
502 if matches!(dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
503 vbitmatvec2(self.quant_bytes(), vbit_offsets, x1, x2, *rows, *cols, o1, o2, pool);
504 return;
505 }
506 let x1s = prescale(x1, col_field, *dtype);
507 let x2s = prescale(x2, col_field, *dtype);
508 qmatvec2(self.quant_bytes(), row_scale, &x1s, &x2s, *rows, *cols, o1, o2, pool);
509 }
510 }
511 }
512}
513
514impl QTensor {
515 pub fn matmat(&self, xs_all: &[f32], b: usize, out: &mut [f32], pool: Option<&Pool>) {
520 let cols = self.cols();
521 let rows = self.rows();
522 debug_assert_eq!(xs_all.len(), b * cols);
523 debug_assert_eq!(out.len(), b * rows);
524 match self {
525 Self::F32 { data, .. } => {
526 let out_addr = SendMut(out.as_mut_ptr());
527 let run = |start: usize, end: usize| {
528 for o in start..end {
529 let row = &data[o * cols..(o + 1) * cols];
530 for bi in 0..b {
531 let x = &xs_all[bi * cols..(bi + 1) * cols];
532 let mut acc = 0f32;
533 for j in 0..cols {
534 acc += row[j] * x[j];
535 }
536 unsafe { *out_addr.at(bi * rows + o) = acc };
537 }
538 }
539 };
540 dispatch_rows(pool, rows, &run);
541 }
542 Self::Mapped {
543 dtype,
544 row_scale,
545 col_field,
546 vbit_offsets,
547 ..
548 } => {
549 if *dtype == TensorDtype::Q4Block {
550 q4matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
551 return;
552 }
553 if *dtype == TensorDtype::Q4Tiled {
554 q4t_matmat(self.quant_bytes(), xs_all, b, rows, cols, out, pool);
555 return;
556 }
557 if matches!(dtype, TensorDtype::Vbit | TensorDtype::VbitRo) {
558 vbitmatmat(self.quant_bytes(), vbit_offsets, xs_all, b, rows, cols, out, pool);
559 return;
560 }
561 let pre: Vec<std::borrow::Cow<'_, [f32]>> = (0..b)
562 .map(|bi| {
563 prescale(&xs_all[bi * cols..(bi + 1) * cols], col_field, *dtype)
564 })
565 .collect();
566 if b >= 8
572 && b * rows * cols >= 128_000_000
573 && crate::gpu::enabled_here()
574 {
575 if let Self::Mapped { model, idx, .. } = self {
576 let t0 = std::time::Instant::now();
577 match crate::gpu::probe_arm(crate::gpu::OpClass::Matmat) {
578 crate::gpu::ProbeArm::Gpu
579 if crate::gpu::probe_deciding(crate::gpu::OpClass::Matmat)
580 && !crate::gpu::q8_resident_or_upload(model, *idx) =>
581 {
582 let q = self.quant_bytes();
586 qmatmat(q, row_scale, &pre, rows, cols, out, pool);
587 return;
588 }
589 crate::gpu::ProbeArm::Gpu => {
590 let flat: Vec<f32> =
591 pre.iter().flat_map(|v| v.iter().copied()).collect();
592 if crate::gpu::q8_matmat(
593 model, *idx, row_scale, &flat, b, rows, cols, out)
594 {
595 crate::gpu::probe_record(
596 crate::gpu::OpClass::Matmat,
597 true,
598 t0.elapsed(),
599 );
600 return;
601 }
602 }
603 crate::gpu::ProbeArm::CpuTimed => {
604 let q = self.quant_bytes();
605 qmatmat(q, row_scale, &pre, rows, cols, out, pool);
606 crate::gpu::probe_record(
607 crate::gpu::OpClass::Matmat,
608 false,
609 t0.elapsed(),
610 );
611 return;
612 }
613 crate::gpu::ProbeArm::Cpu => {}
614 }
615 }
616 }
617 let q = self.quant_bytes();
618 qmatmat(q, row_scale, &pre, rows, cols, out, pool);
619 }
620 }
621 }
622}
623
624impl QTensor {
625 pub fn matvec_many<const N: usize>(
632 ts: [&QTensor; N],
633 x: &[f32],
634 mut outs: [&mut [f32]; N],
635 pool: Option<&Pool>,
636 ) {
637 let total_rows: usize = ts.iter().map(|t| t.rows()).sum();
638 let uniform_q8 = ts.iter().all(|t| {
639 matches!(
640 t,
641 Self::Mapped { dtype: TensorDtype::Q8Row | TensorDtype::Q8_2f, .. }
642 )
643 });
644 let uniform_f32 = ts.iter().all(|t| matches!(t, Self::F32 { .. }));
645 let uniform_q4 = ts
646 .iter()
647 .all(|t| matches!(t, Self::Mapped { dtype: TensorDtype::Q4Block, .. }));
648 let uniform_vbit = ts
649 .iter()
650 .all(|t| matches!(
651 t,
652 Self::Mapped { dtype: TensorDtype::Vbit | TensorDtype::VbitRo, .. }
653 ));
654 let Some(pool) = pool else {
655 for (t, o) in ts.iter().zip(outs.iter_mut()) {
656 t.matvec(x, o, None);
657 }
658 return;
659 };
660 if total_rows < 256 || !(uniform_q8 || uniform_f32 || uniform_q4 || uniform_vbit) {
661 for (t, o) in ts.iter().zip(outs.iter_mut()) {
662 t.matvec(x, o, Some(pool));
663 }
664 return;
665 }
666
667 if uniform_q4 || uniform_vbit {
668 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
669 if a8w8_enabled() {
671 let act = split_act(x);
672 let act = &act;
673 if uniform_q4 {
674 let closures: [_; N] = std::array::from_fn(|i| {
675 let (packed, scales) =
676 q4_split(ts[i].quant_bytes(), ts[i].rows(), ts[i].cols());
677 let (gpr, cols, out) =
678 (ts[i].cols() / GROUP_SIZE, ts[i].cols(), outs_addr[i]);
679 move |s: usize, e: usize| {
680 q4_range_a8w8(packed, scales, gpr, cols, act, out, s, e)
681 }
682 });
683 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
684 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
685 pool.run_many(&parts);
686 } else {
687 let closures: [_; N] = std::array::from_fn(|i| {
688 let Self::Mapped { vbit_offsets, .. } = ts[i] else { unreachable!() };
689 let (bytes, rows, cols, out) =
690 (ts[i].quant_bytes(), ts[i].rows(), ts[i].cols(), outs_addr[i]);
691 move |s: usize, e: usize| {
692 vbit_range_a8w8(bytes, vbit_offsets, x, act, rows, cols, out, s, e)
693 }
694 });
695 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
696 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
697 pool.run_many(&parts);
698 }
699 return;
700 }
701 if uniform_q4 {
702 let closures: [_; N] = std::array::from_fn(|i| {
703 let (packed, scales) =
704 q4_split(ts[i].quant_bytes(), ts[i].rows(), ts[i].cols());
705 let (gpr, out) = (ts[i].cols() / GROUP_SIZE, outs_addr[i]);
706 move |s: usize, e: usize| q4_range_f32(packed, scales, gpr, x, out, s, e)
707 });
708 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
709 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
710 pool.run_many(&parts);
711 } else {
712 let closures: [_; N] = std::array::from_fn(|i| {
713 let Self::Mapped { vbit_offsets, .. } = ts[i] else { unreachable!() };
714 let (bytes, rows, cols, out) =
715 (ts[i].quant_bytes(), ts[i].rows(), ts[i].cols(), outs_addr[i]);
716 move |s: usize, e: usize| {
717 vbit_range_f32(bytes, vbit_offsets, x, rows, cols, out, s, e)
718 }
719 });
720 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
721 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
722 pool.run_many(&parts);
723 }
724 return;
725 }
726
727 if uniform_f32 {
728 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
729 let closures: [_; N] = std::array::from_fn(|i| {
730 let Self::F32 { data, cols, .. } = ts[i] else { unreachable!() };
731 let out = outs_addr[i];
732 move |start: usize, end: usize| {
733 for o in start..end {
734 let row = &data[o * cols..(o + 1) * cols];
735 let mut sum = 0.0f32;
736 for j in 0..*cols {
737 sum += row[j] * x[j];
738 }
739 unsafe { *out.at(o) = sum };
741 }
742 }
743 });
744 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
745 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
746 pool.run_many(&parts);
747 return;
748 }
749
750 struct Ctx<'a> {
753 bytes: &'a [u8],
754 row_scale: &'a [f32],
755 cols: usize,
756 xs: std::borrow::Cow<'a, [f32]>,
757 }
758 let ctxs: [Ctx<'_>; N] = std::array::from_fn(|i| {
759 let Self::Mapped { dtype, cols, row_scale, col_field, .. } = ts[i] else {
760 unreachable!()
761 };
762 Ctx {
763 bytes: ts[i].quant_bytes(),
764 row_scale,
765 cols: *cols,
766 xs: prescale(x, col_field, *dtype),
767 }
768 });
769 let outs_addr: [SendMut; N] = std::array::from_fn(|i| SendMut(outs[i].as_mut_ptr()));
770 #[cfg(target_arch = "aarch64")]
771 if sdot_enabled() {
772 let acts: [SplitAct; N] = std::array::from_fn(|i| split_act(&ctxs[i].xs));
773 let closures: [_; N] = std::array::from_fn(|i| {
774 let (c, act, out) = (&ctxs[i], &acts[i], outs_addr[i]);
775 move |start: usize, end: usize| {
776 q8_range_sdot(c.bytes, c.row_scale, act, c.cols, out, start, end)
777 }
778 });
779 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
780 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
781 pool.run_many(&parts);
782 return;
783 }
784 #[cfg(target_arch = "x86_64")]
785 if avx2_a8w8_enabled() {
786 let acts: [SplitAct; N] = std::array::from_fn(|i| split_act(&ctxs[i].xs));
787 let closures: [_; N] = std::array::from_fn(|i| {
788 let (c, act, out) = (&ctxs[i], &acts[i], outs_addr[i]);
789 move |start: usize, end: usize| {
790 q8_range_avx2(c.bytes, c.row_scale, act, c.cols, out, start, end)
791 }
792 });
793 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
794 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
795 pool.run_many(&parts);
796 return;
797 }
798 let closures: [_; N] = std::array::from_fn(|i| {
799 let (c, out) = (&ctxs[i], outs_addr[i]);
800 move |start: usize, end: usize| {
801 q8_range_f32(c.bytes, c.row_scale, &c.xs, c.cols, out, start, end)
802 }
803 });
804 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
805 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
806 pool.run_many(&parts);
807 }
808}
809
810impl QTensor {
811 #[allow(clippy::needless_range_loop)]
816 pub fn matvec2_many<const N: usize>(
817 ts: [&QTensor; N],
818 x1: &[f32],
819 x2: &[f32],
820 mut o1s: [&mut [f32]; N],
821 mut o2s: [&mut [f32]; N],
822 pool: Option<&Pool>,
823 ) {
824 let total_rows: usize = ts.iter().map(|t| t.rows()).sum();
825 let uniform_q8 = ts.iter().all(|t| {
826 matches!(
827 t,
828 Self::Mapped { dtype: TensorDtype::Q8Row | TensorDtype::Q8_2f, .. }
829 )
830 });
831 let uniform_f32 = ts.iter().all(|t| matches!(t, Self::F32 { .. }));
832 let uniform_q4 = ts
833 .iter()
834 .all(|t| matches!(t, Self::Mapped { dtype: TensorDtype::Q4Block, .. }));
835 let uniform_vbit = ts
836 .iter()
837 .all(|t| matches!(
838 t,
839 Self::Mapped { dtype: TensorDtype::Vbit | TensorDtype::VbitRo, .. }
840 ));
841 let fusable = pool.is_some()
842 && total_rows >= 256
843 && (uniform_q8 || uniform_f32 || uniform_q4 || uniform_vbit);
844 if !fusable {
845 for i in 0..N {
846 ts[i].matvec2(x1, x2, o1s[i], o2s[i], pool);
847 }
848 return;
849 }
850 let pool = pool.unwrap();
851
852 if uniform_q4 || uniform_vbit {
853 let p1: [SendMut; N] = std::array::from_fn(|i| SendMut(o1s[i].as_mut_ptr()));
854 let p2: [SendMut; N] = std::array::from_fn(|i| SendMut(o2s[i].as_mut_ptr()));
855 if a8w8_enabled() {
857 let a1 = split_act(x1);
858 let a2 = split_act(x2);
859 let (a1, a2) = (&a1, &a2);
860 if uniform_q4 {
861 let closures: [_; N] = std::array::from_fn(|i| {
862 let (packed, scales) =
863 q4_split(ts[i].quant_bytes(), ts[i].rows(), ts[i].cols());
864 let (gpr, cols, o1, o2) =
865 (ts[i].cols() / GROUP_SIZE, ts[i].cols(), p1[i], p2[i]);
866 move |s: usize, e: usize| {
867 q4_range2_a8w8(packed, scales, gpr, cols, a1, a2, o1, o2, s, e)
868 }
869 });
870 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
871 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
872 pool.run_many(&parts);
873 } else {
874 let closures: [_; N] = std::array::from_fn(|i| {
875 let Self::Mapped { vbit_offsets, .. } = ts[i] else { unreachable!() };
876 let (bytes, rows, cols, o1, o2) =
877 (ts[i].quant_bytes(), ts[i].rows(), ts[i].cols(), p1[i], p2[i]);
878 move |s: usize, e: usize| {
879 vbit_range2_a8w8(
880 bytes, vbit_offsets, x1, x2, a1, a2, rows, cols, o1, o2, s, e,
881 )
882 }
883 });
884 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
885 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
886 pool.run_many(&parts);
887 }
888 return;
889 }
890 if uniform_q4 {
891 let closures: [_; N] = std::array::from_fn(|i| {
892 let (packed, scales) =
893 q4_split(ts[i].quant_bytes(), ts[i].rows(), ts[i].cols());
894 let (gpr, o1, o2) = (ts[i].cols() / GROUP_SIZE, p1[i], p2[i]);
895 move |s: usize, e: usize| {
896 q4_range2_f32(packed, scales, gpr, x1, x2, o1, o2, s, e)
897 }
898 });
899 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
900 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
901 pool.run_many(&parts);
902 } else {
903 let closures: [_; N] = std::array::from_fn(|i| {
904 let Self::Mapped { vbit_offsets, .. } = ts[i] else { unreachable!() };
905 let (bytes, rows, cols, o1, o2) =
906 (ts[i].quant_bytes(), ts[i].rows(), ts[i].cols(), p1[i], p2[i]);
907 move |s: usize, e: usize| {
908 vbit_range2_f32(bytes, vbit_offsets, x1, x2, rows, cols, o1, o2, s, e)
909 }
910 });
911 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
912 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
913 pool.run_many(&parts);
914 }
915 return;
916 }
917
918 if uniform_f32 {
919 let p1: [SendMut; N] = std::array::from_fn(|i| SendMut(o1s[i].as_mut_ptr()));
920 let p2: [SendMut; N] = std::array::from_fn(|i| SendMut(o2s[i].as_mut_ptr()));
921 let closures: [_; N] = std::array::from_fn(|i| {
922 let Self::F32 { data, cols, .. } = ts[i] else { unreachable!() };
923 let (o1, o2) = (p1[i], p2[i]);
924 move |start: usize, end: usize| {
925 for o in start..end {
926 let row = &data[o * cols..(o + 1) * cols];
927 let (mut s1, mut s2) = (0.0f32, 0.0f32);
928 for j in 0..*cols {
929 s1 += row[j] * x1[j];
930 s2 += row[j] * x2[j];
931 }
932 unsafe {
934 *o1.at(o) = s1;
935 *o2.at(o) = s2;
936 }
937 }
938 }
939 });
940 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
941 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
942 pool.run_many(&parts);
943 return;
944 }
945
946 struct Ctx<'a> {
947 bytes: &'a [u8],
948 row_scale: &'a [f32],
949 cols: usize,
950 xs1: std::borrow::Cow<'a, [f32]>,
951 xs2: std::borrow::Cow<'a, [f32]>,
952 }
953 let ctxs: [Ctx<'_>; N] = std::array::from_fn(|i| {
954 let Self::Mapped { dtype, cols, row_scale, col_field, .. } = ts[i] else {
955 unreachable!()
956 };
957 Ctx {
958 bytes: ts[i].quant_bytes(),
959 row_scale,
960 cols: *cols,
961 xs1: prescale(x1, col_field, *dtype),
962 xs2: prescale(x2, col_field, *dtype),
963 }
964 });
965 let p1: [SendMut; N] = std::array::from_fn(|i| SendMut(o1s[i].as_mut_ptr()));
966 let p2: [SendMut; N] = std::array::from_fn(|i| SendMut(o2s[i].as_mut_ptr()));
967 #[cfg(target_arch = "aarch64")]
968 if sdot_enabled() {
969 let acts: [(SplitAct, SplitAct); N] =
970 std::array::from_fn(|i| (split_act(&ctxs[i].xs1), split_act(&ctxs[i].xs2)));
971 let closures: [_; N] = std::array::from_fn(|i| {
972 let (c, a, o1, o2) = (&ctxs[i], &acts[i], p1[i], p2[i]);
973 move |start: usize, end: usize| {
974 q8_range2_sdot(c.bytes, c.row_scale, &a.0, &a.1, c.cols, o1, o2, start, end)
975 }
976 });
977 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
978 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
979 pool.run_many(&parts);
980 return;
981 }
982 #[cfg(target_arch = "x86_64")]
983 if avx2_a8w8_enabled() {
984 let acts: [(SplitAct, SplitAct); N] =
985 std::array::from_fn(|i| (split_act(&ctxs[i].xs1), split_act(&ctxs[i].xs2)));
986 let closures: [_; N] = std::array::from_fn(|i| {
987 let (c, a, o1, o2) = (&ctxs[i], &acts[i], p1[i], p2[i]);
988 move |start: usize, end: usize| {
989 q8_range2_avx2(c.bytes, c.row_scale, &a.0, &a.1, c.cols, o1, o2, start, end)
990 }
991 });
992 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
993 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
994 pool.run_many(&parts);
995 return;
996 }
997 let closures: [_; N] = std::array::from_fn(|i| {
998 let (c, o1, o2) = (&ctxs[i], p1[i], p2[i]);
999 move |start: usize, end: usize| {
1000 q8_range2_f32(c.bytes, c.row_scale, &c.xs1, &c.xs2, c.cols, o1, o2, start, end)
1001 }
1002 });
1003 let parts: [(usize, &(dyn Fn(usize, usize) + Sync)); N] =
1004 std::array::from_fn(|i| (ts[i].rows(), &closures[i] as _));
1005 pool.run_many(&parts);
1006 }
1007}
1008
1009fn qmatmat(
1012 q: &[u8],
1013 row_scale: &[f32],
1014 pre: &[std::borrow::Cow<'_, [f32]>],
1015 rows: usize,
1016 cols: usize,
1017 out: &mut [f32],
1018 pool: Option<&Pool>,
1019) {
1020 let b = pre.len();
1021 debug_assert_eq!(out.len(), b * rows);
1022 #[cfg(target_arch = "aarch64")]
1023 if sdot_enabled() {
1024 let acts: Vec<SplitAct> = pre.iter().map(|x| split_act(x)).collect();
1025 let out_addr = SendMut(out.as_mut_ptr());
1026 let run = |start: usize, end: usize| {
1027 for o in start..end {
1028 let row = &q[o * cols..(o + 1) * cols];
1029 for (bi, act) in acts.iter().enumerate() {
1030 let v = row_dot_sdot(row, act) * row_scale[o];
1031 unsafe { *out_addr.at(bi * rows + o) = v };
1032 }
1033 }
1034 };
1035 dispatch_rows(pool, rows, &run);
1036 return;
1037 }
1038 #[cfg(target_arch = "x86_64")]
1042 if avx2_a8w8_enabled() {
1043 let acts: Vec<SplitAct> = pre.iter().map(|x| split_act(x)).collect();
1044 let out_addr = SendMut(out.as_mut_ptr());
1045 let run = |start: usize, end: usize| {
1046 for o in start..end {
1047 let row = &q[o * cols..(o + 1) * cols];
1048 for (bi, act) in acts.iter().enumerate() {
1049 let v = row_dot_avx2(row, act) * row_scale[o];
1050 unsafe { *out_addr.at(bi * rows + o) = v };
1051 }
1052 }
1053 };
1054 dispatch_rows(pool, rows, &run);
1055 return;
1056 }
1057 let out_addr = SendMut(out.as_mut_ptr());
1058 let run = |start: usize, end: usize| {
1059 for o in start..end {
1060 let row = &q[o * cols..(o + 1) * cols];
1061 for (bi, x) in pre.iter().enumerate() {
1062 let mut acc = 0f32;
1063 for j in 0..cols {
1064 acc += (row[j] as i8) as f32 * x[j];
1065 }
1066 unsafe { *out_addr.at(bi * rows + o) = acc * row_scale[o] };
1067 }
1068 }
1069 };
1070 dispatch_rows(pool, rows, &run);
1071}
1072
1073fn dispatch_rows(pool: Option<&Pool>, rows: usize, run: &(dyn Fn(usize, usize) + Sync)) {
1076 match pool {
1077 Some(pool) if rows >= 256 => pool.run_rows(rows, run),
1078 _ => run(0, rows),
1079 }
1080}
1081
1082fn q4_split(bytes: &[u8], rows: usize, cols: usize) -> (&[u8], &[u8]) {
1084 let groups = rows * cols / GROUP_SIZE;
1085 bytes.split_at(groups * 16)
1086}
1087
1088#[inline]
1093fn vbit_fill4(data: &[u8], buf: &mut [u8]) {
1094 #[cfg(target_arch = "aarch64")]
1095 unsafe {
1096 return vbit_fill4_neon(data, buf);
1097 }
1098 #[cfg(target_arch = "x86_64")]
1099 if avx2_enabled() {
1100 return unsafe { vbit_fill4_avx2(data, buf) };
1101 }
1102 #[allow(unreachable_code)]
1103 for (blk, chunk) in buf.chunks_exact_mut(8).enumerate() {
1104 let u = unpack8::<4>(&data[blk * 4..]);
1105 for k in 0..8 {
1106 chunk[k] = (u[k] - 7) as i8 as u8;
1107 }
1108 }
1109}
1110
1111#[cfg(target_arch = "aarch64")]
1112#[target_feature(enable = "neon")]
1113unsafe fn vbit_fill4_neon(data: &[u8], buf: &mut [u8]) {
1114 unsafe {
1117 use core::arch::aarch64::*;
1118 let n = buf.len();
1119 let mask = vdupq_n_u8(0x0F);
1120 let seven = vdupq_n_s8(7);
1121 let mut g = 0usize;
1122 while g * 32 + 32 <= n {
1123 let b = vld1q_u8(data.as_ptr().add(g * 16));
1124 let hi = vshrq_n_u8::<4>(b);
1125 let lo = vandq_u8(b, mask);
1126 let z0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(hi, lo)), seven);
1127 let z1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(hi, lo)), seven);
1128 vst1q_u8(buf.as_mut_ptr().add(g * 32), vreinterpretq_u8_s8(z0));
1129 vst1q_u8(buf.as_mut_ptr().add(g * 32 + 16), vreinterpretq_u8_s8(z1));
1130 g += 1;
1131 }
1132 }
1133}
1134
1135#[cfg(target_arch = "x86_64")]
1136#[target_feature(enable = "avx2")]
1137unsafe fn vbit_fill4_avx2(data: &[u8], buf: &mut [u8]) {
1138 unsafe {
1140 use core::arch::x86_64::*;
1141 let n = buf.len();
1142 let mask = _mm_set1_epi8(0x0F);
1143 let seven = _mm256_set1_epi8(7);
1144 let mut g = 0usize;
1145 while g * 32 + 32 <= n {
1146 let b = _mm_loadu_si128(data.as_ptr().add(g * 16) as *const __m128i);
1147 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), mask);
1148 let lo = _mm_and_si128(b, mask);
1149 let z = _mm256_sub_epi8(
1150 _mm256_set_m128i(_mm_unpackhi_epi8(hi, lo), _mm_unpacklo_epi8(hi, lo)),
1151 seven,
1152 );
1153 _mm256_storeu_si256(buf.as_mut_ptr().add(g * 32) as *mut __m256i, z);
1154 g += 1;
1155 }
1156 }
1157}
1158
1159#[inline(always)]
1164fn unpack8<const B: usize>(data: &[u8]) -> [i32; 8] {
1165 let mut acc = 0u64;
1166 for i in 0..B {
1167 acc = (acc << 8) | data[i] as u64;
1168 }
1169 let mask = (1u64 << B) - 1;
1170 let mut out = [0i32; 8];
1171 for (k, o) in out.iter_mut().enumerate() {
1172 *o = ((acc >> ((7 - k) * B)) & mask) as i32;
1173 }
1174 out
1175}
1176
1177#[allow(clippy::too_many_arguments)]
1183fn vbitmatvec(
1184 bytes: &[u8],
1185 offsets: &[usize],
1186 x: &[f32],
1187 rows: usize,
1188 cols: usize,
1189 out: &mut [f32],
1190 pool: Option<&Pool>,
1191) {
1192 debug_assert_eq!(out.len(), rows);
1193 debug_assert_eq!(offsets.len(), rows + 1);
1194
1195 if a8w8_enabled() {
1199 let act = split_act(x);
1200 let out_addr = SendMut(out.as_mut_ptr());
1201 let run = move |start: usize, end: usize| {
1202 vbit_range_a8w8(bytes, offsets, x, &act, rows, cols, out_addr, start, end)
1203 };
1204 dispatch_rows(pool, rows, &run);
1205 return;
1206 }
1207
1208 let out_addr = SendMut(out.as_mut_ptr());
1209 let run = move |start: usize, end: usize| {
1210 vbit_range_f32(bytes, offsets, x, rows, cols, out_addr, start, end)
1211 };
1212 dispatch_rows(pool, rows, &run);
1213}
1214
1215#[allow(clippy::too_many_arguments)]
1219fn vbit_range_a8w8(
1220 bytes: &[u8],
1221 offsets: &[usize],
1222 x: &[f32],
1223 act: &SplitAct,
1224 rows: usize,
1225 cols: usize,
1226 out: SendMut,
1227 start: usize,
1228 end: usize,
1229) {
1230 let ng = cols / GROUP_SIZE;
1231 let bits = &bytes[..rows];
1232 let sc_off = rows;
1233 let row_dot = |r: usize| -> f32 {
1234 let b = bits[r] as usize;
1235 let l = ((1i32 << (b - 1)) - 1) as i32;
1236 let mask = (1u64 << b) - 1;
1237 let data = &bytes[offsets[r]..offsets[r + 1]];
1238 if b == 8 {
1239 let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
1241 let mut dot = 0f32;
1242 for g in 0..ng {
1243 let so = (r * ng + g) * 2;
1244 let sgf = f16_to_f32(u16::from_le_bytes([
1245 bytes[sc_off + so],
1246 bytes[sc_off + so + 1],
1247 ]));
1248 let xg = &x[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
1249 let mut gd = 0f32;
1250 for &xv in xg.iter() {
1251 if nbits < 8 {
1252 acc = (acc << 8) | data[idx] as u64;
1253 idx += 1;
1254 nbits += 8;
1255 }
1256 let u = ((acc >> (nbits - 8)) & 0xFF) as i32;
1257 nbits -= 8;
1258 gd += (u - l) as f32 * xv;
1259 }
1260 dot += gd * sgf;
1261 }
1262 return dot;
1263 }
1264 thread_local! {
1268 static VBIT_SCRATCH: std::cell::RefCell<Vec<u8>> =
1269 const { std::cell::RefCell::new(Vec::new()) };
1270 }
1271 #[inline(always)]
1272 fn fill<const B: usize>(data: &[u8], l: i32, buf: &mut [u8]) {
1273 for (blk, chunk) in buf.chunks_exact_mut(8).enumerate() {
1274 let u = unpack8::<B>(&data[blk * B..]);
1275 for k in 0..8 {
1276 chunk[k] = (u[k] - l) as i8 as u8;
1277 }
1278 }
1279 }
1280 let _ = mask;
1281 VBIT_SCRATCH.with(|scratch| {
1282 let mut buf = scratch.borrow_mut();
1283 buf.resize(cols, 0);
1284 match b {
1285 3 => fill::<3>(data, l, &mut buf),
1286 4 => vbit_fill4(data, &mut buf),
1287 5 => fill::<5>(data, l, &mut buf),
1288 6 => fill::<6>(data, l, &mut buf),
1289 _ => unreachable!(),
1290 }
1291 let mut dot = 0f32;
1292 for g in 0..ng {
1293 let so = (r * ng + g) * 2;
1294 let s = f16_to_f32(u16::from_le_bytes([
1295 bytes[sc_off + so],
1296 bytes[sc_off + so + 1],
1297 ]));
1298 let d = dot_i8_i8(
1299 &buf[g * GROUP_SIZE..(g + 1) * GROUP_SIZE],
1300 &act.xq[g * GROUP_SIZE..(g + 1) * GROUP_SIZE],
1301 ) as f32
1302 * act.sx;
1303 dot += d * s;
1304 }
1305 for &(j, xv) in &act.outliers {
1306 let so = (r * ng + j / GROUP_SIZE) * 2;
1307 let s = f16_to_f32(u16::from_le_bytes([
1308 bytes[sc_off + so],
1309 bytes[sc_off + so + 1],
1310 ]));
1311 dot += (buf[j] as i8) as f32 * s * xv;
1313 }
1314 dot
1315 })
1316 };
1317 for r in start..end {
1318 unsafe { *out.at(r) = row_dot(r) };
1320 }
1321}
1322
1323#[allow(clippy::too_many_arguments)]
1325fn vbit_range_f32(
1326 bytes: &[u8],
1327 offsets: &[usize],
1328 x: &[f32],
1329 rows: usize,
1330 cols: usize,
1331 out: SendMut,
1332 start: usize,
1333 end: usize,
1334) {
1335 let ng = cols / GROUP_SIZE;
1336 let bits = &bytes[..rows];
1337 let sc_off = rows;
1338 #[inline(always)]
1342 fn dot_row<const B: usize>(
1343 data: &[u8],
1344 bytes: &[u8],
1345 sc_off: usize,
1346 r: usize,
1347 ng: usize,
1348 x: &[f32],
1349 ) -> f32 {
1350 let l = ((1i32 << (B - 1)) - 1) as f32;
1351 let gbytes = GROUP_SIZE * B / 8;
1352 let mut dot = 0f32;
1353 for g in 0..ng {
1354 let so = (r * ng + g) * 2;
1355 let s = f16_to_f32(u16::from_le_bytes([bytes[sc_off + so], bytes[sc_off + so + 1]]));
1356 let xg = &x[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
1357 let gd0 = &data[g * gbytes..(g + 1) * gbytes];
1358 let mut gd = 0f32;
1359 for blk in 0..GROUP_SIZE / 8 {
1360 let u = unpack8::<B>(&gd0[blk * B..]);
1361 let xb = &xg[blk * 8..blk * 8 + 8];
1362 for k in 0..8 {
1363 gd += (u[k] as f32 - l) * xb[k];
1364 }
1365 }
1366 dot += gd * s;
1367 }
1368 dot
1369 }
1370 for r in start..end {
1371 let data = &bytes[offsets[r]..offsets[r + 1]];
1372 let v = match bits[r] {
1373 3 => dot_row::<3>(data, bytes, sc_off, r, ng, x),
1374 4 => dot_row::<4>(data, bytes, sc_off, r, ng, x),
1375 5 => dot_row::<5>(data, bytes, sc_off, r, ng, x),
1376 6 => dot_row::<6>(data, bytes, sc_off, r, ng, x),
1377 8 => dot_row::<8>(data, bytes, sc_off, r, ng, x),
1378 b => unreachable!("vbit bit-width {b} (validated at load)"),
1379 };
1380 unsafe { *out.at(r) = v };
1382 }
1383}
1384
1385#[allow(clippy::too_many_arguments)]
1390fn vbitmatvec2(
1391 bytes: &[u8],
1392 offsets: &[usize],
1393 x1: &[f32],
1394 x2: &[f32],
1395 rows: usize,
1396 cols: usize,
1397 o1: &mut [f32],
1398 o2: &mut [f32],
1399 pool: Option<&Pool>,
1400) {
1401 debug_assert_eq!(o1.len(), rows);
1402 debug_assert_eq!(o2.len(), rows);
1403
1404 if a8w8_enabled() {
1405 let a1 = split_act(x1);
1406 let a2 = split_act(x2);
1407 let p1 = SendMut(o1.as_mut_ptr());
1408 let p2 = SendMut(o2.as_mut_ptr());
1409 let run = move |start: usize, end: usize| {
1410 vbit_range2_a8w8(bytes, offsets, x1, x2, &a1, &a2, rows, cols, p1, p2, start, end)
1411 };
1412 dispatch_rows(pool, rows, &run);
1413 return;
1414 }
1415
1416 let p1 = SendMut(o1.as_mut_ptr());
1417 let p2 = SendMut(o2.as_mut_ptr());
1418 let run = move |start: usize, end: usize| {
1419 vbit_range2_f32(bytes, offsets, x1, x2, rows, cols, p1, p2, start, end)
1420 };
1421 dispatch_rows(pool, rows, &run);
1422}
1423
1424#[allow(clippy::too_many_arguments)]
1428fn vbit_range2_a8w8(
1429 bytes: &[u8],
1430 offsets: &[usize],
1431 x1: &[f32],
1432 x2: &[f32],
1433 a1: &SplitAct,
1434 a2: &SplitAct,
1435 rows: usize,
1436 cols: usize,
1437 p1: SendMut,
1438 p2: SendMut,
1439 start: usize,
1440 end: usize,
1441) {
1442 let ng = cols / GROUP_SIZE;
1443 let bits = &bytes[..rows];
1444 let sc_off = rows;
1445 let row_dots = |r: usize| -> (f32, f32) {
1446 let b = bits[r] as usize;
1447 let l = (1i32 << (b - 1)) - 1;
1448 let data = &bytes[offsets[r]..offsets[r + 1]];
1449 if b == 8 {
1450 let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
1453 let (mut d1, mut d2) = (0f32, 0f32);
1454 for g in 0..ng {
1455 let so = (r * ng + g) * 2;
1456 let sgf = f16_to_f32(u16::from_le_bytes([
1457 bytes[sc_off + so],
1458 bytes[sc_off + so + 1],
1459 ]));
1460 let (mut g1, mut g2) = (0f32, 0f32);
1461 for k in 0..GROUP_SIZE {
1462 if nbits < 8 {
1463 acc = (acc << 8) | data[idx] as u64;
1464 idx += 1;
1465 nbits += 8;
1466 }
1467 let u = ((acc >> (nbits - 8)) & 0xFF) as i32;
1468 nbits -= 8;
1469 let w = (u - l) as f32;
1470 g1 += w * x1[g * GROUP_SIZE + k];
1471 g2 += w * x2[g * GROUP_SIZE + k];
1472 }
1473 d1 += g1 * sgf;
1474 d2 += g2 * sgf;
1475 }
1476 return (d1, d2);
1477 }
1478 thread_local! {
1479 static VBIT_SCRATCH2: std::cell::RefCell<Vec<u8>> =
1480 const { std::cell::RefCell::new(Vec::new()) };
1481 }
1482 #[inline(always)]
1483 fn fill<const B: usize>(data: &[u8], l: i32, buf: &mut [u8]) {
1484 for (blk, chunk) in buf.chunks_exact_mut(8).enumerate() {
1485 let u = unpack8::<B>(&data[blk * B..]);
1486 for k in 0..8 {
1487 chunk[k] = (u[k] - l) as i8 as u8;
1488 }
1489 }
1490 }
1491 VBIT_SCRATCH2.with(|scratch| {
1492 let mut buf = scratch.borrow_mut();
1493 buf.resize(cols, 0);
1494 match b {
1495 3 => fill::<3>(data, l, &mut buf),
1496 4 => vbit_fill4(data, &mut buf),
1497 5 => fill::<5>(data, l, &mut buf),
1498 6 => fill::<6>(data, l, &mut buf),
1499 _ => unreachable!(),
1500 }
1501 let (mut d1, mut d2) = (0f32, 0f32);
1502 for g in 0..ng {
1503 let so = (r * ng + g) * 2;
1504 let s = f16_to_f32(u16::from_le_bytes([
1505 bytes[sc_off + so],
1506 bytes[sc_off + so + 1],
1507 ]));
1508 let wg = &buf[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
1509 let v1 =
1510 dot_i8_i8(wg, &a1.xq[g * GROUP_SIZE..(g + 1) * GROUP_SIZE]) as f32 * a1.sx;
1511 let v2 =
1512 dot_i8_i8(wg, &a2.xq[g * GROUP_SIZE..(g + 1) * GROUP_SIZE]) as f32 * a2.sx;
1513 d1 += v1 * s;
1514 d2 += v2 * s;
1515 }
1516 for &(j, xv) in &a1.outliers {
1517 let so = (r * ng + j / GROUP_SIZE) * 2;
1518 let s = f16_to_f32(u16::from_le_bytes([
1519 bytes[sc_off + so],
1520 bytes[sc_off + so + 1],
1521 ]));
1522 d1 += (buf[j] as i8) as f32 * s * xv;
1523 }
1524 for &(j, xv) in &a2.outliers {
1525 let so = (r * ng + j / GROUP_SIZE) * 2;
1526 let s = f16_to_f32(u16::from_le_bytes([
1527 bytes[sc_off + so],
1528 bytes[sc_off + so + 1],
1529 ]));
1530 d2 += (buf[j] as i8) as f32 * s * xv;
1531 }
1532 (d1, d2)
1533 })
1534 };
1535 for r in start..end {
1536 let (v1, v2) = row_dots(r);
1537 unsafe {
1539 *p1.at(r) = v1;
1540 *p2.at(r) = v2;
1541 }
1542 }
1543}
1544
1545#[allow(clippy::too_many_arguments)]
1549fn vbit_range2_f32(
1550 bytes: &[u8],
1551 offsets: &[usize],
1552 x1: &[f32],
1553 x2: &[f32],
1554 rows: usize,
1555 cols: usize,
1556 p1: SendMut,
1557 p2: SendMut,
1558 start: usize,
1559 end: usize,
1560) {
1561 let ng = cols / GROUP_SIZE;
1562 let bits = &bytes[..rows];
1563 let sc_off = rows;
1564 #[inline(always)]
1565 #[allow(clippy::too_many_arguments)]
1566 fn dot_row2<const B: usize>(
1567 data: &[u8],
1568 bytes: &[u8],
1569 sc_off: usize,
1570 r: usize,
1571 ng: usize,
1572 x1: &[f32],
1573 x2: &[f32],
1574 ) -> (f32, f32) {
1575 let l = ((1i32 << (B - 1)) - 1) as f32;
1576 let gbytes = GROUP_SIZE * B / 8;
1577 let (mut d1, mut d2) = (0f32, 0f32);
1578 for g in 0..ng {
1579 let so = (r * ng + g) * 2;
1580 let s = f16_to_f32(u16::from_le_bytes([bytes[sc_off + so], bytes[sc_off + so + 1]]));
1581 let x1g = &x1[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
1582 let x2g = &x2[g * GROUP_SIZE..(g + 1) * GROUP_SIZE];
1583 let gd0 = &data[g * gbytes..(g + 1) * gbytes];
1584 let (mut g1, mut g2) = (0f32, 0f32);
1585 for blk in 0..GROUP_SIZE / 8 {
1586 let u = unpack8::<B>(&gd0[blk * B..]);
1587 for k in 0..8 {
1588 let w = u[k] as f32 - l;
1589 g1 += w * x1g[blk * 8 + k];
1590 g2 += w * x2g[blk * 8 + k];
1591 }
1592 }
1593 d1 += g1 * s;
1594 d2 += g2 * s;
1595 }
1596 (d1, d2)
1597 }
1598 for r in start..end {
1599 let data = &bytes[offsets[r]..offsets[r + 1]];
1600 let (v1, v2) = match bits[r] {
1601 3 => dot_row2::<3>(data, bytes, sc_off, r, ng, x1, x2),
1602 4 => dot_row2::<4>(data, bytes, sc_off, r, ng, x1, x2),
1603 5 => dot_row2::<5>(data, bytes, sc_off, r, ng, x1, x2),
1604 6 => dot_row2::<6>(data, bytes, sc_off, r, ng, x1, x2),
1605 8 => dot_row2::<8>(data, bytes, sc_off, r, ng, x1, x2),
1606 b => unreachable!("vbit bit-width {b} (validated at load)"),
1607 };
1608 unsafe {
1610 *p1.at(r) = v1;
1611 *p2.at(r) = v2;
1612 }
1613 }
1614}
1615
1616#[inline]
1623#[allow(unreachable_code)]
1624fn dot_q4t_row_i8(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
1625 #[cfg(target_arch = "aarch64")]
1626 unsafe {
1627 return dot_q4t_row_sdot(bytes, r, gpr, xq);
1628 }
1629 #[cfg(target_arch = "x86_64")]
1630 unsafe {
1631 return dot_q4t_row_avx2(bytes, r, gpr, xq);
1632 }
1633 let mut acc = 0f32;
1634 for gi in 0..gpr {
1635 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
1636 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
1637 let mut d = 0i32;
1638 for (k, &b) in tile[2..].iter().enumerate() {
1639 d += ((b & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2] as i32
1640 + (((b >> 4) & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2 + 1] as i32;
1641 }
1642 acc += d as f32 * s;
1643 }
1644 acc
1645}
1646
1647#[cfg(target_arch = "aarch64")]
1648#[target_feature(enable = "neon,dotprod")]
1649unsafe fn dot_q4t_row_sdot(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
1650 unsafe {
1653 use core::arch::aarch64::*;
1654 use core::arch::asm;
1655 let lomask = vdupq_n_u8(0x0F);
1656 let eight = vdupq_n_s8(8);
1657 let mut acc = 0f32;
1658 for gi in 0..gpr {
1659 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
1660 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
1661 let b = vld1q_u8(t.add(2));
1662 let lo = vandq_u8(b, lomask);
1663 let hi = vshrq_n_u8::<4>(b);
1664 let e0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(lo, hi)), eight);
1665 let e1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(lo, hi)), eight);
1666 let x0 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE));
1667 let x1 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE + 16));
1668 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
1669 asm!(
1670 "sdot {a0:v}.4s, {e0:v}.16b, {x0:v}.16b",
1671 "sdot {a1:v}.4s, {e1:v}.16b, {x1:v}.16b",
1672 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
1673 e0 = in(vreg) e0, x0 = in(vreg) x0, e1 = in(vreg) e1, x1 = in(vreg) x1,
1674 options(pure, nomem, nostack),
1675 );
1676 acc += vaddvq_s32(vaddq_s32(a0, a1)) as f32 * s;
1677 }
1678 acc
1679 }
1680}
1681
1682#[cfg(target_arch = "x86_64")]
1683#[target_feature(enable = "avx2")]
1684unsafe fn dot_q4t_row_avx2(bytes: &[u8], r: usize, gpr: usize, xq: &[i8]) -> f32 {
1685 unsafe {
1687 use core::arch::x86_64::*;
1688 let lomask = _mm_set1_epi8(0x0F);
1689 let eight = _mm256_set1_epi8(8);
1690 let ones = _mm256_set1_epi16(1);
1691 let mut acc = 0f32;
1692 for gi in 0..gpr {
1693 let t = bytes.as_ptr().add((r * gpr + gi) * Q4_TILE);
1694 let s = f16_to_f32(u16::from_le_bytes([*t, *t.add(1)]));
1695 let b = _mm_loadu_si128(t.add(2) as *const __m128i);
1696 let lo = _mm_and_si128(b, lomask);
1697 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), lomask);
1698 let w = _mm256_sub_epi8(
1699 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
1700 eight,
1701 );
1702 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
1703 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(w), _mm256_sign_epi8(x, w));
1704 let d = _mm256_madd_epi16(p16, ones);
1705 let hi128 = _mm256_extracti128_si256::<1>(d);
1706 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
1707 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
1708 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
1709 acc += _mm_cvtsi128_si32(s32) as f32 * s;
1710 }
1711 acc
1712 }
1713}
1714
1715#[inline]
1717fn q4t_outlier(bytes: &[u8], r: usize, gpr: usize, j: usize) -> (f32, f32) {
1718 let gi = j / GROUP_SIZE;
1719 let k = j % GROUP_SIZE;
1720 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
1721 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
1722 let byte = tile[2 + k / 2];
1723 let nib = if k & 1 == 0 { byte & 0x0F } else { byte >> 4 };
1724 ((nib as i32 - 8) as f32, s)
1725}
1726
1727#[inline]
1730fn q4t_row_exact(bytes: &[u8], r: usize, gpr: usize, x: &[f32]) -> f32 {
1731 let mut acc = 0f32;
1732 for gi in 0..gpr {
1733 let tile = &bytes[(r * gpr + gi) * Q4_TILE..(r * gpr + gi + 1) * Q4_TILE];
1734 let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
1735 let xg = &x[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
1736 let mut ga = 0f32;
1737 for (k, &b) in tile[2..].iter().enumerate() {
1738 ga += ((b & 0x0F) as f32 - 8.0) * xg[k * 2]
1739 + (((b >> 4) & 0x0F) as f32 - 8.0) * xg[k * 2 + 1];
1740 }
1741 acc += ga * s;
1742 }
1743 acc
1744}
1745
1746fn q4t_matvec(bytes: &[u8], x: &[f32], rows: usize, cols: usize, out: &mut [f32], pool: Option<&Pool>) {
1748 debug_assert_eq!(out.len(), rows);
1749 let gpr = cols / GROUP_SIZE;
1750 let out_addr = SendMut(out.as_mut_ptr());
1751 if a8w8_enabled() {
1752 let act = split_act(x);
1753 let run = move |start: usize, end: usize| {
1754 for r in start..end {
1755 let mut acc = dot_q4t_row_i8(bytes, r, gpr, &act.xq) * act.sx;
1756 for &(j, xv) in &act.outliers {
1757 let (w, s) = q4t_outlier(bytes, r, gpr, j);
1758 acc += w * s * xv;
1759 }
1760 unsafe { *out_addr.at(r) = acc };
1762 }
1763 };
1764 dispatch_rows(pool, rows, &run);
1765 return;
1766 }
1767 let run = move |start: usize, end: usize| {
1768 for r in start..end {
1769 unsafe { *out_addr.at(r) = q4t_row_exact(bytes, r, gpr, x) };
1771 }
1772 };
1773 dispatch_rows(pool, rows, &run);
1774}
1775
1776#[allow(clippy::too_many_arguments)]
1778fn q4t_matvec2(
1779 bytes: &[u8],
1780 x1: &[f32],
1781 x2: &[f32],
1782 rows: usize,
1783 cols: usize,
1784 o1: &mut [f32],
1785 o2: &mut [f32],
1786 pool: Option<&Pool>,
1787) {
1788 let gpr = cols / GROUP_SIZE;
1789 let p1 = SendMut(o1.as_mut_ptr());
1790 let p2 = SendMut(o2.as_mut_ptr());
1791 if a8w8_enabled() {
1792 let a1 = split_act(x1);
1793 let a2 = split_act(x2);
1794 let run = move |start: usize, end: usize| {
1795 for r in start..end {
1796 let mut v1 = dot_q4t_row_i8(bytes, r, gpr, &a1.xq) * a1.sx;
1797 let mut v2 = dot_q4t_row_i8(bytes, r, gpr, &a2.xq) * a2.sx;
1798 for &(j, xv) in &a1.outliers {
1799 let (w, s) = q4t_outlier(bytes, r, gpr, j);
1800 v1 += w * s * xv;
1801 }
1802 for &(j, xv) in &a2.outliers {
1803 let (w, s) = q4t_outlier(bytes, r, gpr, j);
1804 v2 += w * s * xv;
1805 }
1806 unsafe {
1808 *p1.at(r) = v1;
1809 *p2.at(r) = v2;
1810 }
1811 }
1812 };
1813 dispatch_rows(pool, rows, &run);
1814 return;
1815 }
1816 let run = move |start: usize, end: usize| {
1817 for r in start..end {
1818 unsafe {
1820 *p1.at(r) = q4t_row_exact(bytes, r, gpr, x1);
1821 *p2.at(r) = q4t_row_exact(bytes, r, gpr, x2);
1822 }
1823 }
1824 };
1825 dispatch_rows(pool, rows, &run);
1826}
1827
1828#[allow(clippy::too_many_arguments)]
1830fn q4t_matmat(
1831 bytes: &[u8],
1832 xs_all: &[f32],
1833 b: usize,
1834 rows: usize,
1835 cols: usize,
1836 out: &mut [f32],
1837 pool: Option<&Pool>,
1838) {
1839 debug_assert_eq!(out.len(), b * rows);
1840 let gpr = cols / GROUP_SIZE;
1841 let out_addr = SendMut(out.as_mut_ptr());
1842 if a8w8_enabled() {
1843 let acts: Vec<SplitAct> =
1844 (0..b).map(|bi| split_act(&xs_all[bi * cols..(bi + 1) * cols])).collect();
1845 let acts = &acts;
1846 let run = move |start: usize, end: usize| {
1847 for r in start..end {
1848 for (bi, act) in acts.iter().enumerate() {
1849 let mut acc = dot_q4t_row_i8(bytes, r, gpr, &act.xq) * act.sx;
1850 for &(j, xv) in &act.outliers {
1851 let (w, s) = q4t_outlier(bytes, r, gpr, j);
1852 acc += w * s * xv;
1853 }
1854 unsafe { *out_addr.at(bi * rows + r) = acc };
1856 }
1857 }
1858 };
1859 dispatch_rows(pool, rows, &run);
1860 return;
1861 }
1862 let run = move |start: usize, end: usize| {
1863 for r in start..end {
1864 for bi in 0..b {
1865 let x = &xs_all[bi * cols..(bi + 1) * cols];
1866 unsafe { *out_addr.at(bi * rows + r) = q4t_row_exact(bytes, r, gpr, x) };
1868 }
1869 }
1870 };
1871 dispatch_rows(pool, rows, &run);
1872}
1873
1874fn q4matvec(bytes: &[u8], x: &[f32], rows: usize, cols: usize, out: &mut [f32], pool: Option<&Pool>) {
1880 debug_assert_eq!(out.len(), rows);
1881 let (packed, scales) = q4_split(bytes, rows, cols);
1882 let gpr = cols / GROUP_SIZE;
1883 let out_addr = SendMut(out.as_mut_ptr());
1884
1885 if a8w8_enabled() {
1886 let act = split_act(x);
1887 let run = move |start: usize, end: usize| {
1888 q4_range_a8w8(packed, scales, gpr, cols, &act, out_addr, start, end)
1889 };
1890 dispatch_rows(pool, rows, &run);
1891 return;
1892 }
1893
1894 let run = move |start: usize, end: usize| {
1895 q4_range_f32(packed, scales, gpr, x, out_addr, start, end)
1896 };
1897 dispatch_rows(pool, rows, &run);
1898}
1899
1900#[inline]
1903#[allow(unreachable_code)]
1904fn dot_q4_row_i8(packed: &[u8], scales: &[u8], g0: usize, gpr: usize, xq: &[i8]) -> f32 {
1905 #[cfg(target_arch = "aarch64")]
1906 unsafe {
1907 return dot_q4_row_sdot(packed, scales, g0, gpr, xq);
1908 }
1909 #[cfg(target_arch = "x86_64")]
1910 unsafe {
1911 return dot_q4_row_avx2(packed, scales, g0, gpr, xq);
1912 }
1913 let mut acc = 0f32;
1914 for gi in 0..gpr {
1915 let g = g0 + gi;
1916 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
1917 let mut d = 0i32;
1918 for (k, &b) in packed[g * 16..(g + 1) * 16].iter().enumerate() {
1919 d += ((b & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2] as i32
1920 + (((b >> 4) & 0x0F) as i32 - 8) * xq[gi * GROUP_SIZE + k * 2 + 1] as i32;
1921 }
1922 acc += d as f32 * s;
1923 }
1924 acc
1925}
1926
1927#[inline]
1929#[allow(unreachable_code)]
1930fn dot_q4_row_i8_2(
1931 packed: &[u8],
1932 scales: &[u8],
1933 g0: usize,
1934 gpr: usize,
1935 xq1: &[i8],
1936 xq2: &[i8],
1937) -> (f32, f32) {
1938 #[cfg(target_arch = "aarch64")]
1939 unsafe {
1940 return dot_q4_row_sdot2(packed, scales, g0, gpr, xq1, xq2);
1941 }
1942 #[cfg(target_arch = "x86_64")]
1943 unsafe {
1944 return dot_q4_row_avx2_2(packed, scales, g0, gpr, xq1, xq2);
1945 }
1946 (
1947 dot_q4_row_i8(packed, scales, g0, gpr, xq1),
1948 dot_q4_row_i8(packed, scales, g0, gpr, xq2),
1949 )
1950}
1951
1952#[allow(clippy::too_many_arguments)]
1955fn q4_range_a8w8(
1956 packed: &[u8],
1957 scales: &[u8],
1958 gpr: usize,
1959 cols: usize,
1960 act: &SplitAct,
1961 out: SendMut,
1962 start: usize,
1963 end: usize,
1964) {
1965 for r in start..end {
1966 let mut acc = dot_q4_row_i8(packed, scales, r * gpr, gpr, &act.xq) * act.sx;
1967 for &(j, xv) in &act.outliers {
1969 let flat = r * cols + j;
1970 let byte = packed[flat / 2];
1971 let nib = if flat & 1 == 0 { byte & 0x0F } else { byte >> 4 };
1972 let s = f16_to_f32(u16::from_le_bytes([
1973 scales[(flat / GROUP_SIZE) * 2],
1974 scales[(flat / GROUP_SIZE) * 2 + 1],
1975 ]));
1976 acc += ((nib as i32 - 8) as f32) * s * xv;
1977 }
1978 unsafe { *out.at(r) = acc };
1980 }
1981}
1982
1983#[allow(clippy::too_many_arguments)]
1986fn q4_range2_a8w8(
1987 packed: &[u8],
1988 scales: &[u8],
1989 gpr: usize,
1990 cols: usize,
1991 a1: &SplitAct,
1992 a2: &SplitAct,
1993 p1: SendMut,
1994 p2: SendMut,
1995 start: usize,
1996 end: usize,
1997) {
1998 for r in start..end {
1999 let (s1, s2) = dot_q4_row_i8_2(packed, scales, r * gpr, gpr, &a1.xq, &a2.xq);
2000 let mut acc1 = s1 * a1.sx;
2001 let mut acc2 = s2 * a2.sx;
2002 let fix = |outliers: &[(usize, f32)], acc: &mut f32| {
2004 for &(j, xv) in outliers {
2005 let flat = r * cols + j;
2006 let byte = packed[flat / 2];
2007 let nib = if flat & 1 == 0 { byte & 0x0F } else { byte >> 4 };
2008 let s = f16_to_f32(u16::from_le_bytes([
2009 scales[(flat / GROUP_SIZE) * 2],
2010 scales[(flat / GROUP_SIZE) * 2 + 1],
2011 ]));
2012 *acc += ((nib as i32 - 8) as f32) * s * xv;
2013 }
2014 };
2015 fix(&a1.outliers, &mut acc1);
2016 fix(&a2.outliers, &mut acc2);
2017 unsafe {
2019 *p1.at(r) = acc1;
2020 *p2.at(r) = acc2;
2021 }
2022 }
2023}
2024
2025fn q4_range_f32(
2027 packed: &[u8],
2028 scales: &[u8],
2029 gpr: usize,
2030 x: &[f32],
2031 out: SendMut,
2032 start: usize,
2033 end: usize,
2034) {
2035 for r in start..end {
2036 let mut acc = 0f32;
2037 for gi in 0..gpr {
2038 let g = r * gpr + gi;
2039 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
2040 let pk = &packed[g * 16..(g + 1) * 16];
2041 let xg = &x[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
2042 let mut ga = 0f32;
2043 for (k, &b) in pk.iter().enumerate() {
2044 ga += ((b & 0x0F) as f32 - 8.0) * xg[k * 2]
2045 + (((b >> 4) & 0x0F) as f32 - 8.0) * xg[k * 2 + 1];
2046 }
2047 acc += ga * s;
2048 }
2049 unsafe { *out.at(r) = acc };
2051 }
2052}
2053
2054#[allow(clippy::too_many_arguments)]
2058fn q4matvec2(
2059 bytes: &[u8],
2060 x1: &[f32],
2061 x2: &[f32],
2062 rows: usize,
2063 cols: usize,
2064 o1: &mut [f32],
2065 o2: &mut [f32],
2066 pool: Option<&Pool>,
2067) {
2068 debug_assert_eq!(o1.len(), rows);
2069 debug_assert_eq!(o2.len(), rows);
2070 let (packed, scales) = q4_split(bytes, rows, cols);
2071 let gpr = cols / GROUP_SIZE;
2072
2073 if a8w8_enabled() {
2074 let a1 = split_act(x1);
2075 let a2 = split_act(x2);
2076 let p1 = SendMut(o1.as_mut_ptr());
2077 let p2 = SendMut(o2.as_mut_ptr());
2078 let run = move |start: usize, end: usize| {
2079 q4_range2_a8w8(packed, scales, gpr, cols, &a1, &a2, p1, p2, start, end)
2080 };
2081 dispatch_rows(pool, rows, &run);
2082 return;
2083 }
2084
2085 let p1 = SendMut(o1.as_mut_ptr());
2086 let p2 = SendMut(o2.as_mut_ptr());
2087 let run = move |start: usize, end: usize| {
2088 q4_range2_f32(packed, scales, gpr, x1, x2, p1, p2, start, end)
2089 };
2090 dispatch_rows(pool, rows, &run);
2091}
2092
2093#[allow(clippy::too_many_arguments)]
2095fn q4_range2_f32(
2096 packed: &[u8],
2097 scales: &[u8],
2098 gpr: usize,
2099 x1: &[f32],
2100 x2: &[f32],
2101 p1: SendMut,
2102 p2: SendMut,
2103 start: usize,
2104 end: usize,
2105) {
2106 for r in start..end {
2107 let (mut acc1, mut acc2) = (0f32, 0f32);
2108 for gi in 0..gpr {
2109 let g = r * gpr + gi;
2110 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
2111 let pk = &packed[g * 16..(g + 1) * 16];
2112 let x1g = &x1[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
2113 let x2g = &x2[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE];
2114 let (mut g1, mut g2) = (0f32, 0f32);
2115 for (k, &b) in pk.iter().enumerate() {
2116 let wl = (b & 0x0F) as f32 - 8.0;
2117 let wh = ((b >> 4) & 0x0F) as f32 - 8.0;
2118 g1 += wl * x1g[k * 2] + wh * x1g[k * 2 + 1];
2119 g2 += wl * x2g[k * 2] + wh * x2g[k * 2 + 1];
2120 }
2121 acc1 += g1 * s;
2122 acc2 += g2 * s;
2123 }
2124 unsafe {
2126 *p1.at(r) = acc1;
2127 *p2.at(r) = acc2;
2128 }
2129 }
2130}
2131
2132thread_local! {
2133 static ROW_I8: std::cell::RefCell<Vec<u8>> = const { std::cell::RefCell::new(Vec::new()) };
2136 static ROW_F32: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
2137}
2138
2139#[allow(clippy::too_many_arguments)]
2145fn q4matmat(
2146 bytes: &[u8],
2147 xs_all: &[f32],
2148 b: usize,
2149 rows: usize,
2150 cols: usize,
2151 out: &mut [f32],
2152 pool: Option<&Pool>,
2153) {
2154 debug_assert_eq!(xs_all.len(), b * cols);
2155 debug_assert_eq!(out.len(), b * rows);
2156 let (packed, scales) = q4_split(bytes, rows, cols);
2157 let gpr = cols / GROUP_SIZE;
2158 let gscale = |g: usize| f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
2159
2160 if a8w8_enabled() {
2161 let acts: Vec<SplitAct> =
2162 (0..b).map(|bi| split_act(&xs_all[bi * cols..(bi + 1) * cols])).collect();
2163 let acts = &acts;
2164 let out_addr = SendMut(out.as_mut_ptr());
2165 let run = move |start: usize, end: usize| {
2166 ROW_I8.with(|rb| {
2167 let mut buf = rb.borrow_mut();
2168 buf.resize(cols, 0);
2169 for r in start..end {
2170 for gi in 0..gpr {
2174 let g = r * gpr + gi;
2175 for (k, &bt) in packed[g * 16..(g + 1) * 16].iter().enumerate() {
2176 buf[gi * GROUP_SIZE + k * 2] = ((bt & 0x0F) as i32 - 8) as i8 as u8;
2177 buf[gi * GROUP_SIZE + k * 2 + 1] =
2178 (((bt >> 4) & 0x0F) as i32 - 8) as i8 as u8;
2179 }
2180 }
2181 for (bi, act) in acts.iter().enumerate() {
2182 let mut acc = 0f32;
2183 for gi in 0..gpr {
2184 let d = dot_i8_i8(
2185 &buf[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE],
2186 &act.xq[gi * GROUP_SIZE..(gi + 1) * GROUP_SIZE],
2187 );
2188 acc += d as f32 * gscale(r * gpr + gi);
2189 }
2190 acc *= act.sx;
2191 for &(j, xv) in &act.outliers {
2193 acc += (buf[j] as i8) as f32 * gscale((r * cols + j) / GROUP_SIZE) * xv;
2194 }
2195 unsafe { *out_addr.at(bi * rows + r) = acc };
2197 }
2198 }
2199 })
2200 };
2201 dispatch_rows(pool, rows, &run);
2202 return;
2203 }
2204
2205 let out_addr = SendMut(out.as_mut_ptr());
2206 let run = move |start: usize, end: usize| {
2207 ROW_F32.with(|rb| {
2208 let mut buf = rb.borrow_mut();
2209 buf.resize(cols, 0.0);
2210 for r in start..end {
2211 for gi in 0..gpr {
2214 let g = r * gpr + gi;
2215 for (k, &bt) in packed[g * 16..(g + 1) * 16].iter().enumerate() {
2216 buf[gi * GROUP_SIZE + k * 2] = (bt & 0x0F) as f32 - 8.0;
2217 buf[gi * GROUP_SIZE + k * 2 + 1] = ((bt >> 4) & 0x0F) as f32 - 8.0;
2218 }
2219 }
2220 for bi in 0..b {
2221 let x = &xs_all[bi * cols..(bi + 1) * cols];
2222 let mut acc = 0f32;
2223 for gi in 0..gpr {
2224 let mut ga = 0f32;
2225 for k in 0..GROUP_SIZE / 2 {
2230 let e = gi * GROUP_SIZE + k * 2;
2231 ga += buf[e] * x[e] + buf[e + 1] * x[e + 1];
2232 }
2233 acc += ga * gscale(r * gpr + gi);
2234 }
2235 unsafe { *out_addr.at(bi * rows + r) = acc };
2237 }
2238 }
2239 })
2240 };
2241 dispatch_rows(pool, rows, &run);
2242}
2243
2244#[allow(clippy::too_many_arguments)]
2249fn vbitmatmat(
2250 bytes: &[u8],
2251 offsets: &[usize],
2252 xs_all: &[f32],
2253 b: usize,
2254 rows: usize,
2255 cols: usize,
2256 out: &mut [f32],
2257 pool: Option<&Pool>,
2258) {
2259 debug_assert_eq!(xs_all.len(), b * cols);
2260 debug_assert_eq!(out.len(), b * rows);
2261 debug_assert_eq!(offsets.len(), rows + 1);
2262 let ng = cols / GROUP_SIZE;
2263 let bits = &bytes[..rows];
2264 let sc_off = rows;
2265 let gscale = |r: usize, g: usize| {
2266 let so = (r * ng + g) * 2;
2267 f16_to_f32(u16::from_le_bytes([bytes[sc_off + so], bytes[sc_off + so + 1]]))
2268 };
2269
2270 let decode_f32 = |r: usize, dst: &mut [f32]| {
2272 let bw = bits[r] as usize;
2273 let l = ((1i32 << (bw - 1)) - 1) as f32;
2274 let data = &bytes[offsets[r]..offsets[r + 1]];
2275 let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
2276 for d in dst.iter_mut() {
2277 while nbits < bw {
2278 acc = (acc << 8) | data[idx] as u64;
2279 idx += 1;
2280 nbits += 8;
2281 }
2282 let u = ((acc >> (nbits - bw)) & ((1u64 << bw) - 1)) as f32;
2283 nbits -= bw;
2284 *d = u - l;
2285 }
2286 };
2287
2288 if a8w8_enabled() {
2289 let acts: Vec<SplitAct> =
2290 (0..b).map(|bi| split_act(&xs_all[bi * cols..(bi + 1) * cols])).collect();
2291 let acts = &acts;
2292 let out_addr = SendMut(out.as_mut_ptr());
2293 let run = move |start: usize, end: usize| {
2294 for r in start..end {
2295 let bw = bits[r] as usize;
2296 if bw == 8 {
2297 ROW_F32.with(|rb| {
2300 let mut buf = rb.borrow_mut();
2301 buf.resize(cols, 0.0);
2302 decode_f32(r, &mut buf);
2303 for bi in 0..b {
2304 let x = &xs_all[bi * cols..(bi + 1) * cols];
2305 let mut dot = 0f32;
2306 for g in 0..ng {
2307 let mut gd = 0f32;
2308 for k in 0..GROUP_SIZE {
2309 gd += buf[g * GROUP_SIZE + k] * x[g * GROUP_SIZE + k];
2310 }
2311 dot += gd * gscale(r, g);
2312 }
2313 unsafe { *out_addr.at(bi * rows + r) = dot };
2315 }
2316 });
2317 continue;
2318 }
2319 let l = (1i32 << (bw - 1)) - 1;
2320 let data = &bytes[offsets[r]..offsets[r + 1]];
2321 ROW_I8.with(|rb| {
2322 let mut buf = rb.borrow_mut();
2323 buf.resize(cols, 0);
2324 #[inline(always)]
2325 fn fill<const B: usize>(data: &[u8], l: i32, buf: &mut [u8]) {
2326 for (blk, chunk) in buf.chunks_exact_mut(8).enumerate() {
2327 let u = unpack8::<B>(&data[blk * B..]);
2328 for k in 0..8 {
2329 chunk[k] = (u[k] - l) as i8 as u8;
2330 }
2331 }
2332 }
2333 match bw {
2334 3 => fill::<3>(data, l, &mut buf),
2335 4 => vbit_fill4(data, &mut buf),
2336 5 => fill::<5>(data, l, &mut buf),
2337 6 => fill::<6>(data, l, &mut buf),
2338 _ => unreachable!("vbit bit-width {bw} (validated at load)"),
2339 }
2340 for (bi, act) in acts.iter().enumerate() {
2341 let mut dot = 0f32;
2342 for g in 0..ng {
2343 let d = dot_i8_i8(
2344 &buf[g * GROUP_SIZE..(g + 1) * GROUP_SIZE],
2345 &act.xq[g * GROUP_SIZE..(g + 1) * GROUP_SIZE],
2346 ) as f32
2347 * act.sx;
2348 dot += d * gscale(r, g);
2349 }
2350 for &(j, xv) in &act.outliers {
2351 dot += (buf[j] as i8) as f32 * gscale(r, j / GROUP_SIZE) * xv;
2352 }
2353 unsafe { *out_addr.at(bi * rows + r) = dot };
2355 }
2356 });
2357 }
2358 };
2359 dispatch_rows(pool, rows, &run);
2360 return;
2361 }
2362
2363 let out_addr = SendMut(out.as_mut_ptr());
2364 let run = move |start: usize, end: usize| {
2365 ROW_F32.with(|rb| {
2366 let mut buf = rb.borrow_mut();
2367 buf.resize(cols, 0.0);
2368 for r in start..end {
2369 decode_f32(r, &mut buf);
2370 for bi in 0..b {
2371 let x = &xs_all[bi * cols..(bi + 1) * cols];
2372 let mut dot = 0f32;
2373 for g in 0..ng {
2374 let mut gd = 0f32;
2375 for k in 0..GROUP_SIZE {
2376 gd += buf[g * GROUP_SIZE + k] * x[g * GROUP_SIZE + k];
2377 }
2378 dot += gd * gscale(r, g);
2379 }
2380 unsafe { *out_addr.at(bi * rows + r) = dot };
2382 }
2383 }
2384 })
2385 };
2386 dispatch_rows(pool, rows, &run);
2387}
2388
2389pub(crate) fn gpu_batch_job<'a>(
2393 t: &'a QTensor,
2394 x: &[f32],
2395) -> Option<(std::sync::Arc<CmfModel>, crate::gpu::BatchJob<'a>)> {
2396 match t {
2397 QTensor::Mapped {
2398 model,
2399 idx,
2400 dtype: dt @ (TensorDtype::Q8Row | TensorDtype::Q8_2f),
2401 rows,
2402 cols,
2403 row_scale,
2404 col_field,
2405 ..
2406 } => Some((
2407 model.clone(),
2408 crate::gpu::BatchJob {
2409 idx: *idx,
2410 rows: *rows,
2411 cols: *cols,
2412 row_scale,
2413 xs: prescale(x, col_field, *dt).into_owned(),
2414 },
2415 )),
2416 _ => None,
2417 }
2418}
2419
2420pub(crate) fn prescale<'a>(
2424 x: &'a [f32],
2425 col_field: &[f32],
2426 dtype: TensorDtype,
2427) -> std::borrow::Cow<'a, [f32]> {
2428 if dtype == TensorDtype::Q8_2f {
2429 x.iter().zip(col_field).map(|(a, c)| a * c).collect()
2430 } else {
2431 std::borrow::Cow::Borrowed(x)
2432 }
2433}
2434
2435#[cfg(target_arch = "x86_64")]
2440pub(crate) fn avx2_enabled() -> bool {
2441 use std::sync::OnceLock;
2442 static ON: OnceLock<bool> = OnceLock::new();
2443 *ON.get_or_init(|| {
2444 std::env::var("CMF_AVX2").map(|v| v != "0").unwrap_or(true)
2445 && std::arch::is_x86_feature_detected!("avx2")
2446 && std::arch::is_x86_feature_detected!("fma")
2447 })
2448}
2449
2450#[cfg(target_arch = "x86_64")]
2455fn avx2_a8w8_enabled() -> bool {
2456 use std::sync::OnceLock;
2457 static ON: OnceLock<bool> = OnceLock::new();
2458 *ON.get_or_init(|| {
2459 avx2_enabled() && std::env::var("CMF_SDOT").map(|v| v != "0").unwrap_or(true)
2460 })
2461}
2462
2463#[inline]
2467pub(crate) fn a8w8_enabled() -> bool {
2468 #[cfg(target_arch = "aarch64")]
2469 {
2470 sdot_enabled()
2471 }
2472 #[cfg(target_arch = "x86_64")]
2473 {
2474 avx2_a8w8_enabled()
2475 }
2476 #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
2477 {
2478 false
2479 }
2480}
2481
2482#[inline]
2485#[allow(unreachable_code)]
2486fn dot_i8_i8(w: &[u8], xq: &[i8]) -> i32 {
2487 #[cfg(target_arch = "aarch64")]
2488 unsafe {
2489 return dot_i8_sdot(w, xq);
2490 }
2491 #[cfg(target_arch = "x86_64")]
2492 unsafe {
2493 if avx512vnni_enabled() {
2494 return dot_i8_i8_vnni(w, xq);
2495 }
2496 return dot_i8_i8_avx2(w, xq);
2497 }
2498 w.iter().zip(xq).map(|(&a, &b)| (a as i8) as i32 * b as i32).sum()
2499}
2500
2501#[cfg(target_arch = "x86_64")]
2505fn avx512vnni_enabled() -> bool {
2506 use std::sync::OnceLock;
2507 static ON: OnceLock<bool> = OnceLock::new();
2508 *ON.get_or_init(|| {
2509 std::env::var("CMF_AVX512").map(|v| v != "0").unwrap_or(true)
2510 && std::arch::is_x86_feature_detected!("avx512f")
2511 && std::arch::is_x86_feature_detected!("avx512bw")
2512 && std::arch::is_x86_feature_detected!("avx512vl")
2513 && std::arch::is_x86_feature_detected!("avx512vnni")
2514 })
2515}
2516
2517#[cfg(target_arch = "x86_64")]
2522#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
2523unsafe fn dot_i8_i8_vnni(w: &[u8], xq: &[i8]) -> i32 {
2524 unsafe {
2526 use core::arch::x86_64::*;
2527 let n = w.len();
2528 let mut j = 0usize;
2529 let mut total: i32;
2530 {
2535 #[inline(always)]
2536 unsafe fn step(
2537 w: *const u8,
2538 x: *const i8,
2539 acc: core::arch::x86_64::__m512i,
2540 ) -> core::arch::x86_64::__m512i {
2541 unsafe {
2542 use core::arch::x86_64::*;
2543 let wv = _mm512_loadu_si512(w as *const _);
2544 let xv = _mm512_loadu_si512(x as *const _);
2545 let aw = _mm512_abs_epi8(wv);
2546 let neg = _mm512_movepi8_mask(wv);
2547 let sx = _mm512_mask_sub_epi8(xv, neg, _mm512_setzero_si512(), xv);
2548 _mm512_dpbusd_epi32(acc, aw, sx)
2549 }
2550 }
2551 let (mut a0, mut a1, mut a2, mut a3) = (
2552 _mm512_setzero_si512(),
2553 _mm512_setzero_si512(),
2554 _mm512_setzero_si512(),
2555 _mm512_setzero_si512(),
2556 );
2557 while j + 256 <= n {
2558 a0 = step(w.as_ptr().add(j), xq.as_ptr().add(j), a0);
2559 a1 = step(w.as_ptr().add(j + 64), xq.as_ptr().add(j + 64), a1);
2560 a2 = step(w.as_ptr().add(j + 128), xq.as_ptr().add(j + 128), a2);
2561 a3 = step(w.as_ptr().add(j + 192), xq.as_ptr().add(j + 192), a3);
2562 j += 256;
2563 }
2564 while j + 64 <= n {
2565 a0 = step(w.as_ptr().add(j), xq.as_ptr().add(j), a0);
2566 j += 64;
2567 }
2568 let s01 = _mm512_add_epi32(a0, a1);
2569 let s23 = _mm512_add_epi32(a2, a3);
2570 total = _mm512_reduce_add_epi32(_mm512_add_epi32(s01, s23));
2571 }
2572 if j + 32 <= n {
2574 let wv = _mm256_loadu_si256(w.as_ptr().add(j) as *const __m256i);
2575 let xv = _mm256_loadu_si256(xq.as_ptr().add(j) as *const __m256i);
2576 let d = _mm256_dpbusd_epi32(
2577 _mm256_setzero_si256(),
2578 _mm256_abs_epi8(wv),
2579 _mm256_sign_epi8(xv, wv),
2580 );
2581 let hi128 = _mm256_extracti128_si256::<1>(d);
2582 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
2583 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
2584 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
2585 total += _mm_cvtsi128_si32(s32);
2586 j += 32;
2587 }
2588 while j < n {
2589 total += (w[j] as i8) as i32 * xq[j] as i32;
2590 j += 1;
2591 }
2592 total
2593 }
2594}
2595
2596#[cfg(target_arch = "x86_64")]
2598#[target_feature(enable = "avx2,fma")]
2599unsafe fn dot_i8_f32_avx2(w: &[u8], x: &[f32]) -> f32 {
2600 unsafe {
2602 use core::arch::x86_64::*;
2603 let n = x.len();
2604 let wp = w.as_ptr();
2605 let xp = x.as_ptr();
2606 let (mut a0, mut a1) = (_mm256_setzero_ps(), _mm256_setzero_ps());
2607 let mut j = 0usize;
2608 while j + 16 <= n {
2609 let wb = _mm_loadu_si128(wp.add(j) as *const __m128i);
2610 let lo = _mm256_cvtepi8_epi32(wb);
2611 let hi = _mm256_cvtepi8_epi32(_mm_srli_si128::<8>(wb));
2612 a0 = _mm256_fmadd_ps(_mm256_cvtepi32_ps(lo), _mm256_loadu_ps(xp.add(j)), a0);
2613 a1 = _mm256_fmadd_ps(_mm256_cvtepi32_ps(hi), _mm256_loadu_ps(xp.add(j + 8)), a1);
2614 j += 16;
2615 }
2616 let acc = _mm256_add_ps(a0, a1);
2617 let hi128 = _mm256_extractf128_ps::<1>(acc);
2618 let s128 = _mm_add_ps(_mm256_castps256_ps128(acc), hi128);
2619 let s64 = _mm_add_ps(s128, _mm_movehl_ps(s128, s128));
2620 let s32 = _mm_add_ss(s64, _mm_shuffle_ps::<1>(s64, s64));
2621 let mut sum = _mm_cvtss_f32(s32);
2622 while j < n {
2623 sum += (*wp.add(j) as i8) as f32 * *xp.add(j);
2624 j += 1;
2625 }
2626 sum
2627 }
2628}
2629
2630#[cfg(target_arch = "x86_64")]
2635#[target_feature(enable = "avx2")]
2636unsafe fn dot_i8_i8_avx2(w: &[u8], xq: &[i8]) -> i32 {
2637 unsafe {
2639 use core::arch::x86_64::*;
2640 let n = w.len();
2641 let ones = _mm256_set1_epi16(1);
2642 let mut acc = _mm256_setzero_si256();
2643 let mut j = 0usize;
2644 while j + 32 <= n {
2645 let wv = _mm256_loadu_si256(w.as_ptr().add(j) as *const __m256i);
2646 let xv = _mm256_loadu_si256(xq.as_ptr().add(j) as *const __m256i);
2647 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(wv), _mm256_sign_epi8(xv, wv));
2648 acc = _mm256_add_epi32(acc, _mm256_madd_epi16(p16, ones));
2649 j += 32;
2650 }
2651 let hi128 = _mm256_extracti128_si256::<1>(acc);
2652 let s128 = _mm_add_epi32(_mm256_castsi256_si128(acc), hi128);
2653 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
2654 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
2655 let mut s = _mm_cvtsi128_si32(s32);
2656 while j < n {
2657 s += (w[j] as i8) as i32 * xq[j] as i32;
2658 j += 1;
2659 }
2660 s
2661 }
2662}
2663
2664#[cfg(target_arch = "x86_64")]
2669#[inline]
2670fn row_dot_avx2(row: &[u8], act: &SplitAct) -> f32 {
2671 let dot = if avx512vnni_enabled() && row.len() >= 64 {
2672 (unsafe { dot_u8p128_i8_vnni(row, &act.xq) }) - 128 * act.xsum
2673 } else {
2674 unsafe { dot_i8_i8_avx2(row, &act.xq) }
2675 };
2676 let mut acc = dot as f32 * act.sx;
2677 for &(j, xv) in &act.outliers {
2678 acc += (row[j] as i8) as f32 * xv;
2679 }
2680 acc
2681}
2682
2683#[cfg(target_arch = "x86_64")]
2687#[target_feature(enable = "avx2,avx512f,avx512bw,avx512vl,avx512vnni")]
2688unsafe fn dot_u8p128_i8_vnni(w: &[u8], xq: &[i8]) -> i32 {
2689 unsafe {
2691 use core::arch::x86_64::*;
2692 let n = w.len();
2693 let flip = _mm512_set1_epi8(-128); #[inline(always)]
2695 unsafe fn step(
2696 w: *const u8,
2697 x: *const i8,
2698 flip: core::arch::x86_64::__m512i,
2699 acc: core::arch::x86_64::__m512i,
2700 ) -> core::arch::x86_64::__m512i {
2701 unsafe {
2702 use core::arch::x86_64::*;
2703 let wv = _mm512_xor_si512(_mm512_loadu_si512(w as *const _), flip);
2704 _mm512_dpbusd_epi32(acc, wv, _mm512_loadu_si512(x as *const _))
2705 }
2706 }
2707 let (mut a0, mut a1, mut a2, mut a3) = (
2708 _mm512_setzero_si512(),
2709 _mm512_setzero_si512(),
2710 _mm512_setzero_si512(),
2711 _mm512_setzero_si512(),
2712 );
2713 let mut j = 0usize;
2714 while j + 256 <= n {
2715 a0 = step(w.as_ptr().add(j), xq.as_ptr().add(j), flip, a0);
2716 a1 = step(w.as_ptr().add(j + 64), xq.as_ptr().add(j + 64), flip, a1);
2717 a2 = step(w.as_ptr().add(j + 128), xq.as_ptr().add(j + 128), flip, a2);
2718 a3 = step(w.as_ptr().add(j + 192), xq.as_ptr().add(j + 192), flip, a3);
2719 j += 256;
2720 }
2721 while j + 64 <= n {
2722 a0 = step(w.as_ptr().add(j), xq.as_ptr().add(j), flip, a0);
2723 j += 64;
2724 }
2725 let mut total = _mm512_reduce_add_epi32(_mm512_add_epi32(
2726 _mm512_add_epi32(a0, a1),
2727 _mm512_add_epi32(a2, a3),
2728 ));
2729 while j < n {
2731 total += ((w[j] ^ 0x80) as i32) * xq[j] as i32;
2732 j += 1;
2733 }
2734 total
2735 }
2736}
2737
2738#[cfg(target_arch = "x86_64")]
2744#[target_feature(enable = "avx2")]
2745unsafe fn dot_q4_row_avx2(packed: &[u8], scales: &[u8], g0: usize, gpr: usize, xq: &[i8]) -> f32 {
2746 unsafe {
2749 use core::arch::x86_64::*;
2750 let lomask = _mm_set1_epi8(0x0F);
2751 let eight = _mm256_set1_epi8(8);
2752 let ones = _mm256_set1_epi16(1);
2753 let mut acc = 0f32;
2754 for gi in 0..gpr {
2755 let g = g0 + gi;
2756 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
2757 let b = _mm_loadu_si128(packed.as_ptr().add(g * 16) as *const __m128i);
2758 let lo = _mm_and_si128(b, lomask);
2759 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), lomask);
2760 let w = _mm256_sub_epi8(
2761 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
2762 eight,
2763 );
2764 let x = _mm256_loadu_si256(xq.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
2765 let p16 = _mm256_maddubs_epi16(_mm256_abs_epi8(w), _mm256_sign_epi8(x, w));
2766 let d = _mm256_madd_epi16(p16, ones);
2767 let hi128 = _mm256_extracti128_si256::<1>(d);
2768 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
2769 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
2770 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
2771 acc += _mm_cvtsi128_si32(s32) as f32 * s;
2772 }
2773 acc
2774 }
2775}
2776
2777#[cfg(target_arch = "x86_64")]
2780#[target_feature(enable = "avx2")]
2781unsafe fn dot_q4_row_avx2_2(
2782 packed: &[u8],
2783 scales: &[u8],
2784 g0: usize,
2785 gpr: usize,
2786 xq1: &[i8],
2787 xq2: &[i8],
2788) -> (f32, f32) {
2789 unsafe {
2791 use core::arch::x86_64::*;
2792 let lomask = _mm_set1_epi8(0x0F);
2793 let eight = _mm256_set1_epi8(8);
2794 let ones = _mm256_set1_epi16(1);
2795 let (mut acc1, mut acc2) = (0f32, 0f32);
2796 #[inline(always)]
2797 unsafe fn hsum(d: core::arch::x86_64::__m256i) -> i32 {
2798 unsafe {
2799 use core::arch::x86_64::*;
2800 let hi128 = _mm256_extracti128_si256::<1>(d);
2801 let s128 = _mm_add_epi32(_mm256_castsi256_si128(d), hi128);
2802 let s64 = _mm_add_epi32(s128, _mm_srli_si128::<8>(s128));
2803 let s32 = _mm_add_epi32(s64, _mm_srli_si128::<4>(s64));
2804 _mm_cvtsi128_si32(s32)
2805 }
2806 }
2807 for gi in 0..gpr {
2808 let g = g0 + gi;
2809 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
2810 let b = _mm_loadu_si128(packed.as_ptr().add(g * 16) as *const __m128i);
2811 let lo = _mm_and_si128(b, lomask);
2812 let hi = _mm_and_si128(_mm_srli_epi16::<4>(b), lomask);
2813 let w = _mm256_sub_epi8(
2814 _mm256_set_m128i(_mm_unpackhi_epi8(lo, hi), _mm_unpacklo_epi8(lo, hi)),
2815 eight,
2816 );
2817 let aw = _mm256_abs_epi8(w);
2818 let x1 = _mm256_loadu_si256(xq1.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
2819 let x2 = _mm256_loadu_si256(xq2.as_ptr().add(gi * GROUP_SIZE) as *const __m256i);
2820 let d1 = _mm256_madd_epi16(_mm256_maddubs_epi16(aw, _mm256_sign_epi8(x1, w)), ones);
2821 let d2 = _mm256_madd_epi16(_mm256_maddubs_epi16(aw, _mm256_sign_epi8(x2, w)), ones);
2822 acc1 += hsum(d1) as f32 * s;
2823 acc2 += hsum(d2) as f32 * s;
2824 }
2825 (acc1, acc2)
2826 }
2827}
2828
2829#[cfg(target_arch = "x86_64")]
2831fn q8_range_avx2(
2832 q: &[u8],
2833 row_scale: &[f32],
2834 act: &SplitAct,
2835 cols: usize,
2836 out_addr: SendMut,
2837 start: usize,
2838 end: usize,
2839) {
2840 for o in start..end {
2841 let v = row_dot_avx2(&q[o * cols..(o + 1) * cols], act) * row_scale[o];
2842 unsafe { *out_addr.at(o) = v };
2844 }
2845}
2846
2847#[cfg(target_arch = "x86_64")]
2849#[allow(clippy::too_many_arguments)]
2850fn q8_range2_avx2(
2851 q: &[u8],
2852 row_scale: &[f32],
2853 a1: &SplitAct,
2854 a2: &SplitAct,
2855 cols: usize,
2856 p1: SendMut,
2857 p2: SendMut,
2858 start: usize,
2859 end: usize,
2860) {
2861 for o in start..end {
2862 let row = &q[o * cols..(o + 1) * cols];
2863 unsafe {
2865 *p1.at(o) = row_dot_avx2(row, a1) * row_scale[o];
2866 *p2.at(o) = row_dot_avx2(row, a2) * row_scale[o];
2867 }
2868 }
2869}
2870
2871#[cfg_attr(not(target_arch = "aarch64"), allow(dead_code))]
2877fn sdot_enabled() -> bool {
2878 use std::sync::OnceLock;
2879 static ON: OnceLock<bool> = OnceLock::new();
2880 *ON.get_or_init(|| {
2881 let want = std::env::var("CMF_SDOT").map(|v| v != "0").unwrap_or(true);
2882 #[cfg(target_arch = "aarch64")]
2883 {
2884 want && std::arch::is_aarch64_feature_detected!("dotprod")
2885 }
2886 #[cfg(not(target_arch = "aarch64"))]
2887 {
2888 let _ = want;
2889 false
2890 }
2891 })
2892}
2893
2894struct SplitAct {
2899 xq: Vec<i8>,
2900 sx: f32,
2901 outliers: Vec<(usize, f32)>,
2902 #[cfg_attr(not(target_arch = "x86_64"), allow(dead_code))]
2905 xsum: i32,
2906}
2907
2908thread_local! {
2909 static XQ_FREE: std::cell::RefCell<Vec<Vec<i8>>> =
2912 const { std::cell::RefCell::new(Vec::new()) };
2913}
2914
2915impl Drop for SplitAct {
2916 fn drop(&mut self) {
2917 let buf = std::mem::take(&mut self.xq);
2918 if buf.capacity() > 0 {
2919 XQ_FREE.with(|f| {
2920 let mut f = f.borrow_mut();
2921 if f.len() < 16 {
2922 f.push(buf);
2923 }
2924 });
2925 }
2926 }
2927}
2928
2929fn split_act(x: &[f32]) -> SplitAct {
2930 let n = x.len();
2931 let rms = (x.iter().map(|&v| (v * v) as f64).sum::<f64>() / n.max(1) as f64).sqrt() as f32;
2932 let thr = 8.0 * rms;
2933 let mut outliers: Vec<(usize, f32)> = Vec::new();
2937 let mut amax = 0f32;
2938 for (j, &v) in x.iter().enumerate() {
2939 let a = v.abs();
2940 if a > thr {
2941 outliers.push((j, v));
2942 } else if a > amax {
2943 amax = a;
2944 }
2945 }
2946 let sx = if amax > 0.0 { amax / 127.0 } else { 1.0 };
2947 let inv = 1.0 / sx;
2948 let mut xq = XQ_FREE.with(|f| f.borrow_mut().pop()).unwrap_or_default();
2949 xq.clear();
2950 xq.reserve(n);
2951 if outliers.is_empty() {
2952 xq.extend(x.iter().map(|&v| (v * inv).round().clamp(-127.0, 127.0) as i8));
2953 } else {
2954 xq.extend(x.iter().map(|&v| {
2956 if v.abs() > thr {
2957 0
2958 } else {
2959 (v * inv).round().clamp(-127.0, 127.0) as i8
2960 }
2961 }));
2962 }
2963 let xsum = xq.iter().map(|&v| v as i32).sum();
2964 SplitAct { xq, sx, outliers, xsum }
2965}
2966
2967#[cfg(target_arch = "aarch64")]
2970#[target_feature(enable = "neon,dotprod")]
2971unsafe fn dot_i8_sdot(w: &[u8], xq: &[i8]) -> i32 {
2972 unsafe {
2974 use core::arch::aarch64::*;
2975 use core::arch::asm;
2976 let wp = w.as_ptr() as *const i8;
2977 let n = w.len();
2978 let (mut a0, mut a1, mut a2, mut a3) =
2979 (vdupq_n_s32(0), vdupq_n_s32(0), vdupq_n_s32(0), vdupq_n_s32(0));
2980 let mut i = 0;
2981 while i + 64 <= n {
2982 let (w0, x0) = (vld1q_s8(wp.add(i)), vld1q_s8(xq.as_ptr().add(i)));
2983 let (w1, x1) = (vld1q_s8(wp.add(i + 16)), vld1q_s8(xq.as_ptr().add(i + 16)));
2984 let (w2, x2) = (vld1q_s8(wp.add(i + 32)), vld1q_s8(xq.as_ptr().add(i + 32)));
2985 let (w3, x3) = (vld1q_s8(wp.add(i + 48)), vld1q_s8(xq.as_ptr().add(i + 48)));
2986 asm!(
2987 "sdot {a0:v}.4s, {w0:v}.16b, {x0:v}.16b",
2988 "sdot {a1:v}.4s, {w1:v}.16b, {x1:v}.16b",
2989 "sdot {a2:v}.4s, {w2:v}.16b, {x2:v}.16b",
2990 "sdot {a3:v}.4s, {w3:v}.16b, {x3:v}.16b",
2991 a0 = inout(vreg) a0, a1 = inout(vreg) a1, a2 = inout(vreg) a2, a3 = inout(vreg) a3,
2992 w0 = in(vreg) w0, x0 = in(vreg) x0, w1 = in(vreg) w1, x1 = in(vreg) x1,
2993 w2 = in(vreg) w2, x2 = in(vreg) x2, w3 = in(vreg) w3, x3 = in(vreg) x3,
2994 options(pure, nomem, nostack),
2995 );
2996 i += 64;
2997 }
2998 while i + 16 <= n {
2999 let (wv, xv) = (vld1q_s8(wp.add(i)), vld1q_s8(xq.as_ptr().add(i)));
3000 asm!("sdot {a:v}.4s, {w:v}.16b, {x:v}.16b",
3001 a = inout(vreg) a0, w = in(vreg) wv, x = in(vreg) xv, options(pure, nomem, nostack));
3002 i += 16;
3003 }
3004 let mut s = vaddvq_s32(vaddq_s32(vaddq_s32(a0, a1), vaddq_s32(a2, a3)));
3005 while i < n {
3006 s += (*wp.add(i)) as i32 * xq[i] as i32;
3007 i += 1;
3008 }
3009 s
3010}
3011}
3012
3013#[cfg(target_arch = "aarch64")]
3017#[target_feature(enable = "neon,dotprod")]
3018unsafe fn dot_i8_sdot_4rows(w0: &[u8], w1: &[u8], w2: &[u8], w3: &[u8], xq: &[i8]) -> [i32; 4] {
3019 unsafe {
3021 use core::arch::aarch64::*;
3022 use core::arch::asm;
3023 let n = xq.len();
3024 let px = xq.as_ptr();
3025 let (p0, p1, p2, p3) = (
3026 w0.as_ptr() as *const i8,
3027 w1.as_ptr() as *const i8,
3028 w2.as_ptr() as *const i8,
3029 w3.as_ptr() as *const i8,
3030 );
3031 let (mut a0, mut a1, mut a2, mut a3) =
3032 (vdupq_n_s32(0), vdupq_n_s32(0), vdupq_n_s32(0), vdupq_n_s32(0));
3033 let mut i = 0;
3034 while i + 16 <= n {
3035 let x = vld1q_s8(px.add(i));
3036 let v0 = vld1q_s8(p0.add(i));
3037 let v1 = vld1q_s8(p1.add(i));
3038 let v2 = vld1q_s8(p2.add(i));
3039 let v3 = vld1q_s8(p3.add(i));
3040 asm!(
3041 "sdot {a0:v}.4s, {v0:v}.16b, {x:v}.16b",
3042 "sdot {a1:v}.4s, {v1:v}.16b, {x:v}.16b",
3043 "sdot {a2:v}.4s, {v2:v}.16b, {x:v}.16b",
3044 "sdot {a3:v}.4s, {v3:v}.16b, {x:v}.16b",
3045 a0 = inout(vreg) a0, a1 = inout(vreg) a1, a2 = inout(vreg) a2, a3 = inout(vreg) a3,
3046 v0 = in(vreg) v0, v1 = in(vreg) v1, v2 = in(vreg) v2, v3 = in(vreg) v3, x = in(vreg) x,
3047 options(pure, nomem, nostack),
3048 );
3049 i += 16;
3050 }
3051 let mut r = [vaddvq_s32(a0), vaddvq_s32(a1), vaddvq_s32(a2), vaddvq_s32(a3)];
3052 while i < n {
3053 let xi = *px.add(i) as i32;
3054 r[0] += (*p0.add(i)) as i32 * xi;
3055 r[1] += (*p1.add(i)) as i32 * xi;
3056 r[2] += (*p2.add(i)) as i32 * xi;
3057 r[3] += (*p3.add(i)) as i32 * xi;
3058 i += 1;
3059 }
3060 r
3061}
3062}
3063
3064#[cfg(target_arch = "aarch64")]
3068fn q8_range_sdot(
3069 q: &[u8],
3070 row_scale: &[f32],
3071 act: &SplitAct,
3072 cols: usize,
3073 out_addr: SendMut,
3074 start: usize,
3075 end: usize,
3076) {
3077 let mut o = start;
3078 while o + 4 <= end {
3079 let r = unsafe {
3080 dot_i8_sdot_4rows(
3081 &q[o * cols..(o + 1) * cols],
3082 &q[(o + 1) * cols..(o + 2) * cols],
3083 &q[(o + 2) * cols..(o + 3) * cols],
3084 &q[(o + 3) * cols..(o + 4) * cols],
3085 &act.xq,
3086 )
3087 };
3088 for k in 0..4 {
3089 let mut acc = r[k] as f32 * act.sx;
3090 for &(j, xv) in &act.outliers {
3091 acc += (q[(o + k) * cols + j] as i8) as f32 * xv;
3092 }
3093 unsafe { *out_addr.at(o + k) = acc * row_scale[o + k] };
3095 }
3096 o += 4;
3097 }
3098 while o < end {
3099 let v = row_dot_sdot(&q[o * cols..(o + 1) * cols], act) * row_scale[o];
3100 unsafe { *out_addr.at(o) = v };
3101 o += 1;
3102 }
3103}
3104
3105#[cfg(target_arch = "aarch64")]
3108#[allow(clippy::too_many_arguments)]
3109fn q8_range2_sdot(
3110 q: &[u8],
3111 row_scale: &[f32],
3112 a1: &SplitAct,
3113 a2: &SplitAct,
3114 cols: usize,
3115 p1: SendMut,
3116 p2: SendMut,
3117 start: usize,
3118 end: usize,
3119) {
3120 for o in start..end {
3121 let row = &q[o * cols..(o + 1) * cols];
3122 unsafe {
3124 *p1.at(o) = row_dot_sdot(row, a1) * row_scale[o];
3125 *p2.at(o) = row_dot_sdot(row, a2) * row_scale[o];
3126 }
3127 }
3128}
3129
3130#[allow(clippy::too_many_arguments)]
3132fn q8_range2_f32(
3133 q: &[u8],
3134 row_scale: &[f32],
3135 x1: &[f32],
3136 x2: &[f32],
3137 cols: usize,
3138 p1: SendMut,
3139 p2: SendMut,
3140 start: usize,
3141 end: usize,
3142) {
3143 for o in start..end {
3144 let row = &q[o * cols..(o + 1) * cols];
3145 unsafe {
3147 *p1.at(o) = dot_i8_f32(row, x1) * row_scale[o];
3148 *p2.at(o) = dot_i8_f32(row, x2) * row_scale[o];
3149 }
3150 }
3151}
3152
3153fn q8_range_f32(
3155 q: &[u8],
3156 row_scale: &[f32],
3157 xs: &[f32],
3158 cols: usize,
3159 out_addr: SendMut,
3160 start: usize,
3161 end: usize,
3162) {
3163 for o in start..end {
3164 let v = dot_i8_f32(&q[o * cols..(o + 1) * cols], xs) * row_scale[o];
3165 unsafe { *out_addr.at(o) = v };
3167 }
3168}
3169
3170#[cfg(target_arch = "aarch64")]
3173#[inline]
3174fn row_dot_sdot(row: &[u8], act: &SplitAct) -> f32 {
3175 let mut acc = unsafe { dot_i8_sdot(row, &act.xq) } as f32 * act.sx;
3176 for &(j, xv) in &act.outliers {
3177 acc += (row[j] as i8) as f32 * xv;
3178 }
3179 acc
3180}
3181
3182#[cfg(target_arch = "aarch64")]
3190#[target_feature(enable = "neon,dotprod")]
3191unsafe fn dot_q4_row_sdot(packed: &[u8], scales: &[u8], g0: usize, gpr: usize, xq: &[i8]) -> f32 {
3192 unsafe {
3195 use core::arch::aarch64::*;
3196 use core::arch::asm;
3197 let lomask = vdupq_n_u8(0x0F);
3198 let eight = vdupq_n_s8(8);
3199 let mut acc = 0f32;
3200 for gi in 0..gpr {
3201 let g = g0 + gi;
3202 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
3203 let b = vld1q_u8(packed.as_ptr().add(g * 16));
3204 let lo = vandq_u8(b, lomask);
3205 let hi = vshrq_n_u8::<4>(b);
3206 let e0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(lo, hi)), eight);
3207 let e1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(lo, hi)), eight);
3208 let x0 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE));
3209 let x1 = vld1q_s8(xq.as_ptr().add(gi * GROUP_SIZE + 16));
3210 let (mut a0, mut a1) = (vdupq_n_s32(0), vdupq_n_s32(0));
3211 asm!(
3212 "sdot {a0:v}.4s, {e0:v}.16b, {x0:v}.16b",
3213 "sdot {a1:v}.4s, {e1:v}.16b, {x1:v}.16b",
3214 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
3215 e0 = in(vreg) e0, x0 = in(vreg) x0, e1 = in(vreg) e1, x1 = in(vreg) x1,
3216 options(pure, nomem, nostack),
3217 );
3218 acc += vaddvq_s32(vaddq_s32(a0, a1)) as f32 * s;
3219 }
3220 acc
3221 }
3222}
3223
3224#[cfg(target_arch = "aarch64")]
3229#[target_feature(enable = "neon,dotprod")]
3230unsafe fn dot_q4_row_sdot2(
3231 packed: &[u8],
3232 scales: &[u8],
3233 g0: usize,
3234 gpr: usize,
3235 xq1: &[i8],
3236 xq2: &[i8],
3237) -> (f32, f32) {
3238 unsafe {
3241 use core::arch::aarch64::*;
3242 use core::arch::asm;
3243 let lomask = vdupq_n_u8(0x0F);
3244 let eight = vdupq_n_s8(8);
3245 let (mut acc1, mut acc2) = (0f32, 0f32);
3246 for gi in 0..gpr {
3247 let g = g0 + gi;
3248 let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
3249 let b = vld1q_u8(packed.as_ptr().add(g * 16));
3250 let lo = vandq_u8(b, lomask);
3251 let hi = vshrq_n_u8::<4>(b);
3252 let e0 = vsubq_s8(vreinterpretq_s8_u8(vzip1q_u8(lo, hi)), eight);
3253 let e1 = vsubq_s8(vreinterpretq_s8_u8(vzip2q_u8(lo, hi)), eight);
3254 let x10 = vld1q_s8(xq1.as_ptr().add(gi * GROUP_SIZE));
3255 let x11 = vld1q_s8(xq1.as_ptr().add(gi * GROUP_SIZE + 16));
3256 let x20 = vld1q_s8(xq2.as_ptr().add(gi * GROUP_SIZE));
3257 let x21 = vld1q_s8(xq2.as_ptr().add(gi * GROUP_SIZE + 16));
3258 let (mut a0, mut a1, mut b0, mut b1) =
3259 (vdupq_n_s32(0), vdupq_n_s32(0), vdupq_n_s32(0), vdupq_n_s32(0));
3260 asm!(
3261 "sdot {a0:v}.4s, {e0:v}.16b, {x10:v}.16b",
3262 "sdot {a1:v}.4s, {e1:v}.16b, {x11:v}.16b",
3263 "sdot {b0:v}.4s, {e0:v}.16b, {x20:v}.16b",
3264 "sdot {b1:v}.4s, {e1:v}.16b, {x21:v}.16b",
3265 a0 = inout(vreg) a0, a1 = inout(vreg) a1,
3266 b0 = inout(vreg) b0, b1 = inout(vreg) b1,
3267 e0 = in(vreg) e0, e1 = in(vreg) e1,
3268 x10 = in(vreg) x10, x11 = in(vreg) x11,
3269 x20 = in(vreg) x20, x21 = in(vreg) x21,
3270 options(pure, nomem, nostack),
3271 );
3272 acc1 += vaddvq_s32(vaddq_s32(a0, a1)) as f32 * s;
3273 acc2 += vaddvq_s32(vaddq_s32(b0, b1)) as f32 * s;
3274 }
3275 (acc1, acc2)
3276 }
3277}
3278
3279#[inline]
3284pub(crate) fn axpy_i8_f32(acc: &mut [f32], row: &[i8], w: f32) {
3285 #[cfg(target_arch = "aarch64")]
3286 unsafe {
3287 return axpy_i8_f32_neon(acc, row, w);
3288 }
3289 #[cfg(target_arch = "x86_64")]
3290 if avx2_enabled() {
3291 return unsafe { axpy_i8_f32_avx2(acc, row, w) };
3292 }
3293 #[allow(unreachable_code)]
3294 {
3295 for (a, &b) in acc.iter_mut().zip(row) {
3296 *a += w * b as f32;
3297 }
3298 }
3299}
3300
3301#[cfg(target_arch = "x86_64")]
3303#[target_feature(enable = "avx2,fma")]
3304unsafe fn axpy_i8_f32_avx2(acc: &mut [f32], row: &[i8], w: f32) {
3305 unsafe {
3307 use core::arch::x86_64::*;
3308 let n = acc.len().min(row.len());
3309 let ap = acc.as_mut_ptr();
3310 let rp = row.as_ptr();
3311 let wv = _mm256_set1_ps(w);
3312 let mut j = 0usize;
3313 while j + 16 <= n {
3314 let rb = _mm_loadu_si128(rp.add(j) as *const __m128i);
3315 let lo = _mm256_cvtepi32_ps(_mm256_cvtepi8_epi32(rb));
3316 let hi = _mm256_cvtepi32_ps(_mm256_cvtepi8_epi32(_mm_srli_si128::<8>(rb)));
3317 let v0 = _mm256_fmadd_ps(wv, lo, _mm256_loadu_ps(ap.add(j)));
3318 let v1 = _mm256_fmadd_ps(wv, hi, _mm256_loadu_ps(ap.add(j + 8)));
3319 _mm256_storeu_ps(ap.add(j), v0);
3320 _mm256_storeu_ps(ap.add(j + 8), v1);
3321 j += 16;
3322 }
3323 while j < n {
3324 *ap.add(j) += w * (*rp.add(j)) as f32;
3325 j += 1;
3326 }
3327 }
3328}
3329
3330#[cfg(target_arch = "aarch64")]
3331#[target_feature(enable = "neon")]
3332unsafe fn axpy_i8_f32_neon(acc: &mut [f32], row: &[i8], w: f32) {
3333 unsafe {
3335 use core::arch::aarch64::*;
3336 let n = acc.len().min(row.len());
3337 let ap = acc.as_mut_ptr();
3338 let rp = row.as_ptr();
3339 let wv = vdupq_n_f32(w);
3340 let mut j = 0usize;
3341 while j + 16 <= n {
3342 let rb = vld1q_s8(rp.add(j));
3343 let lo = vmovl_s8(vget_low_s8(rb));
3344 let hi = vmovl_s8(vget_high_s8(rb));
3345 for (off, half) in [(0, lo), (8, hi)] {
3346 let f0 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(half)));
3347 let f1 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(half)));
3348 let o = j + off;
3349 vst1q_f32(ap.add(o), vfmaq_f32(vld1q_f32(ap.add(o)), wv, f0));
3350 vst1q_f32(ap.add(o + 4), vfmaq_f32(vld1q_f32(ap.add(o + 4)), wv, f1));
3351 }
3352 j += 16;
3353 }
3354 while j < n {
3355 *ap.add(j) += w * (*rp.add(j)) as f32;
3356 j += 1;
3357 }
3358}
3359}
3360
3361#[inline]
3364pub(crate) fn dot_i8_f32(w: &[u8], x: &[f32]) -> f32 {
3365 #[cfg(target_arch = "aarch64")]
3366 unsafe {
3367 return dot_i8_f32_neon(w, x);
3368 }
3369 #[cfg(target_arch = "x86_64")]
3370 if avx2_enabled() {
3371 return unsafe { dot_i8_f32_avx2(w, x) };
3372 }
3373 #[allow(unreachable_code)]
3374 {
3375 let mut sum = 0.0f32;
3376 for (j, &b) in w.iter().enumerate() {
3377 sum += (b as i8) as f32 * x[j];
3378 }
3379 sum
3380 }
3381}
3382
3383#[inline]
3387fn dot_i8_col_f32(w: &[u8], x: &[f32], col: &[f32]) -> f32 {
3388 #[cfg(target_arch = "aarch64")]
3389 unsafe {
3390 return dot_i8_col_f32_neon(w, x, col);
3391 }
3392 #[allow(unreachable_code)]
3393 {
3394 let mut sum = 0.0f32;
3395 for (j, &b) in w.iter().enumerate() {
3396 sum += (b as i8) as f32 * x[j] * col[j];
3397 }
3398 sum
3399 }
3400}
3401
3402#[cfg(target_arch = "aarch64")]
3403#[target_feature(enable = "neon")]
3404unsafe fn dot_i8_col_f32_neon(w: &[u8], x: &[f32], col: &[f32]) -> f32 {
3405 unsafe {
3407 use core::arch::aarch64::*;
3408 let n = x.len();
3409 let wp = w.as_ptr() as *const i8;
3410 let xp = x.as_ptr();
3411 let cp = col.as_ptr();
3412 let (mut a0, mut a1, mut a2, mut a3) =
3413 (vdupq_n_f32(0.0), vdupq_n_f32(0.0), vdupq_n_f32(0.0), vdupq_n_f32(0.0));
3414 let mut j = 0usize;
3415 while j + 16 <= n {
3416 let wb = vld1q_s8(wp.add(j));
3417 let lo = vmovl_s8(vget_low_s8(wb));
3418 let hi = vmovl_s8(vget_high_s8(wb));
3419 let w0 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(lo)));
3420 let w1 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(lo)));
3421 let w2 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(hi)));
3422 let w3 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(hi)));
3423 a0 = vfmaq_f32(a0, w0, vmulq_f32(vld1q_f32(xp.add(j)), vld1q_f32(cp.add(j))));
3424 a1 = vfmaq_f32(a1, w1, vmulq_f32(vld1q_f32(xp.add(j + 4)), vld1q_f32(cp.add(j + 4))));
3425 a2 = vfmaq_f32(a2, w2, vmulq_f32(vld1q_f32(xp.add(j + 8)), vld1q_f32(cp.add(j + 8))));
3426 a3 = vfmaq_f32(a3, w3, vmulq_f32(vld1q_f32(xp.add(j + 12)), vld1q_f32(cp.add(j + 12))));
3427 j += 16;
3428 }
3429 let mut sum = vaddvq_f32(vaddq_f32(vaddq_f32(a0, a1), vaddq_f32(a2, a3)));
3430 while j < n {
3431 sum += (*wp.add(j)) as f32 * *xp.add(j) * *cp.add(j);
3432 j += 1;
3433 }
3434 sum
3435}
3436}
3437
3438#[cfg(target_arch = "aarch64")]
3439#[target_feature(enable = "neon")]
3440unsafe fn dot_i8_f32_neon(w: &[u8], x: &[f32]) -> f32 {
3441 unsafe {
3443 use core::arch::aarch64::*;
3444 let n = x.len();
3445 let wp = w.as_ptr() as *const i8;
3446 let xp = x.as_ptr();
3447 let (mut a0, mut a1, mut a2, mut a3) =
3448 (vdupq_n_f32(0.0), vdupq_n_f32(0.0), vdupq_n_f32(0.0), vdupq_n_f32(0.0));
3449 let mut j = 0usize;
3450 while j + 16 <= n {
3451 let wb = vld1q_s8(wp.add(j));
3452 let lo = vmovl_s8(vget_low_s8(wb));
3453 let hi = vmovl_s8(vget_high_s8(wb));
3454 let w0 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(lo)));
3455 let w1 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(lo)));
3456 let w2 = vcvtq_f32_s32(vmovl_s16(vget_low_s16(hi)));
3457 let w3 = vcvtq_f32_s32(vmovl_s16(vget_high_s16(hi)));
3458 a0 = vfmaq_f32(a0, w0, vld1q_f32(xp.add(j)));
3459 a1 = vfmaq_f32(a1, w1, vld1q_f32(xp.add(j + 4)));
3460 a2 = vfmaq_f32(a2, w2, vld1q_f32(xp.add(j + 8)));
3461 a3 = vfmaq_f32(a3, w3, vld1q_f32(xp.add(j + 12)));
3462 j += 16;
3463 }
3464 let mut sum = vaddvq_f32(vaddq_f32(vaddq_f32(a0, a1), vaddq_f32(a2, a3)));
3465 while j < n {
3466 sum += (*wp.add(j)) as f32 * *xp.add(j);
3467 j += 1;
3468 }
3469 sum
3470}
3471}
3472
3473#[allow(clippy::too_many_arguments)]
3474fn qmatvec(
3475 q: &[u8],
3476 row_scale: &[f32],
3477 xs: &[f32],
3478 rows: usize,
3479 cols: usize,
3480 out: &mut [f32],
3481 pool: Option<&Pool>,
3482) {
3483 debug_assert_eq!(out.len(), rows);
3484
3485 #[cfg(target_arch = "aarch64")]
3486 if sdot_enabled() {
3487 let act = split_act(xs);
3488 let out_addr = SendMut(out.as_mut_ptr());
3489 let run_range =
3490 |start: usize, end: usize| q8_range_sdot(q, row_scale, &act, cols, out_addr, start, end);
3491 match pool {
3492 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
3493 _ => run_range(0, rows),
3494 }
3495 return;
3496 }
3497 #[cfg(target_arch = "x86_64")]
3500 if avx2_a8w8_enabled() {
3501 let act = split_act(xs);
3502 let out_addr = SendMut(out.as_mut_ptr());
3503 let run_range =
3504 |start: usize, end: usize| q8_range_avx2(q, row_scale, &act, cols, out_addr, start, end);
3505 match pool {
3506 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
3507 _ => run_range(0, rows),
3508 }
3509 return;
3510 }
3511
3512 let row_dot = |o: usize| -> f32 { dot_i8_f32(&q[o * cols..(o + 1) * cols], xs) * row_scale[o] };
3513 match pool {
3514 Some(pool) if rows >= 256 => {
3515 let out_addr = SendMut(out.as_mut_ptr());
3516 pool.run(&move |widx, n| {
3517 let chunk = rows.div_ceil(n);
3518 let start = widx * chunk;
3519 let end = (start + chunk).min(rows);
3520 for o in start..end {
3521 unsafe { *out_addr.at(o) = row_dot(o) };
3523 }
3524 });
3525 }
3526 _ => {
3527 for (o, dst) in out.iter_mut().enumerate() {
3528 *dst = row_dot(o);
3529 }
3530 }
3531 }
3532}
3533
3534#[allow(clippy::too_many_arguments)]
3535fn qmatvec2(
3536 q: &[u8],
3537 row_scale: &[f32],
3538 x1: &[f32],
3539 x2: &[f32],
3540 rows: usize,
3541 cols: usize,
3542 o1: &mut [f32],
3543 o2: &mut [f32],
3544 pool: Option<&Pool>,
3545) {
3546 #[cfg(target_arch = "aarch64")]
3547 if sdot_enabled() {
3548 let a1s = split_act(x1);
3549 let a2s = split_act(x2);
3550 let p1 = SendMut(o1.as_mut_ptr());
3551 let p2 = SendMut(o2.as_mut_ptr());
3552 let run_range = |start: usize, end: usize| {
3553 q8_range2_sdot(q, row_scale, &a1s, &a2s, cols, p1, p2, start, end)
3554 };
3555 match pool {
3556 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
3557 _ => run_range(0, rows),
3558 }
3559 return;
3560 }
3561 #[cfg(target_arch = "x86_64")]
3562 if avx2_a8w8_enabled() {
3563 let a1s = split_act(x1);
3564 let a2s = split_act(x2);
3565 let p1 = SendMut(o1.as_mut_ptr());
3566 let p2 = SendMut(o2.as_mut_ptr());
3567 let run_range = |start: usize, end: usize| {
3568 q8_range2_avx2(q, row_scale, &a1s, &a2s, cols, p1, p2, start, end)
3569 };
3570 match pool {
3571 Some(pool) if rows >= 256 => pool.run_rows(rows, &run_range),
3572 _ => run_range(0, rows),
3573 }
3574 return;
3575 }
3576
3577 let row_dots = |o: usize| -> (f32, f32) {
3578 let row = &q[o * cols..(o + 1) * cols];
3579 (dot_i8_f32(row, x1) * row_scale[o], dot_i8_f32(row, x2) * row_scale[o])
3580 };
3581 match pool {
3582 Some(pool) if rows >= 256 => {
3583 let p1 = SendMut(o1.as_mut_ptr());
3584 let p2 = SendMut(o2.as_mut_ptr());
3585 pool.run(&move |widx, n| {
3586 let chunk = rows.div_ceil(n);
3587 let start = widx * chunk;
3588 let end = (start + chunk).min(rows);
3589 for o in start..end {
3590 let (s1, s2) = row_dots(o);
3591 unsafe {
3593 *p1.at(o) = s1;
3594 *p2.at(o) = s2;
3595 }
3596 }
3597 });
3598 }
3599 _ => {
3600 for o in 0..rows {
3601 let (s1, s2) = row_dots(o);
3602 o1[o] = s1;
3603 o2[o] = s2;
3604 }
3605 }
3606 }
3607}
3608
3609#[derive(Clone, Copy)]
3610struct SendMut(*mut f32);
3611unsafe impl Send for SendMut {}
3612unsafe impl Sync for SendMut {}
3613
3614impl SendMut {
3615 #[inline]
3616 fn at(self, i: usize) -> *mut f32 {
3617 unsafe { self.0.add(i) }
3618 }
3619}
3620
3621#[cfg(test)]
3622mod tests {
3623 use super::*;
3624
3625 #[test]
3626 fn f32_matvec_matches_matvec_rows_bitexact() {
3627 let (rows, cols) = (300, 40);
3628 let w: Vec<f32> = (0..rows * cols).map(|i| (i as f32 * 0.017).sin()).collect();
3629 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.05).cos()).collect();
3630 let qt = QTensor::from_f32(w.clone(), rows, cols);
3631
3632 let mut a = vec![0.0f32; rows];
3633 matvec_rows(None, &w, &x, &mut a);
3634 let mut b = vec![0.0f32; rows];
3635 qt.matvec(&x, &mut b, None);
3636 assert_eq!(a, b);
3637 }
3638
3639 #[test]
3640 fn sdot_kernel_exact_on_grid() {
3641 eprintln!("sdot_enabled = {}", sdot_enabled());
3646 let (rows, cols) = (9, 80); let w: Vec<u8> = (0..rows * cols)
3648 .map(|i| (((i * 37) % 251) as i32 - 125) as i8 as u8)
3649 .collect();
3650 let scales: Vec<f32> = (0..rows).map(|o| 0.005 + o as f32 * 0.001).collect();
3651 let x: Vec<f32> = (0..cols)
3652 .map(|i| match i % 3 {
3653 0 => 1.0,
3654 1 => -1.0,
3655 _ => 0.0,
3656 })
3657 .collect();
3658 let mut a = vec![0.0f32; rows];
3659 qmatvec(&w, &scales, &x, rows, cols, &mut a, None);
3660 for o in 0..rows {
3661 let mut acc = 0.0f32;
3662 for j in 0..cols {
3663 acc += (w[o * cols + j] as i8) as f32 * x[j];
3664 }
3665 let expect = acc * scales[o];
3666 assert!(
3667 (a[o] - expect).abs() < 1e-3 * expect.abs().max(1e-3),
3668 "row {o}: {} vs {expect}",
3669 a[o]
3670 );
3671 }
3672 }
3673
3674 #[test]
3675 fn sdot_a8w8_noise_is_bounded() {
3676 let (rows, cols) = (16, 512);
3680 let w: Vec<u8> = (0..rows * cols)
3681 .map(|i| (((i * 37) % 251) as i32 - 125) as i8 as u8)
3682 .collect();
3683 let scales = vec![0.01f32; rows];
3684 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.21).sin()).collect();
3685 let mut a = vec![0.0f32; rows];
3686 qmatvec(&w, &scales, &x, rows, cols, &mut a, None);
3687 let (mut num, mut den) = (0f64, 0f64);
3688 for o in 0..rows {
3689 let mut acc = 0.0f32;
3690 for j in 0..cols {
3691 acc += (w[o * cols + j] as i8) as f32 * x[j];
3692 }
3693 let expect = acc * scales[o];
3694 num += ((a[o] - expect) as f64).powi(2);
3695 den += (expect as f64).powi(2);
3696 }
3697 let rel = (num / den.max(1e-12)).sqrt();
3698 assert!(rel < 0.05, "A8W8 relative L2 error too high: {rel}");
3699 }
3700
3701 #[test]
3702 fn i8_dot_neon_matches_scalar() {
3703 let n = 100;
3704 let w: Vec<u8> = (0..n).map(|i| ((i * 37 + 11) % 251) as u8).collect();
3705 let x: Vec<f32> = (0..n).map(|i| (i as f32 * 0.13).sin()).collect();
3706 let mut scalar = 0.0f32;
3707 for j in 0..n {
3708 scalar += (w[j] as i8) as f32 * x[j];
3709 }
3710 let fast = dot_i8_f32(&w, &x);
3711 assert!((scalar - fast).abs() < 1e-3 * scalar.abs().max(1.0));
3712 }
3713
3714 #[test]
3716 fn vbitmatvec_matches_full_dequant() {
3717 let (rows, cols) = (6, 64);
3718 let ng = cols / GROUP_SIZE;
3719 let bits: Vec<u8> = vec![3, 4, 5, 6, 8, 4];
3721 let mut bytes = bits.clone();
3722 for g in 0..rows * ng {
3723 let s = 0.02 + 0.001 * g as f32;
3724 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
3725 }
3726 for r in 0..rows {
3727 let b = bits[r] as usize;
3728 let (mut acc, mut nb) = (0u64, 0usize);
3729 let mut rowbytes = Vec::new();
3730 for i in 0..cols {
3731 let v = ((i * 7 + r * 13) % (1 << b)) as u64;
3732 acc = (acc << b) | v;
3733 nb += b;
3734 while nb >= 8 {
3735 nb -= 8;
3736 rowbytes.push(((acc >> nb) & 0xFF) as u8);
3737 }
3738 }
3739 if nb > 0 {
3740 rowbytes.push(((acc << (8 - nb)) & 0xFF) as u8);
3741 }
3742 bytes.extend_from_slice(&rowbytes);
3743 }
3744 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.19).sin()).collect();
3745
3746 let mut reference = vec![0f32; rows * cols];
3747 cortiq_core::quant::dequant_vbit(&bytes, rows, cols, &mut reference).unwrap();
3748 let mut expect = vec![0f32; rows];
3749 for r in 0..rows {
3750 expect[r] = reference[r * cols..(r + 1) * cols]
3751 .iter()
3752 .zip(&x)
3753 .map(|(w, xv)| w * xv)
3754 .sum();
3755 }
3756 let mut got = vec![0f32; rows];
3757 let offsets = vbit_row_offsets(&bytes, rows, cols);
3758 vbitmatvec(&bytes, &offsets, &x, rows, cols, &mut got, None);
3759 let tol = if a8w8_enabled() { 6e-2 } else { 1e-4 };
3763 let scale = expect.iter().fold(0f32, |m, v| m.max(v.abs())).max(1e-6);
3764 for r in 0..rows {
3765 assert!(
3766 (got[r] - expect[r]).abs() < tol * scale,
3767 "row {r}: {} vs {}",
3768 got[r],
3769 expect[r]
3770 );
3771 }
3772 }
3773
3774 #[test]
3777 fn q4matvec_matches_full_dequant() {
3778 let (rows, cols) = (8, 64);
3779 let groups = rows * cols / GROUP_SIZE;
3780 let mut bytes = Vec::with_capacity(groups * 16 + groups * 2);
3782 for i in 0..groups * 16 {
3783 bytes.push((((i * 7 + 3) % 256) & 0xFF) as u8);
3784 }
3785 for g in 0..groups {
3786 let s = 0.01 + 0.003 * g as f32;
3787 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
3788 }
3789 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.17).sin()).collect();
3790
3791 let mut reference = vec![0.0f32; rows * cols];
3792 cortiq_core::quant::dequant_q4_block(&bytes, &mut reference);
3793 let mut expect = vec![0.0f32; rows];
3794 for r in 0..rows {
3795 expect[r] = reference[r * cols..(r + 1) * cols]
3796 .iter()
3797 .zip(&x)
3798 .map(|(w, xv)| w * xv)
3799 .sum();
3800 }
3801
3802 let mut got = vec![0.0f32; rows];
3803 q4matvec(&bytes, &x, rows, cols, &mut got, None);
3804 let tol = if a8w8_enabled() { 6e-2 } else { 1e-4 };
3808 let scale = expect.iter().fold(0f32, |m, v| m.max(v.abs())).max(1.0);
3809 for r in 0..rows {
3810 assert!(
3811 (got[r] - expect[r]).abs() < tol * scale,
3812 "row {r}: {} vs {}",
3813 got[r],
3814 expect[r]
3815 );
3816 }
3817 }
3818
3819 #[test]
3822 fn vbitmatvec2_equals_two_singles() {
3823 let (rows, cols) = (6, 64);
3824 let ng = cols / GROUP_SIZE;
3825 let bits: Vec<u8> = vec![3, 4, 5, 6, 8, 4];
3826 let mut bytes = bits.clone();
3827 for g in 0..rows * ng {
3828 let s = 0.02 + 0.001 * g as f32;
3829 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
3830 }
3831 for r in 0..rows {
3832 let b = bits[r] as usize;
3833 let (mut acc, mut nb) = (0u64, 0usize);
3834 let mut rowbytes = Vec::new();
3835 for i in 0..cols {
3836 let v = ((i * 7 + r * 13) % (1 << b)) as u64;
3837 acc = (acc << b) | v;
3838 nb += b;
3839 while nb >= 8 {
3840 nb -= 8;
3841 rowbytes.push(((acc >> nb) & 0xFF) as u8);
3842 }
3843 }
3844 if nb > 0 {
3845 rowbytes.push(((acc << (8 - nb)) & 0xFF) as u8);
3846 }
3847 bytes.extend_from_slice(&rowbytes);
3848 }
3849 let x1: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.19).sin()).collect();
3850 let x2: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.11).cos()).collect();
3851 let offsets = vbit_row_offsets(&bytes, rows, cols);
3852
3853 let (mut a1, mut a2) = (vec![0f32; rows], vec![0f32; rows]);
3854 vbitmatvec(&bytes, &offsets, &x1, rows, cols, &mut a1, None);
3855 vbitmatvec(&bytes, &offsets, &x2, rows, cols, &mut a2, None);
3856 let (mut b1, mut b2) = (vec![0f32; rows], vec![0f32; rows]);
3857 vbitmatvec2(&bytes, &offsets, &x1, &x2, rows, cols, &mut b1, &mut b2, None);
3858 assert_eq!(a1, b1, "fused vbit lane 1 must be bit-identical");
3859 assert_eq!(a2, b2, "fused vbit lane 2 must be bit-identical");
3860 }
3861
3862 #[test]
3864 fn q4matvec2_equals_two_singles() {
3865 let (rows, cols) = (8, 128);
3866 let groups = rows * cols / GROUP_SIZE;
3867 let mut bytes = Vec::with_capacity(groups * 16 + groups * 2);
3868 for i in 0..groups * 16 {
3869 bytes.push((((i * 7 + 3) % 256) & 0xFF) as u8);
3870 }
3871 for g in 0..groups {
3872 let s = 0.01 + 0.003 * g as f32;
3873 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
3874 }
3875 let mut x1: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.17).sin()).collect();
3878 x1[9] = 250.0;
3879 let x2: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.23).cos()).collect();
3880
3881 let (mut a1, mut a2) = (vec![0f32; rows], vec![0f32; rows]);
3882 q4matvec(&bytes, &x1, rows, cols, &mut a1, None);
3883 q4matvec(&bytes, &x2, rows, cols, &mut a2, None);
3884 let (mut b1, mut b2) = (vec![0f32; rows], vec![0f32; rows]);
3885 q4matvec2(&bytes, &x1, &x2, rows, cols, &mut b1, &mut b2, None);
3886 assert_eq!(a1, b1, "fused q4 lane 1 must be bit-identical");
3887 assert_eq!(a2, b2, "fused q4 lane 2 must be bit-identical");
3888 }
3889
3890 #[test]
3893 fn matvec_many_equals_separate_matvecs() {
3894 use crate::pool::Pool;
3895 let (r1, r2, cols) = (300, 200, 64);
3896 let mk = |salt: usize, rows: usize| {
3897 QTensor::from_f32(
3898 (0..rows * cols).map(|i| ((i * 7 + salt) % 97) as f32 / 97.0 - 0.5).collect(),
3899 rows,
3900 cols,
3901 )
3902 };
3903 let (a, b) = (mk(1, r1), mk(5, r2));
3904 let x: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.11).sin()).collect();
3905 let pool = Pool::new(3);
3906
3907 let (mut ea, mut eb) = (vec![0f32; r1], vec![0f32; r2]);
3908 a.matvec(&x, &mut ea, Some(&pool));
3909 b.matvec(&x, &mut eb, Some(&pool));
3910 let (mut ga, mut gb) = (vec![0f32; r1], vec![0f32; r2]);
3911 QTensor::matvec_many([&a, &b], &x, [&mut ga, &mut gb], Some(&pool));
3912 assert_eq!(ea, ga, "fused multi-matrix lane 1 must be bit-identical");
3913 assert_eq!(eb, gb, "fused multi-matrix lane 2 must be bit-identical");
3914 }
3915
3916 #[test]
3919 fn batched_matmat_equals_per_position_matvec() {
3920 let (rows, cols, b) = (8, 64, 5);
3921 let groups = rows * cols / GROUP_SIZE;
3923 let mut q4 = Vec::new();
3924 for i in 0..groups * 16 {
3925 q4.push((((i * 7 + 3) % 256) & 0xFF) as u8);
3926 }
3927 for g in 0..groups {
3928 q4.extend_from_slice(
3929 &cortiq_core::quant::f32_to_f16(0.01 + 0.003 * g as f32).to_le_bytes(),
3930 );
3931 }
3932 let ng = cols / GROUP_SIZE;
3934 let bits: Vec<u8> = vec![3, 4, 5, 6, 8, 4, 5, 3];
3935 let mut vb = bits.clone();
3936 for g in 0..rows * ng {
3937 vb.extend_from_slice(
3938 &cortiq_core::quant::f32_to_f16(0.02 + 0.001 * g as f32).to_le_bytes(),
3939 );
3940 }
3941 for r in 0..rows {
3942 let bw = bits[r] as usize;
3943 let (mut acc, mut nb) = (0u64, 0usize);
3944 let mut rowbytes = Vec::new();
3945 for i in 0..cols {
3946 let v = ((i * 7 + r * 13) % (1 << bw)) as u64;
3947 acc = (acc << bw) | v;
3948 nb += bw;
3949 while nb >= 8 {
3950 nb -= 8;
3951 rowbytes.push(((acc >> nb) & 0xFF) as u8);
3952 }
3953 }
3954 if nb > 0 {
3955 rowbytes.push(((acc << (8 - nb)) & 0xFF) as u8);
3956 }
3957 vb.extend_from_slice(&rowbytes);
3958 }
3959 let offsets = vbit_row_offsets(&vb, rows, cols);
3960
3961 let xs: Vec<f32> = (0..b * cols).map(|i| (i as f32 * 0.13).sin()).collect();
3962
3963 let mut got = vec![0f32; b * rows];
3965 q4matmat(&q4, &xs, b, rows, cols, &mut got, None);
3966 for bi in 0..b {
3967 let mut expect = vec![0f32; rows];
3968 q4matvec(&q4, &xs[bi * cols..(bi + 1) * cols], rows, cols, &mut expect, None);
3969 assert_eq!(&got[bi * rows..(bi + 1) * rows], &expect[..], "q4 batch pos {bi}");
3970 }
3971
3972 let mut got = vec![0f32; b * rows];
3974 vbitmatmat(&vb, &offsets, &xs, b, rows, cols, &mut got, None);
3975 for bi in 0..b {
3976 let mut expect = vec![0f32; rows];
3977 vbitmatvec(
3978 &vb, &offsets, &xs[bi * cols..(bi + 1) * cols], rows, cols, &mut expect, None,
3979 );
3980 assert_eq!(&got[bi * rows..(bi + 1) * rows], &expect[..], "vbit batch pos {bi}");
3981 }
3982 }
3983
3984 #[test]
3988 fn q4_tiled_matches_q4_block_bitexact() {
3989 let (rows, cols, b) = (8usize, 128usize, 3usize);
3990 let groups = rows * cols / GROUP_SIZE;
3991 let mut split = Vec::with_capacity(groups * 18);
3992 for i in 0..groups * 16 {
3993 split.push((((i * 7 + 3) % 256) & 0xFF) as u8);
3994 }
3995 for g in 0..groups {
3996 split.extend_from_slice(
3997 &cortiq_core::quant::f32_to_f16(0.01 + 0.003 * g as f32).to_le_bytes(),
3998 );
3999 }
4000 let (packed, scales) = split.split_at(groups * 16);
4002 let mut tiled = Vec::with_capacity(groups * Q4_TILE);
4003 for g in 0..groups {
4004 tiled.extend_from_slice(&scales[g * 2..g * 2 + 2]);
4005 tiled.extend_from_slice(&packed[g * 16..(g + 1) * 16]);
4006 }
4007
4008 let mut x1: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.17).sin()).collect();
4009 x1[9] = 250.0; let x2: Vec<f32> = (0..cols).map(|i| (i as f32 * 0.23).cos()).collect();
4011
4012 let (mut a, mut t) = (vec![0f32; rows], vec![0f32; rows]);
4013 q4matvec(&split, &x1, rows, cols, &mut a, None);
4014 q4t_matvec(&tiled, &x1, rows, cols, &mut t, None);
4015 assert_eq!(a, t, "q4t matvec must match q4 bit-for-bit");
4016
4017 let (mut a1, mut a2) = (vec![0f32; rows], vec![0f32; rows]);
4018 let (mut t1, mut t2) = (vec![0f32; rows], vec![0f32; rows]);
4019 q4matvec2(&split, &x1, &x2, rows, cols, &mut a1, &mut a2, None);
4020 q4t_matvec2(&tiled, &x1, &x2, rows, cols, &mut t1, &mut t2, None);
4021 assert_eq!(a1, t1);
4022 assert_eq!(a2, t2);
4023
4024 let xs: Vec<f32> = (0..b * cols).map(|i| (i as f32 * 0.13).sin()).collect();
4025 let (mut am, mut tm) = (vec![0f32; b * rows], vec![0f32; b * rows]);
4026 q4matmat(&split, &xs, b, rows, cols, &mut am, None);
4027 q4t_matmat(&tiled, &xs, b, rows, cols, &mut tm, None);
4028 assert_eq!(am, tm, "q4t matmat must match q4 bit-for-bit");
4029 }
4030
4031 #[test]
4038 fn q4matvec_sdot_outlier_exact() {
4039 let (rows, cols) = (4, 128);
4040 let groups = rows * cols / GROUP_SIZE;
4041 let mut bytes = Vec::with_capacity(groups * 16 + groups * 2);
4042 for i in 0..groups * 16 {
4043 bytes.push(((i * 11 + 5) % 256) as u8);
4044 }
4045 for g in 0..groups {
4046 let s = 0.02 + 0.002 * g as f32;
4047 bytes.extend_from_slice(&cortiq_core::quant::f32_to_f16(s).to_le_bytes());
4048 }
4049 let mut x: Vec<f32> = (0..cols)
4050 .map(|i| match i % 3 {
4051 0 => 1.0,
4052 1 => -1.0,
4053 _ => 0.0,
4054 })
4055 .collect();
4056 x[17] = 300.0; let mut reference = vec![0.0f32; rows * cols];
4059 cortiq_core::quant::dequant_q4_block(&bytes, &mut reference);
4060 let mut expect = vec![0.0f32; rows];
4061 for r in 0..rows {
4062 expect[r] = reference[r * cols..(r + 1) * cols]
4063 .iter()
4064 .zip(&x)
4065 .map(|(w, xv)| w * xv)
4066 .sum();
4067 }
4068 let mut got = vec![0.0f32; rows];
4069 q4matvec(&bytes, &x, rows, cols, &mut got, None);
4070 let scale = expect.iter().fold(0f32, |m, v| m.max(v.abs())).max(1.0);
4071 for r in 0..rows {
4072 assert!(
4073 (got[r] - expect[r]).abs() < 2e-3 * scale,
4074 "row {r}: {} vs {} (outlier term must be exact)",
4075 got[r],
4076 expect[r]
4077 );
4078 }
4079 }
4080}