use crate::harness::{Row, Section, random_matrix, time_per_call_ns};
use ndarray::{Array1, Array2, Axis};
use rayon::prelude::*;
use std::hint::black_box;
const BENCH_CHANNEL_CHUNK: usize = 16;
fn bench_par_col_sum(x: &Array2<f32>) -> Array1<f32> {
let c = x.ncols();
let slice = x.as_slice().unwrap();
let mut out = Array1::<f32>::zeros(c);
out.as_slice_mut()
.unwrap()
.par_chunks_mut(BENCH_CHANNEL_CHUNK)
.enumerate()
.for_each(|(g, acc)| {
let j0 = g * BENCH_CHANNEL_CHUNK;
let width = acc.len();
for row in slice.chunks_exact(c) {
for (a, &v) in acc.iter_mut().zip(&row[j0..j0 + width]) {
*a += v;
}
}
});
out
}
pub fn calibrate_bn_col_stats() -> Section {
let mut rows = Vec::new();
for &(m, c) in &[
(4_096usize, 64usize),
(16_384, 64),
(65_536, 64),
(524_288, 64),
(2_048, 512),
(16_384, 512),
(262_144, 8),
] {
let x = random_matrix(m, c, 103);
let s = time_per_call_ns(|| {
black_box(x.mean_axis(Axis(0)).unwrap());
});
let p = time_per_call_ns(|| {
let sums = bench_par_col_sum(&x);
black_box(sums / m as f32);
});
rows.push(Row {
label: format!("M={m} C={c}"),
work: m * c,
serial_ns: s,
parallel_ns: p,
});
}
Section {
title: "BatchNorm column stats, channel-chunked (negative result; see row-block section)",
work_unit: "elements (M x C)",
pick_fastest: false,
rows,
}
}
fn bench_par_col_sum_rowblock(x: &Array2<f32>) -> Array1<f32> {
let (m, c) = x.dim();
let slice = x.as_slice().unwrap();
let rows_per_block = (16_384usize / c).max(1);
let parts: Vec<Array1<f32>> = slice
.par_chunks(rows_per_block * c)
.map(|chunk| {
let mut acc = Array1::<f32>::zeros(c);
let acc_slice = acc.as_slice_mut().unwrap();
for row in chunk.chunks_exact(c) {
for (a, &v) in acc_slice.iter_mut().zip(row) {
*a += v;
}
}
acc
})
.collect();
let mut out = Array1::<f32>::zeros(c);
for p in parts {
out += &p;
}
debug_assert_eq!(m * c, slice.len());
out
}
pub fn calibrate_bn_col_stats_rowblock() -> Section {
let mut rows = Vec::new();
for &(m, c) in &[
(1_024usize, 64usize),
(4_096, 64),
(16_384, 64),
(65_536, 64),
(524_288, 64),
(2_048, 512),
(16_384, 512),
(262_144, 8),
] {
let x = random_matrix(m, c, 103);
let s = time_per_call_ns(|| {
black_box(x.mean_axis(Axis(0)).unwrap());
});
let p = time_per_call_ns(|| {
let sums = bench_par_col_sum_rowblock(&x);
black_box(sums / m as f32);
});
rows.push(Row {
label: format!("M={m} C={c}"),
work: m * c,
serial_ns: s,
parallel_ns: p,
});
}
Section {
title: "BatchNorm column stats, row-block fold (BN_COL_STATS_PARALLEL_MIN_ELEMS)",
work_unit: "elements (M x C)",
pick_fastest: false,
rows,
}
}
fn bench_plane_sum(x: &[f32], c: usize, p: usize, parallel: bool) -> Array1<f32> {
const BLOCK: usize = 16_384;
let len_per_chan = x.len() / c;
let n_blocks = len_per_chan.div_ceil(BLOCK);
let segment_sum = |seg: &[f32]| -> f32 {
let mut lanes = [0.0f32; 8];
let mut chunks = seg.chunks_exact(8);
for ch in chunks.by_ref() {
for (l, &v) in lanes.iter_mut().zip(ch) {
*l += v;
}
}
let mut tail = 0.0f32;
for &v in chunks.remainder() {
tail += v;
}
((lanes[0] + lanes[1]) + (lanes[2] + lanes[3]))
+ ((lanes[4] + lanes[5]) + (lanes[6] + lanes[7]))
+ tail
};
let fold = |t: usize| {
let (ch, blk) = (t / n_blocks, t % n_blocks);
let (start, end) = (blk * BLOCK, ((blk + 1) * BLOCK).min(len_per_chan));
let mut acc = 0.0f32;
let mut pos = start;
while pos < end {
let (bi, off) = (pos / p, pos % p);
let take = (p - off).min(end - pos);
let base = (bi * c + ch) * p + off;
acc += segment_sum(&x[base..base + take]);
pos += take;
}
acc
};
let partials: Vec<f32> = if parallel {
(0..c * n_blocks).into_par_iter().map(fold).collect()
} else {
(0..c * n_blocks).map(fold).collect()
};
Array1::from_iter(
partials
.chunks(n_blocks)
.map(|parts| parts.iter().fold(0.0f32, |acc, &v| acc + v)),
)
}
pub fn calibrate_bn_plane_stats() -> Section {
let mut rows = Vec::new();
for &(b, c, p) in &[
(4usize, 16usize, 256usize),
(8, 16, 512),
(8, 32, 1_024),
(16, 32, 2_048),
(32, 8, 4_096),
(8, 512, 256),
(16, 64, 4_096),
(32, 64, 4_096),
] {
let x = random_matrix(b * c, p, 107);
let xs = x.as_slice().unwrap();
let s = time_per_call_ns(|| {
black_box(bench_plane_sum(xs, c, p, false));
});
let par = time_per_call_ns(|| {
black_box(bench_plane_sum(xs, c, p, true));
});
rows.push(Row {
label: format!("B={b} C={c} P={p}"),
work: b * c * p,
serial_ns: s,
parallel_ns: par,
});
}
Section {
title: "BatchNorm plane stats, native-layout fold (BN_PLANE_STATS_PARALLEL_MIN_ELEMS)",
work_unit: "elements (B x C x P)",
pick_fastest: false,
rows,
}
}
fn bench_ln_row_pass(
x: &[f32],
n: usize,
gamma: &[f32],
beta: &[f32],
parallel: bool,
bufs: &mut (Vec<f32>, Vec<f32>, Vec<f32>),
) {
let segment_sum = |seg: &[f32]| -> f32 {
let mut lanes = [0.0f32; 8];
let mut chunks = seg.chunks_exact(8);
for ch in chunks.by_ref() {
for (l, &v) in lanes.iter_mut().zip(ch) {
*l += v;
}
}
let mut tail = 0.0f32;
for &v in chunks.remainder() {
tail += v;
}
((lanes[0] + lanes[1]) + (lanes[2] + lanes[3]))
+ ((lanes[4] + lanes[5]) + (lanes[6] + lanes[7]))
+ tail
};
let (xc, xn, out) = (&mut bufs.0, &mut bufs.1, &mut bufs.2);
let chunk = (16_384usize / n).max(1) * n;
type RowChunks<'a> = (((&'a mut [f32], &'a mut [f32]), &'a mut [f32]), &'a [f32]);
let task = |(((xc_c, xn_c), out_c), x_c): RowChunks| {
let rows = x_c
.chunks_exact(n)
.zip(xc_c.chunks_exact_mut(n))
.zip(xn_c.chunks_exact_mut(n))
.zip(out_c.chunks_exact_mut(n));
for (((x_row, xc_row), xn_row), out_row) in rows {
let mean = segment_sum(x_row) / n as f32;
for (o, &v) in xc_row.iter_mut().zip(x_row) {
*o = v - mean;
}
let var = segment_sum(xc_row) / n as f32; let std_val = (var.abs() + 1e-5).sqrt();
for (((xn_v, out_v), &xc_v), (&g, &b)) in xn_row
.iter_mut()
.zip(out_row.iter_mut())
.zip(xc_row.iter())
.zip(gamma.iter().zip(beta))
{
*xn_v = xc_v / std_val;
*out_v = *xn_v * g + b;
}
}
};
if parallel {
xc.par_chunks_mut(chunk)
.zip(xn.par_chunks_mut(chunk))
.zip(out.par_chunks_mut(chunk))
.zip(x.par_chunks(chunk))
.for_each(task);
} else {
xc.chunks_mut(chunk)
.zip(xn.chunks_mut(chunk))
.zip(out.chunks_mut(chunk))
.zip(x.chunks(chunk))
.for_each(task);
}
}
pub fn calibrate_ln_row_pass() -> Section {
let mut rows = Vec::new();
for &(r, n) in &[
(64usize, 256usize),
(128, 512),
(512, 512),
(2_048, 512),
(64, 16_384),
(32_768, 32),
(16_384, 768),
] {
let x = random_matrix(r, n, 109);
let xs = x.as_slice().unwrap();
let gamma = vec![1.0f32; n];
let beta = vec![0.0f32; n];
let mut bufs = (
vec![0.0f32; r * n],
vec![0.0f32; r * n],
vec![0.0f32; r * n],
);
let s = time_per_call_ns(|| {
bench_ln_row_pass(xs, n, &gamma, &beta, false, &mut bufs);
black_box(&bufs.2);
});
let p = time_per_call_ns(|| {
bench_ln_row_pass(xs, n, &gamma, &beta, true, &mut bufs);
black_box(&bufs.2);
});
rows.push(Row {
label: format!("R={r} N={n}"),
work: r * n,
serial_ns: s,
parallel_ns: p,
});
}
Section {
title: "LayerNorm fused row pass, trailing axis (LN_ROW_PARALLEL_MIN_ELEMS)",
work_unit: "elements (R x N)",
pick_fastest: false,
rows,
}
}