#[derive(Debug, Clone)]
pub struct Matrix {
rows: usize,
cols: usize,
data: Vec<f32>,
}
impl Matrix {
pub fn new(rows: usize, cols: usize) -> Self {
Matrix {
rows,
cols,
data: vec![0.0; rows * cols],
}
}
pub fn from_data(data: Vec<f32>, rows: usize, cols: usize) -> Self {
assert_eq!(data.len(), rows * cols);
Matrix { rows, cols, data }
}
pub fn rows(&self) -> usize {
self.rows
}
pub fn cols(&self) -> usize {
self.cols
}
pub fn size(&self) -> usize {
self.rows * self.cols
}
pub fn get(&self, row: usize, col: usize) -> f32 {
self.data[row * self.cols + col]
}
pub fn set(&mut self, row: usize, col: usize, value: f32) {
self.data[row * self.cols + col] = value;
}
pub fn data(&self) -> &[f32] {
&self.data
}
pub fn data_mut(&mut self) -> &mut [f32] {
&mut self.data
}
pub fn transpose(&self) -> Matrix {
let mut result = Matrix::new(self.cols, self.rows);
for i in 0..self.rows {
for j in 0..self.cols {
result.set(j, i, self.get(i, j));
}
}
result
}
}
#[derive(Debug, Clone)]
pub struct MatrixPair {
pub a: Matrix,
pub b: Matrix,
}
#[derive(Debug, Clone)]
pub struct OptimizedMatrix {
pub original: Matrix,
pub is_optimized: bool,
pub block_size: usize,
pub memory_accesses: u64,
}
#[derive(Debug, Clone)]
pub struct MatrixAcceleratorStats {
pub total_operations: u64,
pub cache_hits: u64,
pub cache_misses: u64,
pub simd_used: u64,
pub block_size_used: usize,
}
pub struct MatrixAccelerator {
hardware_features: AiHardwareFeatures,
stats: MatrixAcceleratorStats,
operation_count: AtomicU64,
cache_hits: AtomicU64,
cache_misses: AtomicU64,
simd_used: AtomicU64,
}
impl MatrixAccelerator {
pub fn new() -> Self {
MatrixAccelerator {
hardware_features: AiHardwareFeatures {
has_avx512: false,
has_avx2: true,
has_sse4_2: true,
threads_support: true,
optimal_vector_width: crate::wasm::simd_engine::VectorWidth::Avx2(256),
},
stats: MatrixAcceleratorStats {
total_operations: 0,
cache_hits: 0,
cache_misses: 0,
simd_used: 0,
block_size_used: 64,
},
operation_count: AtomicU64::new(0),
cache_hits: AtomicU64::new(0),
cache_misses: AtomicU64::new(0),
simd_used: AtomicU64::new(0),
}
}
pub fn gemm_optimized(&self, a: &Matrix, b: &Matrix) -> Matrix {
assert_eq!(a.cols(), b.rows());
let rows: _ = a.rows();
let cols: _ = b.cols();
let k: _ = a.cols();
let mut result = Matrix::new(rows, cols);
let block_size: _ = self.select_block_size();
for ii in (0..rows).step_by(block_size) {
for jj in (0..cols).step_by(block_size) {
for kk in (0..k).step_by(block_size) {
let i_end: _ = (ii + block_size).min(rows);
let j_end: _ = (jj + block_size).min(cols);
let k_end: _ = (kk + block_size).min(k);
self.multiply_blocks(
a,
b,
&mut result,
ii,
jj,
kk,
i_end,
j_end,
k_end,
);
}
}
}
self.operation_count.fetch_add(1, Ordering::Relaxed);
result
}
pub fn vector_dot_product(&self, a: &[f32], b: &[f32]) -> f32 {
assert_eq!(a.len(), b.len());
let len: _ = a.len();
let mut sum = 0.0f32;
if self.hardware_features.has_avx512 {
self.simd_used.fetch_add(1, Ordering::Relaxed);
sum = self.simd512_vector_dot(a, b);
} else if self.hardware_features.has_avx2 {
self.simd_used.fetch_add(1, Ordering::Relaxed);
sum = self.simd256_vector_dot(a, b);
} else if self.hardware_features.has_sse4_2 {
self.simd_used.fetch_add(1, Ordering::Relaxed);
sum = self.simd128_vector_dot(a, b);
} else {
for i in 0..len {
sum += a[i] * b[i];
}
}
self.operation_count.fetch_add(1, Ordering::Relaxed);
sum
}
pub fn batch_gemm(&self, batch: &[MatrixPair]) -> Vec<Matrix> {
let mut results = Vec::with_capacity(batch.len());
for pair in batch {
let result: _ = self.gemm_optimized(&pair.a, &pair.b);
results.push(result);
}
self.operation_count.fetch_add(batch.len() as u64, Ordering::Relaxed);
results
}
pub fn optimize_layout(&self, matrix: &Matrix) -> OptimizedMatrix {
let block_size: _ = self.select_block_size();
let memory_accesses: _ = (matrix.rows() * matrix.cols()) as u64;
OptimizedMatrix {
original: matrix.clone(),
is_optimized: true,
block_size,
memory_accesses,
}
}
fn select_block_size(&self) -> usize {
if self.hardware_features.has_avx512 {
128
} else if self.hardware_features.has_avx2 {
64
} else {
32
}
}
fn multiply_blocks(
&self,
a: &Matrix,
b: &Matrix,
result: &mut Matrix,
i_start: usize,
j_start: usize,
k_start: usize,
i_end: usize,
j_end: usize,
k_end: usize,
) {
for i in i_start..i_end {
for j in j_start..j_end {
let mut sum = result.get(i, j);
for k in k_start..k_end {
sum += a.get(i, k) * b.get(k, j);
}
result.set(i, j, sum);
}
}
}
fn simd512_vector_dot(&self, a: &[f32], b: &[f32]) -> f32 {
#[cfg(target_arch = "x86_64")]
if is_x86_feature_detected!("avx512f") {
let mut sum = _mm512_setzero_ps();
let len: _ = a.len() / 16;
for i in 0..len {
let a_chunk: _ = _mm512_loadu_ps(&a[i * 16..]);
let b_chunk: _ = _mm512_loadu_ps(&b[i * 16..]);
let prod: _ = _mm512_mul_ps(a_chunk, b_chunk);
sum = _mm512_add_ps(sum, prod);
}
let result: _ = _mm512_reduce_add_ps(sum);
return result + self.accumulate_remainder(a, b, len * 16);
}
self.scalar_vector_dot(a, b)
}
fn simd256_vector_dot(&self, a: &[f32], b: &[f32]) -> f32 {
#[cfg(target_arch = "x86_64")]
if is_x86_feature_detected!("avx2") {
let mut sum = _mm256_setzero_ps();
let len: _ = a.len() / 8;
for i in 0..len {
let a_chunk: _ = _mm256_loadu_ps(&a[i * 8..]);
let b_chunk: _ = _mm256_loadu_ps(&b[i * 8..]);
let prod: _ = _mm256_mul_ps(a_chunk, b_chunk);
sum = _mm256_add_ps(sum, prod);
}
let result: _ = _mm256_hadd_ps(sum, sum);
let result: _ = _mm256_hadd_ps(result, result);
let result: _ = _mm256_hadd_ps(result, result);
let result: _ = _mm256_extract_epi32(_mm256_castps_si256(result), 0) as f32;
return result + self.accumulate_remainder(a, b, len * 8);
}
self.scalar_vector_dot(a, b)
}
fn simd128_vector_dot(&self, a: &[f32], b: &[f32]) -> f32 {
#[cfg(target_arch = "x86_64")]
if is_x86_feature_detected!("sse4.2") {
use std::collections::{HashMap, BTreeMap};
use std::sync::atomic::Ordering;
use std::sync::atomic::AtomicU64;
let mut sum = _mm_setzero_ps();
let len: _ = a.len() / 4;
for i in 0..len {
let a_chunk: _ = _mm_loadu_ps(&a[i * 4..]);
let b_chunk: _ = _mm_loadu_ps(&b[i * 4..]);
let prod: _ = _mm_mul_ps(a_chunk, b_chunk);
sum = _mm_add_ps(sum, prod);
}
let result: _ = _mm_hadd_ps(sum, sum);
let result: _ = _mm_hadd_ps(result, result);
let result: _ = _mm_extract_ps(result, 0) as f32;
return result + self.accumulate_remainder(a, b, len * 4);
}
self.scalar_vector_dot(a, b)
}
fn scalar_vector_dot(&self, a: &[f32], b: &[f32]) -> f32 {
let len: _ = a.len();
let mut sum = 0.0f32;
for i in 0..len {
sum += a[i] * b[i];
}
sum
}
fn accumulate_remainder(&self, a: &[f32], b: &[f32], start: usize) -> f32 {
let len: _ = a.len();
let mut sum = 0.0f32;
for i in start..len {
sum += a[i] * b[i];
}
sum
}
pub fn get_stats(&self) -> MatrixAcceleratorStats {
MatrixAcceleratorStats {
total_operations: self.operation_count.load(Ordering::Relaxed),
cache_hits: self.cache_hits.load(Ordering::Relaxed),
cache_misses: self.cache_misses.load(Ordering::Relaxed),
simd_used: self.simd_used.load(Ordering::Relaxed),
block_size_used: self.select_block_size(),
}
}
}
impl Default for MatrixAccelerator {
fn default() -> Self {
Self::new()
}
}
impl std::ops::Add for &Matrix {
type Output = Matrix;
fn add(self, other: &Matrix) -> Matrix {
assert_eq!(self.rows(), other.rows());
assert_eq!(self.cols(), other.cols());
let mut result = Matrix::new(self.rows(), self.cols());
for i in 0..self.rows() {
for j in 0..self.cols() {
result.set(i, j, self.get(i, j) + other.get(i, j));
}
}
result
}
}
impl std::ops::Sub for &Matrix {
type Output = Matrix;
fn sub(self, other: &Matrix) -> Matrix {
assert_eq!(self.rows(), other.rows());
assert_eq!(self.cols(), other.cols());
let mut result = Matrix::new(self.rows(), self.cols());
for i in 0..self.rows() {
for j in 0..self.cols() {
result.set(i, j, self.get(i, j) - other.get(i, j));
}
}
result
}
}
impl std::ops::Mul<f32> for &Matrix {
type Output = Matrix;
fn mul(self, scalar: f32) -> Matrix {
let mut result = Matrix::new(self.rows(), self.cols());
for i in 0..self.rows() {
for j in 0..self.cols() {
result.set(i, j, self.get(i, j) * scalar);
}
}
result
}
}