use crate::backend::Backend;
use crate::scalar::{FloatScalar, Scalar};
mod sealed {
pub trait Sealed {}
}
pub trait Role: sealed::Sealed + Copy + 'static {
const USE: u32;
type Repr<'a, E: Scalar, const R: usize, const C: usize>: CpuTile<'a, E, R, C>;
}
#[derive(Clone, Copy)]
pub struct MatrixA;
#[derive(Clone, Copy)]
pub struct MatrixB;
#[derive(Clone, Copy)]
pub struct Accumulator;
impl sealed::Sealed for MatrixA {}
impl sealed::Sealed for MatrixB {}
impl sealed::Sealed for Accumulator {}
impl Role for MatrixA {
const USE: u32 = 0;
type Repr<'a, E: Scalar, const R: usize, const C: usize> = View<'a, E, R, C>;
}
impl Role for MatrixB {
const USE: u32 = 1;
type Repr<'a, E: Scalar, const R: usize, const C: usize> = View<'a, E, R, C>;
}
impl Role for Accumulator {
const USE: u32 = 2;
type Repr<'a, E: Scalar, const R: usize, const C: usize> = [[E; C]; R];
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Layout {
RowMajor,
ColMajor,
}
pub struct View<'a, E, const R: usize, const C: usize> {
ptr: *const E,
row_stride: usize,
layout: Layout,
_p: core::marker::PhantomData<&'a [E]>,
}
impl<E, const R: usize, const C: usize> Clone for View<'_, E, R, C> {
fn clone(&self) -> Self {
*self
}
}
impl<E, const R: usize, const C: usize> Copy for View<'_, E, R, C> {}
unsafe impl<E: Sync, const R: usize, const C: usize> Send for View<'_, E, R, C> {}
unsafe impl<E: Sync, const R: usize, const C: usize> Sync for View<'_, E, R, C> {}
impl<'a, E: Scalar, const R: usize, const C: usize> View<'a, E, R, C> {
#[inline]
pub fn get(self, r: usize, c: usize) -> E {
unsafe { *self.ptr.add(tile_index(r, c, self.row_stride, self.layout)) }
}
#[inline]
pub fn dense_ptr(self) -> Option<*const E> {
if matches!(self.layout, Layout::RowMajor) && self.row_stride == C {
Some(self.ptr)
} else {
None
}
}
}
pub trait CpuTile<'a, E: Scalar, const R: usize, const C: usize>: Copy {
fn ct_load(mem: &'a [E], row_stride: usize, layout: Layout) -> Self;
fn ct_store(self, out: &mut [E], row_stride: usize, layout: Layout);
fn ct_splat(v: E) -> Self;
fn ct_map(self, f: impl Fn(E) -> E) -> Self;
fn ct_get(self, r: usize, c: usize) -> E;
fn ct_dense_ptr(self) -> Option<*const E>;
}
impl<'a, E: Scalar, const R: usize, const C: usize> CpuTile<'a, E, R, C> for View<'a, E, R, C> {
#[inline]
fn ct_load(mem: &'a [E], row_stride: usize, layout: Layout) -> Self {
View { ptr: mem.as_ptr(), row_stride, layout, _p: core::marker::PhantomData }
}
#[inline]
fn ct_store(self, _out: &mut [E], _row_stride: usize, _layout: Layout) {
unreachable!("a read-only input tile is never stored")
}
#[inline]
fn ct_splat(_v: E) -> Self {
unreachable!("a read-only input tile is never splatted")
}
#[inline]
fn ct_map(self, _f: impl Fn(E) -> E) -> Self {
unreachable!("a read-only input tile is never mapped")
}
#[inline]
fn ct_get(self, r: usize, c: usize) -> E {
View::get(self, r, c)
}
#[inline]
fn ct_dense_ptr(self) -> Option<*const E> {
View::dense_ptr(self)
}
}
impl<'a, E: Scalar, const R: usize, const C: usize> CpuTile<'a, E, R, C> for [[E; C]; R] {
#[inline]
fn ct_load(mem: &'a [E], row_stride: usize, layout: Layout) -> Self {
let mut t = [[E::ZERO; C]; R];
let mut r = 0;
while r < R {
let mut c = 0;
while c < C {
t[r][c] = mem[tile_index(r, c, row_stride, layout)];
c += 1;
}
r += 1;
}
t
}
#[inline]
fn ct_store(self, out: &mut [E], row_stride: usize, layout: Layout) {
let mut r = 0;
while r < R {
let mut c = 0;
while c < C {
out[tile_index(r, c, row_stride, layout)] = self[r][c];
c += 1;
}
r += 1;
}
}
#[inline]
fn ct_splat(v: E) -> Self {
[[v; C]; R]
}
#[inline]
fn ct_map(mut self, f: impl Fn(E) -> E) -> Self {
let mut r = 0;
while r < R {
let mut c = 0;
while c < C {
self[r][c] = f(self[r][c]);
c += 1;
}
r += 1;
}
self
}
#[inline]
fn ct_get(self, r: usize, c: usize) -> E {
self[r][c]
}
#[inline]
fn ct_dense_ptr(self) -> Option<*const E> {
None
}
}
pub trait MatrixBackend<T: FloatScalar>: Backend<T> {
type Tile<'a, E: Scalar, const R: usize, const C: usize, Ro: Role>: Copy;
fn tile_load<'a, E: Scalar, const R: usize, const C: usize, Ro: Role>(
self,
mem: &'a [E],
row_stride: usize,
layout: Layout,
) -> Self::Tile<'a, E, R, C, Ro>;
fn tile_store<E: Scalar, const R: usize, const C: usize, Ro: Role>(
self,
t: Self::Tile<'_, E, R, C, Ro>,
out: &mut [E],
row_stride: usize,
layout: Layout,
);
fn tile_splat<'a, E: Scalar, const R: usize, const C: usize, Ro: Role>(
self,
v: E,
) -> Self::Tile<'a, E, R, C, Ro>;
fn tile_map<'a, E: Scalar, const R: usize, const C: usize, Ro: Role>(
self,
t: Self::Tile<'a, E, R, C, Ro>,
f: impl Fn(E) -> E,
) -> Self::Tile<'a, E, R, C, Ro>;
fn mma<'i, const M: usize, const N: usize, const K: usize>(
self,
a: Self::Tile<'i, T, M, K, MatrixA>,
b: Self::Tile<'i, T, K, N, MatrixB>,
c: Self::Tile<'i, T::Compute, M, N, Accumulator>,
) -> Self::Tile<'i, T::Compute, M, N, Accumulator>;
}
#[inline]
fn tile_index(r: usize, c: usize, row_stride: usize, layout: Layout) -> usize {
match layout {
Layout::RowMajor => r * row_stride + c,
Layout::ColMajor => c * row_stride + r,
}
}
#[inline]
fn simd_gemm<C: FloatScalar, B: Backend<C>, const M: usize, const N: usize, const K: usize>(
backend: B,
a: [[C; K]; M],
b: [[C; N]; K],
mut c: [[C; N]; M],
) -> [[C; N]; M] {
let lanes = backend.lanes();
let mut i = 0;
while i < M {
let mut j = 0;
while j + lanes <= N {
let mut acc = backend.load(&c[i][j..j + lanes]);
let mut k = 0;
while k < K {
let aik = backend.splat(a[i][k]);
let br = backend.load(&b[k][j..j + lanes]);
acc = backend.fma(aik, br, acc);
k += 1;
}
backend.store(acc, &mut c[i][j..j + lanes]);
j += lanes;
}
while j < N {
let mut s = c[i][j];
let mut k = 0;
while k < K {
s = a[i][k].fma(b[k][j], s);
k += 1;
}
c[i][j] = s;
j += 1;
}
i += 1;
}
c
}
#[cfg(feature = "std")]
#[inline]
#[allow(clippy::needless_range_loop)]
fn packed_gemm<
C: FloatScalar,
B: Backend<C>,
const M: usize,
const N: usize,
const K: usize,
const MR: usize,
const NR: usize,
>(
backend: B,
a: [[C; K]; M],
b: [[C; N]; K],
mut c: [[C; N]; M],
) -> [[C; N]; M] {
let lanes = backend.lanes();
let nb = N / lanes;
let (full, panel) = (nb * lanes, K * lanes);
let mut bp = vec![C::ZERO; nb * panel];
for jb in 0..nb {
let j0 = jb * lanes;
for k in 0..K {
let dst = jb * panel + k * lanes;
backend.store(backend.load(&b[k][j0..j0 + lanes]), &mut bp[dst..dst + lanes]);
}
}
let bvec = |jb: usize, k: usize| backend.load(&bp[jb * panel + k * lanes..jb * panel + k * lanes + lanes]);
let edge = |c: &mut [[C; N]; M], i: usize, jb: usize| {
let cj = jb * lanes;
let mut acc = backend.load(&c[i][cj..cj + lanes]);
for k in 0..K {
acc = backend.fma(backend.splat(a[i][k]), bvec(jb, k), acc);
}
backend.store(acc, &mut c[i][cj..cj + lanes]);
};
let mut i = 0;
while i + MR <= M {
let mut jb = 0;
while jb + NR <= nb {
let mut acc = [[backend.splat(C::ZERO); NR]; MR];
for (mr, row) in acc.iter_mut().enumerate() {
for (nv, cell) in row.iter_mut().enumerate() {
let cj = (jb + nv) * lanes;
*cell = backend.load(&c[i + mr][cj..cj + lanes]);
}
}
for k in 0..K {
let bv: [B::Vector; NR] = core::array::from_fn(|nv| bvec(jb + nv, k));
for (mr, row) in acc.iter_mut().enumerate() {
let av = backend.splat(a[i + mr][k]);
for (nv, cell) in row.iter_mut().enumerate() {
*cell = backend.fma(av, bv[nv], *cell);
}
}
}
for (mr, row) in acc.iter().enumerate() {
for (nv, cell) in row.iter().enumerate() {
let cj = (jb + nv) * lanes;
backend.store(*cell, &mut c[i + mr][cj..cj + lanes]);
}
}
jb += NR;
}
while jb < nb {
for mr in 0..MR {
edge(&mut c, i + mr, jb);
}
jb += 1;
}
i += MR;
}
while i < M {
for jb in 0..nb {
edge(&mut c, i, jb);
}
i += 1;
}
for i in 0..M {
for j in full..N {
let mut s = c[i][j];
for k in 0..K {
s = a[i][k].fma(b[k][j], s);
}
c[i][j] = s;
}
}
c
}
#[cfg(all(target_vendor = "apple", not(hp_no_apple_accelerate)))]
pub(crate) mod accel {
use core::ffi::{c_int, c_uint};
pub const ROW_MAJOR: c_uint = 101;
pub const COL_MAJOR: c_uint = 102;
pub const NO_TRANS: c_uint = 111;
pub const TRANS: c_uint = 112;
pub const UPPER: c_uint = 121;
pub const LOWER: c_uint = 122;
pub const NON_UNIT: c_uint = 131;
pub const UNIT: c_uint = 132;
pub const LEFT: c_uint = 141;
pub const RIGHT: c_uint = 142;
#[link(name = "Accelerate", kind = "framework")]
unsafe extern "C" {
#[allow(clippy::too_many_arguments)]
pub fn cblas_sgemm(
order: c_uint, transa: c_uint, transb: c_uint, m: c_int, n: c_int, k: c_int,
alpha: f32, a: *const f32, lda: c_int, b: *const f32, ldb: c_int,
beta: f32, c: *mut f32, ldc: c_int,
);
#[allow(clippy::too_many_arguments)]
pub fn cblas_dgemm(
order: c_uint, transa: c_uint, transb: c_uint, m: c_int, n: c_int, k: c_int,
alpha: f64, a: *const f64, lda: c_int, b: *const f64, ldb: c_int,
beta: f64, c: *mut f64, ldc: c_int,
);
#[allow(clippy::too_many_arguments)]
pub fn cblas_sgemv(
order: c_uint, trans: c_uint, m: c_int, n: c_int,
alpha: f32, a: *const f32, lda: c_int, x: *const f32, incx: c_int,
beta: f32, y: *mut f32, incy: c_int,
);
#[allow(clippy::too_many_arguments)]
pub fn cblas_dgemv(
order: c_uint, trans: c_uint, m: c_int, n: c_int,
alpha: f64, a: *const f64, lda: c_int, x: *const f64, incx: c_int,
beta: f64, y: *mut f64, incy: c_int,
);
#[allow(clippy::too_many_arguments)]
pub fn cblas_ssyrk(
order: c_uint, uplo: c_uint, trans: c_uint, n: c_int, k: c_int,
alpha: f32, a: *const f32, lda: c_int, beta: f32, c: *mut f32, ldc: c_int,
);
#[allow(clippy::too_many_arguments)]
pub fn cblas_dsyrk(
order: c_uint, uplo: c_uint, trans: c_uint, n: c_int, k: c_int,
alpha: f64, a: *const f64, lda: c_int, beta: f64, c: *mut f64, ldc: c_int,
);
#[allow(clippy::too_many_arguments)]
pub fn cblas_strsm(
order: c_uint, side: c_uint, uplo: c_uint, transa: c_uint, diag: c_uint,
m: c_int, n: c_int, alpha: f32, a: *const f32, lda: c_int, b: *mut f32, ldb: c_int,
);
#[allow(clippy::too_many_arguments)]
pub fn cblas_dtrsm(
order: c_uint, side: c_uint, uplo: c_uint, transa: c_uint, diag: c_uint,
m: c_int, n: c_int, alpha: f64, a: *const f64, lda: c_int, b: *mut f64, ldb: c_int,
);
}
}
#[cfg(all(target_vendor = "apple", not(hp_no_apple_accelerate)))]
const ACCEL_MIN_DIM: usize = 48;
#[cfg(all(
target_arch = "aarch64",
not(hp_no_sme),
any(not(target_vendor = "apple"), hp_no_apple_accelerate)
))]
const SME_MIN_DIM: usize = 16;
#[cfg(all(
target_arch = "aarch64",
feature = "std",
not(hp_no_sme),
any(not(target_vendor = "apple"), hp_no_apple_accelerate)
))]
pub(crate) mod sme_pack {
#![allow(clippy::needless_range_loop)]
use crate::scalar::{FloatScalar, Scalar};
use core::cell::RefCell;
use half::{bf16, f16};
thread_local! {
pub static F32: RefCell<Vec<f32>> = const { RefCell::new(Vec::new()) };
pub static F64: RefCell<Vec<f64>> = const { RefCell::new(Vec::new()) };
pub static BF16: RefCell<Vec<bf16>> = const { RefCell::new(Vec::new()) };
pub static F16: RefCell<Vec<f16>> = const { RefCell::new(Vec::new()) };
}
#[inline]
pub unsafe fn pack_a_f32(ac: *const f32, ap: &mut [f32], pm: usize, k: usize, blk: usize) {
use core::arch::aarch64::*;
#[inline(always)]
unsafe fn t4x4(col: *const f32, k: usize, j: usize, o: *mut f32, blk: usize) {
unsafe {
let (r0, r1) = (col.add(j), col.add(k + j));
let (r2, r3) = (col.add(2 * k + j), col.add(3 * k + j));
let (a0, a1, a2, a3) = (vld1q_f32(r0), vld1q_f32(r1), vld1q_f32(r2), vld1q_f32(r3));
let (t0, t1) = (vtrn1q_f32(a0, a1), vtrn2q_f32(a0, a1));
let (t2, t3) = (vtrn1q_f32(a2, a3), vtrn2q_f32(a2, a3));
let (d0, d2) = (vreinterpretq_f64_f32(t0), vreinterpretq_f64_f32(t2));
let (d1, d3) = (vreinterpretq_f64_f32(t1), vreinterpretq_f64_f32(t3));
vst1q_f32(o, vreinterpretq_f32_f64(vtrn1q_f64(d0, d2)));
vst1q_f32(o.add(blk), vreinterpretq_f32_f64(vtrn1q_f64(d1, d3)));
vst1q_f32(o.add(2 * blk), vreinterpretq_f32_f64(vtrn2q_f64(d0, d2)));
vst1q_f32(o.add(3 * blk), vreinterpretq_f32_f64(vtrn2q_f64(d1, d3)));
}
}
let k4 = k & !3;
let ap = ap.as_mut_ptr();
for p in 0..pm {
let base = p * k * blk;
let mut kb = 0;
while kb < k4 {
let kend = (kb + 32).min(k4);
let mut m = 0;
while m + 8 <= blk {
let c0 = unsafe { ac.add((p * blk + m) * k) };
let c4 = unsafe { ac.add((p * blk + m + 4) * k) };
let mut j = kb;
while j < kend {
unsafe {
t4x4(c0, k, j, ap.add(base + j * blk + m), blk);
t4x4(c4, k, j, ap.add(base + j * blk + m + 4), blk);
}
j += 4;
}
m += 8;
}
while m < blk {
let col = unsafe { ac.add((p * blk + m) * k) };
let mut j = kb;
while j < kend {
unsafe { t4x4(col, k, j, ap.add(base + j * blk + m), blk) };
j += 4;
}
m += 4;
}
kb += 32;
}
for kk in k4..k {
for m in 0..blk {
unsafe { *ap.add(base + kk * blk + m) = *ac.add((p * blk + m) * k + kk) };
}
}
}
}
#[inline]
pub unsafe fn pack_a_f64(ac: *const f64, ap: &mut [f64], pm: usize, k: usize, blk: usize) {
use core::arch::aarch64::*;
#[inline(always)]
unsafe fn t2x2(col: *const f64, k: usize, j: usize, o: *mut f64, blk: usize) {
unsafe {
let (a0, a1) = (vld1q_f64(col.add(j)), vld1q_f64(col.add(k + j)));
vst1q_f64(o, vtrn1q_f64(a0, a1));
vst1q_f64(o.add(blk), vtrn2q_f64(a0, a1));
}
}
let k2 = k & !1;
let ap = ap.as_mut_ptr();
for p in 0..pm {
let base = p * k * blk;
let mut kb = 0;
while kb < k2 {
let kend = (kb + 32).min(k2);
let mut m = 0;
while m + 4 <= blk {
let c0 = unsafe { ac.add((p * blk + m) * k) };
let c2 = unsafe { ac.add((p * blk + m + 2) * k) };
let mut j = kb;
while j < kend {
unsafe {
t2x2(c0, k, j, ap.add(base + j * blk + m), blk);
t2x2(c2, k, j, ap.add(base + j * blk + m + 2), blk);
}
j += 2;
}
m += 4;
}
while m < blk {
let col = unsafe { ac.add((p * blk + m) * k) };
let mut j = kb;
while j < kend {
unsafe { t2x2(col, k, j, ap.add(base + j * blk + m), blk) };
j += 2;
}
m += 2;
}
kb += 32;
}
for kk in k2..k {
for m in 0..blk {
unsafe { *ap.add(base + kk * blk + m) = *ac.add((p * blk + m) * k + kk) };
}
}
}
}
#[inline]
pub unsafe fn pack_b<T: Copy>(bc: *const T, bp: &mut [T], pn: usize, k: usize, n: usize, blk: usize) {
for p in 0..pn {
let base = p * k * blk;
for kk in 0..k {
unsafe {
core::ptr::copy_nonoverlapping(
bc.add(kk * n + p * blk),
bp.as_mut_ptr().add(base + kk * blk),
blk,
);
}
}
}
}
#[inline]
pub unsafe fn pack_a_pairs<T: FloatScalar>(ac: *const T, ap: &mut [T], pm: usize, k: usize, blk: usize) {
debug_assert_eq!(core::mem::size_of::<T>(), 2);
if k.is_multiple_of(2) {
let apf = unsafe { core::slice::from_raw_parts_mut(ap.as_mut_ptr() as *mut f32, ap.len() / 2) };
unsafe { pack_a_f32(ac as *const f32, apf, pm, k / 2, blk) };
return;
}
let pairs = k.div_ceil(2);
for pi in 0..pm {
let base = pi * pairs * blk * 2;
for pp in 0..pairs {
let pbase = base + pp * blk * 2;
for r in 0..blk {
let row = unsafe { ac.add((pi * blk + r) * k) };
let (k0, k1) = (2 * pp, 2 * pp + 1);
ap[pbase + r * 2] = unsafe { *row.add(k0) };
ap[pbase + r * 2 + 1] = if k1 < k { unsafe { *row.add(k1) } } else { T::ZERO };
}
}
}
}
#[inline]
pub unsafe fn pack_b_pairs<T: FloatScalar>(bc: *const T, bp: &mut [T], pn: usize, k: usize, n: usize, blk: usize) {
use core::arch::aarch64::*;
debug_assert_eq!(core::mem::size_of::<T>(), 2);
let pairs = k.div_ceil(2);
let (bc, bp) = (bc as *const u16, bp.as_mut_ptr() as *mut u16);
for pj in 0..pn {
let base = pj * pairs * blk * 2;
for pp in 0..pairs {
let pbase = base + pp * blk * 2;
let (k0, k1) = (2 * pp, 2 * pp + 1);
let r0 = unsafe { bc.add(k0 * n + pj * blk) };
if k1 < k {
let r1 = unsafe { bc.add(k1 * n + pj * blk) };
let mut c = 0;
while c + 8 <= blk {
unsafe {
let (v0, v1) = (vld1q_u16(r0.add(c)), vld1q_u16(r1.add(c)));
vst1q_u16(bp.add(pbase + c * 2), vzip1q_u16(v0, v1));
vst1q_u16(bp.add(pbase + c * 2 + 8), vzip2q_u16(v0, v1));
}
c += 8;
}
} else {
for c in 0..blk {
unsafe {
*bp.add(pbase + c * 2) = *r0.add(c);
*bp.add(pbase + c * 2 + 1) = 0;
}
}
}
}
}
}
}
#[cfg(all(
target_arch = "aarch64",
feature = "std",
not(hp_no_sme),
any(not(target_vendor = "apple"), hp_no_apple_accelerate)
))]
#[inline]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn sme2_blocked_f32<const M: usize, const N: usize, const K: usize, const BLK: usize>(
ac: *const f32,
bc: *const f32,
c: *mut f32,
) {
let (pm, pn) = (M / BLK, N / BLK);
sme_pack::F32.with_borrow_mut(|buf| {
let need = M * K + K * N;
if buf.len() < need {
buf.resize(need, 0.0);
}
let (ap, bp) = buf.split_at_mut(M * K);
unsafe {
sme_pack::pack_a_f32(ac, ap, pm, K, BLK);
sme_pack::pack_b(bc, &mut bp[..K * N], pn, K, N, BLK);
crate::arch::sme2::mma_f32_grid_packed(ap.as_ptr(), bp.as_ptr(), c, N * 4, pm, pn, K);
}
});
}
#[cfg(all(
target_arch = "aarch64",
feature = "std",
not(hp_no_sme),
any(not(target_vendor = "apple"), hp_no_apple_accelerate)
))]
#[inline]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn sme2_blocked_f64<const M: usize, const N: usize, const K: usize, const BLK: usize>(
ac: *const f64,
bc: *const f64,
c: *mut f64,
) {
let (pm, pn) = (M / BLK, N / BLK);
sme_pack::F64.with_borrow_mut(|buf| {
let need = M * K + K * N;
if buf.len() < need {
buf.resize(need, 0.0);
}
let (ap, bp) = buf.split_at_mut(M * K);
unsafe {
sme_pack::pack_a_f64(ac, ap, pm, K, BLK);
sme_pack::pack_b(bc, &mut bp[..K * N], pn, K, N, BLK);
crate::arch::sme2::mma_f64_grid_packed(ap.as_ptr(), bp.as_ptr(), c, N * 8, pm, pn, K);
}
});
}
macro_rules! sme2_blocked_widen {
($name:ident, $t:ty, $tl:ident, $kern:path) => {
#[cfg(all(
target_arch = "aarch64",
feature = "std",
not(hp_no_sme),
any(not(target_vendor = "apple"), hp_no_apple_accelerate)
))]
#[inline]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn $name<const M: usize, const N: usize, const K: usize, const BLK: usize>(
a: *const $t,
b: *const $t,
c: *mut f32,
) {
let (pm, pn) = (M / BLK, N / BLK);
let pairs = K.div_ceil(2);
let (asz, bsz) = (pm * pairs * BLK * 2, pn * pairs * BLK * 2);
sme_pack::$tl.with_borrow_mut(|buf| {
if buf.len() < asz + bsz {
buf.resize(asz + bsz, <$t>::ZERO);
}
let (ap, bp) = buf.split_at_mut(asz);
unsafe {
sme_pack::pack_a_pairs(a, ap, pm, K, BLK);
sme_pack::pack_b_pairs(b, &mut bp[..bsz], pn, K, N, BLK);
$kern(ap.as_ptr(), bp.as_ptr(), c, N * 4, pm, pn, pairs);
}
});
}
};
}
sme2_blocked_widen!(sme2_blocked_bf16, half::bf16, BF16, crate::arch::sme2::mma_bf16_grid_packed);
sme2_blocked_widen!(sme2_blocked_f16, half::f16, F16, crate::arch::sme2::mma_f16_grid_packed);
#[cfg(all(target_arch = "x86_64", feature = "std", not(hp_no_amx)))]
const AMX_MIN_DIM: usize = 8;
#[cfg(all(
target_arch = "x86_64",
not(any(hp_no_avx, hp_no_avx512)),
any(feature = "std", hp_static_dispatch)
))]
#[target_feature(enable = "avx512bf16,avx512f,avx512bw")]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn bf16_dpbf16_gemm<const M: usize, const N: usize, const K: usize>(
a: &[[half::bf16; K]; M],
b: &[[half::bf16; N]; K],
mut c: [[f32; N]; M],
) -> [[f32; N]; M] {
use crate::arch::avx512bf16 as p;
use core::arch::x86_64::*;
let kpairs = K / 2;
let mut i = 0;
while i < M {
let mut j = 0;
while j + 16 <= N {
let mut acc = _mm512_loadu_ps(c[i].as_ptr().add(j));
let mut t = 0;
while t < kpairs {
let (k0, k1) = (2 * t, 2 * t + 1);
let av = p::bcast_pair(a[i][k0], a[i][k1]);
let bv = p::pack_pair(b[k0].as_ptr().add(j), b[k1].as_ptr().add(j));
acc = p::dp(acc, av, bv);
t += 1;
}
if K & 1 == 1 {
let k = K - 1;
let av = _mm512_set1_ps(a[i][k].to_f32());
let bv = p::widen(b[k].as_ptr().add(j));
acc = _mm512_fmadd_ps(av, bv, acc);
}
_mm512_storeu_ps(c[i].as_mut_ptr().add(j), acc);
j += 16;
}
i += 1;
}
c
}
#[inline]
fn array_mma_simd<'i, T, S, const M: usize, const N: usize, const K: usize>(
backend: S,
a: View<'i, T, M, K>,
b: View<'i, T, K, N>,
c: [[T::Compute; N]; M],
) -> [[T::Compute; N]; M]
where
T: FloatScalar,
S: Backend<T::Compute>,
{
let needs_widen = core::any::TypeId::of::<T>() != core::any::TypeId::of::<T::Compute>();
let (a_dense, b_dense) = (a.dense_ptr(), b.dense_ptr());
let needs_materialize = needs_widen || a_dense.is_none() || b_dense.is_none();
let mut ac = [[<T::Compute as Scalar>::ZERO; K]; M];
let mut bc = [[<T::Compute as Scalar>::ZERO; N]; K];
let materialize = |ac: &mut [[T::Compute; K]; M], bc: &mut [[T::Compute; N]; K]| {
let mut i = 0;
while i < M {
let mut k = 0;
while k < K {
ac[i][k] = a.get(i, k).widen();
k += 1;
}
i += 1;
}
let mut k = 0;
while k < K {
let mut j = 0;
while j < N {
bc[k][j] = b.get(k, j).widen();
j += 1;
}
k += 1;
}
};
#[cfg(all(
target_arch = "aarch64",
feature = "std",
not(hp_no_sme),
any(not(target_vendor = "apple"), hp_no_apple_accelerate)
))]
if M >= SME_MIN_DIM && N >= SME_MIN_DIM && K >= SME_MIN_DIM {
use core::any::TypeId;
let is_bf16 = TypeId::of::<T>() == TypeId::of::<half::bf16>();
let is_f16 = TypeId::of::<T>() == TypeId::of::<half::f16>();
if (is_bf16 || is_f16)
&& let (Some(adp), Some(bdp)) = (a_dense, b_dense)
&& crate::arch::sme1::is_supported()
&& crate::arch::sme2::is_supported()
{
let blk = crate::arch::sme1::streaming_vl_bytes() / 2;
if (blk == 16 || blk == 32 || blk == 64) && M.is_multiple_of(blk) && N.is_multiple_of(blk) {
let mut c = c;
let cp = c.as_mut_ptr() as *mut f32;
unsafe {
if is_bf16 {
let (a, b) = (adp as *const half::bf16, bdp as *const half::bf16);
match blk {
16 => sme2_blocked_bf16::<M, N, K, 16>(a, b, cp),
32 => sme2_blocked_bf16::<M, N, K, 32>(a, b, cp),
_ => sme2_blocked_bf16::<M, N, K, 64>(a, b, cp),
}
} else {
let (a, b) = (adp as *const half::f16, bdp as *const half::f16);
match blk {
16 => sme2_blocked_f16::<M, N, K, 16>(a, b, cp),
32 => sme2_blocked_f16::<M, N, K, 32>(a, b, cp),
_ => sme2_blocked_f16::<M, N, K, 64>(a, b, cp),
}
}
}
return c;
}
}
}
if needs_materialize {
materialize(&mut ac, &mut bc);
}
let cap: *const T::Compute = if needs_materialize {
ac.as_ptr() as *const T::Compute
} else {
a_dense.unwrap() as _
};
let cbp: *const T::Compute = if needs_materialize {
bc.as_ptr() as *const T::Compute
} else {
b_dense.unwrap() as _
};
let _ = (cap, cbp);
#[cfg(all(target_vendor = "apple", not(hp_no_apple_accelerate)))]
if M >= ACCEL_MIN_DIM && N >= ACCEL_MIN_DIM && K >= ACCEL_MIN_DIM {
use core::any::TypeId;
let compute = TypeId::of::<T::Compute>();
if compute == TypeId::of::<f32>() {
let (acp, bcp) = (cap as *const f32, cbp as *const f32);
let mut c = c;
unsafe {
accel::cblas_sgemm(
accel::ROW_MAJOR, accel::NO_TRANS, accel::NO_TRANS,
M as _, N as _, K as _,
1.0, acp, K as _, bcp, N as _,
1.0, c.as_mut_ptr() as *mut f32, N as _,
);
}
return c;
}
if compute == TypeId::of::<f64>() {
let (acp, bcp) = (cap as *const f64, cbp as *const f64);
let mut c = c;
unsafe {
accel::cblas_dgemm(
accel::ROW_MAJOR, accel::NO_TRANS, accel::NO_TRANS,
M as _, N as _, K as _,
1.0, acp, K as _, bcp, N as _,
1.0, c.as_mut_ptr() as *mut f64, N as _,
);
}
return c;
}
}
#[cfg(all(
target_arch = "aarch64",
feature = "std",
not(hp_no_sme),
any(not(target_vendor = "apple"), hp_no_apple_accelerate)
))]
if M >= SME_MIN_DIM && N >= SME_MIN_DIM && K >= SME_MIN_DIM && crate::arch::sme1::is_supported() {
use core::any::TypeId;
let svl = crate::arch::sme1::streaming_vl_bytes();
let sme2 = crate::arch::sme2::is_supported();
let compute = TypeId::of::<T::Compute>();
if compute == TypeId::of::<f32>() {
let (acp, bcp) = (cap as *const f32, cbp as *const f32);
let blk = svl / 2;
if sme2 && (blk == 16 || blk == 32 || blk == 64) && M.is_multiple_of(blk) && N.is_multiple_of(blk) {
let mut c = c;
let cp = c.as_mut_ptr() as *mut f32;
unsafe {
match blk {
16 => sme2_blocked_f32::<M, N, K, 16>(acp, bcp, cp),
32 => sme2_blocked_f32::<M, N, K, 32>(acp, bcp, cp),
_ => sme2_blocked_f32::<M, N, K, 64>(acp, bcp, cp),
}
}
return c;
}
if sme2 && M <= svl / 2 && N <= svl / 2 {
let mut c = c;
unsafe {
crate::arch::sme2::mma_f32_wide::<M, N, K>(acp, K, bcp, N, c.as_mut_ptr() as *mut f32, N);
}
return c;
}
if M <= svl / 4 && N <= svl / 4 {
let mut c = c;
unsafe {
crate::arch::sme1::mma_f32::<M, N, K>(acp, K, bcp, N, c.as_mut_ptr() as *mut f32, N);
}
return c;
}
}
if compute == TypeId::of::<f64>() {
let (acp, bcp) = (cap as *const f64, cbp as *const f64);
let blk = svl / 4;
if sme2 && (blk == 8 || blk == 16 || blk == 32) && M.is_multiple_of(blk) && N.is_multiple_of(blk) {
let mut c = c;
let cp = c.as_mut_ptr() as *mut f64;
unsafe {
match blk {
8 => sme2_blocked_f64::<M, N, K, 8>(acp, bcp, cp),
16 => sme2_blocked_f64::<M, N, K, 16>(acp, bcp, cp),
_ => sme2_blocked_f64::<M, N, K, 32>(acp, bcp, cp),
}
}
return c;
}
if sme2 && M <= svl / 4 && N <= svl / 4 {
let mut c = c;
unsafe {
crate::arch::sme2::mma_f64_wide::<M, N, K>(acp, K, bcp, N, c.as_mut_ptr() as *mut f64, N);
}
return c;
}
if M <= svl / 8 && N <= svl / 8 {
let mut c = c;
unsafe {
crate::arch::sme1::mma_f64::<M, N, K>(acp, K, bcp, N, c.as_mut_ptr() as *mut f64, N);
}
return c;
}
}
}
#[cfg(all(target_arch = "x86_64", feature = "std", not(hp_no_amx)))]
if M >= AMX_MIN_DIM && N >= AMX_MIN_DIM && K >= AMX_MIN_DIM && M <= 16 && N <= 16 && K <= 32 {
use core::any::TypeId;
if let (Some(adp), Some(bdp)) = (a_dense, b_dense)
&& TypeId::of::<T>() == TypeId::of::<half::bf16>()
&& TypeId::of::<T::Compute>() == TypeId::of::<f32>()
&& crate::arch::amx::is_supported()
{
unsafe {
let ab = &*(adp as *const [[half::bf16; K]; M]);
let bb = &*(bdp as *const [[half::bf16; N]; K]);
let mut cf = *(&c as *const [[T::Compute; N]; M] as *const [[f32; N]; M]);
crate::arch::amx::mma_bf16::<M, N, K>(
ab.as_ptr().cast(), K, bb.as_ptr().cast(), N, cf.as_mut_ptr().cast(), N,
);
return *(&cf as *const [[f32; N]; M] as *const [[T::Compute; N]; M]);
}
}
}
#[cfg(all(target_arch = "x86_64", feature = "std", not(hp_no_amx)))]
if M >= AMX_MIN_DIM && N >= AMX_MIN_DIM && K >= AMX_MIN_DIM && M <= 16 && N <= 16 && K <= 32 {
use core::any::TypeId;
if let (Some(adp), Some(bdp)) = (a_dense, b_dense)
&& TypeId::of::<T>() == TypeId::of::<half::f16>()
&& TypeId::of::<T::Compute>() == TypeId::of::<f32>()
&& crate::arch::amx::is_supported_f16()
{
unsafe {
let ab = &*(adp as *const [[half::f16; K]; M]);
let bb = &*(bdp as *const [[half::f16; N]; K]);
let mut cf = *(&c as *const [[T::Compute; N]; M] as *const [[f32; N]; M]);
crate::arch::amx::mma_f16::<M, N, K>(
ab.as_ptr().cast(), K, bb.as_ptr().cast(), N, cf.as_mut_ptr().cast(), N,
);
return *(&cf as *const [[f32; N]; M] as *const [[T::Compute; N]; M]);
}
}
}
#[cfg(all(
target_arch = "x86_64",
not(any(hp_no_avx, hp_no_avx512)),
any(feature = "std", hp_static_dispatch)
))]
if N >= 16 && N.is_multiple_of(16) && K >= 2 {
use core::any::TypeId;
#[cfg(not(hp_static_dispatch))]
let avx512bf16 = is_x86_feature_detected!("avx512bf16")
&& is_x86_feature_detected!("avx512f")
&& is_x86_feature_detected!("avx512bw");
#[cfg(hp_static_dispatch)]
let avx512bf16 = cfg!(all(
target_feature = "avx512bf16",
target_feature = "avx512f",
target_feature = "avx512bw"
));
if let (Some(adp), Some(bdp)) = (a_dense, b_dense)
&& TypeId::of::<T>() == TypeId::of::<half::bf16>()
&& TypeId::of::<T::Compute>() == TypeId::of::<f32>()
&& avx512bf16
{
unsafe {
let ab = &*(adp as *const [[half::bf16; K]; M]);
let bb = &*(bdp as *const [[half::bf16; N]; K]);
let cf = *(&c as *const [[T::Compute; N]; M] as *const [[f32; N]; M]);
let out = bf16_dpbf16_gemm::<M, N, K>(ab, bb, cf);
return *(&out as *const [[f32; N]; M] as *const [[T::Compute; N]; M]);
}
}
}
if !needs_materialize {
materialize(&mut ac, &mut bc);
}
#[cfg(feature = "std")]
if M >= 32 && N >= 32 && K >= 32 {
const REGS: usize = if cfg!(target_arch = "aarch64") || cfg!(target_feature = "avx512f") {
32
} else {
16
};
let k = crate::varying::Gang::new(backend).unroll_for::<T::Compute>();
let by_regs = (REGS - 3) / 5;
let nr = k.div_ceil(4).clamp(2, by_regs).min(4);
return match nr {
2 => packed_gemm::<_, _, M, N, K, 4, 2>(backend, ac, bc, c),
3 => packed_gemm::<_, _, M, N, K, 4, 3>(backend, ac, bc, c),
_ => packed_gemm::<_, _, M, N, K, 4, 4>(backend, ac, bc, c),
};
}
simd_gemm(backend, ac, bc, c)
}
#[cfg(target_arch = "spirv")]
#[inline]
fn array_mma_scalar<'i, T, const M: usize, const N: usize, const K: usize>(
a: View<'i, T, M, K>,
b: View<'i, T, K, N>,
mut c: [[T::Compute; N]; M],
) -> [[T::Compute; N]; M]
where
T: FloatScalar,
{
let mut i = 0;
while i < M {
let mut j = 0;
while j < N {
let mut s = c[i][j];
let mut k = 0;
while k < K {
s = a.get(i, k).widen().fma(b.get(k, j).widen(), s);
k += 1;
}
c[i][j] = s;
j += 1;
}
i += 1;
}
c
}
macro_rules! array_tile_methods {
() => {
type Tile<'a, E: Scalar, const R: usize, const C: usize, Ro: Role> =
<Ro as $crate::matrix::Role>::Repr<'a, E, R, C>;
#[inline]
fn tile_load<'a, E: Scalar, const R: usize, const C: usize, Ro: Role>(
self,
mem: &'a [E],
row_stride: usize,
layout: Layout,
) -> <Ro as $crate::matrix::Role>::Repr<'a, E, R, C> {
$crate::matrix::CpuTile::ct_load(mem, row_stride, layout)
}
#[inline]
fn tile_store<E: Scalar, const R: usize, const C: usize, Ro: Role>(
self,
t: <Ro as $crate::matrix::Role>::Repr<'_, E, R, C>,
out: &mut [E],
row_stride: usize,
layout: Layout,
) {
$crate::matrix::CpuTile::ct_store(t, out, row_stride, layout)
}
#[inline]
fn tile_splat<'a, E: Scalar, const R: usize, const C: usize, Ro: Role>(
self,
v: E,
) -> <Ro as $crate::matrix::Role>::Repr<'a, E, R, C> {
$crate::matrix::CpuTile::ct_splat(v)
}
#[inline]
fn tile_map<'a, E: Scalar, const R: usize, const C: usize, Ro: Role>(
self,
t: <Ro as $crate::matrix::Role>::Repr<'a, E, R, C>,
f: impl Fn(E) -> E,
) -> <Ro as $crate::matrix::Role>::Repr<'a, E, R, C> {
$crate::matrix::CpuTile::ct_map(t, f)
}
};
}
macro_rules! impl_array_matrix_backend {
($backend:ty, simd) => {
impl<T: FloatScalar> $crate::matrix::MatrixBackend<T> for $backend
where
$backend: $crate::backend::Backend<T> + $crate::backend::Backend<<T as FloatScalar>::Compute>,
{
array_tile_methods!();
#[inline]
fn mma<'i, const M: usize, const N: usize, const K: usize>(
self,
a: Self::Tile<'i, T, M, K, $crate::matrix::MatrixA>,
b: Self::Tile<'i, T, K, N, $crate::matrix::MatrixB>,
c: Self::Tile<'i, <T as FloatScalar>::Compute, M, N, $crate::matrix::Accumulator>,
) -> Self::Tile<'i, <T as FloatScalar>::Compute, M, N, $crate::matrix::Accumulator> {
array_mma_simd::<T, $backend, M, N, K>(self, a, b, c)
}
}
};
($backend:ty, scalar) => {
impl<T: FloatScalar> $crate::matrix::MatrixBackend<T> for $backend
where
$backend: $crate::backend::Backend<T>,
{
array_tile_methods!();
#[inline]
fn mma<'i, const M: usize, const N: usize, const K: usize>(
self,
a: Self::Tile<'i, T, M, K, $crate::matrix::MatrixA>,
b: Self::Tile<'i, T, K, N, $crate::matrix::MatrixB>,
c: Self::Tile<'i, <T as FloatScalar>::Compute, M, N, $crate::matrix::Accumulator>,
) -> Self::Tile<'i, <T as FloatScalar>::Compute, M, N, $crate::matrix::Accumulator> {
array_mma_scalar::<T, M, N, K>(a, b, c)
}
}
};
}
impl_array_matrix_backend!(crate::backend::ScalarBackend, simd);
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
impl_array_matrix_backend!(crate::backend::avx1::Avx1, simd);
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
impl_array_matrix_backend!(crate::backend::avx2::Avx2, simd);
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
impl_array_matrix_backend!(crate::backend::sse4::Sse4, simd);
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
impl_array_matrix_backend!(crate::backend::avx512::Avx512, simd);
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
impl_array_matrix_backend!(crate::backend::avx512fp16::Avx512Fp16, simd);
#[cfg(target_arch = "aarch64")]
impl_array_matrix_backend!(crate::backend::neon::Neon, simd);
#[cfg(target_arch = "arm")]
impl_array_matrix_backend!(crate::backend::neon_a32::Neon, simd);
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
impl_array_matrix_backend!(crate::backend::wasm::Simd128, simd);
#[cfg(all(target_arch = "wasm32", target_feature = "relaxed-simd"))]
impl_array_matrix_backend!(crate::backend::wasm::RelaxedSimd, simd);
#[cfg(target_arch = "aarch64")]
impl<const W: usize, T: FloatScalar> MatrixBackend<T> for crate::backend::sve::Sve<W>
where
crate::backend::sve::Sve<W>:
crate::backend::Backend<T> + crate::backend::Backend<<T as FloatScalar>::Compute>,
{
array_tile_methods!();
#[inline]
fn mma<'i, const M: usize, const N: usize, const K: usize>(
self,
a: Self::Tile<'i, T, M, K, MatrixA>,
b: Self::Tile<'i, T, K, N, MatrixB>,
c: Self::Tile<'i, <T as FloatScalar>::Compute, M, N, Accumulator>,
) -> Self::Tile<'i, <T as FloatScalar>::Compute, M, N, Accumulator> {
array_mma_simd::<T, crate::backend::sve::Sve<W>, M, N, K>(self, a, b, c)
}
}
#[cfg(target_arch = "riscv64")]
impl<const W: usize, T: FloatScalar> MatrixBackend<T> for crate::backend::rvv::Rvv<W>
where
crate::backend::rvv::Rvv<W>:
crate::backend::Backend<T> + crate::backend::Backend<<T as FloatScalar>::Compute>,
{
array_tile_methods!();
#[inline]
fn mma<'i, const M: usize, const N: usize, const K: usize>(
self,
a: Self::Tile<'i, T, M, K, MatrixA>,
b: Self::Tile<'i, T, K, N, MatrixB>,
c: Self::Tile<'i, <T as FloatScalar>::Compute, M, N, Accumulator>,
) -> Self::Tile<'i, <T as FloatScalar>::Compute, M, N, Accumulator> {
array_mma_simd::<T, crate::backend::rvv::Rvv<W>, M, N, K>(self, a, b, c)
}
}
#[cfg(target_arch = "spirv")]
impl_array_matrix_backend!(crate::backend::subgroup::Subgroup, scalar);
use core::marker::PhantomData;
use crate::varying::Gang;
#[derive(Clone, Copy)]
pub struct Tiles<T: FloatScalar, S: MatrixBackend<T>> {
backend: S,
_t: PhantomData<T>,
}
#[derive(Clone, Copy)]
pub struct Tile<
'a,
T: FloatScalar,
S: MatrixBackend<T>,
E: Scalar,
const R: usize,
const C: usize,
Ro: Role,
> {
backend: S,
inner: S::Tile<'a, E, R, C, Ro>,
_p: PhantomData<(T, Ro)>,
}
impl<S: Copy> Gang<S> {
#[inline(always)]
pub fn tiles<T: FloatScalar>(self) -> Tiles<T, S>
where
S: MatrixBackend<T>,
{
Tiles {
backend: self.backend(),
_t: PhantomData,
}
}
}
impl<T: FloatScalar, S: MatrixBackend<T>> Tiles<T, S> {
#[inline]
pub fn load_a<'a, const M: usize, const K: usize>(
self,
mem: &'a [T],
row_stride: usize,
layout: Layout,
) -> Tile<'a, T, S, T, M, K, MatrixA> {
Tile {
backend: self.backend,
inner: self.backend.tile_load(mem, row_stride, layout),
_p: PhantomData,
}
}
#[inline]
pub fn load_b<'a, const K: usize, const N: usize>(
self,
mem: &'a [T],
row_stride: usize,
layout: Layout,
) -> Tile<'a, T, S, T, K, N, MatrixB> {
Tile {
backend: self.backend,
inner: self.backend.tile_load(mem, row_stride, layout),
_p: PhantomData,
}
}
#[inline]
pub fn load_acc<'a, const M: usize, const N: usize>(
self,
mem: &'a [T::Compute],
row_stride: usize,
layout: Layout,
) -> Tile<'a, T, S, T::Compute, M, N, Accumulator> {
Tile {
backend: self.backend,
inner: self.backend.tile_load(mem, row_stride, layout),
_p: PhantomData,
}
}
#[inline]
pub fn load_a_rm<'a, const M: usize, const K: usize>(
self,
mem: &'a [T],
) -> Tile<'a, T, S, T, M, K, MatrixA> {
self.load_a::<M, K>(mem, K, Layout::RowMajor)
}
#[inline]
pub fn load_b_rm<'a, const K: usize, const N: usize>(
self,
mem: &'a [T],
) -> Tile<'a, T, S, T, K, N, MatrixB> {
self.load_b::<K, N>(mem, N, Layout::RowMajor)
}
#[inline]
pub fn load_acc_rm<'a, const M: usize, const N: usize>(
self,
mem: &'a [T::Compute],
) -> Tile<'a, T, S, T::Compute, M, N, Accumulator> {
self.load_acc::<M, N>(mem, N, Layout::RowMajor)
}
#[inline]
pub fn zero_acc<'a, const M: usize, const N: usize>(
self,
) -> Tile<'a, T, S, T::Compute, M, N, Accumulator> {
self.splat_acc(<T::Compute as Scalar>::ZERO)
}
#[inline]
pub fn splat_acc<'a, const M: usize, const N: usize>(
self,
v: T::Compute,
) -> Tile<'a, T, S, T::Compute, M, N, Accumulator> {
Tile {
backend: self.backend,
inner: self.backend.tile_splat(v),
_p: PhantomData,
}
}
#[inline]
pub fn mma<'i, const M: usize, const N: usize, const K: usize>(
self,
a: Tile<'i, T, S, T, M, K, MatrixA>,
b: Tile<'i, T, S, T, K, N, MatrixB>,
c: Tile<'i, T, S, T::Compute, M, N, Accumulator>,
) -> Tile<'i, T, S, T::Compute, M, N, Accumulator> {
Tile {
backend: self.backend,
inner: self.backend.mma(a.inner, b.inner, c.inner),
_p: PhantomData,
}
}
}
impl<'a, T: FloatScalar, S: MatrixBackend<T>, E: Scalar, const R: usize, const C: usize, Ro: Role>
Tile<'a, T, S, E, R, C, Ro>
{
#[inline(always)]
pub fn raw(self) -> S::Tile<'a, E, R, C, Ro> {
self.inner
}
#[inline]
pub fn store(self, out: &mut [E], row_stride: usize, layout: Layout) {
self.backend.tile_store(self.inner, out, row_stride, layout);
}
#[inline]
pub fn store_rm(self, out: &mut [E]) {
self.backend.tile_store(self.inner, out, C, Layout::RowMajor);
}
#[inline]
pub fn store_ex(self, out: &mut [E], row_stride: usize, layout: Layout, f: impl Fn(E) -> E) {
self.map(f).store(out, row_stride, layout);
}
#[inline]
pub fn store_rm_ex(self, out: &mut [E], f: impl Fn(E) -> E) {
self.map(f).store_rm(out);
}
#[inline]
pub fn map(self, f: impl Fn(E) -> E) -> Self {
Tile {
backend: self.backend,
inner: self.backend.tile_map(self.inner, f),
_p: PhantomData,
}
}
}
pub trait MatrixKernel<T: FloatScalar> {
type Output;
fn run<S: MatrixBackend<T>>(self, ctx: Gang<S>) -> Self::Output;
}
#[inline]
pub fn run_matrix_scalar<T: FloatScalar, K: MatrixKernel<T>>(kernel: K) -> K::Output {
kernel.run(Gang::new(crate::backend::ScalarBackend))
}
pub trait MatrixDispatch: FloatScalar {
fn dispatch_matrix<K: MatrixKernel<Self>>(kernel: K) -> K::Output;
}
#[inline]
pub fn dispatch_matrix<T: MatrixDispatch, K: MatrixKernel<T>>(kernel: K) -> K::Output {
T::dispatch_matrix(kernel)
}
macro_rules! impl_matrix_dispatch_simd {
($ty:ty $(, $arm_tail:ident)?) => {
impl MatrixDispatch for $ty {
#[inline]
#[allow(unreachable_code)]
fn dispatch_matrix<K: MatrixKernel<Self>>(kernel: K) -> K::Output {
#[cfg(all(
any(target_arch = "x86_64", target_arch = "x86"),
feature = "std",
not(hp_static_dispatch)
))]
{
#[cfg(not(any(hp_no_avx, hp_no_avx512)))]
if let Some(b) = crate::backend::avx512::Avx512::detect() {
return kernel.run(Gang::new(b));
}
#[cfg(not(hp_no_avx))]
if let Some(b) = crate::backend::avx2::Avx2::detect() {
return kernel.run(Gang::new(b));
}
#[cfg(not(hp_no_avx))]
if let Some(b) = crate::backend::avx1::Avx1::detect() {
return kernel.run(Gang::new(b));
}
if let Some(b) = crate::backend::sse4::Sse4::detect() {
return kernel.run(Gang::new(b));
}
}
#[cfg(all(
any(target_arch = "x86_64", target_arch = "x86"),
any(not(feature = "std"), hp_static_dispatch),
target_feature = "avx512f",
not(any(hp_no_avx, hp_no_avx512))
))]
{
let b = unsafe { crate::backend::avx512::Avx512::new_unchecked() };
return kernel.run(Gang::new(b));
}
#[cfg(all(
any(target_arch = "x86_64", target_arch = "x86"),
any(not(feature = "std"), hp_static_dispatch),
not(all(target_feature = "avx512f", not(any(hp_no_avx, hp_no_avx512)))),
target_feature = "avx2",
target_feature = "fma",
not(hp_no_avx)
))]
{
let b = unsafe { crate::backend::avx2::Avx2::new_unchecked() };
return kernel.run(Gang::new(b));
}
#[cfg(all(
any(target_arch = "x86_64", target_arch = "x86"),
any(not(feature = "std"), hp_static_dispatch),
not(all(target_feature = "avx512f", not(any(hp_no_avx, hp_no_avx512)))),
not(all(target_feature = "avx2", target_feature = "fma", not(hp_no_avx))),
target_feature = "avx",
not(hp_no_avx)
))]
{
let b = unsafe { crate::backend::avx1::Avx1::new_unchecked() };
return kernel.run(Gang::new(b));
}
#[cfg(all(
any(target_arch = "x86_64", target_arch = "x86"),
any(not(feature = "std"), hp_static_dispatch),
not(all(target_feature = "avx512f", not(any(hp_no_avx, hp_no_avx512)))),
not(all(target_feature = "avx2", target_feature = "fma", not(hp_no_avx))),
not(all(target_feature = "avx", not(hp_no_avx))),
target_feature = "sse4.1"
))]
{
let b = unsafe { crate::backend::sse4::Sse4::new_unchecked() };
return kernel.run(Gang::new(b));
}
crate::dispatch::aarch64_dispatch_tail!(kernel, crate::backend::neon::Neon::new());
crate::dispatch::riscv_dispatch_tail!(kernel);
$( crate::dispatch::$arm_tail!(kernel); )?
crate::dispatch::wasm_dispatch_tail!(kernel);
kernel.run(Gang::new(crate::backend::ScalarBackend))
}
}
};
}
impl_matrix_dispatch_simd!(f32, arm_dispatch_tail);
impl_matrix_dispatch_simd!(f64);
mod half_matrix_dispatch {
use super::{MatrixDispatch, MatrixKernel, Gang};
use half::{bf16, f16};
impl MatrixDispatch for f16 {
#[inline]
#[allow(unreachable_code)]
fn dispatch_matrix<K: MatrixKernel<Self>>(kernel: K) -> K::Output {
#[cfg(all(
any(target_arch = "x86_64", target_arch = "x86"),
feature = "std",
not(hp_static_dispatch),
not(hp_no_avx)
))]
{
if let Some(b) = crate::backend::avx2::Avx2::detect() {
return kernel.run(Gang::new(b));
}
}
#[cfg(all(
any(target_arch = "x86_64", target_arch = "x86"),
any(not(feature = "std"), hp_static_dispatch),
target_feature = "avx2",
target_feature = "fma",
target_feature = "f16c",
not(hp_no_avx)
))]
{
let b = unsafe { crate::backend::avx2::Avx2::new_unchecked() };
return kernel.run(Gang::new(b));
}
crate::dispatch::aarch64_dispatch_tail!(kernel, crate::backend::ScalarBackend);
kernel.run(Gang::new(crate::backend::ScalarBackend))
}
}
impl MatrixDispatch for bf16 {
#[inline]
#[allow(unreachable_code)]
fn dispatch_matrix<K: MatrixKernel<Self>>(kernel: K) -> K::Output {
#[cfg(all(
any(target_arch = "x86_64", target_arch = "x86"),
feature = "std",
not(hp_static_dispatch)
))]
{
#[cfg(not(any(hp_no_avx, hp_no_avx512)))]
if let Some(b) = crate::backend::avx512::Avx512::detect() {
return kernel.run(Gang::new(b));
}
#[cfg(not(hp_no_avx))]
if let Some(b) = crate::backend::avx2::Avx2::detect() {
return kernel.run(Gang::new(b));
}
}
#[cfg(all(
any(target_arch = "x86_64", target_arch = "x86"),
any(not(feature = "std"), hp_static_dispatch),
target_feature = "avx512f",
not(any(hp_no_avx, hp_no_avx512))
))]
{
let b = unsafe { crate::backend::avx512::Avx512::new_unchecked() };
return kernel.run(Gang::new(b));
}
#[cfg(all(
any(target_arch = "x86_64", target_arch = "x86"),
any(not(feature = "std"), hp_static_dispatch),
not(all(target_feature = "avx512f", not(any(hp_no_avx, hp_no_avx512)))),
target_feature = "avx2",
target_feature = "fma",
not(hp_no_avx)
))]
{
let b = unsafe { crate::backend::avx2::Avx2::new_unchecked() };
return kernel.run(Gang::new(b));
}
crate::dispatch::aarch64_dispatch_tail!(kernel, crate::backend::neon::Neon::new());
crate::dispatch::wasm_dispatch_tail!(kernel);
kernel.run(Gang::new(crate::backend::ScalarBackend))
}
}
}
#[cfg(all(test, feature = "std"))]
mod packed_parity {
use crate::{Backend, Gang, Kernel, dispatch};
const M: usize = 9;
const N: usize = 22;
const K: usize = 7;
#[allow(clippy::needless_range_loop, clippy::type_complexity)]
fn data() -> ([[f32; K]; M], [[f32; N]; K], [[f32; N]; M]) {
let mut a = [[0.0f32; K]; M];
let mut b = [[0.0f32; N]; K];
let mut c = [[0.0f32; N]; M];
for i in 0..M {
for k in 0..K {
a[i][k] = ((i * K + k) as f32 * 0.13).sin();
}
}
for k in 0..K {
for j in 0..N {
b[k][j] = ((k * N + j) as f32 * 0.07).cos();
}
}
for i in 0..M {
for j in 0..N {
c[i][j] = ((i + j) as f32 * 0.01) - 0.5;
}
}
(a, b, c)
}
struct Probe;
impl Kernel<f32> for Probe {
type Output = ();
fn run<S: crate::backend::BackendAll + Backend<f32>>(self, ctx: Gang<S>) {
let be = ctx.backend();
let (a, b, c) = data();
let want = super::simd_gemm::<f32, S, M, N, K>(be, a, b, c);
assert_eq!(super::packed_gemm::<_, _, M, N, K, 4, 2>(be, a, b, c), want);
assert_eq!(super::packed_gemm::<_, _, M, N, K, 4, 3>(be, a, b, c), want);
assert_eq!(super::packed_gemm::<_, _, M, N, K, 4, 4>(be, a, b, c), want);
}
}
#[test]
fn packed_matches_simd_every_block_width() {
dispatch(Probe);
}
}