pub fn layer_normalization(
input: &[f32],
gamma: &[f32],
beta: Option<&[f32]>,
rows: usize,
cols: usize,
eps: f32,
) -> Vec<f32> {
assert_eq!(input.len(), rows * cols, "input length must be rows * cols");
assert!(
gamma.len() == cols || gamma.len() == 1,
"gamma must be `cols` or 1 long, got {}",
gamma.len()
);
if let Some(beta) = beta {
assert!(
beta.len() == cols || beta.len() == 1,
"beta must be `cols` or 1 long, got {}",
beta.len()
);
}
let g = |i: usize| if gamma.len() == 1 { gamma[0] } else { gamma[i] };
let b = |i: usize| match beta {
Some(beta) if beta.len() == 1 => beta[0],
Some(beta) => beta[i],
None => 0.0,
};
let cols_f = cols as f32;
let mut out = vec![0.0f32; input.len()];
for row in 0..rows {
let src = &input[row * cols..(row + 1) * cols];
let dst = &mut out[row * cols..(row + 1) * cols];
let mean = src.iter().sum::<f32>() / cols_f;
let sq_sum: f32 = src.iter().map(|&x| (x - mean) * (x - mean)).sum();
let sigma = (sq_sum / cols_f + eps).sqrt();
for i in 0..cols {
dst[i] = g(i) * ((src[i] - mean) / sigma) + b(i);
}
}
out
}
pub fn relu(input: &[f32]) -> Vec<f32> {
input.iter().map(|&x| x.max(0.0)).collect()
}
pub fn negate(input: &[f32]) -> Vec<f32> {
input.iter().map(|&x| -x).collect()
}
pub fn scalar_mult(input: &[f32], scalar: f32) -> Vec<f32> {
input.iter().map(|&x| x * scalar).collect()
}
pub fn scalar_add(input: &[f32], scalar: f32) -> Vec<f32> {
input.iter().map(|&x| x + scalar).collect()
}
pub fn highway(a: &[f32], b: &[f32], t: &[f32]) -> Vec<f32> {
assert!(
a.len() == b.len() && b.len() == t.len(),
"highway inputs must be equal length: {}, {}, {}",
a.len(),
b.len(),
t.len()
);
a.iter()
.zip(b)
.zip(t)
.map(|((&a, &b), &t)| {
let g = 1.0 / (1.0 + (-t).exp());
g * a + (1.0 - g) * b
})
.collect()
}
pub fn softmax(input: &[f32], rows: usize, cols: usize) -> Vec<f32> {
let mut out = input.to_vec();
softmax_in_place(&mut out, rows, cols);
out
}
pub fn softmax_in_place(buf: &mut [f32], rows: usize, cols: usize) {
assert_eq!(buf.len(), rows * cols, "buf length must be rows * cols");
for row in 0..rows {
let r = &mut buf[row * cols..(row + 1) * cols];
let max = r.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let mut sum = 0.0f32;
for v in r.iter_mut() {
let ex = (*v - max).exp();
*v = ex;
sum += ex;
}
for v in r.iter_mut() {
*v /= sum;
}
}
}
pub fn add(a: &[f32], a_shape: &[i32], b: &[f32], b_shape: &[i32]) -> (Vec<f32>, Vec<i32>) {
broadcast_binary(a, a_shape, b, b_shape, |x, y| x + y)
}
fn broadcast_shape(a: &[i32], b: &[i32]) -> Option<Vec<i32>> {
let rank = a.len().max(b.len());
let mut out = vec![0i32; rank];
for i in 0..rank {
let da = a.get(a.len().wrapping_sub(rank - i)).copied().unwrap_or(1);
let db = b.get(b.len().wrapping_sub(rank - i)).copied().unwrap_or(1);
out[i] = if da == db || db == 1 {
da
} else if da == 1 {
db
} else {
return None;
};
}
Some(out)
}
fn broadcast_binary(
a: &[f32],
a_shape: &[i32],
b: &[f32],
b_shape: &[i32],
f: impl Fn(f32, f32) -> f32,
) -> (Vec<f32>, Vec<i32>) {
let numel = |s: &[i32]| s.iter().map(|&d| d as usize).product::<usize>();
assert_eq!(a.len(), numel(a_shape), "a length disagrees with a_shape");
assert_eq!(b.len(), numel(b_shape), "b length disagrees with b_shape");
let out_shape = broadcast_shape(a_shape, b_shape)
.unwrap_or_else(|| panic!("incompatible shapes {a_shape:?} and {b_shape:?}"));
let rank = out_shape.len();
let strides = |shape: &[i32]| -> Vec<usize> {
let mut s = vec![0usize; rank];
let mut acc = 1usize;
for i in (0..shape.len()).rev() {
let out_dim = out_shape[rank - shape.len() + i];
s[rank - shape.len() + i] = if shape[i] == 1 && out_dim != 1 {
0
} else {
acc
};
acc *= shape[i] as usize;
}
s
};
let sa = strides(a_shape);
let sb = strides(b_shape);
let total: usize = out_shape.iter().map(|&d| d as usize).product();
let mut out = vec![0.0f32; total];
let mut idx = vec![0usize; rank];
for o in out.iter_mut() {
let (mut ia, mut ib) = (0usize, 0usize);
for d in 0..rank {
ia += idx[d] * sa[d];
ib += idx[d] * sb[d];
}
*o = f(a[ia], b[ib]);
for d in (0..rank).rev() {
idx[d] += 1;
if idx[d] < out_shape[d] as usize {
break;
}
idx[d] = 0;
}
}
(out, out_shape)
}
pub fn reshape(input: &[f32], new_shape: &[i32]) -> Vec<f32> {
let n: usize = new_shape.iter().map(|&d| d as usize).product();
assert_eq!(n, input.len(), "reshape must preserve element count");
input.to_vec()
}
pub fn transpose(input: &[f32], in_shape: &[i32], perm: &[usize]) -> (Vec<f32>, Vec<i32>) {
let rank = in_shape.len();
assert_eq!(perm.len(), rank, "perm rank must match shape rank");
let mut seen = vec![false; rank];
for &p in perm {
assert!(
p < rank && !seen[p],
"perm must be a permutation of 0..{rank}"
);
seen[p] = true;
}
let numel: usize = in_shape.iter().map(|&d| d as usize).product();
assert_eq!(input.len(), numel, "input length disagrees with in_shape");
let in_strides = row_major_strides(in_shape);
let out_shape: Vec<i32> = perm.iter().map(|&p| in_shape[p]).collect();
let mut out = vec![0.0f32; numel];
let mut oidx = vec![0usize; rank];
for slot in out.iter_mut() {
let mut in_off = 0usize;
for i in 0..rank {
in_off += oidx[i] * in_strides[perm[i]];
}
*slot = input[in_off];
for d in (0..rank).rev() {
oidx[d] += 1;
if oidx[d] < out_shape[d] as usize {
break;
}
oidx[d] = 0;
}
}
(out, out_shape)
}
pub fn slice_contiguous(input: &[f32], offset: usize, len: usize) -> Vec<f32> {
assert!(offset + len <= input.len(), "slice out of range");
input[offset..offset + len].to_vec()
}
pub fn rows(data: &[f32], num_rows: usize, width: usize, indices: &[u32]) -> Vec<f32> {
assert_eq!(
data.len(),
num_rows * width,
"data length disagrees with shape"
);
let mut out = vec![0.0f32; indices.len() * width];
for (i, &idx) in indices.iter().enumerate() {
let idx = idx as usize;
assert!(idx < num_rows, "row index {idx} out of range {num_rows}");
out[i * width..(i + 1) * width].copy_from_slice(&data[idx * width..(idx + 1) * width]);
}
out
}
pub fn cols(data: &[f32], num_rows: usize, width: usize, indices: &[u32]) -> Vec<f32> {
assert_eq!(
data.len(),
num_rows * width,
"data length disagrees with shape"
);
let k = indices.len();
let mut out = vec![0.0f32; num_rows * k];
for r in 0..num_rows {
for (j, &idx) in indices.iter().enumerate() {
let idx = idx as usize;
assert!(idx < width, "col index {idx} out of range {width}");
out[r * k + j] = data[r * width + idx];
}
}
out
}
pub fn bdot(
a: &[f32],
a_shape: &[i32],
transa: bool,
b: &[f32],
b_shape: &[i32],
transb: bool,
scalar: f32,
) -> (Vec<f32>, Vec<i32>) {
assert!(
a_shape.len() >= 2 && b_shape.len() >= 2,
"bdot needs rank >= 2"
);
let (ra, ca) = (
a_shape[a_shape.len() - 2] as usize,
a_shape[a_shape.len() - 1] as usize,
);
let (rb, cb) = (
b_shape[b_shape.len() - 2] as usize,
b_shape[b_shape.len() - 1] as usize,
);
let (m, k) = if transa { (ca, ra) } else { (ra, ca) };
let (kb, n) = if transb { (cb, rb) } else { (rb, cb) };
assert_eq!(k, kb, "bdot inner dims must match: {k} vs {kb}");
let mat_a = ra * ca;
let mat_b = rb * cb;
let batch_a = a.len() / mat_a;
let batch_b = b.len() / mat_b;
let batch_c = batch_a.max(batch_b);
assert!(
batch_c % batch_a == 0 && batch_c % batch_b == 0,
"incompatible batch counts {batch_a} and {batch_b}"
);
let mut out = vec![0.0f32; batch_c * m * n];
for i in 0..batch_c {
let a_off = (i % batch_a) * mat_a;
let b_off = (i % batch_b) * mat_b;
let c_off = i * m * n;
let opa = |p: usize, q: usize| {
if transa {
a[a_off + q * ca + p]
} else {
a[a_off + p * ca + q]
}
};
let opb = |q: usize, j: usize| {
if transb {
b[b_off + j * cb + q]
} else {
b[b_off + q * cb + j]
}
};
for p in 0..m {
for j in 0..n {
let mut acc = 0.0f32;
for q in 0..k {
acc += opa(p, q) * opb(q, j);
}
out[c_off + p * n + j] = scalar * acc;
}
}
}
let base = if batch_b >= batch_a { b_shape } else { a_shape };
let mut out_shape: Vec<i32> = base[..base.len() - 2].to_vec();
out_shape.push(m as i32);
out_shape.push(n as i32);
(out, out_shape)
}
pub fn prepare_a(input: &[f32], quant_mult: f32) -> Vec<u8> {
let mut out = Vec::new();
prepare_a_into(input, quant_mult, &mut out);
out
}
pub fn prepare_a_into(input: &[f32], quant_mult: f32, out: &mut Vec<u8>) {
out.clear();
out.reserve(input.len());
out.extend(input.iter().map(|&x| {
let q = (x * quant_mult).round_ties_even();
let clamped = q.clamp(-127.0, 127.0);
(clamped as i32 + 127) as u8
}));
}
pub fn prepare_bias(
b_transposed: &[i8],
n: usize,
k: usize,
raw_bias: &[f32],
unquant_mult: f32,
) -> Vec<f32> {
assert_eq!(b_transposed.len(), n * k, "B length must be n * k");
assert_eq!(raw_bias.len(), n, "raw_bias length must be n");
(0..n)
.map(|col| {
let colsum: i32 = b_transposed[col * k..(col + 1) * k]
.iter()
.map(|&w| w as i32)
.sum();
raw_bias[col] - 127.0 * unquant_mult * colsum as f32
})
.collect()
}
pub fn intgemm_affine(
a: &[u8],
m: usize,
k: usize,
b_transposed: &[i8],
n: usize,
unquant_mult: f32,
bias: &[f32],
) -> Vec<f32> {
assert_eq!(a.len(), m * k, "A length must be m * k");
assert_eq!(b_transposed.len(), n * k, "B length must be n * k");
assert_eq!(bias.len(), n, "bias length must be n");
let mut out = vec![0.0f32; m * n];
for row in 0..m {
let a_row = &a[row * k..(row + 1) * k];
for col in 0..n {
let b_col = &b_transposed[col * k..(col + 1) * k];
let mut acc: i32 = 0;
for i in 0..k {
acc += a_row[i] as i32 * b_col[i] as i32;
}
out[row * n + col] = unquant_mult * acc as f32 + bias[col];
}
}
out
}
fn row_major_strides(shape: &[i32]) -> Vec<usize> {
let mut strides = vec![1usize; shape.len()];
for i in (0..shape.len().saturating_sub(1)).rev() {
strides[i] = strides[i + 1] * shape[i + 1] as usize;
}
strides
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compare::{assert_close, Tolerance};
#[test]
fn matches_hand_computed_single_row() {
let x = [1.0f32, 2.0, 3.0, 4.0];
let inv = 1.0 / 1.25f32.sqrt();
let expected = [-1.5 * inv, -0.5 * inv, 0.5 * inv, 1.5 * inv];
let out = layer_normalization(&x, &[1.0], None, 1, 4, 0.0);
assert_close(&out, &expected, Tolerance::default());
}
#[test]
fn applies_gamma_and_beta_per_column() {
let x = [1.0f32, 2.0, 3.0, 4.0];
let inv = 1.0 / 1.25f32.sqrt();
let norm = [-1.5 * inv, -0.5 * inv, 0.5 * inv, 1.5 * inv];
let expected: Vec<f32> = norm.iter().map(|&n| 2.0 * n + 10.0).collect();
let gamma = [2.0f32; 4];
let beta = [10.0f32; 4];
let out = layer_normalization(&x, &gamma, Some(&beta), 1, 4, 0.0);
assert_close(&out, &expected, Tolerance::default());
}
#[test]
fn normalizes_each_row_independently() {
let x = [1.0f32, 2.0, 3.0, 4.0, 100.0, 200.0, 300.0, 400.0];
let out = layer_normalization(&x, &[1.0], None, 2, 4, 0.0);
for row in 0..2 {
let r = &out[row * 4..row * 4 + 4];
let mean: f32 = r.iter().sum::<f32>() / 4.0;
let var: f32 = r.iter().map(|&v| (v - mean) * (v - mean)).sum::<f32>() / 4.0;
assert!(mean.abs() < 1e-5, "row {row} mean {mean}");
assert!((var - 1.0).abs() < 1e-4, "row {row} var {var}");
}
}
#[test]
#[should_panic(expected = "rows * cols")]
fn rejects_wrong_input_length() {
layer_normalization(&[1.0, 2.0, 3.0], &[1.0], None, 1, 4, 0.0);
}
#[test]
fn relu_negate_scalars() {
assert_eq!(relu(&[-2.0, -0.0, 3.0, -1.5]), vec![0.0, 0.0, 3.0, 0.0]);
assert_eq!(negate(&[1.0, -2.0, 0.0]), vec![-1.0, 2.0, -0.0]);
assert_eq!(scalar_mult(&[1.0, 2.0, 3.0], 2.5), vec![2.5, 5.0, 7.5]);
assert_eq!(scalar_add(&[1.0, 2.0], 10.0), vec![11.0, 12.0]);
}
#[test]
fn highway_gates_between_inputs() {
let out = highway(&[10.0], &[0.0], &[0.0]);
assert_close(&out, &[5.0], Tolerance::default());
let out = highway(&[10.0, 10.0], &[0.0, 0.0], &[20.0, -20.0]);
assert_close(&out, &[10.0, 0.0], Tolerance::new(1e-3, 1e-6));
}
#[test]
fn softmax_uniform_and_peaked() {
let out = softmax(&[0.0, 0.0, 0.0, 0.0], 1, 4);
assert_close(&out, &[0.25, 0.25, 0.25, 0.25], Tolerance::default());
let out = softmax(&[1000.0, 1001.0, 1002.0], 1, 3);
let sum: f32 = out.iter().sum();
assert!((sum - 1.0).abs() < 1e-5, "sum {sum}");
assert!(out[2] > out[1] && out[1] > out[0]);
}
#[test]
fn add_same_shape() {
let (out, shape) = add(&[1.0, 2.0, 3.0], &[3], &[10.0, 20.0, 30.0], &[3]);
assert_eq!(shape, vec![3]);
assert_eq!(out, vec![11.0, 22.0, 33.0]);
}
#[test]
fn add_broadcasts_bias_row() {
let a = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let (out, shape) = add(&a, &[2, 3], &[10.0, 20.0, 30.0], &[1, 3]);
assert_eq!(shape, vec![2, 3]);
assert_eq!(out, vec![11.0, 22.0, 33.0, 14.0, 25.0, 36.0]);
}
#[test]
fn add_broadcasts_scalar_and_ranks() {
let (out, _) = add(&[1.0, 2.0, 3.0, 4.0], &[2, 2], &[100.0], &[1]);
assert_eq!(out, vec![101.0, 102.0, 103.0, 104.0]);
let (out, shape) = add(&[1.0, 2.0], &[1, 1, 2], &[10.0, 20.0], &[2]);
assert_eq!(shape, vec![1, 1, 2]);
assert_eq!(out, vec![11.0, 22.0]);
}
#[test]
#[should_panic(expected = "incompatible shapes")]
fn add_rejects_incompatible_shapes() {
add(&[1.0, 2.0, 3.0], &[3], &[1.0, 2.0], &[2]);
}
#[test]
fn reshape_preserves_order() {
assert_eq!(
reshape(&[1.0, 2.0, 3.0, 4.0], &[2, 2]),
vec![1.0, 2.0, 3.0, 4.0]
);
}
#[test]
fn transpose_swaps_last_two_axes() {
let x = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let (out, shape) = transpose(&x, &[2, 3], &[1, 0]);
assert_eq!(shape, vec![3, 2]);
assert_eq!(out, vec![1.0, 4.0, 2.0, 5.0, 3.0, 6.0]);
}
#[test]
fn transpose_identity_and_4d() {
let x = [1.0, 2.0, 3.0, 4.0];
let (out, shape) = transpose(&x, &[1, 1, 2, 2], &[0, 1, 2, 3]);
assert_eq!(shape, vec![1, 1, 2, 2]);
assert_eq!(out, x);
let y: Vec<f32> = (0..6).map(|v| v as f32).collect();
let (out, shape) = transpose(&y, &[1, 2, 3, 1], &[0, 2, 1, 3]);
assert_eq!(shape, vec![1, 3, 2, 1]);
assert_eq!(out, vec![0.0, 3.0, 1.0, 4.0, 2.0, 5.0]);
}
#[test]
fn slice_contiguous_extracts_block() {
let x = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
assert_eq!(slice_contiguous(&x, 2, 3), vec![2.0, 3.0, 4.0]);
}
#[test]
fn strides_are_row_major() {
assert_eq!(row_major_strides(&[2, 3, 4]), vec![12, 4, 1]);
assert_eq!(row_major_strides(&[5]), vec![1]);
}
#[test]
fn rows_gathers_embeddings() {
let data = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
let out = rows(&data, 3, 2, &[2, 0, 2]);
assert_eq!(out, vec![4.0, 5.0, 0.0, 1.0, 4.0, 5.0]);
}
#[test]
fn cols_gathers_columns() {
let data = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
let out = cols(&data, 2, 3, &[2, 0]);
assert_eq!(out, vec![2.0, 0.0, 5.0, 3.0]);
}
#[test]
fn bdot_plain_matmul() {
let a = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; let b = [7.0, 8.0, 9.0, 10.0, 11.0, 12.0]; let (out, shape) = bdot(&a, &[1, 2, 3], false, &b, &[1, 3, 2], false, 1.0);
assert_eq!(shape, vec![1, 2, 2]);
assert_eq!(out, vec![58.0, 64.0, 139.0, 154.0]);
}
#[test]
fn bdot_transb_and_scale() {
let a = [1.0, 2.0, 3.0, 4.0];
let b = [1.0, 0.0, 0.0, 1.0]; let (out, shape) = bdot(&a, &[2, 2], false, &b, &[2, 2], true, 0.5);
assert_eq!(shape, vec![2, 2]);
assert_eq!(out, vec![0.5, 1.0, 1.5, 2.0]);
}
#[test]
fn intgemm_affine_matches_reference() {
let a = [10u8, 20];
let b_transposed = [1i8, 2, 3, 4, 5, 6]; let bias = [100.0f32, 200.0, 300.0];
let out = intgemm_affine(&a, 1, 2, &b_transposed, 3, 0.5, &bias);
assert_eq!(out, vec![125.0, 255.0, 385.0]);
}
#[test]
fn prepare_bias_cancels_shift() {
let b = [1i8, 2, 3, 4];
let out = prepare_bias(&b, 2, 2, &[10.0, 20.0], 0.5);
assert_eq!(
out,
vec![10.0 - 127.0 * 0.5 * 3.0, 20.0 - 127.0 * 0.5 * 7.0]
);
}
#[test]
fn prepare_a_shifts_and_clamps() {
let out = prepare_a(&[0.0, 1.4, 2.6, -300.0], 1.0);
assert_eq!(out, vec![127, 128, 130, 0]);
let out = prepare_a(&[0.5, 1.5], 1.0);
assert_eq!(out, vec![127, 129]);
assert_eq!(prepare_a(&[1000.0], 1.0), vec![254]);
}
#[test]
#[should_panic(expected = "B length must be n * k")]
fn intgemm_affine_rejects_bad_b() {
intgemm_affine(&[1, 2], 1, 2, &[1, 2, 3], 3, 1.0, &[0.0, 0.0, 0.0]);
}
#[test]
fn bdot_broadcasts_batch() {
let a = [1.0, 1.0, 1.0, 1.0]; let b = [1.0, 2.0, 3.0, 4.0, 10.0, 20.0, 30.0, 40.0]; let (out, shape) = bdot(&a, &[1, 2, 2], false, &b, &[2, 2, 2], false, 1.0);
assert_eq!(shape, vec![2, 2, 2]);
assert_eq!(out, vec![4.0, 6.0, 4.0, 6.0, 40.0, 60.0, 40.0, 60.0]);
}
}