Initial extraction from isis: direct /dev/kfd GPU compute

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>
This commit is contained in:
2026-04-06 20:38:10 +07:00
commit 66a9edb898
75 changed files with 5858 additions and 0 deletions

160
tests/kfd_bench.rs Normal file
View File

@@ -0,0 +1,160 @@
//! 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!();
}