use diskann_wide::arch::Architecture;
use half::f16;
use crate::matrix_kernels::{
Cache,
blocks::{packed, unpacked},
bounds, driver,
num::DimK,
ptr::{MutSlice, Slice},
util::{self, Convert, Converter},
};
use super::packed_f32_x_unpacked_f32::{PanelKernel, Params};
pub(crate) struct Driver<'a, A, const MR: usize, const NR: usize> {
arch: A,
a: packed::View<'a, f32, MR>,
b: unpacked::View<'a, f16>,
c: &'a mut [f32],
k: DimK,
b_converted: Vec<f32>,
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, f16>,
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",
);
let params = Params::new(
cache,
a.block_stride(k).bytes(),
b.stride(k).cast::<f32>().bytes(),
NR,
);
unsafe { Self::new_inner(arch, a, b, c, k, params) }
}
unsafe fn new_inner(
arch: A,
a: packed::View<'a, f32, MR>,
b: unpacked::View<'a, f16>,
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,
b_converted: vec![0.0f32; (b.stride(k) * params.b_cols_in_l1.get()).value()],
params,
}
}
}
impl<A, const MR: usize, const NR: usize> driver::Drive for Driver<'_, A, MR, NR>
where
A: util::LoadStore<f32, MR> + Architecture,
Converter<A>: Convert<f32, f16>,
for<'a> PanelKernel<'a, A, MR, NR>: driver::PanelKernel,
{
#[inline(never)]
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<'_, f16>, _| {
let b_flat = unsafe { b_panels.as_std_slice(self.k) };
let b_converted = &mut self.b_converted[..b_flat.len()];
Converter::new(self.arch).convert(b_converted, b_flat);
let b_panels_converted = unsafe {
unpacked::View::new(Slice::new(b_converted), b_panels.extent(), self.k)
};
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_converted, 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)
};
},
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::num::NonZeroUsize;
use diskann_utils::views::rowmajor::Matrix;
use diskann_wide::arch::Scalar;
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_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(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.map(|v| diskann_wide::cast_f32_to_f16(*v)).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),
);
}