//! blake-bench โ€” GPU Blake2b-256 throughput benchmark on Apple Silicon.
//!
//! Subcommands:
//!   verify         verify all kernel variants against CPU reference
//!   bench          throughput sweep: batch sizes ร— threadgroup widths ร— variants
//!   bench-once V N W   one measurement: variant V, N hashes, threadgroup W
//!
//! Variants:
//!   v1   baseline (12-round loop, SIGMA table indexed at runtime)
//!   v2   fully unrolled rounds, SIGMA indices baked into m-word references

use std::time::Instant;

use blake_bench::{GpuBlake2b, Variant, fill_inputs, verify_zero_copy};

const VARIANTS: &[Variant] = &[
    Variant::V1Baseline,
    Variant::V2Unrolled,
    Variant::V3DualHash,
    Variant::V4DualHashFastRot,
];

fn parse_variant(s: &str) -> Option<Variant> {
    match s {
        "v1" => Some(Variant::V1Baseline),
        "v2" => Some(Variant::V2Unrolled),
        "v3" => Some(Variant::V3DualHash),
        "v4" => Some(Variant::V4DualHashFastRot),
        _ => None,
    }
}

fn print_pipeline_info(g: &GpuBlake2b) {
    println!(
        "  {:<8} thread_execution_width={}  max_threads_per_group={}",
        format!("[{:?}]", g.variant),
        g.thread_execution_width,
        g.max_threads_per_group,
    );
}

fn bench_once(g: &GpuBlake2b, count: u32, tg_width: usize) -> Option<f64> {
    if tg_width > g.max_threads_per_group {
        return None;
    }
    let (in_buf, out_buf) = g.alloc_buffers(count).ok()?;
    fill_inputs(&in_buf, count, 0xCAFE_BABE_DEAD_BEEF);

    // Warmup
    g.dispatch(&in_buf, &out_buf, count, tg_width);

    let iters = if count >= 4_000_000 {
        4
    } else if count >= 1_000_000 {
        8
    } else {
        16
    };
    let t0 = Instant::now();
    for _ in 0..iters {
        g.dispatch(&in_buf, &out_buf, count, tg_width);
    }
    let dt = t0.elapsed().as_secs_f64();
    let total_hashes = (count as u64) * (iters as u64);
    let mhs = (total_hashes as f64) / dt / 1e6;
    Some(mhs)
}

fn verify_variant(variant: Variant) -> bool {
    let g = match GpuBlake2b::open(variant) {
        Ok(g) => g,
        Err(e) => {
            eprintln!("  open {:?} failed: {e:?}", variant);
            return false;
        }
    };
    let count: u32 = 4096;
    let (in_buf, out_buf) = g.alloc_buffers(count).unwrap();
    fill_inputs(&in_buf, count, 1234);
    g.dispatch(&in_buf, &out_buf, count, 64);
    let mismatches = verify_zero_copy(&in_buf, &out_buf, count);
    if mismatches == 0 {
        println!("  {:<24} PASS  ({count} hashes match CPU reference)", variant.label());
        true
    } else {
        println!("  {:<24} FAIL  {mismatches}/{count} mismatches", variant.label());
        false
    }
}

fn cmd_verify_all() -> bool {
    println!("Verifying GPU kernels against CPU reference:");
    let mut all_ok = true;
    for &v in VARIANTS {
        all_ok &= verify_variant(v);
    }
    all_ok
}

fn bench_stable(g: &GpuBlake2b, count: u32, tg_width: usize, trials: usize) -> Option<f64> {
    let (in_buf, out_buf) = g.alloc_buffers(count).ok()?;
    fill_inputs(&in_buf, count, 0xCAFE_BABE_DEAD_BEEF);

    // 3 warmup dispatches
    for _ in 0..3 {
        g.dispatch(&in_buf, &out_buf, count, tg_width);
    }

    let mut best = 0.0f64;
    for _ in 0..trials {
        let iters = 8;
        let t0 = Instant::now();
        for _ in 0..iters {
            g.dispatch(&in_buf, &out_buf, count, tg_width);
        }
        let dt = t0.elapsed().as_secs_f64();
        let total = (count as u64) * (iters as u64);
        let mhs = (total as f64) / dt / 1e6;
        if mhs > best {
            best = mhs;
        }
    }
    Some(best)
}

