//! CPU vs GPU crossover benchmark
//! run: cargo test --release --test kfd_matmul_bench_small -- --nocapture --test-threads=1

use isis::host::device::kfd::{self, HsaDevice};
use isis::host::device::kfd::dispatch::{CodeObject, KernArgs};
use isis::organism::compute::dot;
use std::time::Instant;

static MATMUL_SMALL_CO: &[u8] = include_bytes!("../src/host/device/kfd/kernels/matmul_small.co");
static MATMUL_BLOCKED_CO: &[u8] = include_bytes!("../src/host/device/kfd/kernels/matmul_blocked.co");

/// CPU matmul using AVX-512 dot product: Y[j][i] = dot(W[i], X[j])
fn cpu_matmul(w: &[f32], x: &[f32], y: &mut [f32], m: usize, k: usize, n: usize) {
    for j in 0..n {
        let x_row = &x[j * k..(j + 1) * k];
        for i in 0..m {
            let w_row = &w[i * k..(i + 1) * k];
            y[j * m + i] = dot(w_row, x_row);
        }
    }
}

#[test]
fn matmul_bench_small() {
    if !kfd::is_available() { eprintln!("skip: no kfd"); return; }
    let mut dev = match HsaDevice::open() { Ok(d) => d, Err(e) => { eprintln!("skip: {e}"); return; }};

    for co_bytes in [MATMUL_SMALL_CO, MATMUL_BLOCKED_CO] {
        let co = CodeObject::load(&dev.alloc, co_bytes).unwrap();
        if dev.kernels.is_none() { dev.kernels = Some(std::collections::HashMap::new()); }
        for (name, entry) in &co.kernels { dev.kernels.as_mut().unwrap().insert(name.clone(), entry.clone()); }
        std::mem::forget(co);
    }

    println!();
    println!("  CPU vs GPU crossover (N=32 batch)");
    println!("  {:>10} {:>8} {:>10} {:>10} {:>10} {:>6}",
        "shape", "flops", "cpu_us", "gpu_us", "gflop/s", "pick");
    println!("  {}", "-".repeat(62));

    let shapes: &[(u32, u32)] = &[
        (32, 32), (64, 64), (128, 128), (256, 256), (512, 512),
        (1024, 1024), (2048, 2048), (4096, 4096),
    ];
    let n = 32u32;

    for &(m, k) in shapes {
        let flops = 2.0 * m as f64 * k as f64 * n as f64;

        // --- CPU ---
        let w_data = vec![0.001f32; (m * k) as usize];
        let x_data = vec![0.001f32; (n * k) as usize];
        let mut y_cpu = vec![0.0f32; (n * m) as usize];
        cpu_matmul(&w_data, &x_data, &mut y_cpu, m as usize, k as usize, n as usize);

        let iters_cpu = if m <= 256 { 10000 } else if m <= 1024 { 1000 } else { 100 };
        let t0 = Instant::now();
        for _ in 0..iters_cpu {
            cpu_matmul(&w_data, &x_data, &mut y_cpu, m as usize, k as usize, n as usize);
        }
        let cpu_us = t0.elapsed().as_nanos() as f64 / iters_cpu as f64 / 1000.0;

        // --- GPU ---
        let (kernel_name, nwg, w_buf) = if m >= 1536 {
            ("matmul_blocked", ((m + 127) / 128) * ((n + 31) / 32),
             dev.upload_f32(&w_data).unwrap())
        } else {
            ("matmul_small", ((m + 31) / 32) * ((n + 31) / 32),
             dev.upload_f32_col_major(&w_data, m as usize, k as usize).unwrap())
        };
        let b_buf = dev.upload_f32(&vec![0.0f32; m as usize]).unwrap();
        let x_buf = dev.upload_f32(&x_data).unwrap();
        let y_buf = dev.alloc_output((n as usize * m as usize * 4 + 64) as usize).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(m); args.push_u32(k); args.push_u32(n);
        let args_buf = args.upload(&dev.alloc).unwrap();

        for _ in 0..50 { dev.dispatch_enqueue(kernel_name, &args_buf, [nwg, 1, 1], [256, 1, 1]); }
        assert!(dev.submit_wait(30_000), "warmup timeout");

        let iters_gpu = 500;
        let t0 = Instant::now();
        for _ in 0..iters_gpu { dev.dispatch_enqueue(kernel_name, &args_buf, [nwg, 1, 1], [256, 1, 1]); }
        assert!(dev.submit_wait(60_000), "bench timeout");
        let gpu_us = t0.elapsed().as_nanos() as f64 / iters_gpu as f64 / 1000.0;

        let best_gf = flops / gpu_us.min(cpu_us) / 1e3;
        let pick = if cpu_us < gpu_us { "CPU" } else { "GPU" };

        println!("  {:>4}x{:<4} {:>8.0} {:>8.1} {:>8.1} {:>8.0}   {:>4}",
            m, k, flops, cpu_us, gpu_us, best_gf, pick);
    }
    println!();
}
