1use std::cell::RefCell;
5use std::marker::PhantomData;
6use std::mem::MaybeUninit;
7use std::ops::{Add, Mul, Range};
8
9use rayon::prelude::*;
10use rten_base::byte_cast::FromByteArray;
11use rten_base::iter::range_chunks;
12use rten_base::num::Identities;
13use rten_parallel::par_iter::MaybeParIter;
14use rten_tensor::layout::{MutLayout, OverlapPolicy};
15use rten_tensor::prelude::*;
16use rten_tensor::storage::{Alloc, GlobalAlloc};
17use rten_tensor::{
18 AssumeInit, Matrix, MatrixLayout, MatrixMut, NdLayout, NdTensor, NdTensorView, Storage,
19};
20
21mod block_quant;
22mod errors;
23mod i8dot;
24mod im2col;
25mod kernels;
26mod packing;
27mod prepack;
28mod tiles;
29
30pub use block_quant::{BlockQuantizedGemm, BlockQuantizedMatrix, ComputeMode};
31pub use errors::{BlockQuantizedError, GemmError};
32pub use im2col::{ColOffsets, Im2Col, RowOffsets};
33pub use kernels::QuantParams;
34use kernels::generic::GenericKernel;
35use kernels::{Kernel, MatVecOutput};
36use packing::PackingBuffer;
37pub use prepack::{PackedAMatrix, PackedBMatrix};
38use tiles::OutputTiles;
39
40pub type GemmResult<T = ()> = Result<T, GemmError>;
41
42#[derive(Copy, Clone)]
44pub enum GemmInputA<'a, T> {
45 Unpacked(Matrix<'a, T>),
47
48 Packed(&'a PackedAMatrix<T>),
50}
51
52impl<T> GemmInputA<'_, T> {
53 pub fn rows(&self) -> usize {
54 match self {
55 Self::Unpacked(m) => m.rows(),
56 Self::Packed(pm) => pm.rows(),
57 }
58 }
59
60 pub fn cols(&self) -> usize {
61 match self {
62 Self::Unpacked(m) => m.cols(),
63 Self::Packed(pm) => pm.cols(),
64 }
65 }
66}
67
68pub trait GemmInT: Copy + Default + Send + Sync + Identities + FromByteArray {}
70impl GemmInT for i8 {}
71impl GemmInT for u8 {}
72impl GemmInT for f32 {}
73
74pub trait GemmOutT:
76 Copy
77 + Default
78 + PartialEq
79 + Send
80 + Sync
81 + Mul<Self, Output = Self>
82 + Add<Self, Output = Self>
83 + Identities
84 + FromByteArray
85{
86}
87impl GemmOutT for i32 {}
88impl GemmOutT for f32 {}
89
90#[derive(Copy, Clone)]
92pub enum GemmInputB<'a, T> {
93 Unpacked(Matrix<'a, T>),
95
96 Packed(&'a PackedBMatrix<T>),
98
99 Im2Col(&'a Im2Col<'a, T>),
104
105 BlockQuantized(BlockQuantizedMatrix<'a, T>),
110}
111
112impl<T: Copy + Default> GemmInputB<'_, T> {
113 pub fn rows(&self) -> usize {
114 match self {
115 Self::Unpacked(m) => m.rows(),
116 Self::Packed(pm) => pm.rows(),
117 Self::Im2Col(im) => im.rows(),
118 Self::BlockQuantized(m) => m.rows(),
119 }
120 }
121
122 pub fn cols(&self) -> usize {
123 match self {
124 Self::Unpacked(m) => m.cols(),
125 Self::Packed(pm) => pm.cols(),
126 Self::Im2Col(im) => im.cols(),
127 Self::BlockQuantized(m) => m.cols(),
128 }
129 }
130}
131
132#[derive(Copy, Clone, PartialEq, Debug)]
134pub enum BiasVector<'a, T> {
135 Column(&'a [T]),
138
139 Row(&'a [T]),
142}
143
144#[derive(Clone, Copy, Debug)]
146enum F32KernelType {
147 #[allow(unused)]
149 Generic,
150
151 #[cfg(target_arch = "x86_64")]
153 Fma,
154
155 #[cfg(target_arch = "x86_64")]
157 Avx512,
158
159 #[cfg(target_arch = "aarch64")]
161 ArmNeon,
162
163 #[cfg(target_arch = "wasm32")]
165 #[cfg(target_feature = "simd128")]
166 Wasm,
167}
168
169#[derive(Clone, Copy, Debug)]
170enum Int8KernelType {
171 #[allow(unused)]
172 Generic,
173
174 #[cfg(target_arch = "x86_64")]
175 Avx2,
176 #[cfg(target_arch = "x86_64")]
177 Avx512,
178
179 #[cfg(target_arch = "aarch64")]
180 ArmI8mm,
181 #[cfg(target_arch = "aarch64")]
182 ArmDot,
183 #[cfg(target_arch = "aarch64")]
184 ArmNeon,
185
186 #[cfg(target_arch = "wasm32")]
187 #[cfg(target_feature = "simd128")]
188 Wasm,
189}
190
191pub struct GemmExecutor<LhsT: GemmInT = f32, RhsT: GemmInT = LhsT, OutT: GemmOutT = LhsT> {
200 kernel: Box<dyn Kernel<LhsT, RhsT, OutT>>,
201}
202
203impl<LhsT: GemmInT, RhsT: GemmInT, OutT: GemmOutT> GemmExecutor<LhsT, RhsT, OutT> {
204 pub fn new() -> Self
205 where
206 Self: Default,
207 {
208 Self::default()
209 }
210
211 #[allow(dead_code)]
213 pub fn kernel_name(&self) -> &str {
214 self.kernel.name()
215 }
216
217 #[allow(unused)]
219 pub fn prepack_a(&self, a: Matrix<LhsT>) -> PackedAMatrix<LhsT> {
220 self.prepack_a_in(GlobalAlloc::new(), a)
221 }
222
223 pub fn prepack_a_in<A: Alloc>(&self, alloc: A, a: Matrix<LhsT>) -> PackedAMatrix<LhsT> {
226 prepack::prepack_a(&*self.kernel, alloc, a)
227 }
228
229 pub fn im2col_col_count_step(&self) -> usize {
233 self.kernel.im2col_col_count_step()
234 }
235
236 pub fn im2col_row_count_step(&self) -> usize {
240 self.kernel.im2col_row_count_step()
241 }
242
243 #[allow(unused)]
245 pub fn prepack_b(&self, b: Matrix<RhsT>) -> PackedBMatrix<RhsT> {
246 self.prepack_b_in(GlobalAlloc::new(), b)
247 }
248
249 pub fn prepack_b_in<A: Alloc>(&self, alloc: A, b: Matrix<RhsT>) -> PackedBMatrix<RhsT> {
252 prepack::prepack_b(&*self.kernel, alloc, b)
253 }
254
255 pub fn gemm(
264 &self,
265 out_data: &mut [OutT],
266 a: GemmInputA<LhsT>,
267 b: GemmInputB<RhsT>,
268 opts: GemmOptions<LhsT, RhsT, OutT>,
269 ) -> GemmResult {
270 let GemmOptions {
271 alpha,
272 beta,
273 bias,
274 a_quant,
275 b_quant,
276 } = opts;
277
278 gemm_impl(
279 &*self.kernel,
280 unsafe { std::mem::transmute::<&mut [OutT], &mut [MaybeUninit<OutT>]>(out_data) },
282 a,
283 b,
284 alpha,
285 beta,
286 bias,
287 a_quant,
288 b_quant,
289 )
290 .map(|_| ())
291 }
292
293 pub fn gemm_uninit<'a>(
298 &self,
299 out_data: &'a mut [MaybeUninit<OutT>],
300 a: GemmInputA<LhsT>,
301 b: GemmInputB<RhsT>,
302 opts: GemmUninitOptions<LhsT, RhsT, OutT>,
303 ) -> GemmResult<&'a mut [OutT]> {
304 let GemmUninitOptions {
305 alpha,
306 bias,
307 a_quant,
308 b_quant,
309 } = opts;
310
311 gemm_impl(
312 &*self.kernel,
313 out_data,
314 a,
315 b,
316 alpha,
317 OutT::zero(),
318 bias,
319 a_quant,
320 b_quant,
321 )
322 }
323
324 pub fn batched_gemm_uninit<'a>(
330 &self,
331 out_data: &'a mut [MaybeUninit<OutT>],
332 a: &[GemmInputA<LhsT>],
333 b: &[GemmInputB<RhsT>],
334 opts: GemmUninitOptions<LhsT, RhsT, OutT>,
335 ) -> GemmResult<&'a mut [OutT]> {
336 if a.len() != b.len() {
337 return Err(GemmError::BatchSizeMismatch);
338 }
339
340 let out_mat_stride = match (a, b) {
341 ([a, ..], [b, ..]) => a.rows() * b.cols(),
342 _ => 0,
343 };
344
345 if a.len() * out_mat_stride != out_data.len() {
346 return Err(GemmError::OutputSizeMismatch);
347 }
348
349 match (a, b) {
350 ([], []) => {
351 Ok(unsafe { out_data.assume_init() })
353 }
354 ([a], [b]) => {
355 self.gemm_uninit(out_data, *a, *b, opts)
357 }
358 (a, b) => {
359 a.par_iter()
360 .zip(b)
361 .zip(out_data.par_chunks_mut(out_mat_stride))
362 .try_for_each(|((a_mat, b_mat), out_mat)| {
363 self.gemm_uninit(out_mat, *a_mat, *b_mat, opts.clone())
364 .map(|_| ())
365 })?;
366
367 Ok(unsafe { out_data.assume_init() })
370 }
371 }
372 }
373
374 pub fn may_saturate(&self) -> bool {
383 self.kernel.may_saturate()
384 }
385
386 fn from_kernel<K: Kernel<LhsT, RhsT, OutT> + 'static>() -> Option<Self> {
387 K::new().map(|kernel| GemmExecutor {
388 kernel: Box::new(kernel),
389 })
390 }
391}
392
393#[derive(Clone)]
394pub struct GemmOptions<'a, LhsT, RhsT, OutT: Default> {
395 pub alpha: f32,
397
398 pub beta: OutT,
400
401 pub bias: Option<BiasVector<'a, OutT>>,
403
404 pub a_quant: Option<QuantParams<'a, LhsT>>,
406
407 pub b_quant: Option<QuantParams<'a, RhsT>>,
409}
410
411impl<'a, LhsT, RhsT, OutT: Default> Default for GemmOptions<'a, LhsT, RhsT, OutT> {
412 fn default() -> Self {
413 Self {
414 alpha: 1.0,
415 beta: OutT::default(),
416 bias: None,
417 a_quant: None,
418 b_quant: None,
419 }
420 }
421}
422
423#[derive(Clone)]
428pub struct GemmUninitOptions<'a, LhsT, RhsT, OutT> {
429 pub alpha: f32,
431
432 pub bias: Option<BiasVector<'a, OutT>>,
434
435 pub a_quant: Option<QuantParams<'a, LhsT>>,
437
438 pub b_quant: Option<QuantParams<'a, RhsT>>,
440}
441
442impl<'a, LhsT, RhsT, OutT> Default for GemmUninitOptions<'a, LhsT, RhsT, OutT> {
443 fn default() -> Self {
444 Self {
445 alpha: 1.0,
446 bias: None,
447 a_quant: None,
448 b_quant: None,
449 }
450 }
451}
452
453macro_rules! try_kernel {
455 ($hint:expr) => {
456 if let Some(gemm) = Self::with_kernel($hint) {
457 return gemm;
458 }
459 };
460}
461
462trait WithKernel: Sized {
467 type KernelType;
469
470 fn with_kernel(kern_type: Self::KernelType) -> Option<Self>;
473
474 fn with_generic_kernel() -> Self;
476
477 #[allow(unused)]
479 fn kernel_types() -> Vec<Self::KernelType>;
480}
481
482impl WithKernel for GemmExecutor<f32, f32, f32> {
483 type KernelType = F32KernelType;
484
485 #[allow(dead_code)] fn with_kernel(hint: F32KernelType) -> Option<Self> {
489 match hint {
490 #[cfg(target_arch = "x86_64")]
491 F32KernelType::Avx512 => Self::from_kernel::<kernels::x86_64::Avx512Kernel>(),
492 #[cfg(target_arch = "x86_64")]
493 F32KernelType::Fma => Self::from_kernel::<kernels::x86_64::FmaKernel>(),
494 #[cfg(target_arch = "aarch64")]
495 F32KernelType::ArmNeon => Self::from_kernel::<kernels::aarch64::ArmNeonKernel>(),
496 #[cfg(target_arch = "wasm32")]
497 #[cfg(target_feature = "simd128")]
498 F32KernelType::Wasm => Self::from_kernel::<kernels::wasm::WasmKernel>(),
499 F32KernelType::Generic => Some(Self::with_generic_kernel()),
500 }
501 }
502
503 fn kernel_types() -> Vec<F32KernelType> {
504 let mut types = Vec::new();
505
506 #[cfg(target_arch = "x86_64")]
507 {
508 types.push(F32KernelType::Avx512);
509 types.push(F32KernelType::Fma);
510 }
511
512 #[cfg(target_arch = "aarch64")]
513 {
514 types.push(F32KernelType::ArmNeon);
515 }
516
517 #[cfg(target_arch = "wasm32")]
518 #[cfg(target_feature = "simd128")]
519 {
520 types.push(F32KernelType::Wasm);
521 }
522
523 types.push(F32KernelType::Generic);
524
525 types
526 }
527
528 fn with_generic_kernel() -> Self {
530 Self::from_kernel::<GenericKernel>().unwrap()
531 }
532}
533
534impl Default for GemmExecutor<f32, f32, f32> {
535 fn default() -> Self {
536 #[cfg(target_arch = "x86_64")]
537 try_kernel!(F32KernelType::Avx512);
538 #[cfg(target_arch = "x86_64")]
539 try_kernel!(F32KernelType::Fma);
540 #[cfg(target_arch = "aarch64")]
541 try_kernel!(F32KernelType::ArmNeon);
542 #[cfg(target_arch = "wasm32")]
543 #[cfg(target_feature = "simd128")]
544 try_kernel!(F32KernelType::Wasm);
545 Self::with_generic_kernel()
546 }
547}
548
549impl WithKernel for GemmExecutor<u8, i8, i32> {
550 type KernelType = Int8KernelType;
551
552 fn with_kernel(hint: Int8KernelType) -> Option<Self> {
553 match hint {
554 #[cfg(target_arch = "x86_64")]
555 Int8KernelType::Avx512 => Self::from_kernel::<kernels::x86_64::Avx512Int8Kernel>(),
556 #[cfg(target_arch = "x86_64")]
557 Int8KernelType::Avx2 => Self::from_kernel::<kernels::x86_64::Avx2Int8Kernel>(),
558 #[cfg(target_arch = "aarch64")]
559 Int8KernelType::ArmNeon => Self::from_kernel::<kernels::aarch64::ArmInt8MlalKernel>(),
560 #[cfg(target_arch = "aarch64")]
561 Int8KernelType::ArmDot => Self::from_kernel::<kernels::aarch64::ArmInt8DotKernel>(),
562 #[cfg(target_arch = "aarch64")]
563 Int8KernelType::ArmI8mm => Self::from_kernel::<kernels::aarch64::ArmInt8MMKernel>(),
564 #[cfg(target_arch = "wasm32")]
565 #[cfg(target_feature = "simd128")]
566 Int8KernelType::Wasm => Self::from_kernel::<kernels::wasm::WasmInt8Kernel>(),
567 Int8KernelType::Generic => Self::from_kernel::<GenericKernel>(),
568 }
569 }
570
571 fn kernel_types() -> Vec<Int8KernelType> {
572 let mut types = Vec::new();
573
574 #[cfg(target_arch = "x86_64")]
575 {
576 types.push(Int8KernelType::Avx512);
577 types.push(Int8KernelType::Avx2);
578 }
579
580 #[cfg(target_arch = "aarch64")]
581 {
582 types.push(Int8KernelType::ArmI8mm);
583 types.push(Int8KernelType::ArmDot);
584 types.push(Int8KernelType::ArmNeon);
585 }
586
587 #[cfg(target_arch = "wasm32")]
588 #[cfg(target_feature = "simd128")]
589 {
590 types.push(Int8KernelType::Wasm);
591 }
592
593 types.push(Int8KernelType::Generic);
594
595 types
596 }
597
598 fn with_generic_kernel() -> Self {
599 Self::from_kernel::<GenericKernel>().unwrap()
600 }
601}
602
603impl Default for GemmExecutor<u8, i8, i32> {
604 fn default() -> Self {
605 #[cfg(target_arch = "x86_64")]
606 {
607 try_kernel!(Int8KernelType::Avx512);
608 try_kernel!(Int8KernelType::Avx2);
609 }
610 #[cfg(target_arch = "aarch64")]
611 {
612 try_kernel!(Int8KernelType::ArmI8mm);
613 try_kernel!(Int8KernelType::ArmDot);
614 try_kernel!(Int8KernelType::ArmNeon);
615 }
616 #[cfg(target_arch = "wasm32")]
617 #[cfg(target_feature = "simd128")]
618 {
619 try_kernel!(Int8KernelType::Wasm);
620 }
621 Self::with_generic_kernel()
622 }
623}
624
625fn depth_block_size<RhsT>(a_cols: usize, min_size: Option<usize>) -> usize {
631 let max = 1024 / size_of::<RhsT>();
632 max.min(a_cols).max(min_size.unwrap_or(0))
633}
634
635fn col_block_size(b_cols: usize, nr: usize) -> usize {
647 let parallelism = rayon::current_num_threads();
648 let lower_bound = 128.min(b_cols);
649 let unrounded = (b_cols / parallelism).max(lower_bound).min(1024);
650 unrounded.next_multiple_of(nr)
651}
652
653fn row_block_size(a_rows: usize, mr: usize) -> usize {
662 64.min(a_rows).next_multiple_of(mr)
663}
664
665fn gemv<'a, LhsT: GemmInT, RhsT: GemmInT, OutT: GemmOutT>(
669 kernel: &dyn Kernel<LhsT, RhsT, OutT>,
670 a: NdTensorView<LhsT, 1>,
671 b: Matrix<RhsT>,
672 out_data: &'a mut [MaybeUninit<OutT>],
673 alpha: f32,
674 beta: OutT,
675 bias: Option<BiasVector<OutT>>,
676 a_quant: Option<QuantParams<LhsT>>,
677 b_quant: Option<QuantParams<RhsT>>,
678) -> &'a mut [OutT] {
679 let a_cols = a.size(0);
680 let b_cols = b.cols();
681
682 let a = a.to_contiguous();
683 let a_data = a.data();
684
685 let b_block_size = b_cols.div_ceil(rayon::current_num_threads()).max(128);
693 let k_block_size = if b.row_stride() == 1 { 512 } else { 8 };
694
695 out_data
696 .par_chunks_mut(b_block_size)
697 .enumerate()
698 .for_each(|(col_block_idx, out_chunk)| {
699 let col_block =
700 (col_block_idx * b_block_size)..((col_block_idx + 1) * b_block_size).min(b_cols);
701 let mut effective_beta = beta;
702
703 let b_quant = b_quant.map(|bq| QuantParams {
704 zero_point: &bq.zero_point[col_block.clone()],
705 });
706
707 for (k_block, a_block) in
708 range_chunks(0..a_cols, k_block_size).zip(a_data.chunks(k_block_size))
709 {
710 let b_block = slice_matrix(b, k_block, col_block.clone());
711
712 let mat_vec_out = if effective_beta == OutT::zero() {
713 MatVecOutput::from_uninit_slice(out_chunk)
714 } else {
715 MatVecOutput::from_slice(unsafe { out_chunk.assume_init() }, effective_beta)
717 };
718
719 kernel.gemv_kernel(mat_vec_out, a_block, b_block, alpha, a_quant, b_quant);
720
721 effective_beta = OutT::one();
724 }
725
726 let out_chunk = unsafe { out_chunk.assume_init() };
728 match bias {
729 Some(BiasVector::Column(bias)) => {
730 let bias = bias[0];
731 for x in out_chunk {
732 *x = *x + bias;
733 }
734 }
735 Some(BiasVector::Row(bias)) => {
736 let bias_block = &bias[col_block.clone()];
737 for (x, bias) in out_chunk.iter_mut().zip(bias_block) {
738 *x = *x + *bias;
739 }
740 }
741 None => {}
742 }
743 });
744
745 unsafe { out_data.assume_init() }
747}
748
749fn slice_matrix<T>(mat: Matrix<T>, rows: Range<usize>, cols: Range<usize>) -> Matrix<T> {
754 let layout = NdLayout::from_shape_and_strides(
755 [rows.len(), cols.len()],
756 [mat.row_stride(), mat.col_stride()],
757 OverlapPolicy::AllowOverlap,
758 )
759 .unwrap();
760 let offset = rows.start * mat.row_stride() + cols.start * mat.col_stride();
761 Matrix::from_storage_and_layout(
762 mat.storage().slice(offset..offset + layout.min_data_len()),
763 layout,
764 )
765}
766
767fn gemm_impl<'a, LhsT: GemmInT, RhsT: GemmInT, OutT: GemmOutT>(
795 kernel: &dyn Kernel<LhsT, RhsT, OutT>,
796 out_data: &'a mut [MaybeUninit<OutT>],
797 a: GemmInputA<LhsT>,
798 b: GemmInputB<RhsT>,
799 alpha: f32,
800 beta: OutT,
801 bias: Option<BiasVector<OutT>>,
802 a_quant: Option<QuantParams<LhsT>>,
803 b_quant: Option<QuantParams<RhsT>>,
804) -> GemmResult<&'a mut [OutT]> {
805 if a.cols() != b.rows() {
806 return Err(GemmError::KSizeMismatch);
807 }
808
809 let bias_ok = match bias {
810 Some(BiasVector::Row(bias)) => bias.len() == b.cols(),
811 Some(BiasVector::Column(bias)) => bias.len() == a.rows(),
812 None => true,
813 };
814 if !bias_ok {
815 return Err(GemmError::WrongBiasSize);
816 }
817
818 if let Some(a_quant) = a_quant
819 && a_quant.zero_point.len() != a.rows()
820 {
821 return Err(GemmError::WrongQuantParamSize);
822 }
823
824 if let Some(b_quant) = b_quant
825 && b_quant.zero_point.len() != b.cols()
826 {
827 return Err(GemmError::WrongQuantParamSize);
828 }
829
830 let mut output_mat =
832 MatrixMut::<MaybeUninit<OutT>>::try_from_data([a.rows(), b.cols()], out_data)
833 .map_err(|_| GemmError::OutputSizeMismatch)?;
834
835 if a.rows() == 0 || b.cols() == 0 {
837 let empty = NdTensor::zeros(output_mat.shape());
838 return Ok(output_mat.init_from(&empty).into_slice_mut().unwrap());
839 }
840
841 if a.cols() == 0 {
844 let mut output_mat = if beta == OutT::zero() {
845 output_mat.fill(MaybeUninit::new(OutT::zero()));
846
847 unsafe { output_mat.assume_init() }
849 } else {
850 let mut output_mat = unsafe { output_mat.assume_init() };
852 output_mat.apply(|x| *x * beta);
853 output_mat
854 };
855
856 if let Some(bias) = bias {
857 let bias_mat = match bias {
858 BiasVector::Column(bias) => {
859 NdTensorView::from_data([a.rows(), 1], bias).broadcast([a.rows(), b.cols()])
860 }
861 BiasVector::Row(bias) => {
862 NdTensorView::from_data([1, b.cols()], bias).broadcast([a.rows(), b.cols()])
863 }
864 };
865 for r in 0..a.rows() {
866 for c in 0..b.cols() {
867 let out_el = &mut output_mat[[r, c]];
868 *out_el = *out_el + bias_mat[[r, c]];
869 }
870 }
871 }
872 return Ok(output_mat.into_slice_mut().unwrap());
873 }
874
875 if let (1, GemmInputA::Unpacked(a), GemmInputB::Unpacked(b)) = (a.rows(), a, b) {
877 let output = gemv(
878 kernel,
879 a.slice(0),
880 b,
881 output_mat.into_slice_mut().unwrap(),
882 alpha,
883 beta,
884 bias,
887 a_quant,
888 b_quant,
889 );
890 return Ok(output);
891 }
892
893 let output_tiles = OutputTiles::new(output_mat.view_mut(), kernel.mr(), kernel.nr());
894
895 let depth_min = match &b {
898 GemmInputB::BlockQuantized(mat) => Some(mat.elements_per_block()),
899 _ => None,
900 };
901
902 let nc = col_block_size(b.cols(), kernel.nr());
908 let mc = row_block_size(a.rows(), kernel.mr());
909 let kc = depth_block_size::<RhsT>(a.cols(), depth_min);
910
911 if let GemmInputA::Packed(packed) = &a {
914 packed.validate(kernel, kc)?;
915 }
916 if let GemmInputB::Packed(packed) = &b {
917 packed.validate(kernel, kc)?;
918 }
919
920 let rhs_packer = match b {
921 GemmInputB::BlockQuantized(mat) => {
922 let packer = kernel
923 .pack_block_quant(mat)
924 .ok_or(GemmError::BlockQuantizedInputNotSupported)?;
925 Some(packer)
926 }
927 _ => None,
928 };
929
930 thread_local!(static PACKED_A: RefCell<PackingBuffer> = const { RefCell::new(PackingBuffer::new()) });
932 thread_local!(static PACKED_B: RefCell<PackingBuffer> = const { RefCell::new(PackingBuffer::new()) });
933
934 let n_col_blocks = b.cols().div_ceil(nc);
935 let n_row_blocks = a.rows().div_ceil(mc);
936
937 let parallel = rayon::current_num_threads() > 1;
940
941 let (mr, nr) = (kernel.mr(), kernel.nr());
942
943 (0..n_col_blocks)
945 .maybe_par_iter(parallel)
946 .for_each(|col_idx| {
947 let col_start = col_idx * nc;
948 let col_end = (col_start + nc).min(b.cols());
949 let col_range = col_start..col_end;
950
951 for (depth_block_idx, depth_range) in range_chunks(0..a.cols(), kc).enumerate() {
954 let mut thread_local_packed_b: Option<PackingBuffer> = None;
957
958 let rhs_block = match b {
959 GemmInputB::Unpacked(_)
960 | GemmInputB::Im2Col(_)
961 | GemmInputB::BlockQuantized(_) => PACKED_B.with(|cell| {
962 let mut packed_b = cell.take();
963
964 let layout =
965 kernel.packed_b_layout(depth_range.len(), col_end - col_start, b_quant);
966 let packed_uninit = packed_b.alloc(layout.size(), layout.align());
967
968 match b {
969 GemmInputB::Unpacked(b) => kernel.pack_b_block(
970 packed_uninit,
971 b,
972 depth_range.clone(),
973 col_start..col_end,
974 b_quant,
975 ),
976 GemmInputB::Im2Col(im) => kernel.pack_im2col(
977 packed_uninit,
978 im,
979 depth_range.clone(),
980 col_start..col_end,
981 b_quant.map(|q| q.zero_point[0]),
985 ),
986 GemmInputB::BlockQuantized(_) => rhs_packer
987 .as_deref()
988 .expect("should have packer")
989 .pack(packed_uninit, depth_range.clone(), col_start..col_end),
990 GemmInputB::Packed(_) => unreachable!(),
991 }
992
993 unsafe {
995 packed_b.set_len(layout.size());
996 }
997 thread_local_packed_b = Some(packed_b);
998 RhsBlock {
999 data: thread_local_packed_b.as_ref().unwrap().as_bytes(),
1000 panel_stride: layout.panel_stride(),
1001 _marker: PhantomData,
1002 }
1003 }),
1004 GemmInputB::Packed(pm) => pm.block(col_range.clone(), depth_block_idx),
1005 };
1006
1007 let effective_beta = if depth_range.start == 0 {
1010 beta
1011 } else {
1012 OutT::one()
1013 };
1014
1015 (0..n_row_blocks)
1017 .maybe_par_iter(parallel)
1018 .for_each(|row_idx| {
1019 let row_start = row_idx * mc;
1020 let row_end = (row_start + mc).min(a.rows());
1021 let row_range = row_start..row_end;
1022
1023 let mut thread_local_packed_a: Option<PackingBuffer> = None;
1026
1027 let lhs_block = match a {
1028 GemmInputA::Unpacked(a) => PACKED_A.with(|cell| {
1029 let layout = kernel.packed_a_layout(
1030 a,
1031 row_end - row_start,
1032 depth_range.len(),
1033 a_quant,
1034 );
1035 if !layout.must_pack {
1036 return LhsBlock::Unpacked(a);
1037 };
1038
1039 let mut packed_a = cell.take();
1040 let packed_uninit = packed_a.alloc(layout.size(), layout.align());
1041
1042 kernel.pack_a_block(
1043 packed_uninit,
1044 a,
1045 row_start..row_end,
1046 depth_range.clone(),
1047 a_quant,
1048 );
1049
1050 unsafe {
1052 packed_a.set_len(layout.size());
1053 }
1054 thread_local_packed_a = Some(packed_a);
1055 LhsBlock::Packed {
1056 data: thread_local_packed_a.as_ref().unwrap().as_bytes(),
1057 panel_stride: layout.panel_stride(),
1058 }
1059 }),
1060 GemmInputA::Packed(pm) => pm.block(row_range.clone(), depth_block_idx),
1061 };
1062
1063 gemm_block(
1064 kernel,
1065 &output_tiles,
1066 col_start / nr..col_end.div_ceil(nr),
1067 row_start / mr..row_end.div_ceil(mr),
1068 depth_range.clone(),
1069 lhs_block,
1070 rhs_block,
1071 alpha,
1072 effective_beta,
1073 bias,
1074 a_quant,
1075 b_quant,
1076 );
1077
1078 if let Some(packed_a) = thread_local_packed_a {
1079 PACKED_A.with(|cell| cell.replace(packed_a));
1080 }
1081 });
1082
1083 if let Some(packed_b) = thread_local_packed_b {
1084 PACKED_B.with(|cell| cell.replace(packed_b));
1085 }
1086 }
1087 });
1088
1089 let output = unsafe { output_mat.assume_init() };
1091
1092 Ok(output.into_slice_mut().unwrap())
1093}
1094
1095#[derive(Copy, Clone)]
1097enum LhsBlock<'a, T> {
1098 Packed {
1100 data: &'a [u8],
1101
1102 panel_stride: usize,
1104 },
1105
1106 Unpacked(Matrix<'a, T>),
1108}
1109
1110#[derive(Copy, Clone)]
1113struct RhsBlock<'a, T> {
1114 data: &'a [u8],
1115
1116 panel_stride: usize,
1118
1119 _marker: PhantomData<T>,
1120}
1121
1122fn gemm_block<LhsT: Sync, RhsT: Sync, OutT: GemmOutT>(
1129 kernel: &dyn Kernel<LhsT, RhsT, OutT>,
1130 output: &OutputTiles<MaybeUninit<OutT>>,
1131 col_tiles: Range<usize>,
1132 row_tiles: Range<usize>,
1133 depth_range: Range<usize>,
1134 a: LhsBlock<LhsT>,
1135 b: RhsBlock<RhsT>,
1136 alpha: f32,
1137 beta: OutT,
1138 bias: Option<BiasVector<OutT>>,
1139 a_quant: Option<QuantParams<LhsT>>,
1140 b_quant: Option<QuantParams<RhsT>>,
1141) {
1142 let (mr, nr) = (kernel.mr(), kernel.nr());
1143
1144 if let LhsBlock::Unpacked(mat) = &a {
1146 assert!(mat.rows().div_ceil(mr) >= row_tiles.end);
1147 assert!(mat.cols() >= depth_range.end);
1148 }
1149
1150 col_tiles
1152 .enumerate()
1153 .for_each(|(block_col_tile, col_tile)| {
1154 let b_panel_offset = block_col_tile * b.panel_stride;
1155 let b_panel = &b.data[b_panel_offset..b_panel_offset + b.panel_stride];
1156 let b_quant_tile = b_quant.map(|bq| {
1157 let col_range = col_tile * nr..(col_tile * nr + nr).min(bq.zero_point.len());
1158 QuantParams {
1159 zero_point: &bq.zero_point[col_range],
1160 }
1161 });
1162
1163 for (block_row_tile, row_tile) in row_tiles.clone().enumerate() {
1165 let out_tile = unsafe { output.tile(row_tile, col_tile) };
1169
1170 let a_quant_tile = a_quant.map(|aq| {
1171 let row_range = row_tile * mr..(row_tile * mr + mr).min(aq.zero_point.len());
1172 QuantParams {
1173 zero_point: &aq.zero_point[row_range],
1174 }
1175 });
1176
1177 let kernel_lhs = match a {
1178 LhsBlock::Packed {
1179 data,
1180 panel_stride,
1181 } => {
1183 let a_panel_offset = block_row_tile * panel_stride;
1184 let a_panel = &data[a_panel_offset..a_panel_offset + panel_stride];
1185 kernels::Lhs::Packed(a_panel)
1186 }
1187 LhsBlock::Unpacked(mat) => {
1188 let storage = mat.storage();
1189 let offset =
1190 row_tile * mr * mat.row_stride() + depth_range.start * mat.col_stride();
1191 kernels::Lhs::Unpacked {
1192 data: unsafe { storage.as_ptr().add(offset) },
1195 len: storage.len().saturating_sub(offset),
1196 row_stride: mat.row_stride(),
1197 }
1198 }
1199 };
1200
1201 unsafe {
1205 kernel.kernel(
1206 out_tile.ptr as *mut OutT,
1207 out_tile.row_stride,
1208 kernel_lhs,
1209 b_panel,
1210 out_tile.used_rows,
1211 out_tile.used_cols,
1212 depth_range.len(),
1213 alpha,
1214 beta,
1215 a_quant_tile,
1216 b_quant_tile,
1217 );
1218 }
1219
1220 if depth_range.start == 0 {
1222 let out_ptr = out_tile.ptr as *mut OutT;
1225 match bias {
1226 Some(BiasVector::Column(bias)) => {
1227 for row in 0..out_tile.used_rows {
1228 for col in 0..out_tile.used_cols {
1229 unsafe {
1233 let out_el = out_ptr.add(row * out_tile.row_stride + col);
1234 *out_el =
1235 *out_el + *bias.get_unchecked(row_tile * mr + row);
1236 }
1237 }
1238 }
1239 }
1240 Some(BiasVector::Row(bias)) => {
1241 for row in 0..out_tile.used_rows {
1242 for col in 0..out_tile.used_cols {
1243 unsafe {
1247 let out_el = out_ptr.add(row * out_tile.row_stride + col);
1248 *out_el =
1249 *out_el + *bias.get_unchecked(col_tile * nr + col);
1250 }
1251 }
1252 }
1253 }
1254 None => {}
1255 }
1256 }
1257 }
1258 });
1259}
1260
1261mod reduced_range_rng;
1262
1263#[doc(hidden)]
1265pub use reduced_range_rng::ReducedRangeRng;
1266
1267#[cfg(test)]
1268mod tests;