#[cfg(all(target_os = "macos", feature = "metal"))]
fn main() {
use rlx_ir::{DType, Graph, ScaleLayout, ScaledFormat, Shape, SynthKind};
use rlx_runtime::{Device, Session, is_available};
use std::time::Instant;
const M: usize = 256; const K: usize = 2048; const N: usize = 2048; const D: usize = 4; const NE: usize = 256; const WARMUP: usize = 8;
const ITERS: usize = 40;
let kb = K / D;
let dev = if is_available(Device::Metal) {
Device::Metal
} else {
eprintln!("Metal unavailable — falling back to CPU");
Device::Cpu
};
let x: Vec<f32> = (0..M * K).map(|i| (i as f32 * 0.011).sin() * 1.1).collect();
let indices: Vec<u8> = (0..N * kb).map(|i| (i % NE) as u8).collect();
let codebook: Vec<f32> = (0..NE * D)
.map(|i| (i as f32 * 0.017).cos() * 0.9)
.collect();
let kind = SynthKind::Codebook {
entry_dim: D as u32,
num_entries: NE as u32,
};
let out_shape = Shape::new(&[M, N], DType::F32);
let compile = |fmt: Option<ScaledFormat>| {
let mut g = Graph::new("synth_prec");
let xn = g.input("x", Shape::new(&[M, K], DType::F32));
let cbn = g.input("cb", Shape::new(&[NE, D], DType::F32));
let idx = g.param("idx", Shape::new(&[N, kb], DType::U8));
let y = match fmt {
None => g.synth_matmul(xn, idx, cbn, kind, out_shape.clone()),
Some(f) => {
let (codes, scale) = g.scaled_quantize(cbn, f, ScaleLayout::PerTensor);
g.synth_matmul_qcodebook(
xn,
idx,
codes,
scale,
kind,
f,
ScaleLayout::PerTensor,
out_shape.clone(),
)
}
};
g.set_outputs(vec![y]);
let mut c = Session::new(dev).compile(g);
c.set_param_typed("idx", &indices, DType::U8);
c
};
let bench = |fmt: Option<ScaledFormat>| -> (f64, Vec<f32>) {
let mut c = compile(fmt);
let mut out = vec![];
for _ in 0..WARMUP {
out = c.run(&[("x", &x), ("cb", &codebook)]).pop().unwrap();
}
let mut best = f64::INFINITY;
for _ in 0..3 {
let t0 = Instant::now();
for _ in 0..ITERS {
out = c.run(&[("x", &x), ("cb", &codebook)]).pop().unwrap();
}
best = best.min(t0.elapsed().as_secs_f64() * 1e3 / ITERS as f64);
}
(best, out)
};
fn cosine(a: &[f32], b: &[f32]) -> f64 {
let d: f64 = a.iter().zip(b).map(|(x, y)| *x as f64 * *y as f64).sum();
let na = a.iter().map(|x| (*x as f64).powi(2)).sum::<f64>().sqrt();
let nb = b.iter().map(|x| (*x as f64).powi(2)).sum::<f64>().sqrt();
d / (na * nb + 1e-12)
}
let cb_recon_relerr = |fmt: ScaledFormat| -> f64 {
let mut g = Graph::new("cb_roundtrip");
let cbn = g.input("cb", Shape::new(&[NE, D], DType::F32));
let (codes, scale) = g.scaled_quantize(cbn, fmt, ScaleLayout::PerTensor);
let deq = g.scaled_dequantize(codes, scale, fmt, ScaleLayout::PerTensor);
g.set_outputs(vec![deq]);
let mut c = Session::new(Device::Cpu).compile(g);
let recon = c.run(&[("cb", &codebook)]).pop().unwrap();
let num: f64 = codebook
.iter()
.zip(&recon)
.map(|(a, b)| (*a as f64 - *b as f64).powi(2))
.sum();
let den: f64 = codebook.iter().map(|a| (*a as f64).powi(2)).sum();
(num / den).sqrt()
};
let (base_ms, reference) = bench(None);
let cb_bytes_f32 = NE * D * 4;
println!("device: {dev:?} shape {M}×{K}×{N} (entry_dim={D}, entries={NE})\n");
println!(
" dense f32 weight would be {} — SynthMatMul stores {} indices + a {}-entry codebook\n",
fmt_bytes(K * N * 4),
fmt_bytes(N * kb),
NE
);
println!(
"{:>10} {:>4} {:>11} {:>11} {:>10} {:>9} {:>8}",
"codebook", "bits", "cb_bytes", "cb_recon_e", "cos_vs_f32", "fwd_ms", "vs_f32"
);
println!("{}", "-".repeat(70));
println!(
"{:>10} {:>4} {:>11} {:>11} {:>10.5} {:>9.3} {:>7.2}x",
"f32",
32,
fmt_bytes(cb_bytes_f32),
"0",
1.0,
base_ms,
1.0
);
macro_rules! sweep {
($($label:literal => $fmt:expr, $bits:expr);+ $(;)?) => {{
$(
let (ms, out) = bench(Some($fmt));
let cos = cosine(&reference, &out);
let recon_e = cb_recon_relerr($fmt);
let cb_bytes = NE * D + 4; println!(
"{:>10} {:>4} {:>11} {:>11.5} {:>10.5} {:>9.3} {:>7.2}x",
$label, $bits, fmt_bytes(cb_bytes), recon_e, cos, ms, base_ms / ms
);
)+
}};
}
sweep! {
"f8 e4m3" => ScaledFormat::F8E4M3, 8; "f8 e5m2" => ScaledFormat::F8E5M2, 8; "f6 e2m3" => ScaledFormat::F6E2M3, 6; "f6 e3m2" => ScaledFormat::F6E3M2, 6; "f4 e2m1" => ScaledFormat::F4E2M1, 4; "f5 e3m1" => ScaledFormat::custom(3, 1), 5; "f4 e3m0" => ScaledFormat::custom(3, 0), 4; "f3 e2m0" => ScaledFormat::custom(2, 0), 3; }
println!(
"\nnote: precision is a FOOTPRINT + ACCURACY knob, not a speed knob. cb_recon_e (the\n\
codebook round-trip error) is the real precision ladder — driven by mantissa bits:\n\
*m3 ≈ 0.022, *m2 ≈ 0.044, *m1 ≈ 0.09, *m0 ≈ 0.15 — while cos_vs_f32 stays ≥ 0.9999\n\
because the K-long matmul averages per-code error away. fwd_ms carries per-call\n\
Session readback overhead (2 MB D2H each run), so treat it as format-INDEPENDENT at\n\
the kernel level (the matmul is identical f32 after a negligible {}-byte codebook\n\
dequant); for clean kernel timing see `synth_roofline`. The bandwidth win is the u8\n\
indices ({} vs a {} dense weight), not the codebook dtype.",
fmt_bytes(cb_bytes_f32),
fmt_bytes(N * kb),
fmt_bytes(K * N * 4)
);
}
#[cfg(all(target_os = "macos", feature = "metal"))]
fn fmt_bytes(b: usize) -> String {
if b >= 1 << 20 {
format!("{:.1} MB", b as f64 / (1 << 20) as f64)
} else if b >= 1 << 10 {
format!("{:.1} KB", b as f64 / (1 << 10) as f64)
} else {
format!("{b} B")
}
}
#[cfg(not(all(target_os = "macos", feature = "metal")))]
fn main() {
eprintln!(
"build with: cargo run --release --example synth_precision_bench -p rlx-runtime --features metal"
);
}