use crate::scalar::Scalar;
#[cfg(feature = "complex")]
#[macro_use]
mod complex;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod avx512;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod fma;
#[cfg(target_arch = "aarch64")]
mod neon;
mod scalar;
#[cfg(target_arch = "wasm32")]
mod wasm;
#[cfg(all(feature = "half", any(target_arch = "x86", target_arch = "x86_64")))]
pub use self::avx512::Avx512Bf16;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub use self::avx512::Avx512F;
#[cfg(all(feature = "int8", any(target_arch = "x86", target_arch = "x86_64")))]
pub use self::avx512::Avx512Vnni;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub use self::fma::Fma;
#[cfg(target_arch = "aarch64")]
pub use self::neon::Neon;
pub use self::scalar::ScalarTok;
#[cfg(target_arch = "wasm32")]
pub use self::wasm::Simd128;
pub trait KernelSimd<L: Scalar, R: Scalar, A: Scalar, O: Scalar>: SimdOps<A> {
unsafe fn load_lhs(self, p: *const L) -> <Self as SimdOps<A>>::Reg;
unsafe fn splat_rhs(self, v: R) -> <Self as SimdOps<A>>::Reg;
unsafe fn load_out(self, p: *const O) -> <Self as SimdOps<A>>::Reg;
unsafe fn store_out(self, p: *mut O, v: <Self as SimdOps<A>>::Reg);
#[inline(always)]
unsafe fn dot_accumulate<const MR_REG: usize, const NR: usize>(
self,
_kc: usize,
_a: *const L,
_b: *const R,
_acc: &mut [[<Self as SimdOps<A>>::Reg; MR_REG]; NR],
) {
unreachable!("dot_accumulate is provided only by dot-capable ISA tokens")
}
const REQUANT_VECTOR: bool = false;
#[inline(always)]
unsafe fn requant_store(
self,
_dst: *mut i8,
_v: <Self as SimdOps<A>>::Reg,
_scale: f64,
_zp: i32,
_lo: i32,
_hi: i32,
) {
unreachable!("requant_store is provided only by requant-vector-capable ISA tokens")
}
}
#[cfg(feature = "int8")]
pub(crate) const VNNI_A_BIAS: i32 = 128;
impl<A: Scalar, S: SimdOps<A>> KernelSimd<A, A, A, A> for S {
#[inline(always)]
unsafe fn load_lhs(self, p: *const A) -> <S as SimdOps<A>>::Reg {
unsafe { self.loadu(p) }
}
#[inline(always)]
unsafe fn splat_rhs(self, v: A) -> <S as SimdOps<A>>::Reg {
unsafe { self.splat(v) }
}
#[inline(always)]
unsafe fn load_out(self, p: *const A) -> <S as SimdOps<A>>::Reg {
unsafe { self.loadu(p) }
}
#[inline(always)]
unsafe fn store_out(self, p: *mut A, v: <S as SimdOps<A>>::Reg) {
unsafe { self.storeu(p, v) }
}
}
#[cfg(feature = "int8")]
macro_rules! impl_requant_blanket {
($out:ty) => {
impl<S: KernelSimd<i8, i8, i32, i32>> KernelSimd<i8, i8, i32, $out> for S {
#[inline(always)]
unsafe fn load_lhs(self, p: *const i8) -> <Self as SimdOps<i32>>::Reg {
unsafe { <Self as KernelSimd<i8, i8, i32, i32>>::load_lhs(self, p) }
}
#[inline(always)]
unsafe fn splat_rhs(self, v: i8) -> <Self as SimdOps<i32>>::Reg {
unsafe { <Self as KernelSimd<i8, i8, i32, i32>>::splat_rhs(self, v) }
}
#[inline(always)]
unsafe fn dot_accumulate<const MR_REG: usize, const NR: usize>(
self,
kc: usize,
a: *const i8,
b: *const i8,
acc: &mut [[<Self as SimdOps<i32>>::Reg; MR_REG]; NR],
) {
unsafe {
<Self as KernelSimd<i8, i8, i32, i32>>::dot_accumulate::<MR_REG, NR>(
self, kc, a, b, acc,
)
}
}
const REQUANT_VECTOR: bool = <S as KernelSimd<i8, i8, i32, i32>>::REQUANT_VECTOR;
#[inline(always)]
unsafe fn requant_store(
self,
dst: *mut i8,
v: <Self as SimdOps<i32>>::Reg,
scale: f64,
zp: i32,
lo: i32,
hi: i32,
) {
unsafe {
<S as KernelSimd<i8, i8, i32, i32>>::requant_store(
self, dst, v, scale, zp, lo, hi,
)
}
}
#[inline(always)]
unsafe fn load_out(self, _p: *const $out) -> <Self as SimdOps<i32>>::Reg {
unreachable!("requant families never touch Out-typed C")
}
#[inline(always)]
unsafe fn store_out(self, _p: *mut $out, _v: <Self as SimdOps<i32>>::Reg) {
unreachable!("requant families never touch Out-typed C")
}
}
};
}
#[cfg(feature = "int8")]
impl_requant_blanket!(i8);
#[cfg(feature = "int8")]
impl_requant_blanket!(u8);
#[cfg(feature = "half")]
impl<S: KernelSimd<half::f16, half::f16, f32, half::f16>> KernelSimd<half::f16, half::f16, f32, f32>
for S
{
#[inline(always)]
unsafe fn load_lhs(self, p: *const half::f16) -> <Self as SimdOps<f32>>::Reg {
unsafe { <Self as KernelSimd<half::f16, half::f16, f32, half::f16>>::load_lhs(self, p) }
}
#[inline(always)]
unsafe fn splat_rhs(self, v: half::f16) -> <Self as SimdOps<f32>>::Reg {
unsafe { <Self as KernelSimd<half::f16, half::f16, f32, half::f16>>::splat_rhs(self, v) }
}
#[inline(always)]
unsafe fn load_out(self, p: *const f32) -> <Self as SimdOps<f32>>::Reg {
unsafe { self.loadu(p) }
}
#[inline(always)]
unsafe fn store_out(self, p: *mut f32, v: <Self as SimdOps<f32>>::Reg) {
unsafe { self.storeu(p, v) }
}
#[inline(always)]
unsafe fn dot_accumulate<const MR_REG: usize, const NR: usize>(
self,
kc: usize,
a: *const half::f16,
b: *const half::f16,
acc: &mut [[<Self as SimdOps<f32>>::Reg; MR_REG]; NR],
) {
unsafe {
<Self as KernelSimd<half::f16, half::f16, f32, half::f16>>::dot_accumulate::<MR_REG, NR>(
self, kc, a, b, acc,
)
}
}
}
#[cfg(feature = "half")]
impl<S: KernelSimd<half::bf16, half::bf16, f32, half::bf16>>
KernelSimd<half::bf16, half::bf16, f32, f32> for S
{
#[inline(always)]
unsafe fn load_lhs(self, p: *const half::bf16) -> <Self as SimdOps<f32>>::Reg {
unsafe { <Self as KernelSimd<half::bf16, half::bf16, f32, half::bf16>>::load_lhs(self, p) }
}
#[inline(always)]
unsafe fn splat_rhs(self, v: half::bf16) -> <Self as SimdOps<f32>>::Reg {
unsafe { <Self as KernelSimd<half::bf16, half::bf16, f32, half::bf16>>::splat_rhs(self, v) }
}
#[inline(always)]
unsafe fn load_out(self, p: *const f32) -> <Self as SimdOps<f32>>::Reg {
unsafe { self.loadu(p) }
}
#[inline(always)]
unsafe fn store_out(self, p: *mut f32, v: <Self as SimdOps<f32>>::Reg) {
unsafe { self.storeu(p, v) }
}
#[inline(always)]
unsafe fn dot_accumulate<const MR_REG: usize, const NR: usize>(
self,
kc: usize,
a: *const half::bf16,
b: *const half::bf16,
acc: &mut [[<Self as SimdOps<f32>>::Reg; MR_REG]; NR],
) {
unsafe {
<Self as KernelSimd<half::bf16, half::bf16, f32, half::bf16>>::dot_accumulate::<
MR_REG,
NR,
>(self, kc, a, b, acc)
}
}
}
pub trait Simd: Copy + Send + Sync + 'static {
unsafe fn vectorize<R>(self, f: impl FnOnce() -> R) -> R;
}
pub trait SimdOps<T: Scalar>: Simd {
type Reg: Copy;
const LANES: usize;
const LANE_FMA: bool = false;
unsafe fn zero(self) -> Self::Reg;
unsafe fn splat(self, v: T) -> Self::Reg;
unsafe fn loadu(self, p: *const T) -> Self::Reg;
unsafe fn storeu(self, p: *mut T, v: Self::Reg);
unsafe fn mul(self, a: Self::Reg, b: Self::Reg) -> Self::Reg;
unsafe fn add(self, a: Self::Reg, b: Self::Reg) -> Self::Reg;
unsafe fn mul_add(self, a: Self::Reg, b: Self::Reg, c: Self::Reg) -> Self::Reg;
unsafe fn fnma(self, a: Self::Reg, b: Self::Reg, c: Self::Reg) -> Self::Reg;
unsafe fn reduce_sum(self, v: Self::Reg) -> T;
#[inline(always)]
unsafe fn max(self, _a: Self::Reg, _b: Self::Reg) -> Self::Reg {
unreachable!("max is provided only by the real-float SimdOps tokens")
}
#[inline(always)]
unsafe fn min(self, _a: Self::Reg, _b: Self::Reg) -> Self::Reg {
unreachable!("min is provided only by the real-float SimdOps tokens")
}
#[inline(always)]
unsafe fn fma_bvec<const MR_REG: usize>(
self,
a_regs: &[Self::Reg; MR_REG],
bvec: Self::Reg,
acc: &mut [[Self::Reg; MR_REG]],
) {
debug_assert_eq!(acc.len(), Self::LANES);
unsafe {
let mut buf = [T::ZERO; 16];
self.storeu(buf.as_mut_ptr(), bvec);
for l in 0..acc.len() {
let bl = self.splat(buf[l]);
for i in 0..MR_REG {
acc[l][i] = self.mul_add(a_regs[i], bl, acc[l][i]);
}
}
}
}
#[allow(clippy::too_many_arguments, clippy::needless_range_loop)]
#[inline(always)]
unsafe fn accumulate_tile<const MR_REG: usize, const NR: usize>(
self,
kc: usize,
a: *const T,
a_cs: isize,
b: *const T,
b_rs: isize,
b_cs: isize,
acc: &mut [[Self::Reg; MR_REG]; NR],
) {
let lanes = Self::LANES;
unsafe {
if Self::LANE_FMA && b_cs == 1 && NR.is_multiple_of(lanes) {
for p in 0..kc {
let pa = a.offset(p as isize * a_cs);
let a_regs: [Self::Reg; MR_REG] =
core::array::from_fn(|i| self.loadu(pa.add(i * lanes)));
let pb = b.offset(p as isize * b_rs);
for jb in (0..NR).step_by(lanes) {
let bvec = self.loadu(pb.add(jb));
self.fma_bvec(&a_regs, bvec, &mut acc[jb..jb + lanes]);
}
}
} else {
for p in 0..kc {
let pa = a.offset(p as isize * a_cs);
let a_regs: [Self::Reg; MR_REG] =
core::array::from_fn(|i| self.loadu(pa.add(i * lanes)));
let pb = b.offset(p as isize * b_rs);
for j in 0..NR {
let bj = self.splat(*pb.offset(j as isize * b_cs));
for i in 0..MR_REG {
acc[j][i] = self.mul_add(a_regs[i], bj, acc[j][i]);
}
}
}
}
}
}
#[allow(clippy::too_many_arguments)]
#[inline(always)]
unsafe fn cplx_microkernel<const MR_REG: usize, const NR: usize>(
self,
_kc: usize,
_alpha: T,
_beta: T,
_alpha_is_one: bool,
_beta_is_zero: bool,
_beta_is_one: bool,
_a: *const T,
_a_cs: isize,
_b: *const T,
_b_rs: isize,
_c: *mut T,
_rsc: isize,
_csc: isize,
_mr_eff: usize,
_nr_eff: usize,
_scratch: *mut T,
) {
unreachable!("cplx_microkernel is provided only by the complex `SimdOps` impls")
}
}
#[cfg(all(
test,
feature = "int8",
any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")
))]
mod requant_store_tests {
#![allow(clippy::needless_range_loop)]
use super::{KernelSimd, SimdOps};
fn scalar_low_byte(v: i32, scale: f64, zp: i32, lo: i32, hi: i32) -> u8 {
let scaled = (v as f64 * scale).round_ties_even();
let q = (scaled as i64).saturating_add(zp as i64);
q.clamp(lo as i64, hi as i64) as u8
}
unsafe fn check_token<S: KernelSimd<i8, i8, i32, i32>>(simd: S, label: &str) {
unsafe {
simd.vectorize(|| {
let lanes = <S as SimdOps<i32>>::LANES;
assert!(
<S as KernelSimd<i8, i8, i32, i32>>::REQUANT_VECTOR,
"{label}: token is not requant-vector-capable",
);
let mut vals: Vec<i32> = vec![
i32::MIN,
i32::MIN + 1,
-1,
0,
1,
1 << 30,
i32::MAX - 1,
i32::MAX,
];
let mut lcg = 0x1234_5678_9abc_def0u64;
for _ in 0..96 {
lcg = lcg
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
vals.push((lcg >> 32) as i32);
}
for &scale in &[1.0f64, 0.1, 1e30, 1e-30, 0.0078125] {
for &zp in &[0i32, -128, 127] {
for &(lo, hi) in &[(-128i32, 127i32), (0i32, 255i32)] {
let mut idx = 0;
while idx < vals.len() {
let mut inbuf = [0i32; 16];
for l in 0..lanes {
inbuf[l] = vals.get(idx + l).copied().unwrap_or(0);
}
let reg = simd.loadu(inbuf.as_ptr());
let mut out = [0i8; 16];
<S as KernelSimd<i8, i8, i32, i32>>::requant_store(
simd,
out.as_mut_ptr(),
reg,
scale,
zp,
lo,
hi,
);
for l in 0..lanes {
let want = scalar_low_byte(inbuf[l], scale, zp, lo, hi);
assert_eq!(
out[l] as u8, want,
"{label}: v={} scale={scale} zp={zp} bounds=({lo},{hi})",
inbuf[l],
);
}
idx += lanes;
}
}
}
}
});
}
}
#[test]
fn requant_store_matches_scalar_map() {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe {
use super::{Avx512F, Avx512Vnni, Fma};
if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
check_token(Fma, "fma");
}
if is_x86_feature_detected!("avx512f") {
check_token(Avx512F, "avx512f");
}
if is_x86_feature_detected!("avx512f")
&& is_x86_feature_detected!("avx512bw")
&& is_x86_feature_detected!("avx512vnni")
{
check_token(Avx512Vnni, "avx512vnni");
}
}
#[cfg(target_arch = "aarch64")]
unsafe {
use super::Neon;
check_token(Neon, "neon");
}
}
}