use std::num::NonZeroUsize;
use diskann_wide::{
SIMDMinMax, SIMDMulAdd, SIMDVector,
arch::{Architecture, Scalar},
};
use crate::matrix_kernels::{
Cache,
blocks::{packed, unpacked},
bounds, driver,
num::{Bytes, DimK, Elements, value_or_one},
ptr::{MutSlice, Slice},
util::{self, Fold, Folder},
};
#[derive(Debug, Clone, Copy)]
pub(super) struct Params {
pub(super) a_panels_in_l2: NonZeroUsize,
pub(super) b_cols_in_l1: NonZeroUsize,
}
impl Params {
pub(super) fn new(cache: Cache, a_panel: Bytes, b_col: Bytes, nr: usize) -> Self {
let a_panels_in_l2 = value_or_one(cache.l2().get() / a_panel.value());
let b_budget = cache.l1().get().saturating_sub(a_panel.value()).max(1);
let b_cols_in_l1 = value_or_one(nr * (b_budget.div_ceil(nr * b_col.value())));
Self {
a_panels_in_l2,
b_cols_in_l1,
}
}
}
pub(crate) struct Driver<'a, A, const MR: usize, const NR: usize> {
arch: A,
a: packed::View<'a, f32, MR>,
b: unpacked::View<'a, f32>,
c: &'a mut [f32],
k: DimK,
params: Params,
}
impl<'a, A, const MR: usize, const NR: usize> Driver<'a, A, MR, NR> {
pub(crate) unsafe fn new(
arch: A,
a: packed::View<'a, f32, MR>,
b: unpacked::View<'a, f32>,
c: &'a mut [f32],
k: DimK,
cache: Cache,
) -> Self {
bounds::check_eq!(a.k(), k, "contraction dimensions do not agree");
bounds::check_eq!(b.k(), k, "contraction dimensions do not agree");
bounds::check_eq!(
bounds::Bound::new(a.blocks().get()),
c.len().div_ceil(MR),
"output length must occupy exactly the packed A blocks",
);
unsafe {
Self::new_inner(
arch,
a,
b,
c,
k,
Params::new(cache, a.block_stride(k).bytes(), b.stride(k).bytes(), NR),
)
}
}
unsafe fn new_inner(
arch: A,
a: packed::View<'a, f32, MR>,
b: unpacked::View<'a, f32>,
c: &'a mut [f32],
k: DimK,
params: Params,
) -> Self {
bounds::check_eq!(a.k(), k, "contraction dimensions do not agree");
bounds::check_eq!(b.k(), k, "contraction dimensions do not agree");
bounds::check_eq!(
bounds::Bound::new(a.blocks().get()),
c.len().div_ceil(MR),
"output length must occupy exactly the packed A blocks",
);
Self {
arch,
a,
b,
c,
k,
params,
}
}
}
impl<A, const MR: usize, const NR: usize> driver::Drive for Driver<'_, A, MR, NR>
where
A: util::LoadStore<f32, MR> + Architecture,
for<'a> PanelKernel<'a, A, MR, NR>: driver::PanelKernel,
{
fn drive(&mut self) {
self.arch.run(
#[inline]
|| {
self.c.fill(f32::NEG_INFINITY);
let remainder = self.c.len() % MR;
let last_a_block = self.a.blocks().get() - 1;
let mut c = MutSlice::new(self.c);
let on_a_panels = |a_panels: packed::View<'_, f32, MR>, a_block_base| {
let on_b_panels = |b_panels: unpacked::View<'_, f32>, _| {
let panel_kernel = |a_panel: packed::Panel<'_, f32, MR>, a_block_offset| {
let a_block = a_block_base + a_block_offset;
let handling_tail = a_block == last_a_block && remainder != 0;
let bound =
bounds::Bound::from_fn(
|| if handling_tail { remainder } else { MR },
);
let mut region = unsafe { c.subslice(MR * a_block, bound) };
let c = if handling_tail {
util::LoadStore::<f32, MR>::load(
self.arch,
unsafe { region.as_std_slice(remainder) },
)
} else {
unsafe { *region.as_array::<MR>() }
};
let mut kernel = unsafe {
PanelKernel::new(self.arch, a_panel, b_panels, c, self.k)
};
driver::PanelKernel::panel_kernel(&mut kernel);
let c_final = kernel.take();
if handling_tail {
util::LoadStore::<f32, MR>::store(
self.arch,
c_final,
unsafe { region.as_std_mut_slice(remainder) },
);
} else {
unsafe { *region.as_array::<MR>() = c_final };
}
};
unsafe {
a_panels.visit_panels(self.k, panel_kernel);
}
};
unsafe {
self.b
.visit_sub_views(self.params.b_cols_in_l1, self.k, on_b_panels);
}
};
unsafe {
self.a
.visit_sub_views(self.params.a_panels_in_l2, self.k, on_a_panels)
};
},
);
}
}
#[derive(Debug)]
pub(super) struct PanelKernel<'a, A, const MR: usize, const NR: usize> {
arch: A,
a: packed::Panel<'a, f32, MR>,
b: unpacked::View<'a, f32>,
c: [f32; MR],
k: DimK,
}
impl<'a, A, const MR: usize, const NR: usize> PanelKernel<'a, A, MR, NR> {
pub(super) unsafe fn new(
arch: A,
a: packed::Panel<'a, f32, MR>,
b: unpacked::View<'a, f32>,
c: [f32; MR],
k: DimK,
) -> Self {
bounds::check_eq!(a.k(), k);
bounds::check_eq!(b.k(), k);
Self { arch, a, b, c, k }
}
pub(super) fn take(self) -> [f32; MR] {
self.c
}
}
#[derive(Debug)]
struct Visitor<'a, A, const MR: usize, const NR: usize> {
arch: A,
a: packed::Panel<'a, f32, MR>,
c: &'a mut [f32; MR],
k: DimK,
}
impl<A, const MR: usize, const NR: usize> unpacked::PanelVisitor<f32, NR> for Visitor<'_, A, MR, NR>
where
A: Copy,
for<'a> MicroKernel<'a, A, MR, NR>: driver::MicroKernel,
{
#[inline(always)]
fn visit(&mut self, b: unpacked::Panel<'_, f32, NR>, _: usize) {
let mut micro = unsafe { MicroKernel::new(self.arch, self.a, b, self.c, self.k) };
driver::MicroKernel::micro_kernel(&mut micro);
}
}
macro_rules! panel_kernel {
($arch:ty, $mr:literal, $nr: literal, [ $($ns:literal),+ $(,)? ]) => {
impl driver::PanelKernel for PanelKernel<'_, $arch, $mr, $nr> {
#[inline(always)]
fn panel_kernel(&mut self) {
let on_b_panels = Visitor {
arch: self.arch,
a: self.a,
c: &mut self.c,
k: self.k,
};
let b_tail = unsafe { self.b.visit_panels::<$nr>(self.k, on_b_panels) };
if let Some(b_tail) = b_tail {
$(
const { assert!($ns < $nr) };
if let Some(b_panel) = b_tail.try_as_panel::<$ns>() {
let mut micro = unsafe {
MicroKernel::new(
self.arch,
self.a,
b_panel,
&mut self.c,
self.k,
)
};
driver::MicroKernel::micro_kernel(&mut micro);
}
)+
}
}
}
}
}
panel_kernel!(Scalar, 8, 2, [1]);
struct MicroKernel<'a, A, const MR: usize, const NR: usize> {
arch: A,
a: packed::Panel<'a, f32, MR>,
b: unpacked::Panel<'a, f32, NR>,
c: &'a mut [f32; MR],
k: DimK,
}
impl<'a, A, const MR: usize, const NR: usize> MicroKernel<'a, A, MR, NR> {
unsafe fn new(
arch: A,
a: packed::Panel<'a, f32, MR>,
b: unpacked::Panel<'a, f32, NR>,
c: &'a mut [f32; MR],
k: DimK,
) -> Self {
bounds::check_eq!(a.k(), k);
bounds::check_eq!(b.k(), k);
Self { arch, a, b, c, k }
}
}
#[inline(always)]
unsafe fn micro_kernel<W, const MR: usize, const NR: usize>(
wide: W,
a: packed::Panel<'_, f32, MR>,
b: unpacked::Panel<'_, f32, NR>,
c: &mut [f32; MR],
k: DimK,
) where
W: ExtraWide<MR>,
Folder: Fold<NR>,
{
bounds::check_eq!(a.k(), k);
bounds::check_eq!(b.k(), k);
let ap = a.as_ptr();
let bp = b.as_ptr();
let mut acc = [wide.default(); NR];
let astride = Elements::<f32>::new(MR);
let bstride = b.stride(k);
for i in 0..k.value().get() {
let ai = unsafe { wide.load(ap.add(astride * i).truncate(astride)) };
for (j, acc) in acc.iter_mut().enumerate() {
let bj =
wide.splat(*unsafe { bp.add(bstride * j + Elements::new(i)).as_unit().as_ref() });
*acc = W::mul_add_splat(ai, bj, *acc);
}
}
wide.max_into(Folder::fold(acc, W::max), c);
}
diskann_wide::alias!(f32x4<A> = f32x4);
diskann_wide::alias!(f32x8<A> = f32x8);
diskann_wide::alias!(f32x16<A> = f32x16);
macro_rules! micro_kernel {
($arch:ty, $mr:literal, $nr:literal) => {
impl driver::MicroKernel for MicroKernel<'_, $arch, $mr, $nr> {
#[inline(always)]
fn micro_kernel(&mut self) {
unsafe { micro_kernel(self.arch, self.a, self.b, self.c, self.k) }
}
}
};
($arch:ty, $mr:literal, { $($nr:literal),+ $(,)? }) => {
$(micro_kernel!($arch, $mr, $nr);)+
}
}
micro_kernel!(Scalar, 8, { 2, 1 });
trait ExtraWide<const ELEMENTS: usize>: Copy {
type Wide: Copy;
type Splat: Copy;
unsafe fn load(self, slice: Slice<'_, f32>) -> Self::Wide;
fn default(self) -> Self::Wide;
fn splat(self, value: f32) -> Self::Splat;
fn mul_add_splat(a: Self::Wide, b: Self::Splat, acc: Self::Wide) -> Self::Wide;
fn max(lhs: Self::Wide, rhs: Self::Wide) -> Self::Wide;
fn max_into(self, max: Self::Wide, into: &mut [f32; ELEMENTS]);
}
impl ExtraWide<8> for Scalar {
type Wide = [f32x4<Scalar>; 2];
type Splat = f32x4<Scalar>;
#[inline(always)]
fn default(self) -> Self::Wide {
[SIMDVector::default(self), SIMDVector::default(self)]
}
#[inline(always)]
unsafe fn load(self, slice: Slice<'_, f32>) -> Self::Wide {
bounds::check_eq!(slice.len(), 8);
unsafe {
[
SIMDVector::load_simd(self, slice.as_ptr()),
SIMDVector::load_simd(self, slice.add(Elements::new(4)).as_ptr()),
]
}
}
#[inline(always)]
fn splat(self, value: f32) -> Self::Splat {
SIMDVector::splat(self, value)
}
#[inline(always)]
fn mul_add_splat(a: Self::Wide, b: Self::Splat, acc: Self::Wide) -> Self::Wide {
core::array::from_fn(|i| (a[i] * b) + acc[i])
}
#[inline(always)]
fn max(lhs: Self::Wide, rhs: Self::Wide) -> Self::Wide {
core::array::from_fn(|i| lhs[i].max_simd(rhs[i]))
}
#[inline(always)]
fn max_into(self, lhs: Self::Wide, into: &mut [f32; 8]) {
let previous = unsafe { self.load(Slice::new(into)) };
let max = Self::max(lhs, previous);
unsafe {
max[0].store_simd(into.as_mut_ptr());
max[1].store_simd(into.as_mut_ptr().add(4));
}
}
}
#[cfg(target_arch = "x86_64")]
mod x86_64 {
use super::*;
use diskann_wide::arch::x86_64::{V3, V4};
panel_kernel!(V3, 16, 6, [1, 2, 3, 4, 5]);
panel_kernel!(V4, 16, 6, [1, 2, 3, 4, 5]);
panel_kernel!(V4, 32, 6, [1, 2, 3, 4, 5]);
micro_kernel!(V3, 16, { 6, 5, 4, 3, 2, 1 });
micro_kernel!(V4, 16, { 6, 5, 4, 3, 2, 1 });
micro_kernel!(V4, 32, { 6, 5, 4, 3, 2, 1 });
impl ExtraWide<16> for V3 {
type Wide = [f32x8<V3>; 2];
type Splat = f32x8<V3>;
#[inline(always)]
fn default(self) -> Self::Wide {
[SIMDVector::default(self), SIMDVector::default(self)]
}
#[inline(always)]
unsafe fn load(self, slice: Slice<'_, f32>) -> Self::Wide {
bounds::check_eq!(slice.len(), 16);
unsafe {
[
SIMDVector::load_simd(self, slice.as_ptr()),
SIMDVector::load_simd(self, slice.add(Elements::new(8)).as_ptr()),
]
}
}
#[inline(always)]
fn splat(self, value: f32) -> Self::Splat {
SIMDVector::splat(self, value)
}
#[inline(always)]
fn mul_add_splat(a: Self::Wide, b: Self::Splat, acc: Self::Wide) -> Self::Wide {
core::array::from_fn(|i| a[i].mul_add_simd(b, acc[i]))
}
#[inline(always)]
fn max(lhs: Self::Wide, rhs: Self::Wide) -> Self::Wide {
core::array::from_fn(|i| lhs[i].max_simd(rhs[i]))
}
#[inline(always)]
fn max_into(self, lhs: Self::Wide, into: &mut [f32; 16]) {
let previous = unsafe { self.load(Slice::new(into)) };
let max = Self::max(lhs, previous);
unsafe {
max[0].store_simd(into.as_mut_ptr());
max[1].store_simd(into.as_mut_ptr().add(8));
}
}
}
impl ExtraWide<16> for V4 {
type Wide = f32x16<V4>;
type Splat = f32x16<V4>;
#[inline(always)]
fn default(self) -> Self::Wide {
SIMDVector::default(self)
}
#[inline(always)]
unsafe fn load(self, slice: Slice<'_, f32>) -> Self::Wide {
bounds::check_eq!(slice.len(), 16);
unsafe { SIMDVector::load_simd(self, slice.as_ptr()) }
}
#[inline(always)]
fn splat(self, value: f32) -> Self::Splat {
SIMDVector::splat(self, value)
}
#[inline(always)]
fn mul_add_splat(a: Self::Wide, b: Self::Splat, acc: Self::Wide) -> Self::Wide {
a.mul_add_simd(b, acc)
}
#[inline(always)]
fn max(lhs: Self::Wide, rhs: Self::Wide) -> Self::Wide {
lhs.max_simd(rhs)
}
#[inline(always)]
fn max_into(self, lhs: Self::Wide, into: &mut [f32; 16]) {
let previous = unsafe { ExtraWide::<16>::load(self, Slice::new(into)) };
let max = <Self as ExtraWide<16>>::max(lhs, previous);
unsafe {
max.store_simd(into.as_mut_ptr());
}
}
}
impl ExtraWide<32> for V4 {
type Wide = [f32x16<V4>; 2];
type Splat = f32x16<V4>;
#[inline(always)]
fn default(self) -> Self::Wide {
[SIMDVector::default(self), SIMDVector::default(self)]
}
#[inline(always)]
unsafe fn load(self, slice: Slice<'_, f32>) -> Self::Wide {
bounds::check_eq!(slice.len(), 32);
unsafe {
[
SIMDVector::load_simd(self, slice.as_ptr()),
SIMDVector::load_simd(self, slice.add(Elements::new(16)).as_ptr()),
]
}
}
#[inline(always)]
fn splat(self, value: f32) -> Self::Splat {
SIMDVector::splat(self, value)
}
#[inline(always)]
fn mul_add_splat(a: Self::Wide, b: Self::Splat, acc: Self::Wide) -> Self::Wide {
core::array::from_fn(|i| a[i].mul_add_simd(b, acc[i]))
}
#[inline(always)]
fn max(lhs: Self::Wide, rhs: Self::Wide) -> Self::Wide {
core::array::from_fn(|i| lhs[i].max_simd(rhs[i]))
}
#[inline(always)]
fn max_into(self, lhs: Self::Wide, into: &mut [f32; 32]) {
let previous = unsafe { ExtraWide::<32>::load(self, Slice::new(into)) };
let max = <Self as ExtraWide<32>>::max(lhs, previous);
unsafe {
max[0].store_simd(into.as_mut_ptr());
max[1].store_simd(into.as_mut_ptr().add(16));
}
}
}
}
#[cfg(target_arch = "aarch64")]
mod aarch64 {
use super::*;
use diskann_wide::arch::aarch64::Neon;
panel_kernel!(Neon, 8, 6, [1, 2, 3, 4, 5]);
micro_kernel!(Neon, 8, { 6, 5, 4, 3, 2, 1 });
impl ExtraWide<8> for Neon {
type Wide = [f32x4<Neon>; 2];
type Splat = f32x4<Neon>;
#[inline(always)]
fn default(self) -> Self::Wide {
[SIMDVector::default(self), SIMDVector::default(self)]
}
#[inline(always)]
unsafe fn load(self, slice: Slice<'_, f32>) -> Self::Wide {
bounds::check_eq!(slice.len(), 8);
unsafe {
[
SIMDVector::load_simd(self, slice.as_ptr()),
SIMDVector::load_simd(self, slice.add(Elements::new(4)).as_ptr()),
]
}
}
#[inline(always)]
fn splat(self, value: f32) -> Self::Splat {
SIMDVector::splat(self, value)
}
#[inline(always)]
fn mul_add_splat(a: Self::Wide, b: Self::Splat, acc: Self::Wide) -> Self::Wide {
core::array::from_fn(|i| a[i].mul_add_simd(b, acc[i]))
}
#[inline(always)]
fn max(lhs: Self::Wide, rhs: Self::Wide) -> Self::Wide {
core::array::from_fn(|i| lhs[i].max_simd(rhs[i]))
}
#[inline(always)]
fn max_into(self, lhs: Self::Wide, into: &mut [f32; 8]) {
let previous = unsafe { self.load(Slice::new(into)) };
let max = Self::max(lhs, previous);
unsafe {
max[0].store_simd(into.as_mut_ptr());
max[1].store_simd(into.as_mut_ptr().add(4));
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use diskann_utils::views::rowmajor::Matrix;
use rand::{SeedableRng, rngs::StdRng};
#[cfg(target_arch = "x86_64")]
use diskann_wide::arch::x86_64::{V3, V4};
#[cfg(target_arch = "aarch64")]
use diskann_wide::arch::aarch64::Neon;
use crate::{matrix_kernels::maxsim, multi_vector::BlockTransposed};
fn test_micro_kernel<A, const MR: usize, const NR: usize>(
arch: A,
k: DimK,
rng: &mut impl rand::Rng,
ctx: std::fmt::Arguments<'_>,
) where
for<'a> MicroKernel<'a, A, MR, NR>: driver::MicroKernel,
{
let (ref_a, ref_b, ref_c) = maxsim::test::generate_f32(MR, k.value().get(), NR, rng);
let ref_a = ref_a.transpose();
let ref_b = ref_b.transpose();
let mut c = [f32::NEG_INFINITY; MR];
let mut kernel = unsafe {
MicroKernel::new(
arch,
packed::Panel::new(Slice::new(ref_a.as_slice()), k),
unpacked::Panel::new(Slice::new(ref_b.as_slice()), k),
&mut c,
k,
)
};
driver::MicroKernel::micro_kernel(&mut kernel);
assert_eq!(&*ref_c, kernel.c, "{ctx}");
let new_c = kernel.c.map(|i| i + 1.0);
*kernel.c = new_c;
driver::MicroKernel::micro_kernel(&mut kernel);
assert_eq!(new_c, *kernel.c, "{ctx}");
}
macro_rules! test_micro_kernel {
(
$fn:ident,
$arch:expr,
$seed:literal,
$(
$MR:literal => { $($NR:literal),+ $(,)? }
),+ $(,)?
) => {
#[test]
fn $fn() {
if let Some(arch) = $arch {
let mut rng = StdRng::seed_from_u64($seed);
for k in [1, 2, 5, 8] {
let k = DimK::new(NonZeroUsize::new(k).unwrap());
$(
$(
test_micro_kernel::<_, $MR, $NR>(
arch,
k,
&mut rng,
format_args!("k = {:?}", k),
);
)+
)+
}
}
}
}
}
test_micro_kernel!(
test_micro_kernel_scalar,
Some(Scalar::new()),
0x759722e1a83e4566,
8 => { 2, 1 },
);
#[cfg(target_arch = "x86_64")]
test_micro_kernel!(
test_micro_kernel_v3,
V3::new_checked(),
0x157f59d99f648437,
16 => { 6, 5, 4, 3, 2, 1},
);
#[cfg(target_arch = "x86_64")]
test_micro_kernel!(
test_micro_kernel_v4,
V4::new_checked_miri(),
0xca13f736977f96fe,
16 => { 6, 5, 4, 3, 2, 1},
32 => { 6, 5, 4, 3, 2, 1},
);
#[cfg(target_arch = "aarch64")]
test_micro_kernel!(
test_micro_kernel_neon,
Neon::new_checked(),
0x157f59d99f648437,
8 => { 6, 5, 4, 3, 2, 1},
);
fn test_panel_kernel<A, const MR: usize, const NR: usize>(
arch: A,
k: DimK,
rng: &mut impl rand::Rng,
ctx: std::fmt::Arguments<'_>,
) where
A: Copy,
for<'a> PanelKernel<'a, A, MR, NR>: driver::PanelKernel,
{
for blocks in 0..4 {
for remainder in 0..NR {
let cols = NR * blocks + remainder;
if cols == 0 {
continue;
}
let (ref_a, ref_b, ref_c) =
maxsim::test::generate_f32(MR, k.value().get(), cols, rng);
let ref_a = ref_a.transpose();
let ref_b = ref_b.transpose();
let extent = NonZeroUsize::new(cols).unwrap();
let c = [f32::NEG_INFINITY; MR];
let mut kernel = unsafe {
PanelKernel::new(
arch,
packed::Panel::new(Slice::new(ref_a.as_slice()), k),
unpacked::View::new(Slice::new(ref_b.as_slice()), extent, k),
c,
k,
)
};
driver::PanelKernel::panel_kernel(&mut kernel);
assert_eq!(&*ref_c, kernel.c, "{ctx}");
let new_c = kernel.c.map(|i| i + 1.0);
kernel.c = new_c;
driver::PanelKernel::panel_kernel(&mut kernel);
assert_eq!(new_c, kernel.c, "{ctx}");
}
}
}
macro_rules! test_panel_kernel {
(
$fn:ident,
$arch:expr,
$seed:literal,
$(
(
$MR:literal, $NR:literal
)
),+ $(,)?
) => {
#[test]
fn $fn() {
if let Some(arch) = $arch {
let mut rng = StdRng::seed_from_u64($seed);
for k in [1, 2, 5, 8] {
let k = DimK::new(NonZeroUsize::new(k).unwrap());
$(
test_panel_kernel::<_, $MR, $NR>(
arch,
k,
&mut rng,
format_args!("k = {:?}", k),
);
)+
}
}
}
}
}
test_panel_kernel!(
test_panel_kernel_scalar,
Some(Scalar::new()),
0x2c03eb9ee51d30c3,
(8, 2),
);
#[cfg(target_arch = "x86_64")]
test_panel_kernel!(
test_panel_kernel_v3,
V3::new_checked(),
0x2c03eb9ee51d30c3,
(16, 6),
);
#[cfg(target_arch = "x86_64")]
test_panel_kernel!(
test_panel_kernel_v4,
V4::new_checked_miri(),
0x2c03eb9ee51d30c3,
(16, 6),
(32, 6),
);
#[cfg(target_arch = "aarch64")]
test_panel_kernel!(
test_panel_kernel_neon,
Neon::new_checked(),
0x2c03eb9ee51d30c3,
(8, 6),
);
fn test_driver<A, const MR: usize, const NR: usize>(arch: A, rng: &mut impl rand::Rng)
where
A: Copy,
for<'a> Driver<'a, A, MR, NR>: driver::Drive,
{
let cases = maxsim::test::packed_x_unpacked_test_dims(MR, NR);
for case in cases {
let maxsim::test::TestDims {
a_panels_per_tile,
total_a_rows,
b_cols_per_tile,
total_b_cols,
k,
} = case.clone();
let k = DimK::new(NonZeroUsize::new(k).unwrap());
let (ref_a, ref_b, ref_c) =
maxsim::test::generate_f32(total_a_rows, k.value().get(), total_b_cols, rng);
let a_bt = BlockTransposed::<f32, MR>::from_matrix_view(ref_a.as_view());
let b = ref_b.transpose();
let mut c = vec![f32::NAN; a_bt.nrows()];
let mut driver = unsafe {
Driver::new_inner(
arch,
packed::View::from_block_transposed(a_bt.as_view()).unwrap(),
unpacked::View::from_matrix_view(b.as_view()).unwrap(),
&mut c,
k,
Params {
a_panels_in_l2: NonZeroUsize::new(a_panels_per_tile).unwrap(),
b_cols_in_l1: NonZeroUsize::new(b_cols_per_tile).unwrap(),
},
)
};
driver::Drive::drive(&mut driver);
assert_eq!(ref_c, c, "setup: {:?}", case)
}
}
macro_rules! test_driver {
(
$fn:ident,
$arch:expr,
$seed:literal,
$(
(
$MR:literal, $NR:literal
)
),+ $(,)?
) => {
#[test]
fn $fn() {
if let Some(arch) = $arch {
let mut rng = StdRng::seed_from_u64($seed);
$(test_driver::<_, $MR, $NR>(arch, &mut rng);)+
}
}
}
}
test_driver!(
test_driver_scalar,
Some(Scalar::new()),
0x2c03eb9ee51d30c3,
(8, 2),
);
#[cfg(target_arch = "x86_64")]
test_driver!(
test_driver_v3,
V3::new_checked(),
0x2c03eb9ee51d30c3,
(16, 6),
);
#[cfg(target_arch = "x86_64")]
test_driver!(
test_driver_v4,
V4::new_checked_miri(),
0x2c03eb9ee51d30c3,
(16, 6),
(32, 6),
);
#[cfg(target_arch = "aarch64")]
test_driver!(
test_driver_neon,
Neon::new_checked(),
0x2c03eb9ee51d30c3,
(8, 6),
);
}