diff --git a/acpu/bench/common.rs b/acpu/bench/common.rs
index f7ea3ef..c4dd470 100644
--- a/acpu/bench/common.rs
+++ b/acpu/bench/common.rs
@@ -179,59 +179,59 @@ extern "C" {
pub fn vvtanhf(result: *mut f32, src: *const f32, n: *const i32);
// vDSP
- pub fn vDSP_sve(a: *const f32, stride: i32, result: *mut f32, n: u64);
- pub fn vDSP_svesq(a: *const f32, stride: i32, result: *mut f32, n: u64);
- pub fn vDSP_maxv(a: *const f32, stride: i32, result: *mut f32, n: u64);
- pub fn vDSP_minv(a: *const f32, stride: i32, result: *mut f32, n: u64);
- pub fn vDSP_vneg(a: *const f32, stride_a: i32, c: *mut f32, stride_c: i32, n: u64);
+ pub fn vDSP_sve(a: *const f32, stride: i64, result: *mut f32, n: u64);
+ pub fn vDSP_svesq(a: *const f32, stride: i64, result: *mut f32, n: u64);
+ pub fn vDSP_maxv(a: *const f32, stride: i64, result: *mut f32, n: u64);
+ pub fn vDSP_minv(a: *const f32, stride: i64, result: *mut f32, n: u64);
+ pub fn vDSP_vneg(a: *const f32, stride_a: i64, c: *mut f32, stride_c: i64, n: u64);
pub fn vDSP_vadd(
a: *const f32,
- stride_a: i32,
+ stride_a: i64,
b: *const f32,
- stride_b: i32,
+ stride_b: i64,
c: *mut f32,
- stride_c: i32,
+ stride_c: i64,
n: u64,
);
pub fn vDSP_vmul(
a: *const f32,
- stride_a: i32,
+ stride_a: i64,
b: *const f32,
- stride_b: i32,
+ stride_b: i64,
c: *mut f32,
- stride_c: i32,
+ stride_c: i64,
n: u64,
);
pub fn vDSP_vsadd(
a: *const f32,
- stride_a: i32,
+ stride_a: i64,
b: *const f32,
c: *mut f32,
- stride_c: i32,
+ stride_c: i64,
n: u64,
);
pub fn vDSP_vsmul(
a: *const f32,
- stride_a: i32,
+ stride_a: i64,
b: *const f32,
c: *mut f32,
- stride_c: i32,
+ stride_c: i64,
n: u64,
);
pub fn vDSP_vsdiv(
a: *const f32,
- stride_a: i32,
+ stride_a: i64,
b: *const f32,
c: *mut f32,
- stride_c: i32,
+ stride_c: i64,
n: u64,
);
pub fn vDSP_svdiv(
a: *const f32,
b: *const f32,
- stride_b: i32,
+ stride_b: i64,
c: *mut f32,
- stride_c: i32,
+ stride_c: i64,
n: u64,
);
}
diff --git a/acpu/bench/crypto.rs b/acpu/bench/crypto.rs
index 7e5136d..0844334 100644
--- a/acpu/bench/crypto.rs
+++ b/acpu/bench/crypto.rs
@@ -4,7 +4,6 @@
#[path = "common.rs"]
mod common;
use common::*;
-use std::time::Instant;
#[link(name = "System", kind = "dylib")]
extern "C" {
diff --git a/acpu/bench/integer.rs b/acpu/bench/integer.rs
index 1adf813..af1868b 100644
--- a/acpu/bench/integer.rs
+++ b/acpu/bench/integer.rs
@@ -4,7 +4,6 @@
#[path = "common.rs"]
mod common;
use common::*;
-use std::time::Instant;
const N: usize = 4096;
@@ -59,12 +58,12 @@ fn main() {
let mut score = Score::vs("scalar");
// ── test data ───────────────────────────────────────────────────────
- let i32_a: Vec<i32> = (0..N).map(|i| (i as i32 * 7 - 2000)).collect();
- let i32_b: Vec<i32> = (0..N).map(|i| (i as i32 * 3 + 500)).collect();
+ let i32_a: Vec<i32> = (0..N).map(|i| i as i32 * 7 - 2000).collect();
+ let i32_b: Vec<i32> = (0..N).map(|i| i as i32 * 3 + 500).collect();
let i8_a: Vec<i8> = (0..N).map(|i| ((i * 7) % 255) as i8).collect();
let i8_b: Vec<i8> = (0..N).map(|i| ((i * 13) % 255) as i8).collect();
- let i16_a: Vec<i16> = (0..N).map(|i| (i as i16 * 5 - 2000)).collect();
- let i16_b: Vec<i16> = (0..N).map(|i| (i as i16 * 3 + 1000)).collect();
+ let i16_a: Vec<i16> = (0..N).map(|i| i as i16 * 5 - 2000).collect();
+ let i16_b: Vec<i16> = (0..N).map(|i| i as i16 * 3 + 1000).collect();
let mut dst_i32 = vec![0i32; N];
let mut acc_i32 = vec![0i32; N];
diff --git a/acpu/bench/numeric.rs b/acpu/bench/numeric.rs
index faf0f8d..b1f83c4 100644
--- a/acpu/bench/numeric.rs
+++ b/acpu/bench/numeric.rs
@@ -7,10 +7,7 @@ use common::*;
#[link(name = "Accelerate", kind = "framework")]
extern "C" {
- // vDSP element-wise multiply (used as baseline for complex)
- fn vDSP_vmul(a: *const f32, ia: i64, b: *const f32, ib: i64, c: *mut f32, ic: i64, n: u64);
fn vDSP_vsub(b: *const f32, ib: i64, a: *const f32, ia: i64, c: *mut f32, ic: i64, n: u64);
- fn vDSP_vadd(a: *const f32, ia: i64, b: *const f32, ib: i64, c: *mut f32, ic: i64, n: u64);
}
const N: usize = 4096;
diff --git a/acpu/bench/summary.rs b/acpu/bench/summary.rs
index 3f8307a..51f1465 100644
--- a/acpu/bench/summary.rs
+++ b/acpu/bench/summary.rs
@@ -5,7 +5,6 @@
#[path = "common.rs"]
mod common;
use common::*;
-use std::time::Instant;
// ── Pure Rust Goldilocks (nebu cross-platform baseline, no acpu asm) ──
mod gl_pure {
@@ -2051,7 +2050,7 @@ fn main() {
// PMULL (hardware vs scalar carry-less multiply)
let pmull_iters = 1_000_000u64;
- let mut pmull_a = 0xDEADBEEFCAFEBABEu64;
+ let pmull_a = 0xDEADBEEFCAFEBABEu64;
let pmull_b = 0x1234567890ABCDEFu64;
let t_a = best_of(
|| {
diff --git a/acpu/bench/zk.rs b/acpu/bench/zk.rs
index 2ee00e0..4dc1175 100644
--- a/acpu/bench/zk.rs
+++ b/acpu/bench/zk.rs
@@ -19,7 +19,7 @@ fn main() {
println!("Goldilocks p = 2^64 - 2^32 + 1");
println!();
- let mut score = Score::new();
+ let score = Score::new();
let a: Vec<u64> = (0..N)
.map(|i| (i as u64).wrapping_mul(0x9E3779B97F4A7C15) | 1)
.collect();
diff --git a/acpu/examples/amx_reverse.rs b/acpu/examples/amx_reverse.rs
index fef15d2..17ceda2 100644
--- a/acpu/examples/amx_reverse.rs
+++ b/acpu/examples/amx_reverse.rs
@@ -56,41 +56,6 @@ impl Snapshot {
s
}
- fn diff(&self, other: &Snapshot) -> Vec<String> {
- let mut diffs = Vec::new();
- for r in 0..8 {
- for i in 0..16 {
- if self.x[r][i] != other.x[r][i] {
- diffs.push(format!(
- "X[{}][{}]: {:08x}→{:08x}",
- r, i, other.x[r][i], self.x[r][i]
- ));
- }
- }
- }
- for r in 0..8 {
- for i in 0..16 {
- if self.y[r][i] != other.y[r][i] {
- diffs.push(format!(
- "Y[{}][{}]: {:08x}→{:08x}",
- r, i, other.y[r][i], self.y[r][i]
- ));
- }
- }
- }
- for r in 0..64 {
- for i in 0..16 {
- if self.z[r][i] != other.z[r][i] {
- diffs.push(format!(
- "Z[{}][{}]: {:08x}→{:08x}",
- r, i, other.z[r][i], self.z[r][i]
- ));
- }
- }
- }
- diffs
- }
-
fn changed_regs(&self, other: &Snapshot) -> (bool, bool, bool) {
let mut x_changed = false;
let mut y_changed = false;
diff --git a/acpu/examples/pair_test.rs b/acpu/examples/pair_test.rs
index 77a97c8..0686fcd 100644
--- a/acpu/examples/pair_test.rs
+++ b/acpu/examples/pair_test.rs
@@ -7,7 +7,6 @@ fn main() {
amx_set();
const PAIR: u64 = 1u64 << 62;
- let fma_first: u64 = 1 << 27; // skip_z, X[0], Y[0], tile 0
let fma_x1: u64 = (1 << 27) | (64 << 10); // X[1], Y[0], tile 0
#[repr(align(128))]
diff --git a/acpu/examples/trace64.rs b/acpu/examples/trace64.rs
index a521f77..29e340e 100644
--- a/acpu/examples/trace64.rs
+++ b/acpu/examples/trace64.rs
@@ -286,7 +286,6 @@ fn main() {
}
// Direct B loads with stride=N*4, no packing
- let b_stride = N * 4; // bytes
for p in 0..N {
// LDY: packed A column
amx_op::<OP_LDY>(ap.add(p * 64) as u64);
diff --git a/acpu/src/crypto/mod.rs b/acpu/src/crypto/mod.rs
index 0aefa1e..e1e7a34 100644
--- a/acpu/src/crypto/mod.rs
+++ b/acpu/src/crypto/mod.rs
@@ -359,7 +359,7 @@ mod tests {
fn aes128_key_expand(key: &[u8; 16]) -> Vec<[u8; 16]> {
let rcon: [u8; 10] = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];
- let mut w = vec![0u32; 44];
+ let mut w = [0u32; 44];
for i in 0..4 {
w[i] = u32::from_be_bytes([key[4 * i], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3]]);
}
diff --git a/acpu/src/field/goldilocks.rs b/acpu/src/field/goldilocks.rs
index 6530221..a083023 100644
--- a/acpu/src/field/goldilocks.rs
+++ b/acpu/src/field/goldilocks.rs
@@ -153,7 +153,7 @@ pub fn gl_inv(x: u64) -> u64 {
// ── batched operations (interleaved for latency hiding) ──────────────────
-/// Batch field multiply: dst[i] = a[i] * b[i] mod p.
+/// Batch field multiply: `dst[i] = a[i] * b[i] mod p`.
///
/// Hand-scheduled asm interleaves 4 independent mul+umulh chains
/// to hide the 4-cycle multiply latency on M1 P-cores.
@@ -296,12 +296,12 @@ mod tests {
#[test]
fn test_pow7_x16() {
let mut state = [0u64; 16];
- for i in 0..16 {
- state[i] = (i as u64 + 1) * 3;
+ for (i, value) in state.iter_mut().enumerate() {
+ *value = (i as u64 + 1) * 3;
}
let mut expected = state;
- for i in 0..16 {
- expected[i] = gl_pow7(expected[i]);
+ for value in &mut expected {
+ *value = gl_pow7(*value);
}
gl_pow7_x16(&mut state);
assert_eq!(state, expected);
diff --git a/acpu/src/field/permute.rs b/acpu/src/field/permute.rs
index dbc95f3..c1a75c1 100644
--- a/acpu/src/field/permute.rs
+++ b/acpu/src/field/permute.rs
@@ -143,16 +143,16 @@ mod tests {
fn test_rc() -> [u64; 144] {
let mut rc = [0u64; 144];
- for i in 0..144 {
- rc[i] = (i as u64 + 1).wrapping_mul(0x9E3779B97F4A7C15);
+ for (i, value) in rc.iter_mut().enumerate() {
+ *value = (i as u64 + 1).wrapping_mul(0x9E3779B97F4A7C15);
}
rc
}
fn test_diag() -> [u64; 16] {
let mut d = [0u64; 16];
- for i in 0..16 {
- d[i] = (i as u64 + 2).wrapping_mul(0x517CC1B727220A95);
+ for (i, value) in d.iter_mut().enumerate() {
+ *value = (i as u64 + 2).wrapping_mul(0x517CC1B727220A95);
}
d
}
diff --git a/acpu/src/gemm/matvec.rs b/acpu/src/gemm/matvec.rs
index cd12e6d..2503bdd 100644
--- a/acpu/src/gemm/matvec.rs
+++ b/acpu/src/gemm/matvec.rs
@@ -7,7 +7,7 @@
#[cfg(target_arch = "aarch64")]
use core::arch::aarch64::*;
-/// Matrix-vector multiply: c[n] += a[k] * b[k][n], row-major B.
+/// Matrix-vector multiply: `c[n] += a[k] * b[k][n]`, row-major B.
#[cfg(target_arch = "aarch64")]
pub fn matvec_f32(a: &[f32], b: &[f32], c: &mut [f32], n: usize, k: usize) {
debug_assert_eq!(a.len(), k);
diff --git a/acpu/src/gemm/mod.rs b/acpu/src/gemm/mod.rs
index 8faa097..ccf8665 100644
--- a/acpu/src/gemm/mod.rs
+++ b/acpu/src/gemm/mod.rs
@@ -1091,7 +1091,6 @@ fn pack_b_32(b: &[f32], ldb: usize, pc: usize, jc: usize, kc: usize, nc: usize,
/// Odd/rem A × 32-wide B → two 16×16 kernels per B strip.
#[cfg(target_arch = "aarch64")]
#[allow(clippy::too_many_arguments)]
-#[allow(clippy::too_many_arguments)]
pub(super) fn gebp_kernel(
a_pack: &[f32],
b_pack: &[f32],
diff --git a/acpu/src/matrix/fma.rs b/acpu/src/matrix/fma.rs
index 144c326..8ec1d59 100644
--- a/acpu/src/matrix/fma.rs
+++ b/acpu/src/matrix/fma.rs
@@ -98,7 +98,7 @@ impl Default for FmaOp {
// Convenience constructors for common GEMM patterns
// ---------------------------------------------------------------------------
-/// First rank-1 update: Z[tile] = X[xr] × Y[yr] (no accumulate).
+/// First rank-1 update: `Z[tile] = X[xr] × Y[yr]` (no accumulate).
#[inline]
pub const fn fma_first(xr: XRow, yr: YRow, tile: u8) -> u64 {
FmaOp::new()
@@ -109,7 +109,7 @@ pub const fn fma_first(xr: XRow, yr: YRow, tile: u8) -> u64 {
.build()
}
-/// Subsequent rank-1 update: Z[tile] += X[xr] × Y[yr].
+/// Subsequent rank-1 update: `Z[tile] += X[xr] × Y[yr]`.
#[inline]
pub const fn fma_acc(xr: XRow, yr: YRow, tile: u8) -> u64 {
FmaOp::new().x(xr).y(yr).z_tile(tile).build()
diff --git a/acpu/src/matrix/mod.rs b/acpu/src/matrix/mod.rs
index bdee3d5..501f04e 100644
--- a/acpu/src/matrix/mod.rs
+++ b/acpu/src/matrix/mod.rs
@@ -36,11 +36,9 @@ pub struct Matrix {
impl Matrix {
/// Activate the AMX coprocessor on the current thread.
///
- /// # Errors
- ///
- /// Returns [`CpuError::AmxSetFailed`] if the current hardware does
- /// not support AMX (non-Apple-Silicon or virtualised without AMX
- /// passthrough).
+ /// The current implementation returns `Ok` after executing AMX_SET.
+ /// The caller must establish AMX availability with the hardware probe first;
+ /// unsupported hardware can trap instead of returning an error.
///
/// # Safety
///
diff --git a/acpu/src/matrix/tile.rs b/acpu/src/matrix/tile.rs
index e4e38b5..9d6730f 100644
--- a/acpu/src/matrix/tile.rs
+++ b/acpu/src/matrix/tile.rs
@@ -818,10 +818,10 @@ pub unsafe fn microkernel_32x32_gebp(
/// 2×2 Z tile layout. AMX pair loads (bit 62): 128 bytes → 2 registers.
/// **6 ops per k-step**: 1 LDX(pair) + 1 LDY(pair) + 4 FMA.
///
-/// `a_pair`: interleaved A pack — for column p: strip0[p] at offset p*128,
-/// strip1[p] at p*128+64. Must be 128-byte aligned.
+/// `a_pair`: interleaved A pack — for column p: `strip0[p]` at offset p*128,
+/// `strip1[p]` at p*128+64. Must be 128-byte aligned.
/// `b0`: B row-major (columns 0:32 contiguous). Must be 64-byte aligned.
-/// Pair LDX loads cols 0:16 → X[0], cols 16:32 → X[1].
+/// Pair LDX loads cols 0:16 → `X[0]`, cols 16:32 → `X[1]`.
/// `bs`: B row stride in bytes.
///
/// # Safety
diff --git a/acpu/src/matrix/tile_f16.rs b/acpu/src/matrix/tile_f16.rs
index ebbd04c..4918fa1 100644
--- a/acpu/src/matrix/tile_f16.rs
+++ b/acpu/src/matrix/tile_f16.rs
@@ -70,7 +70,7 @@ pub unsafe fn store_c_f16(c: *mut u16, ldc: usize, tile: u8) {
/// a_panel: packed fp16 [k × 32] (each k-step = 64 bytes = 32 fp16)
/// b_panel: packed fp16 [k × 32] (each k-step = 64 bytes = 32 fp16)
///
-/// Computes: Z[32×32] += sum_p( A[p] ⊗ B[p] )
+/// Computes: `Z[32×32] += sum_p( A[p] ⊗ B[p] )`
///
/// # Safety
/// AMX must be active. Panels must be 64-byte aligned with `k*64` readable bytes.
diff --git a/acpu/src/numeric/bf16.rs b/acpu/src/numeric/bf16.rs
index 4fcc3e4..1ed610c 100644
--- a/acpu/src/numeric/bf16.rs
+++ b/acpu/src/numeric/bf16.rs
@@ -164,7 +164,7 @@ mod tests {
0.0f32,
1.0,
-1.0,
- 3.14,
+ f32::from_bits(0x4048_f5c3), // original decimal 3.14 test input
-100.0,
f32::INFINITY,
f32::NEG_INFINITY,
diff --git a/acpu/src/sparse/chebyshev.rs b/acpu/src/sparse/chebyshev.rs
index 163e437..23048e7 100644
--- a/acpu/src/sparse/chebyshev.rs
+++ b/acpu/src/sparse/chebyshev.rs
@@ -18,7 +18,7 @@ use super::admission::Csr;
/// Modified Bessel function I_k(τ) via Miller's backward recurrence.
///
/// Uses the identity: I_0(τ) + 2 * Σ_{j=1}^∞ I_j(τ) = exp(τ).
-/// In Miller's method we compute un-normalized ratios f[j] ∝ I_j(τ) by
+/// In Miller's method we compute un-normalized ratios `f[j] ∝ I_j(τ)` by
/// backward recurrence from k_max, then normalize so the above sum holds.
pub fn bessel_i(k: usize, tau: f32) -> f32 {
if tau == 0.0 {
@@ -183,7 +183,8 @@ pub fn chebyshev_matvec(
csr.set(&az, t1);
// Step 3: t1[i] = −d_inv_sqrt[i] * t1[i]
for i in 0..n {
- t1[i] = -d_inv_sqrt[i] * t1[i];
+ let scaled = -d_inv_sqrt[i] * t1[i];
+ t1[i] = scaled;
}
// Accumulate y = c_0 T_0 + c_1 T_1
@@ -204,8 +205,7 @@ pub fn chebyshev_matvec(
//
// (k starts at 2, so k==2 is even: T_0 in t0, T_1 in t1 → write T_2 into t0)
- for k in 2..=order {
- let ck = coeffs[k];
+ for (k, &ck) in coeffs.iter().enumerate().skip(2) {
if k % 2 == 0 {
// T_{k-2} in t0, T_{k-1} in t1. Compute T_k into t0.
// az = D^{-½} t1
@@ -259,7 +259,7 @@ mod tests {
assert!((bessel_i(0, 0.0) - 1.0).abs() < 1e-5, "I_0(0)");
// I_0(1) ≈ 1.2660658777520082
assert!(
- (bessel_i(0, 1.0) - 1.266_065_9).abs() < 1e-4,
+ (bessel_i(0, 1.0) - 1.266_066).abs() < 1e-4,
"I_0(1) = {}",
bessel_i(0, 1.0)
);
diff --git a/acpu/src/sparse/mod.rs b/acpu/src/sparse/mod.rs
index 15118d9..7654cd6 100644
--- a/acpu/src/sparse/mod.rs
+++ b/acpu/src/sparse/mod.rs
@@ -2,7 +2,7 @@
//!
//! CSR matvec: y = A·x where A is sparse CSR. For the bostrom graph (avg
//! degree ~1.85) most rows are 1–3 entries — AMX offers no advantage here.
-//! NEON prefetch hides gather latency from the scattered x[col_idx[j]] loads.
+//! NEON prefetch hides gather latency from the scattered `x[col_idx[j]]` loads.
mod admission;
pub mod chebyshev;
@@ -12,7 +12,7 @@ use core::arch::aarch64::*;
/// y += A·x (accumulate into y).
///
-/// A in CSR format: row i spans col_idx[row_ptr[i]..row_ptr[i+1]]
+/// A in CSR format: row i spans `col_idx[row_ptr[i]..row_ptr[i+1]]`.
/// with corresponding weights values[...].
///
/// # Panics
@@ -53,8 +53,8 @@ fn inner(row_ptr: &[u32], col_idx: &[u32], values: &[f32], x: &[f32], y: &mut [f
let mut acc = 0.0f32;
// Prefetch the x entries we'll need for this row.
- for pf in 0..len.min(PF) {
- let ptr = x.as_ptr().add(cols[pf] as usize);
+ for &column in cols.iter().take(PF) {
+ let ptr = x.as_ptr().add(column as usize);
// PRFM PLDL1KEEP — bring into L1 dcache.
core::arch::asm!(
"prfm pldl1keep, [{ptr}]",
@@ -189,8 +189,8 @@ mod tests {
expected0
);
// y[i] = x[0] = 0 for i in 1..n
- for i in 1..n {
- assert!(y[i].abs() < 1e-5, "y[{i}]={}", y[i]);
+ for (i, value) in y.iter().enumerate().skip(1) {
+ assert!(value.abs() < 1e-5, "y[{i}]={value}");
}
}
}
diff --git a/acpu/src/vector/integer.rs b/acpu/src/vector/integer.rs
index 471b177..fee6bbb 100644
--- a/acpu/src/vector/integer.rs
+++ b/acpu/src/vector/integer.rs
@@ -280,7 +280,7 @@ pub fn macc_i16(acc: &mut [i32], a: &[i16], b: &[i16]) {
// ── i32 elementwise ─────────────────────────────────────────────────────────
-/// Elementwise i32 add: dst[i] = a[i] + b[i]. 16-wide.
+/// Elementwise i32 add: `dst[i] = a[i] + b[i]`. 16-wide.
pub fn add_i32(dst: &mut [i32], a: &[i32], b: &[i32]) {
let len = dst.len().min(a.len()).min(b.len());
let mut i = 0;
@@ -311,7 +311,7 @@ pub fn add_i32(dst: &mut [i32], a: &[i32], b: &[i32]) {
}
}
-/// Elementwise i32 multiply: dst[i] = a[i] * b[i]. 32-wide.
+/// Elementwise i32 multiply: `dst[i] = a[i] * b[i]`. 32-wide.
pub fn mul_i32(dst: &mut [i32], a: &[i32], b: &[i32]) {
let len = dst.len().min(a.len()).min(b.len());
let mut i = 0;
@@ -408,7 +408,7 @@ mod tests {
#[test]
fn test_sum_i32() {
- let v: Vec<i32> = (0..4096).map(|i| (i % 100) as i32 - 50).collect();
+ let v: Vec<i32> = (0..4096).map(|i| (i % 100) - 50).collect();
let expected: i64 = v.iter().map(|&x| x as i64).sum();
assert_eq!(sum_i32(&v), expected);
}
diff --git a/acpu/src/vector/integer_fused.rs b/acpu/src/vector/integer_fused.rs
index f2c3584..2c6aa7d 100644
--- a/acpu/src/vector/integer_fused.rs
+++ b/acpu/src/vector/integer_fused.rs
@@ -6,7 +6,7 @@
#[cfg(target_arch = "aarch64")]
use core::arch::aarch64::*;
-/// Sum of absolute differences: sum(|a[i] - b[i]|).
+/// Sum of absolute differences: `sum(|a[i] - b[i]|)`.
/// Single pass — reads both arrays once, no intermediate storage.
/// Uses UABD + UADALP chain (unsigned absolute difference + pairwise add long).
pub fn sad_u8(a: &[u8], b: &[u8]) -> u64 {
@@ -57,7 +57,7 @@ pub fn sad_u8(a: &[u8], b: &[u8]) -> u64 {
}
}
-/// Sum of squared differences: sum((a[i] - b[i])²) for i32 slices.
+/// Sum of squared differences: `sum((a[i] - b[i])²)` for i32 slices.
/// Single pass — no intermediate array. Widens to i64 to avoid overflow.
pub fn ssd_i32(a: &[i32], b: &[i32]) -> i64 {
assert_eq!(a.len(), b.len(), "ssd_i32: length mismatch");
@@ -108,7 +108,7 @@ pub fn ssd_i32(a: &[i32], b: &[i32]) -> i64 {
}
}
-/// Scale and accumulate: acc[i] += a[i] * scale.
+/// Scale and accumulate: `acc[i] += a[i] * scale`.
/// Single pass with widening: i16 × i16 → i32 accumulated.
pub fn scale_acc_i16(acc: &mut [i32], a: &[i16], scale: i16) {
let len = acc.len().min(a.len());
@@ -147,7 +147,7 @@ pub fn scale_acc_i16(acc: &mut [i32], a: &[i16], scale: i16) {
}
}
-/// Fused sum of absolute values: sum(|x[i]|) for i8.
+/// Fused sum of absolute values: `sum(|x[i]|)` for i8.
/// Combines abs + widen + accumulate in one pass.
pub fn sum_abs_i8(x: &[i8]) -> u64 {
let len = x.len();
diff --git a/acpu/src/vector/media.rs b/acpu/src/vector/media.rs
index 1958b24..4be53b2 100644
--- a/acpu/src/vector/media.rs
+++ b/acpu/src/vector/media.rs
@@ -383,7 +383,7 @@ mod tests {
#[test]
fn test_rgb_to_yuv_known() {
let mut yuv = vec![0u8; 48];
- rgb_to_yuv(&mut yuv, &vec![0u8; 48]);
+ rgb_to_yuv(&mut yuv, &[0u8; 48]);
for i in 0..16 {
assert_eq!((yuv[i * 3], yuv[i * 3 + 1], yuv[i * 3 + 2]), (16, 128, 128));
}
@@ -414,8 +414,8 @@ mod tests {
let mut out = vec![0u8; 3];
rgb_to_yuv(&mut yuv, &[128, 128, 128]);
yuv_to_rgb(&mut out, &yuv);
- for c in 0..3 {
- assert!((128i32 - out[c] as i32).abs() <= 2, "ch {}: {}", c, out[c]);
+ for (c, &value) in out.iter().enumerate() {
+ assert!((128i32 - value as i32).abs() <= 2, "ch {c}: {value}");
}
}
@@ -464,7 +464,7 @@ mod tests {
#[test]
fn test_resize_downsample() {
let mut dst = vec![0.0f32; 4];
- resize_bilinear_f32(&mut dst, &vec![7.5f32; 16], 4, 4, 2, 2);
+ resize_bilinear_f32(&mut dst, &[7.5f32; 16], 4, 4, 2, 2);
assert!(dst.iter().all(|&v| (v - 7.5).abs() < 1e-4));
}
}
diff --git a/acpu/src/vector/render.rs b/acpu/src/vector/render.rs
index 7a48e5d..9fa0db7 100644
--- a/acpu/src/vector/render.rs
+++ b/acpu/src/vector/render.rs
@@ -9,7 +9,7 @@ use core::arch::aarch64::*;
// rsqrt: fast inverse square root via FRSQRTE + 1 Newton-Raphson step
// ---------------------------------------------------------------------------
-/// In-place fast inverse square root: x[i] = 1/sqrt(x[i]).
+/// In-place fast inverse square root: `x[i] = 1/sqrt(x[i])`.
/// Uses NEON FRSQRTE + one Newton-Raphson refinement step.
pub fn rsqrt(x: &mut [f32]) {
let len = x.len();
@@ -58,7 +58,7 @@ pub fn rsqrt(x: &mut [f32]) {
}
}
-/// Fast inverse square root into separate buffer: dst[i] = 1/sqrt(src[i]).
+/// Fast inverse square root into separate buffer: `dst[i] = 1/sqrt(src[i])`.
/// Panics if lengths differ.
pub fn rsqrt_to(src: &[f32], dst: &mut [f32]) {
assert_eq!(src.len(), dst.len(), "rsqrt_to: length mismatch");
@@ -113,7 +113,7 @@ pub fn rsqrt_to(src: &[f32], dst: &mut [f32]) {
// recip: fast reciprocal via FRECPE + 1 Newton-Raphson step
// ---------------------------------------------------------------------------
-/// In-place fast reciprocal: x[i] = 1/x[i].
+/// In-place fast reciprocal: `x[i] = 1/x[i]`.
/// Uses NEON FRECPE + one Newton-Raphson refinement step.
pub fn recip(x: &mut [f32]) {
let len = x.len();
@@ -285,7 +285,7 @@ pub fn clamp_to(src: &[f32], dst: &mut [f32], lo: f32, hi: f32) {
// lerp: linear interpolation dst = a + t*(b - a)
// ---------------------------------------------------------------------------
-/// Linear interpolation: dst[i] = a[i] + t * (b[i] - a[i]).
+/// Linear interpolation: `dst[i] = a[i] + t * (b[i] - a[i])`.
/// Panics if lengths differ.
pub fn lerp(dst: &mut [f32], a: &[f32], b: &[f32], t: f32) {
assert_eq!(a.len(), b.len(), "lerp: a/b length mismatch");
diff --git a/acpu/src/vector/rope.rs b/acpu/src/vector/rope.rs
index c096bba..c3c4a85 100644
--- a/acpu/src/vector/rope.rs
+++ b/acpu/src/vector/rope.rs
@@ -12,9 +12,11 @@ use core::arch::aarch64::*;
/// `pos` is the token position.
///
/// For each pair (x0, x1) with frequency f:
+/// ```text
/// theta = pos * f
/// out[2*i] = x0 * cos(theta) - x1 * sin(theta)
/// out[2*i+1] = x0 * sin(theta) + x1 * cos(theta)
+/// ```
pub fn rotate(out: &mut [f32], x: &[f32], freqs: &[f32], pos: usize) {
let dim = x.len();
assert!(dim.is_multiple_of(2), "rotate: dimension must be even");
diff --git a/acpu/tests/input_admission.rs b/acpu/tests/input_admission.rs
index 816cb2f..55cc956 100644
--- a/acpu/tests/input_admission.rs
+++ b/acpu/tests/input_admission.rs
@@ -16,12 +16,13 @@ fn rejects(message: &str, f: impl FnOnce()) {
}
type Matvec = fn(&[u32], &[u32], &[f32], &[f32], &mut [f32]);
+type CsrCase<'a> = (&'a [u32], &'a [u32], &'a [f32], usize, &'a str);
#[test]
fn csr_rejects_every_malformed_shape_before_output_mutation() {
// Partition, count, pointer, scalar-tail and late-row failures. Every case
// runs through both public entry points, including the zeroing variant.
- let cases: &[(&[u32], &[u32], &[f32], usize, &str)] = &[
+ let cases: &[CsrCase<'_>] = &[
(&[], &[], &[], 0, "row_ptr must be nonempty"),
(&[1, 1], &[0], &[2.0], 1, "row_ptr must start at zero"),
(&[0, 2, 1], &[0], &[2.0], 2, "row_ptr must be nondecreasing"),
diff --git a/acpu/tests/integration.rs b/acpu/tests/integration.rs
index 369a37f..6643af1 100644
--- a/acpu/tests/integration.rs
+++ b/acpu/tests/integration.rs
@@ -97,7 +97,14 @@ fn fp16_bulk_1024() {
#[test]
fn bf16_roundtrip() {
- for &v in &[0.0f32, 1.0, -1.0, 3.14, -100.0, 256.0] {
+ for &v in &[
+ 0.0f32,
+ 1.0,
+ -1.0,
+ f32::from_bits(0x4048_f5c3), // original decimal 3.14 test input
+ -100.0,
+ 256.0,
+ ] {
let back = bf16_mod::bf16_to_f32(bf16_mod::f32_to_bf16(v));
if v == 0.0 {
assert_eq!(back, 0.0);
@@ -294,7 +301,7 @@ fn softmax_equal() {
fn rmsnorm_unit_weight() {
let x = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let mut out = vec![0.0f32; 8];
- softmax::normalize(&mut out, &x, &vec![1.0; 8], 1e-5);
+ softmax::normalize(&mut out, &x, &[1.0; 8], 1e-5);
let rms2: f32 = out.iter().map(|v| v * v).sum::<f32>() / 8.0;
assert!((rms2 - 1.0).abs() < 1e-3);
}
@@ -302,7 +309,7 @@ fn rmsnorm_unit_weight() {
#[test]
fn rmsnorm_scaling() {
let mut out = vec![0.0f32; 16];
- softmax::normalize(&mut out, &vec![2.0; 16], &vec![3.0; 16], 1e-5);
+ softmax::normalize(&mut out, &[2.0; 16], &[3.0; 16], 1e-5);
for &v in &out {
assert!((v - 3.0).abs() < 1e-3);
}
@@ -362,7 +369,7 @@ fn sgemm_vs_naive() {
const S: usize = 4;
let a: Vec<f32> = (0..S * S).map(|i| (i % 5) as f32 * 0.3).collect();
let b: Vec<f32> = (0..S * S).map(|i| (i % 7) as f32 * 0.2).collect();
- let mut c_ref = vec![0.0f32; S * S];
+ let mut c_ref = [0.0f32; S * S];
for i in 0..S {
for j in 0..S {
let mut acc = 0.0f32;
@@ -654,8 +661,8 @@ fn amx_fma32_outer_product() {
amx_op::<OP_LDZ>((zero_buf as u64) | ((row as u64) << 56));
}
- amx_op::<OP_LDX>((x_buf as u64) | (0u64 << 56));
- amx_op::<OP_LDY>((y_buf as u64) | (0u64 << 56));
+ amx_op::<OP_LDX>(x_buf as u64);
+ amx_op::<OP_LDY>(y_buf as u64);
let op = fma_first(XRow::new_unchecked(0), YRow::new_unchecked(0), 0);
amx_op::<OP_FMA32>(op);
diff --git a/aruminium/CLAUDE.md b/aruminium/CLAUDE.md
index ad0f195..c90c190 100644
--- a/aruminium/CLAUDE.md
+++ b/aruminium/CLAUDE.md
@@ -100,7 +100,7 @@ src/ core library (zero deps)
sync.rs Fence, Event, SharedEvent
texture.rs Texture
probe.rs metal_probe binary — 5-level capability probe
-examples/ runnable demos (vecadd, matmul)
+examples/ runnable demos (vecadd, aruminium_matmul)
benches/ separate crate with heavy deps (bench, compare)
docs/ documentation (tutorial, guide, explanations)
specs/ specification (source of truth)
diff --git a/aruminium/Cargo.toml b/aruminium/Cargo.toml
index eda3c73..3d12ff9 100644
--- a/aruminium/Cargo.toml
+++ b/aruminium/Cargo.toml
@@ -1,5 +1,6 @@
[package]
name = "aruminium"
+autobenches = false # benches/ is the separate metal-benches workspace package.
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -29,8 +30,8 @@ name = "vecadd"
path = "examples/vecadd.rs"
example
-name = "matmul"
-path = "examples/matmul.rs"
+name = "aruminium_matmul"
+path = "examples/aruminium_matmul.rs"
example
name = "triangle"
diff --git a/aruminium/README.md b/aruminium/README.md
index aa4c65d..1fc0a56 100644
--- a/aruminium/README.md
+++ b/aruminium/README.md
@@ -124,7 +124,7 @@ aruminium::cast_f16_f32(&mut [f32], &[u16]) // bulk NEON
```text
cargo build --release
cargo run --example vecadd
-cargo run --example matmul
+cargo run --example aruminium_matmul
cargo run --release -p metal-benches --bin bench
diff --git a/aruminium/benches/objc2.rs b/aruminium/benches/objc2.rs index 2578d18..155dbd6 100644 --- a/aruminium/benches/objc2.rs +++ b/aruminium/benches/objc2.rs @@ -300,8 +300,12 @@ pub fn render_pass_overhead(iters: usize) -> f64 { let queue = dev.newCommandQueue().unwrap(); let src = NSString::from_str(TRIANGLE_SRC); let lib = dev.newLibraryWithSource_options_error(&src, None).unwrap();
- let vfn = lib.newFunctionWithName(&NSString::from_str("vmain")).unwrap();
- let ffn = lib.newFunctionWithName(&NSString::from_str("fmain")).unwrap();
-
let vfn = lib
-
.newFunctionWithName(&NSString::from_str("vmain")) -
.unwrap(); -
let ffn = lib
-
.newFunctionWithName(&NSString::from_str("fmain")) -
.unwrap();let pipe_desc = MTLRenderPipelineDescriptor::new(); pipe_desc.setVertexFunction(Some(&*vfn)); @@ -371,8 +375,12 @@ pub fn render_batch_encode(n_draws: usize, iters: usize) -> f64 { let queue = dev.newCommandQueue().unwrap(); let src = NSString::from_str(TRIANGLE_SRC); let lib = dev.newLibraryWithSource_options_error(&src, None).unwrap();
- let vfn = lib.newFunctionWithName(&NSString::from_str("vmain")).unwrap();
- let ffn = lib.newFunctionWithName(&NSString::from_str("fmain")).unwrap();
-
let vfn = lib
-
.newFunctionWithName(&NSString::from_str("vmain")) -
.unwrap(); -
let ffn = lib
-
.newFunctionWithName(&NSString::from_str("fmain")) -
.unwrap();let pipe_desc = MTLRenderPipelineDescriptor::new(); pipe_desc.setVertexFunction(Some(&*vfn)); @@ -418,9 +426,7 @@ pub fn render_batch_encode(n_draws: usize, iters: usize) -> f64 { let enc = cmd.renderCommandEncoderWithDescriptor(&rpass).unwrap(); enc.setRenderPipelineState(&rpipeline); for _ in 0..n_draws {
-
unsafe { -
enc.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveType::Triangle, 0, 0) -
};
-
unsafe { enc.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveType::Triangle, 0, 0) }; } enc.endEncoding(); cmd.commit();
@@ -433,9 +439,7 @@ pub fn render_batch_encode(n_draws: usize, iters: usize) -> f64 { let enc = cmd.renderCommandEncoderWithDescriptor(&rpass).unwrap(); enc.setRenderPipelineState(&rpipeline); for _ in 0..n_draws {
-
unsafe { -
enc.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveType::Triangle, 0, 0) -
};
-
unsafe { enc.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveType::Triangle, 0, 0) }; } enc.endEncoding(); cmd.commit();
diff --git a/aruminium/docs/tutorial.md b/aruminium/docs/tutorial.md index 04004dd..146b525 100644 --- a/aruminium/docs/tutorial.md +++ b/aruminium/docs/tutorial.md @@ -230,6 +230,6 @@ fn main() -> Result<(), GpuError> {
next steps
-- see examples/matmul.rs for 2D dispatch with struct parameters
+- see examples/aruminium_matmul.rs for 2D dispatch with struct parameters
- see
docs/guide.mdfor Dispatch, batch encoding, pipelining - see
docs/explanations.mdfor why the architecture works this way diff --git a/aruminium/examples/README.md b/aruminium/examples/README.md index 97319c9..b7378a7 100644 --- a/aruminium/examples/README.md +++ b/aruminium/examples/README.md @@ -4,10 +4,10 @@ runnable demos. each one is self-contained and shows a real GPU workflow.
| file | what it does |
|---|---|
vecadd.rs |
vector addition on GPU — simplest possible compute dispatch |
| - | matmul.rs |
| + | aruminium_matmul.rs |
| diff --git a/aruminium/examples/matmul.rs b/aruminium/examples/matmul.rs | |
| deleted file mode 100644 | |
| index b08d98f..0000000 | |
| --- a/aruminium/examples/matmul.rs | |
| +++ /dev/null | |
| @@ -1,111 +0,0 @@ | |
| -//! Matrix multiplication on Metal GPU — compute example |
-use aruminium::{Gpu, GpuError};
-fn main() -> Result<(), GpuError> {
- let device = Gpu::open()?;
- println!("Device: {}", device.name());
- let queue = device.new_command_queue()?;
- // Naive matmul kernel: C[M,N] = A[M,K] * B[K,N]
- let source = r#"
-
#include <metal_stdlib> -
using namespace metal; -
struct MatmulParams { -
uint M; -
uint N; -
uint K; -
}; -
kernel void matmul(device const float *A buffer(0), -
device const float *B buffer(1), -
device float *C buffer(2), -
constant MatmulParams ¶ms buffer(3), -
uint2 gid thread_position_in_grid) { -
uint row = gid.y; -
uint col = gid.x; -
if (row >= params.M || col >= params.N) return; -
float sum = 0.0; -
for (uint k = 0; k < params.K; k++) { -
sum += A[row * params.K + k] * B[k * params.N + col]; -
} -
C[row * params.N + col] = sum; -
} - "#;
- let lib = device.compile(source)?;
- let func = lib.function("matmul")?;
- let pipeline = device.pipeline(&func)?;
- let m = 64usize;
- let n = 64usize;
- let k = 64usize;
- let buf_a = device.buffer(m * k * 4)?;
- let buf_b = device.buffer(k * n * 4)?;
- let buf_c = device.buffer(m * n * 4)?;
- // A = identity, B = all ones => C should be all ones
- buf_a.write_f32(|d| {
-
for i in 0..m { -
for j in 0..k { -
d[i * k + j] = if i == j { 1.0 } else { 0.0 }; -
} -
} - });
- buf_b.write_f32(|d| {
-
for i in 0..d.len() { -
d[i] = 1.0; -
} - });
- #[repr(C)]
- struct MatmulParams {
-
m: u32, -
n: u32, -
k: u32, - }
- let params = MatmulParams {
-
m: m as u32, -
n: n as u32, -
k: k as u32, - };
- let params_bytes = unsafe {
-
std::slice::from_raw_parts( -
¶ms as *const MatmulParams as *const u8, -
std::mem::size_of::<MatmulParams>(), -
) - };
- let cmd = queue.commands()?;
- let enc = cmd.encoder()?;
- enc.bind(&pipeline);
- enc.bind_buffer(&buf_a, 0, 0);
- enc.bind_buffer(&buf_b, 0, 1);
- enc.bind_buffer(&buf_c, 0, 2);
- enc.push(params_bytes, 3);
- enc.launch((n, m, 1), (16, 16, 1));
- enc.finish();
- cmd.submit();
- cmd.wait();
- buf_c.read_f32(|d| {
-
let mut max_err: f32 = 0.0; -
for i in 0..m * n { -
max_err = max_err.max((d[i] - 1.0).abs()); -
} -
if max_err < 1e-5 { -
println!( -
"PASS: {}x{}x{} matmul verified (max_err={:.2e})", -
m, n, k, max_err -
); -
} else { -
println!("FAIL: max_err = {:.6}", max_err); -
} - });
- Ok(()) -} diff --git a/rane/CLAUDE.md b/rane/CLAUDE.md index 5bd1739..cb6b4a2 100644 --- a/rane/CLAUDE.md +++ b/rane/CLAUDE.md @@ -39,7 +39,7 @@ skip for trivial tasks. cargo fmt --all # format cargo clippy --workspace -- -W warnings # lint cargo build --release --workspace # build all -cargo run --example matmul # verify ANE access +cargo run --example rane_matmul # verify ANE access
every commit: format clean, clippy clean, builds, examples run.
@@ -103,7 +103,7 @@ src/ core library (zero deps)
weights.rs checkpoint I/O, LayerWeights, KVCache
mil/ MIL program builder → ANE bytecode
ops/ CPU kernels (rmsnorm, rope, attention, loss, adam)
-examples/ runnable demos (matmul, infer, train, bench)
+examples/ runnable demos (rane_matmul, infer, train, bench)
tools/ separate crate with heavy deps (convert_hf, tokenize, chat)
specs/ specification (source of truth)
api.md API spec — concepts, lifecycle, apple mapping
diff --git a/rane/Cargo.toml b/rane/Cargo.toml
index ff8e491..aa0376b 100644
--- a/rane/Cargo.toml
+++ b/rane/Cargo.toml
@@ -1,5 +1,6 @@
[package]
name = "rane"
+autobenches = false # benches/ is the separate rane-benches workspace package.
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -24,5 +25,5 @@ name = "rane_probe"
path = "src/probe/main.rs"
example
-name = "matmul"
-path = "examples/matmul.rs"
+name = "rane_matmul"
+path = "examples/rane_matmul.rs"
diff --git a/rane/README.md b/rane/README.md
index e5808f4..f74e010 100644
--- a/rane/README.md
+++ b/rane/README.md
@@ -130,7 +130,7 @@ rane::cast_f16_f32(&mut [f32], &[u16]) // bulk NEON
```bash
cargo build --release
-cargo run --example matmul
+cargo run --example rane_matmul
cargo test --test integration -- --test-threads=1
cargo run --release -p rane-benches --bin bench
cargo run --release -p rane-benches --bin compare
@@ -149,7 +149,7 @@ src/ rane crate — zero deps, system frameworks only
mil/mod.rs MIL program builder (matmul, header, footer)
probe/ rane_probe — 7-level reverse engineering tool
examples/
- matmul.rs 64x64 matmul on ANE hardware
+ rane_matmul.rs 64x64 matmul on ANE hardware
benches/ separate crate with bench + compare binaries
tests/
integration.rs 13 tests: surface, fp16, MIL, model lifecycle
diff --git a/rane/examples/matmul.rs b/rane/examples/matmul.rs
deleted file mode 100644
index add39ec..0000000
--- a/rane/examples/matmul.rs
+++ /dev/null
@@ -1,93 +0,0 @@
-//! Minimal ANE matmul demo — pure Rust, zero dependencies
-//!
-//! Compiles a 64×64 matmul on ANE, runs with identity matrix,
-//! verifies output matches input.
-//!
-//! Run: cargo run --example matmul
-
-use rane::mil;
-use rane::{f32_to_fp16, fp16_to_f32, Buffer, Program};
-
-fn main() -> Result<(), Box<dyn std::error::Error>> {
- println!("ANE Matmul — Pure Rust\n");
-
- // Build MIL program: y = x @ W
- // Input: [1, 64, 1, 128] fp16 (64 activations + 64 weights per channel)
- // Output: [1, 64, 1, 64] fp16
- let ic = 64;
- let oc = 64;
- let seq = 64;
- let program = mil::matmul(ic, oc, seq);
- let (in_ch, in_sp) = program.input_shape();
- let (out_ch, out_sp) = program.output_shape();
- println!(" MIL: matmul({ic}x{oc}, seq={seq})");
- println!(
- " Input: [1, {in_ch}, 1, {in_sp}] fp16 ({} KB)",
- program.input_size() / 1024
- );
- println!(
- " Output: [1, {out_ch}, 1, {out_sp}] fp16 ({} KB)\n",
- program.output_size() / 1024
- );
-
- // Compile
- print!(" Compiling...");
- let mut model = Program::compile(&program, &[])?;
- println!(" OK");
-
- // Load into ANE
- print!(" Loading...");
- model.load()?;
- println!(" OK");
-
- // Prepare I/O surfaces
- let input = Buffer::new(program.input_size())?;
- let output = Buffer::new(program.output_size())?;
-
- // Fill input: activations = 1.0, weights = identity matrix
- input.write(|data| {
- for ch in 0..ic {
- for s in 0..seq {
- data[ch * in_sp + s] = f32_to_fp16(1.0);
- }
- for o in 0..oc {
- data[ch * in_sp + seq + o] = if ch == o { f32_to_fp16(1.0) } else { 0 };
- }
- }
- });
- println!(" Input: all 1.0 activations, identity weight matrix");
-
- // Run on ANE
- print!(" Evaluating on ANE...");
- model.run(&input, &output)?;
- println!(" OK\n");
-
- // Read output
- output.read(|data| {
- print!(" Output[0..8] = [");
- for i in 0..8 {
- if i > 0 {
- print!(", ");
- }
- print!("{:.1}", fp16_to_f32(data[i]));
- }
- println!("]");
-
- // Verify: identity matmul should give all 1.0 (each output = sum of row × identity col = 1.0)
- // Actually: [SEQ,IC] @ [IC,OC] with identity → output = ones × I = ones
- // But the matmul is (seq=64) @ (ic=64,oc=64), so each output is dot(ones_64, identity_col) = 1.0
- let all_ones = data[..out_ch * out_sp]
- .iter()
- .all(|&v| fp16_to_f32(v) == 1.0);
- if all_ones {
- println!("\n VERIFIED: all {} output values = 1.0", out_ch * out_sp);
- println!(" Pure Rust → MIL → ANE bytecode → ANE hardware → correct result");
- } else {
- let nonzero: Vec<_> = data[..8].iter().map(|&v| fp16_to_f32(v)).collect();
- println!("\n Output values: {:?}", nonzero);
- }
- });
-
- // Cleanup is automatic (Drop)
- Ok(())
-}
diff --git a/aruminium/examples/aruminium_matmul.rs b/aruminium/examples/aruminium_matmul.rs
new file mode 100644
index 0000000..b08d98f
--- /dev/null
+++ b/aruminium/examples/aruminium_matmul.rs
@@ -0,0 +1,111 @@
+//! Matrix multiplication on Metal GPU — compute example
+
+use aruminium::{Gpu, GpuError};
+
+fn main() -> Result<(), GpuError> {
+ let device = Gpu::open()?;
+ println!("Device: {}", device.name());
+
+ let queue = device.new_command_queue()?;
+
+ // Naive matmul kernel: C[M,N] = A[M,K] * B[K,N]
+ let source = r#"
+ #include <metal_stdlib>
+ using namespace metal;
+
+ struct MatmulParams {
+ uint M;
+ uint N;
+ uint K;
+ };
+
+ kernel void matmul(device const float *A buffer(0),
+ device const float *B buffer(1),
+ device float *C buffer(2),
+ constant MatmulParams ¶ms buffer(3),
+ uint2 gid thread_position_in_grid) {
+ uint row = gid.y;
+ uint col = gid.x;
+ if (row >= params.M || col >= params.N) return;
+
+ float sum = 0.0;
+ for (uint k = 0; k < params.K; k++) {
+ sum += A[row * params.K + k] * B[k * params.N + col];
+ }
+ C[row * params.N + col] = sum;
+ }
+ "#;
+
+ let lib = device.compile(source)?;
+ let func = lib.function("matmul")?;
+ let pipeline = device.pipeline(&func)?;
+
+ let m = 64usize;
+ let n = 64usize;
+ let k = 64usize;
+
+ let buf_a = device.buffer(m * k * 4)?;
+ let buf_b = device.buffer(k * n * 4)?;
+ let buf_c = device.buffer(m * n * 4)?;
+
+ // A = identity, B = all ones => C should be all ones
+ buf_a.write_f32(|d| {
+ for i in 0..m {
+ for j in 0..k {
+ d[i * k + j] = if i == j { 1.0 } else { 0.0 };
+ }
+ }
+ });
+ buf_b.write_f32(|d| {
+ for i in 0..d.len() {
+ d[i] = 1.0;
+ }
+ });
+
+ #[repr(C)]
+ struct MatmulParams {
+ m: u32,
+ n: u32,
+ k: u32,
+ }
+ let params = MatmulParams {
+ m: m as u32,
+ n: n as u32,
+ k: k as u32,
+ };
+ let params_bytes = unsafe {
+ std::slice::from_raw_parts(
+ ¶ms as *const MatmulParams as *const u8,
+ std::mem::size_of::<MatmulParams>(),
+ )
+ };
+
+ let cmd = queue.commands()?;
+ let enc = cmd.encoder()?;
+ enc.bind(&pipeline);
+ enc.bind_buffer(&buf_a, 0, 0);
+ enc.bind_buffer(&buf_b, 0, 1);
+ enc.bind_buffer(&buf_c, 0, 2);
+ enc.push(params_bytes, 3);
+ enc.launch((n, m, 1), (16, 16, 1));
+ enc.finish();
+ cmd.submit();
+ cmd.wait();
+
+ buf_c.read_f32(|d| {
+ let mut max_err: f32 = 0.0;
+ for i in 0..m * n {
+ max_err = max_err.max((d[i] - 1.0).abs());
+ }
+ if max_err < 1e-5 {
+ println!(
+ "PASS: {}x{}x{} matmul verified (max_err={:.2e})",
+ m, n, k, max_err
+ );
+ } else {
+ println!("FAIL: max_err = {:.6}", max_err);
+ }
+ });
+
+ Ok(())
+}
diff --git a/rane/examples/rane_matmul.rs b/rane/examples/rane_matmul.rs
new file mode 100644
index 0000000..6145789
--- /dev/null
+++ b/rane/examples/rane_matmul.rs
@@ -0,0 +1,93 @@
+//! Minimal ANE matmul demo — pure Rust, zero dependencies
+//!
+//! Compiles a 64×64 matmul on ANE, runs with identity matrix,
+//! verifies output matches input.
+//!
+//! Run: cargo run --example rane_matmul
+
+use rane::mil;
+use rane::{f32_to_fp16, fp16_to_f32, Buffer, Program};
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ println!("ANE Matmul — Pure Rust\n");
+
+ // Build MIL program: y = x @ W
+ // Input: [1, 64, 1, 128] fp16 (64 activations + 64 weights per channel)
+ // Output: [1, 64, 1, 64] fp16
+ let ic = 64;
+ let oc = 64;
+ let seq = 64;
+ let program = mil::matmul(ic, oc, seq);
+ let (in_ch, in_sp) = program.input_shape();
+ let (out_ch, out_sp) = program.output_shape();
+ println!(" MIL: matmul({ic}x{oc}, seq={seq})");
+ println!(
+ " Input: [1, {in_ch}, 1, {in_sp}] fp16 ({} KB)",
+ program.input_size() / 1024
+ );
+ println!(
+ " Output: [1, {out_ch}, 1, {out_sp}] fp16 ({} KB)\n",
+ program.output_size() / 1024
+ );
+
+ // Compile
+ print!(" Compiling...");
+ let mut model = Program::compile(&program, &[])?;
+ println!(" OK");
+
+ // Load into ANE
+ print!(" Loading...");
+ model.load()?;
+ println!(" OK");
+
+ // Prepare I/O surfaces
+ let input = Buffer::new(program.input_size())?;
+ let output = Buffer::new(program.output_size())?;
+
+ // Fill input: activations = 1.0, weights = identity matrix
+ input.write(|data| {
+ for ch in 0..ic {
+ for s in 0..seq {
+ data[ch * in_sp + s] = f32_to_fp16(1.0);
+ }
+ for o in 0..oc {
+ data[ch * in_sp + seq + o] = if ch == o { f32_to_fp16(1.0) } else { 0 };
+ }
+ }
+ });
+ println!(" Input: all 1.0 activations, identity weight matrix");
+
+ // Run on ANE
+ print!(" Evaluating on ANE...");
+ model.run(&input, &output)?;
+ println!(" OK\n");
+
+ // Read output
+ output.read(|data| {
+ print!(" Output[0..8] = [");
+ for i in 0..8 {
+ if i > 0 {
+ print!(", ");
+ }
+ print!("{:.1}", fp16_to_f32(data[i]));
+ }
+ println!("]");
+
+ // Verify: identity matmul should give all 1.0 (each output = sum of row × identity col = 1.0)
+ // Actually: [SEQ,IC] @ [IC,OC] with identity → output = ones × I = ones
+ // But the matmul is (seq=64) @ (ic=64,oc=64), so each output is dot(ones_64, identity_col) = 1.0
+ let all_ones = data[..out_ch * out_sp]
+ .iter()
+ .all(|&v| fp16_to_f32(v) == 1.0);
+ if all_ones {
+ println!("\n VERIFIED: all {} output values = 1.0", out_ch * out_sp);
+ println!(" Pure Rust → MIL → ANE bytecode → ANE hardware → correct result");
+ } else {
+ let nonzero: Vec<_> = data[..8].iter().map(|&v| fp16_to_f32(v)).collect();
+ println!("\n Output values: {:?}", nonzero);
+ }
+ });
+
+ // Cleanup is automatic (Drop)
+ Ok(())
+}