use std::time::Instant;
use nuvec_core::{
Array, DType, Device, GatherQmm, Quant, QuantMode, Result, Stream, async_eval, random,
set_default_stream,
};
const SHAPES: &[(&str, usize, usize, usize, bool, i32, i32)] = &[
("qmv4096", 1, 4096, 4096, true, 4, 64),
("qmvup", 1, 4096, 11008, true, 4, 64),
("qmvdown", 1, 11008, 4096, true, 4, 64),
("qmv4096-b8", 1, 4096, 4096, true, 8, 64),
("qmv4096-b3", 1, 4096, 4096, true, 3, 64),
("qmv4096-g32", 1, 4096, 4096, true, 4, 32),
("qmv-m4", 4, 4096, 4096, true, 4, 64),
("qmm-m32", 32, 4096, 4096, true, 4, 64),
("qmm-m512", 512, 4096, 4096, true, 4, 64),
("qvm4096", 1, 4096, 4096, false, 4, 64),
("qmm-n128", 128, 4096, 4096, false, 4, 64),
("qmm-961x8192", 961, 2048, 8192, true, 4, 64),
("qmm-961x2048", 961, 2048, 2048, true, 4, 64),
("gqmm-up", 7688, 2048, 512, true, 4, 64),
("gqmm-down", 7688, 512, 2048, true, 4, 64),
("e4b-up", 1, 2560, 10240, true, 4, 64),
("e4b-down", 1, 10240, 2560, true, 4, 64),
("e4b-q", 1, 2560, 2048, true, 4, 64),
("e4b-kv", 1, 2560, 512, true, 4, 64),
("e4b-o", 1, 2048, 2560, true, 4, 64),
("e4b-pli", 1, 256, 2560, true, 4, 64),
("e4b-head", 1, 2560, 262144, true, 4, 64),
("g26-q1", 1, 2816, 4096, true, 4, 64),
("g26-mlp1", 1, 2816, 2112, true, 4, 64),
("g26-down1", 1, 2112, 2816, true, 4, 64),
("g26-dram1", 1, 2816, 32768, true, 4, 64),
("g26-dram1-k3072", 1, 3072, 32768, true, 4, 64),
("g26-q", 8, 2816, 4096, true, 4, 64),
("g26-kv", 8, 2816, 2048, true, 4, 64),
("g26-o", 8, 4096, 2816, true, 4, 64),
("g26-mlp", 8, 2816, 2112, true, 4, 64),
("g26-down", 8, 2112, 2816, true, 4, 64),
("g26g-up", 64, 2816, 704, true, 4, 64),
("g26g-down", 64, 704, 2816, true, 4, 64),
("qmm-2048x4096", 2048, 4096, 4096, true, 4, 64),
("qmm-2048x12288", 2048, 4096, 12288, true, 4, 64),
("qmm-2048-down", 2048, 12288, 4096, true, 4, 64),
("q36-8192", 2048, 2048, 8192, true, 4, 64),
("q36-4096", 2048, 2048, 4096, true, 4, 64),
("q36-out", 2048, 4096, 2048, true, 4, 64),
("q36-512", 2048, 2048, 512, true, 4, 64),
("q36-shared-down", 2048, 512, 2048, true, 4, 64),
("q06-q", 2047, 1024, 2048, true, 4, 64),
("q06-kv", 2047, 1024, 1024, true, 4, 64),
("q06-o", 2047, 2048, 1024, true, 4, 64),
("q06-up", 2047, 1024, 3072, true, 4, 64),
("q06-down", 2047, 3072, 1024, true, 4, 64),
("q06s-q", 24, 1024, 2048, true, 4, 64),
("q06s-kv", 24, 1024, 1024, true, 4, 64),
("q06s-o", 24, 2048, 1024, true, 4, 64),
("q06s-up", 24, 1024, 3072, true, 4, 64),
("q06s-down", 24, 3072, 1024, true, 4, 64),
("gqmm-2048-up", 16384, 2048, 512, true, 4, 64),
("gqmm-2048-down", 16384, 512, 2048, true, 4, 64),
];
const FP_SHAPES: &[(&str, usize, usize, usize, bool)] = &[
("qmv4096", 1, 4096, 4096, true),
("qmvup", 1, 4096, 11008, true),
("qmvdown", 1, 11008, 4096, true),
("qmv-m4", 4, 4096, 4096, true),
("qmv-m8", 8, 4096, 4096, true),
("qmm-m32", 32, 4096, 4096, true),
("qmm-m512", 512, 4096, 4096, true),
("qvm4096", 1, 4096, 4096, false),
("qmv-k128", 1, 128, 4096, true),
("oss-qmv2880", 1, 2880, 2880, true),
("oss-dram", 1, 2880, 32768, true),
("oss-dram-k3072", 1, 3072, 32768, true),
("oss-qmv2880-m4", 4, 2880, 2880, true),
("oss-qmm2880", 512, 2880, 2880, true),
];
const FP_GATHERS: &[(&str, usize, usize, usize, usize)] = &[
("gather-decode", 8, 2048, 768, 128),
("gather-sorted", 512, 2048, 768, 128),
("oss-gather-decode", 4, 2880, 2880, 32),
("oss-gather-sorted", 512, 2880, 2880, 32),
];
const FP_MODES: [(&str, QuantMode); 3] = [
("mxfp4", QuantMode::Mxfp4),
("mxfp8", QuantMode::Mxfp8),
("nvfp4", QuantMode::Nvfp4),
];
const EXPERTS: usize = 256;
type Case = (String, usize, usize, usize, bool, Quant, usize, bool);
fn main() -> Result<()> {
if !std::env::args().any(|a| a == "--bench") {
return Ok(());
}
set_default_stream(Stream::default_for(Device::gpu(0)));
let filter = std::env::var("BENCH_FILTER").unwrap_or_default();
let mut cases: Vec<Case> = SHAPES
.iter()
.map(|&(name, m, k, n, t, bits, gs)| {
let experts = if name.starts_with("gqmm") {
EXPERTS
} else if name.starts_with("g26g") {
128
} else {
0
};
(
name.to_owned(),
m,
k,
n,
t,
Quant::affine(gs, bits),
experts,
false,
)
})
.collect();
for (label, mode) in FP_MODES {
for &(name, m, k, n, t) in FP_SHAPES {
cases.push((
format!("{label}-{name}"),
m,
k,
n,
t,
Quant::new(mode),
0,
false,
));
}
for &(name, m, k, n, e) in FP_GATHERS {
let q = Quant::new(mode);
cases.push((format!("{label}-{name}"), m, k, n, true, q, e, false));
if mode == QuantMode::Nvfp4 {
cases.push((format!("{label}-{name}-gs"), m, k, n, true, q, e, true));
}
}
}
for (dtype, label) in [(DType::F16, "float16"), (DType::BF16, "bfloat16")] {
for (name, m, k, n, transpose, quant, experts, global) in &cases {
let (name, m, k, n, transpose, quant) = (name.as_str(), *m, *k, *n, *transpose, *quant);
let (experts, global) = (*experts, *global);
let bits = quant.bits;
if !name.contains(filter.as_str()) {
continue;
}
let gathered = experts > 0;
let w_shape = match (gathered, transpose) {
(true, _) => vec![experts, n, k],
(false, true) => vec![n, k],
(false, false) => vec![k, n],
};
let w = random::uniform(-1.0, 1.0, &w_shape[..], DType::F32, None).astype(dtype);
let wq = w.quantize(quant);
let x_shape = if gathered { vec![m, 1, k] } else { vec![m, k] };
let x = random::uniform(-1.0, 1.0, &x_shape[..], DType::F32, None).astype(dtype);
let ids: Vec<u32> = (0..m).map(|i| (i * experts.max(1) / m) as u32).collect();
let indices = Array::from_slice(&ids, [m]);
let scale = random::uniform(0.5, 2.0, [experts.max(1)], DType::F32, None);
nuvec_core::eval(&(&wq, &x, &scale))?;
let product = || -> Array {
let options = GatherQmm::default().sorted_indices(true);
let options = if global {
options.global_scale(&scale)
} else {
options
};
match gathered {
true => x.gather_qmm(&wq, None, Some(&indices), options),
false => x.quantized_matmul(&wq, transpose),
}
};
let bytes = k * n * bits as usize / 8;
let reps = (40_000_000_000 / 10 / (bytes + 2 * m * k * n).max(1)).clamp(20, 4000);
let burst = |count: usize, per_eval: usize| -> Result<f64> {
let t = Instant::now();
let mut pending: Vec<Array> = Vec::new();
for _ in 0..count {
pending.push(product());
if pending.len() == per_eval {
async_eval(&pending)?;
pending.clear();
}
}
nuvec_core::eval(&pending)?;
nuvec_core::eval(&[product()])?;
Ok(t.elapsed().as_secs_f64() / count as f64)
};
let best = |per_eval: usize| -> Result<f64> {
burst(reps, per_eval)?; let times = (0..3)
.map(|_| burst(reps, per_eval))
.collect::<Result<Vec<_>>>()?;
Ok(times.into_iter().fold(f64::INFINITY, f64::min) * 1e6)
};
println!("{name}\t{label}\t{:.2}\t{:.2}", best(1)?, best(8)?);
}
}
Ok(())
}