fn cmd_stable() {
    // Use the largest reasonable batch and a known-good threadgroup width.
    let count: u32 = 16_777_216;
    let widths: &[usize] = &[32, 64, 128, 256];
    let trials = 8;
    println!();
    println!(
        "Stable measurement: count={count}, best of {trials} trials per (variant, tg_width)"
    );
    println!();
    print!("{:<32}", "variant");
    for &w in widths {
        print!("{:>10}", format!("tg={w}"));
    }
    println!("{:>10}", "peak");
    for &variant in VARIANTS {
        let g = match GpuBlake2b::open(variant) {
            Ok(g) => g,
            Err(_) => continue,
        };
        print!("{:<32}", variant.label());
        let mut peak = 0.0f64;
        for &w in widths {
            match bench_stable(&g, count, w, trials) {
                Some(mhs) => {
                    print!("{:>10.1}", mhs);
                    if mhs > peak {
                        peak = mhs;
                    }
                }
                None => print!("{:>10}", "-"),
            }
        }
        println!("{:>10.1}", peak);
    }
    println!();
}

fn cmd_bench_all() {
    println!();
    println!("Throughput sweep (MH/s, higher is better):");
    println!("  rows = batch size  cols = threadgroup width");
    println!();

    let batches: &[u32] = &[262_144, 1_048_576, 4_194_304, 16_777_216, 67_108_864];
    let widths: &[usize] = &[32, 64, 128, 256, 512, 1024];

    for &variant in VARIANTS {
        let g = match GpuBlake2b::open(variant) {
            Ok(g) => g,
            Err(e) => {
                println!("--- {} --- open failed: {e:?}", variant.label());
                continue;
            }
        };
        println!("--- {} ---", variant.label());
        print_pipeline_info(&g);
        print!("{:>12}", "count\\tg_w");
        for &w in widths {
            print!("{:>10}", w);
        }
        println!();
        for &count in batches {
            print!("{:>12}", count);
            for &w in widths {
                match bench_once(&g, count, w) {
                    Some(mhs) => print!("{:>10.1}", mhs),
                    None => print!("{:>10}", "-"),
                }
            }
            println!();
        }
        println!();
    }

    // Summary: peak achieved per variant
    println!("Peak per variant:");
    for &variant in VARIANTS {
        if let Ok(g) = GpuBlake2b::open(variant) {
            let mut best = 0.0f64;
            let mut best_at = (0u32, 0usize);
            for &count in batches {
                for &w in widths {
                    if let Some(mhs) = bench_once(&g, count, w) {
                        if mhs > best {
                            best = mhs;
                            best_at = (count, w);
                        }
                    }
                }
            }
            println!(
                "  {:<40} peak {:.1} MH/s  @ count={} tg={}",
                variant.label(),
                best,
                best_at.0,
                best_at.1
            );
        }
    }
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    match args.get(1).map(|s| s.as_str()) {
        Some("verify") => {
            if !cmd_verify_all() {
                std::process::exit(1);
            }
        }
        Some("bench-once") => {
            let variant = args.get(2).and_then(|s| parse_variant(s)).unwrap_or(Variant::V2Unrolled);
            let count: u32 = args.get(3).and_then(|s| s.parse().ok()).unwrap_or(1_048_576);
            let w: usize = args.get(4).and_then(|s| s.parse().ok()).unwrap_or(64);
            let g = GpuBlake2b::open(variant).expect("open");
            print_pipeline_info(&g);
            match bench_once(&g, count, w) {
                Some(mhs) => println!("{:?} count={count} tg={w} -> {mhs:.2} MH/s", variant),
                None => println!("unsupported tg width"),
            }
        }
        Some("bench") | None => {
            if !cmd_verify_all() {
                eprintln!("verification failed โ€” not running benchmark");
                std::process::exit(1);
            }
            cmd_bench_all();
        }
        Some("stable") => {
            if !cmd_verify_all() {
                eprintln!("verification failed โ€” not running benchmark");
                std::process::exit(1);
            }
            cmd_stable();
        }
        Some(other) => {
            eprintln!("unknown subcommand: {other}");
            eprintln!("usage: blake-bench [verify|bench|bench-once {{v1|v2}} N W]");
            std::process::exit(1);
        }
    }
}

