use crate::bf16::Bf16;
use crate::error::{LatticeError, Result};
use crate::geometry::Geometry;
use crate::lattice::PaddedTileLattice;
pub trait Numeric: Copy + Default {
fn to_acc(self) -> f32;
fn from_acc(value: f32) -> Self;
}
impl Numeric for f32 {
#[inline]
fn to_acc(self) -> f32 {
self
}
#[inline]
fn from_acc(value: f32) -> Self {
value
}
}
impl Numeric for Bf16 {
#[inline]
fn to_acc(self) -> f32 {
self.to_f32()
}
#[inline]
fn from_acc(value: f32) -> Self {
Bf16::from_f32(value)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct SystolicStats {
pub macs: u64,
pub weight_block_loads: u64,
pub output_blocks: u64,
pub padding_macs: u64,
}
impl SystolicStats {
pub fn total_macs(&self) -> u64 {
self.macs + self.padding_macs
}
pub fn utilisation(&self) -> f64 {
let total = self.total_macs();
if total == 0 {
0.0
} else {
self.macs as f64 / total as f64
}
}
}
impl<T: Numeric> PaddedTileLattice<T> {
pub fn matmul_with_stats(
&self,
rhs: &PaddedTileLattice<T>,
) -> Result<(PaddedTileLattice<T>, SystolicStats)> {
if self.cols() != rhs.rows() {
return Err(LatticeError::ContractionMismatch {
lhs_cols: self.cols(),
rhs_rows: rhs.rows(),
});
}
if self.geometry() != rhs.geometry() {
return Err(LatticeError::GeometryMismatch);
}
let geom: Geometry = *self.geometry();
let m = self.rows();
let k = self.cols();
let n = rhs.cols();
let mxu = geom.mxu;
let mut acc = vec![0.0f32; m * n];
let mut stats = SystolicStats::default();
for i0 in (0..m).step_by(mxu) {
for j0 in (0..n).step_by(mxu) {
stats.output_blocks += 1;
for k0 in (0..k).step_by(mxu) {
stats.weight_block_loads += 1;
let i_end = (i0 + mxu).min(m);
let j_end = (j0 + mxu).min(n);
let k_end = (k0 + mxu).min(k);
for i in i0..i_end {
for j in j0..j_end {
let mut sum = acc[i * n + j];
for kk in k0..k_end {
let a = self.get(i, kk).unwrap().to_acc();
let b = rhs.get(kk, j).unwrap().to_acc();
sum += a * b;
stats.macs += 1;
}
acc[i * n + j] = sum;
}
}
}
}
}
let padded_macs = (Geometry::round_up(m, mxu) as u64)
* (Geometry::round_up(n, mxu) as u64)
* (Geometry::round_up(k, mxu) as u64);
stats.padding_macs = padded_macs.saturating_sub(stats.macs);
let dense: Vec<T> = acc.into_iter().map(T::from_acc).collect();
let out = PaddedTileLattice::from_dense(m, n, &dense, geom)?;
Ok((out, stats))
}
pub fn matmul(&self, rhs: &PaddedTileLattice<T>) -> Result<PaddedTileLattice<T>> {
self.matmul_with_stats(rhs).map(|(out, _)| out)
}
}