use crate::primitives::{Matrix, Vector};
pub type MatrixF64 = Matrix<f64>;
pub type VectorF64 = Vector<f64>;
#[derive(Debug, Clone)]
pub struct WasmMemoryConfig {
pub initial_pages: u32,
pub max_pages: Option<u32>,
pub shared: bool,
}
impl Default for WasmMemoryConfig {
fn default() -> Self {
Self {
initial_pages: 256, max_pages: Some(16384), shared: false,
}
}
}
impl WasmMemoryConfig {
#[must_use]
pub fn small_model() -> Self {
Self {
initial_pages: 512, max_pages: Some(4096), shared: false,
}
}
#[must_use]
pub fn medium_model() -> Self {
Self {
initial_pages: 2048, max_pages: Some(8192), shared: false,
}
}
#[must_use]
pub fn qwen2_0_5b() -> Self {
Self {
initial_pages: 4096, max_pages: Some(8192), shared: false,
}
}
#[must_use]
pub fn initial_bytes(&self) -> usize {
self.initial_pages as usize * 65536
}
#[must_use]
pub fn max_bytes(&self) -> Option<usize> {
self.max_pages.map(|p| p as usize * 65536)
}
}
#[derive(Debug, Clone)]
pub struct SimdVerification {
pub f32x4_verified: bool,
pub i32x4_verified: bool,
pub speedup_factor: f64,
}
#[must_use]
pub fn verify_f32x4_operations() -> bool {
let a = [1.0_f32, 2.0, 3.0, 4.0];
let b = [0.5_f32, 1.0, 1.5, 2.0];
let add_expected = [1.5_f32, 3.0, 4.5, 6.0];
let mul_expected = [0.5_f32, 2.0, 4.5, 8.0];
let add_result: Vec<f32> = a.iter().zip(b.iter()).map(|(x, y)| x + y).collect();
let mul_result: Vec<f32> = a.iter().zip(b.iter()).map(|(x, y)| x * y).collect();
let add_ok = add_result
.iter()
.zip(add_expected.iter())
.all(|(a, b)| (a - b).abs() < 1e-6);
let mul_ok = mul_result
.iter()
.zip(mul_expected.iter())
.all(|(a, b)| (a - b).abs() < 1e-6);
add_ok && mul_ok
}
#[must_use]
pub fn verify_i32x4_operations() -> bool {
let a = [1_i32, 2, 3, 4];
let b = [5_i32, 6, 7, 8];
let add_expected = [6_i32, 8, 10, 12];
let mul_expected = [5_i32, 12, 21, 32];
let add_result: Vec<i32> = a.iter().zip(b.iter()).map(|(x, y)| x + y).collect();
let mul_result: Vec<i32> = a.iter().zip(b.iter()).map(|(x, y)| x * y).collect();
add_result == add_expected && mul_result == mul_expected
}
#[derive(Debug)]
pub struct WasmTensorView {
pub offset: usize,
pub len: usize,
pub element_size: usize,
}
impl WasmTensorView {
#[must_use]
pub fn new(offset: usize, len: usize, element_size: usize) -> Self {
Self {
offset,
len,
element_size,
}
}
#[must_use]
pub fn size_bytes(&self) -> usize {
self.len * self.element_size
}
#[must_use]
pub fn f32_tensor(offset: usize, len: usize) -> Self {
Self::new(offset, len, 4)
}
#[must_use]
pub fn i32_tensor(offset: usize, len: usize) -> Self {
Self::new(offset, len, 4)
}
#[must_use]
pub fn i8_tensor(offset: usize, len: usize) -> Self {
Self::new(offset, len, 1)
}
}
#[derive(Debug)]
pub struct WasmInferenceSession {
kv_cache_size: usize,
max_seq_len: usize,
position: usize,
tokens_generated: usize,
}
impl WasmInferenceSession {
#[must_use]
pub fn new_qwen2_0_5b() -> Self {
Self {
kv_cache_size: 128 * 1024 * 1024, max_seq_len: 2048,
position: 0,
tokens_generated: 0,
}
}
#[must_use]
pub fn kv_cache_fits(&self, memory_budget: usize) -> bool {
self.kv_cache_size <= memory_budget
}
#[must_use]
pub fn estimated_memory(&self) -> usize {
self.kv_cache_size
}
#[must_use]
pub fn can_continue(&self) -> bool {
self.position < self.max_seq_len
}
pub fn advance(&mut self) {
if self.can_continue() {
self.position += 1;
self.tokens_generated += 1;
}
}
#[must_use]
pub fn tokens_generated(&self) -> usize {
self.tokens_generated
}
}
#[must_use]
pub fn matmul_simd_friendly(a: &MatrixF64, b: &MatrixF64) -> Option<MatrixF64> {
if a.n_cols() != b.n_rows() {
return None;
}
let m = a.n_rows();
let n = b.n_cols();
let k = a.n_cols();
let mut result = vec![0.0; m * n];
for i in 0..m {
for j in 0..n {
let mut sum = 0.0;
for l in 0..k {
sum += a.get(i, l) * b.get(l, j);
}
result[i * n + j] = sum;
}
}
MatrixF64::from_vec(m, n, result).ok()
}
#[must_use]
pub fn dot_simd_friendly(a: &VectorF64, b: &VectorF64) -> f64 {
if a.len() != b.len() {
return 0.0;
}
a.as_slice()
.iter()
.zip(b.as_slice().iter())
.map(|(x, y)| x * y)
.sum()
}
#[cfg(test)]
#[path = "wasm_tests.rs"]
mod tests;