Homonyms

neural/rune/cli/main.rs
cyb/optica/src/main.rs
soft3/nox/cli/main.rs
warriors/trisha/cli/main.rs
soft3/glia/import/main.rs
cyb/shell/src/main.rs
cyb/cli/src/main.rs
neural/trident/src/main.rs
soft3/tru/cli/main.rs
cyb/apps/src/main.rs
cyberia/cyberia-my/src/main.rs
soft3/lens/cli/src/main.rs
soft3/glia/run/cli/main.rs
warriors/erga/cli/src/main.rs
cyberia/research/cyberia-my/src/main.rs
soft3/glia/cli/src/main.rs
soft3/radio/iroh-relay/src/main.rs
soft3/strata/cli/src/main.rs
neural/rs/cli/src/main.rs
neural/rs/macho-linker/src/main.rs
cyb/prysm/cli/src/main.rs
soft3/radio/iroh-dns-server/src/main.rs
soft3/cybergraph/cli/src/main.rs
soft3/hemera/cli/src/main.rs
soft3/radio/particle/src/main.rs
soft3/radio/radio-cli/src/main.rs
neural/rs/link/src/main.rs
soft3/bbg/cli/src/main.rs
neural/eidos/cli/src/main.rs
neural/rs/pure-rust-check/src/main.rs
neural/rs/rsc/src/main.rs
soft3/zheng/cli/src/main.rs
soft3/foculus/src/bin/main.rs
soft3/lytics/rs/agent/src/main.rs
warriors/erga/rs/mine-bench/src/main.rs
soft3/strata/trop/cli/src/main.rs
soft3/strata/genies/cli/src/main.rs
soft3/strata/kuro/cli/src/main.rs
soft3/strata/jali/cli/src/main.rs
cyb/wysm/crates/cli/src/main.rs
warriors/erga/rs/rtable-bench/src/main.rs
soft3/lytics/rs/ingest/src/main.rs
cyb/honeycrisp/acpu/src/probe/main.rs
neural/inf/rs/cli/src/main.rs
soft3/strata/nebu/cli/src/main.rs
cyb/honeycrisp/rane/src/probe/main.rs
neural/inf/rs/cozo/cozo-bin/src/main.rs
cyb/honeycrisp/unimem/experiments/hyp_probe/src/main.rs
cyb/honeycrisp/unimem/experiments/iosurface_probe/src/main.rs
cyb/honeycrisp/unimem/experiments/dext_contiguous_alloc/client/src/main.rs
cyb/honeycrisp/unimem/experiments/dext_iosurface_pa/client/src/main.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/rane/src/probe/main.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/acpu/src/probe/main.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/acpu/src/probe/main.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/rane/src/probe/main.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/unimem/experiments/iosurface_probe/src/main.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/unimem/experiments/iosurface_probe/src/main.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/unimem/experiments/hyp_probe/src/main.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/unimem/experiments/hyp_probe/src/main.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/unimem/experiments/dext_contiguous_alloc/client/src/main.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/unimem/experiments/dext_contiguous_alloc/client/src/main.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/unimem/experiments/dext_iosurface_pa/client/src/main.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/unimem/experiments/dext_iosurface_pa/client/src/main.rs

Graph