#![cfg(all(feature = "metal", target_os = "macos"))]
use std::path::PathBuf;
use std::time::Instant;
use pictor::gemm::gemm_abt;
use pictor::te::gpu::{te_gpu_enabled, te_gpu_was_used, te_matmul_gpu};
use pictor::te::{TeWeights, TextEncoder};
fn build_weight(n: usize, k: usize) -> Vec<f32> {
let mut w = vec![0f32; n * k];
let mut lcg: u32 = 0x2545_F491 ^ ((n as u32) << 5) ^ (k as u32).wrapping_mul(2_246_822_519);
for v in w.iter_mut() {
lcg = lcg.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
*v = ((lcg >> 8) as f32 / (1u32 << 24) as f32) - 0.5;
}
w
}
type BenchCase = (usize, usize, usize, Vec<f32>, Vec<f32>);
fn median(mut t: Vec<f64>) -> f64 {
t.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
t[t.len() / 2]
}
#[test]
#[ignore = "GPU benchmark — run explicitly with --ignored --nocapture"]
fn bench_te_gemm_f32_cpu_vs_gpu_ratio() {
{
let w = build_weight(64, 256);
let x = vec![0.01f32; 256];
let mut o = vec![0f32; 64];
if te_matmul_gpu(&w, &x, &mut o, 1, 64, 256).is_err() {
eprintln!("no usable Metal GPU — skipping TE f32 GEMM ratio bench");
return;
}
}
let shapes = [(512usize, 9728usize, 2560usize), (512, 2560, 9728)];
const WARMUP: usize = 3;
const ITERS: usize = 9;
let data: Vec<BenchCase> = shapes
.iter()
.map(|&(m, n, k)| {
let weight = build_weight(n, k);
let input: Vec<f32> = (0..m * k)
.map(|i| ((i % 251) as f32) * 0.001 - 0.12)
.collect();
(m, n, k, weight, input)
})
.collect();
for (m, n, k, weight, input) in &data {
let (m, n, k) = (*m, *n, *k);
let mut gpu_out = vec![0f32; m * n];
te_matmul_gpu(weight, input, &mut gpu_out, m, n, k).expect("GPU matmul failed");
let mut cpu_out = vec![0f32; m * n];
gemm_abt(input, weight, &mut cpu_out, m, n, k);
let max_abs = gpu_out
.iter()
.zip(cpu_out.iter())
.map(|(a, b)| (a - b).abs())
.fold(0f32, f32::max);
assert!(
max_abs < 1e-3,
"M={m} N={n} K={k}: GPU/CPU max-abs {max_abs:e} exceeds 1e-3"
);
let gpu_once = || {
let mut out = vec![0f32; m * n];
te_matmul_gpu(weight, input, &mut out, m, n, k).expect("GPU matmul failed");
};
let cpu_once = || {
let mut out = vec![0f32; m * n];
gemm_abt(input, weight, &mut out, m, n, k);
};
let measure = |mut f: Box<dyn FnMut()>| -> f64 {
for _ in 0..WARMUP {
f();
}
let mut times = Vec::with_capacity(ITERS);
for _ in 0..ITERS {
let t0 = Instant::now();
f();
times.push(t0.elapsed().as_secs_f64() * 1e3);
}
median(times)
};
let gpu_a = measure(Box::new(gpu_once));
let cpu_a = measure(Box::new(cpu_once));
let gpu_b = measure(Box::new(gpu_once));
let cpu_b = measure(Box::new(cpu_once));
let gpu_med = (gpu_a + gpu_b) / 2.0;
let cpu_med = (cpu_a + cpu_b) / 2.0;
let gflop = 2.0 * m as f64 * n as f64 * k as f64 / 1e9;
eprintln!(
"TE-GEMM M={m} N={n} K={k}: CPU={cpu_med:.3}ms ({:.1} GFLOP/s) GPU={gpu_med:.3}ms ({:.1} GFLOP/s) \
speedup CPU/GPU={:.2}x (parity max-abs={max_abs:e}, median of {ITERS}, warmup {WARMUP}, 2 reps)",
gflop / (cpu_med / 1e3),
gflop / (gpu_med / 1e3),
cpu_med / gpu_med,
);
}
assert!(te_gpu_was_used(), "te_gpu_was_used() == false after bench");
}
#[test]
#[ignore = "loads ~16 GB TE weights — run explicitly with --ignored --nocapture"]
fn bench_te_forward_cold_vs_warm() {
let weights_dir = std::env::var("TE_WEIGHTS_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("/tmp/bonsai_golden/te/weights"));
if !weights_dir.is_dir() {
eprintln!(
"TE weights dir not found ({}) — skipping end-to-end wall-time bench",
weights_dir.display()
);
return;
}
let weights = match TeWeights::open(&weights_dir) {
Ok(w) => w,
Err(e) => {
eprintln!("open TE weights failed: {e} — skipping");
return;
}
};
let encoder = TextEncoder::new(&weights);
let seq = 32usize;
let input_ids: Vec<u32> = (0..seq).map(|i| (1000 + i * 7) as u32 % 150_000).collect();
let attention_mask: Vec<i32> = vec![1; seq];
let gpu = te_gpu_enabled();
eprintln!(
"TE end-to-end (seq={seq}, PICTOR_TE_GPU={}):",
if gpu { "1 (GPU)" } else { "0 (CPU)" }
);
let t0 = Instant::now();
let _o1 = encoder
.forward(&input_ids, &attention_mask)
.expect("forward 1");
let cold = t0.elapsed().as_secs_f64();
let t1 = Instant::now();
let _o2 = encoder
.forward(&input_ids, &attention_mask)
.expect("forward 2");
let warm = t1.elapsed().as_secs_f64();
eprintln!(
" cold forward = {cold:.2}s warm forward = {warm:.2}s (cold includes one-time weight \
upload when GPU)"
);
if gpu {
assert!(
te_gpu_was_used(),
"PICTOR_TE_GPU=1 but te_gpu_was_used() == false (CPU fallback)"
);
}
}