AMD RDNA3 GPU driver via raw KFD ioctls. No ROCm, no OpenCL. - Device discovery and topology queries - VRAM/GTT/Userptr memory with shared address space - PM4 compute queue and kernel dispatch - Hand-written RDNA3 ASM kernels: matmul (3100 GFLOP/s), matvec, superlinear - Tile-safe buffer padding for OOB protection - Event-based interrupt wait (no CPU polling) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
161 lines
6.3 KiB
Rust
161 lines
6.3 KiB
Rust
//! kfd gpu performance benchmarks.
|
|
//! run: cargo test --release --test kfd_bench -- --nocapture --test-threads=1
|
|
//!
|
|
//! measures gpu dispatch latency vs cpu at various matrix sizes
|
|
//! to find the crossover point where gpu wins.
|
|
|
|
use isis::host::device::kfd::{self, HsaDevice};
|
|
use isis::host::device::kfd::dispatch::KernArgs;
|
|
use isis::organism::compute::{ComputeBackend, CpuBackend};
|
|
use std::time::Instant;
|
|
|
|
fn open_gpu() -> Option<std::sync::MutexGuard<'static, HsaDevice>> {
|
|
use std::sync::{OnceLock, Mutex};
|
|
static GPU: OnceLock<Option<Mutex<HsaDevice>>> = OnceLock::new();
|
|
GPU.get_or_init(|| {
|
|
if !kfd::is_available() { return None; }
|
|
HsaDevice::open().ok().map(Mutex::new)
|
|
}).as_ref().map(|m| m.lock().unwrap())
|
|
}
|
|
|
|
/// cpu reference matvec
|
|
fn cpu_matvec_timed(cpu: &CpuBackend, w: &[f32], b: &[f32], x: &[f32],
|
|
y: &mut [f32], out: usize, inp: usize, iters: usize) -> f64 {
|
|
let t = Instant::now();
|
|
for _ in 0..iters {
|
|
cpu.matvec(w, b, x, y, out, inp);
|
|
}
|
|
t.elapsed().as_micros() as f64 / iters as f64
|
|
}
|
|
|
|
/// gpu matvec — allocate once, dispatch N times
|
|
fn gpu_matvec_timed(dev: &mut HsaDevice, w: &[f32], b: &[f32], x: &[f32],
|
|
out: u32, inp: u32, iters: usize) -> f64 {
|
|
let w_buf = dev.upload_f32(w).unwrap();
|
|
let b_buf = dev.upload_f32(b).unwrap();
|
|
let x_buf = dev.upload_f32(x).unwrap();
|
|
let y_buf = dev.alloc.alloc_userptr_public(((out as u64) * 4 + 4095) & !4095).unwrap();
|
|
|
|
let mut args = KernArgs::new();
|
|
args.push_ptr(&w_buf);
|
|
args.push_ptr(&b_buf);
|
|
args.push_ptr(&x_buf);
|
|
args.push_ptr(&y_buf);
|
|
args.push_u32(out);
|
|
args.push_u32(inp);
|
|
let args_buf = args.upload(&dev.alloc).unwrap();
|
|
let block = out.min(256);
|
|
|
|
// warmup
|
|
dev.dispatch_kernel("matvec", &args_buf, [out, 1, 1], [block, 1, 1]);
|
|
|
|
let t = Instant::now();
|
|
for _ in 0..iters {
|
|
dev.dispatch_kernel("matvec", &args_buf, [out, 1, 1], [block, 1, 1]);
|
|
}
|
|
t.elapsed().as_micros() as f64 / iters as f64
|
|
}
|
|
|
|
#[test]
|
|
fn bench_matvec_sizes() {
|
|
let mut dev = match open_gpu() { Some(d) => d, None => {
|
|
eprintln!(" SKIP: no gpu"); return;
|
|
}};
|
|
if dev.kernels.is_none() { eprintln!(" SKIP: no kernels"); return; }
|
|
let cpu = CpuBackend::new();
|
|
|
|
println!();
|
|
println!(" {:>6} x {:<6} {:>10} {:>10} {:>8} winner", "out", "in", "cpu (us)", "gpu (us)", "speedup");
|
|
println!(" {}", "-".repeat(62));
|
|
|
|
for &(out, inp) in &[
|
|
(4, 4), (8, 8), (16, 16), (32, 32), (64, 64),
|
|
(128, 128), (256, 256), (512, 512),
|
|
(1024, 1024),
|
|
(64, 1024), (1024, 64),
|
|
] {
|
|
let n = out * inp;
|
|
let w: Vec<f32> = (0..n).map(|i| ((i * 7 + 3) % 100) as f32 / 100.0 - 0.5).collect();
|
|
let b: Vec<f32> = (0..out).map(|i| (i % 10) as f32 * 0.1).collect();
|
|
let x: Vec<f32> = (0..inp).map(|i| ((i * 13 + 5) % 100) as f32 / 100.0).collect();
|
|
let mut y_cpu = vec![0.0f32; out];
|
|
|
|
let iters = if n < 10000 { 1000 } else if n < 100000 { 100 } else { 10 };
|
|
|
|
let cpu_us = cpu_matvec_timed(&cpu, &w, &b, &x, &mut y_cpu, out, inp, iters);
|
|
let gpu_us = gpu_matvec_timed(&mut dev, &w, &b, &x, out as u32, inp as u32, iters);
|
|
|
|
let speedup = cpu_us / gpu_us;
|
|
let winner = if gpu_us < cpu_us { "gpu" } else { "cpu" };
|
|
|
|
println!(" {:>6} x {:<6} {:>10.1} {:>10.1} {:>7.2}x {}",
|
|
out, inp, cpu_us, gpu_us, speedup, winner);
|
|
}
|
|
println!();
|
|
|
|
// isis-relevant sizes (from CTM config)
|
|
println!(" isis workload sizes:");
|
|
println!(" {:>6} x {:<6} {:>10} {:>10} {:>8} context", "out", "in", "cpu (us)", "gpu (us)", "speedup");
|
|
println!(" {}", "-".repeat(72));
|
|
|
|
let isis_sizes = [
|
|
(128, 20, "input synapse (d_input=20 → 128 neurons)"),
|
|
(256, 128, "attention synapse (128 → 256)"),
|
|
(256, 256, "output synapse (256 → 256)"),
|
|
(128, 256, "motor synapse (256 → 128)"),
|
|
(1024, 20, "large input (d_input=20 → 1024)"),
|
|
(1024, 1024, "large attention (1024 → 1024)"),
|
|
(4096, 1024, "xl attention (1024 → 4096)"),
|
|
(256, 4096, "sync accumulator readout"),
|
|
];
|
|
|
|
for &(out, inp, label) in &isis_sizes {
|
|
let n = out * inp;
|
|
let w: Vec<f32> = (0..n).map(|i| ((i * 7 + 3) % 100) as f32 / 100.0 - 0.5).collect();
|
|
let b: Vec<f32> = (0..out).map(|i| (i % 10) as f32 * 0.1).collect();
|
|
let x: Vec<f32> = (0..inp).map(|i| ((i * 13 + 5) % 100) as f32 / 100.0).collect();
|
|
let mut y_cpu = vec![0.0f32; out];
|
|
|
|
let iters = if n < 10000 { 1000 } else if n < 100000 { 100 } else { 10 };
|
|
|
|
let cpu_us = cpu_matvec_timed(&cpu, &w, &b, &x, &mut y_cpu, out, inp, iters);
|
|
let gpu_us = gpu_matvec_timed(&mut dev, &w, &b, &x, out as u32, inp as u32, iters);
|
|
|
|
let speedup = cpu_us / gpu_us;
|
|
println!(" {:>6} x {:<6} {:>10.1} {:>10.1} {:>7.2}x {}",
|
|
out, inp, cpu_us, gpu_us, speedup, label);
|
|
}
|
|
// llm-scale matmul sizes (matvec = batch=1 inference)
|
|
println!(" llm inference sizes (batch=1 matvec):");
|
|
println!(" {:>6} x {:<6} {:>10} {:>10} {:>8} context", "out", "in", "cpu (us)", "gpu (us)", "speedup");
|
|
println!(" {}", "-".repeat(72));
|
|
|
|
let llm_sizes = [
|
|
(896, 896, "qwen2.5-0.5B hidden"),
|
|
(4864, 896, "qwen2.5-0.5B mlp up"),
|
|
(896, 4864, "qwen2.5-0.5B mlp down"),
|
|
(2048, 2048, "llama-1B hidden"),
|
|
(5632, 2048, "llama-1B mlp up"),
|
|
];
|
|
|
|
for &(out, inp, label) in &llm_sizes {
|
|
let n = out * inp;
|
|
let w: Vec<f32> = (0..n).map(|i| ((i * 7 + 3) % 100) as f32 / 100.0 - 0.5).collect();
|
|
let b = vec![0.0f32; out];
|
|
let x: Vec<f32> = (0..inp).map(|i| ((i * 13 + 5) % 100) as f32 / 100.0).collect();
|
|
let mut y_cpu = vec![0.0f32; out];
|
|
|
|
let iters = 10;
|
|
let cpu_us = cpu_matvec_timed(&cpu, &w, &b, &x, &mut y_cpu, out, inp, iters);
|
|
let gpu_us = gpu_matvec_timed(&mut dev, &w, &b, &x, out as u32, inp as u32, iters);
|
|
|
|
let speedup = cpu_us / gpu_us;
|
|
let flops = (out * inp * 2) as f64;
|
|
let gpu_gflops = flops / (gpu_us * 1e3); // GFLOP/s
|
|
let cpu_gflops = flops / (cpu_us * 1e3);
|
|
println!(" {:>6} x {:<6} {:>8.0} ({:>4.1} GF/s) {:>8.0} ({:>4.1} GF/s) {:>5.2}x {}",
|
|
out, inp, cpu_us, cpu_gflops, gpu_us, gpu_gflops, speedup, label);
|
|
}
|
|
println!();
|
|
}
|