libmir-cuda 0.3.0

CUDA inference backend for libmir
use std::{
    env,
    io::{self, Write},
    path::PathBuf,
};

use libmir_cuda::kernels::{
    BlockFp8LinearKernels, BlockFp8LinearSpec, Fp8OutputSpec, Fp8RefinementKernels,
};
use mircuda::{
    Compiler, Context, DenseVectorPlan, DenseVectorSpec, Driver, MemoryPool, Stream, bf16,
};

const INPUT: usize = 2_880;
const OUTPUT: usize = 201_088;
const ITERATIONS: u16 = 32;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let driver = Driver::initialize()?;
    let device = driver.devices()?.into_iter().next().ok_or(mircuda::Error::InvalidLaunch)?;
    let context = driver.create_context(device)?;
    let info = context.device_info()?;
    let stream = context.create_stream()?;
    let pool = context.default_memory_pool()?;
    pool.set_release_threshold(2 * 1_024 * 1_024 * 1_024)?;
    let report = profile(&context, &stream, &pool)?;
    let mut output = io::stdout().lock();
    writeln!(
        output,
        "device: {} (compute {}.{})",
        info.name, info.compute_capability.0, info.compute_capability.1
    )?;
    writeln!(output, "GPT-OSS output head: {INPUT} -> {OUTPUT}")?;
    writeln!(output, "  exact BF16:       {:.3} us/token", report.exact)?;
    writeln!(output, "  block FP8:        {:.3} us/token", report.fp8)?;
    writeln!(output, "  refinement only:  {:.3} us/token", report.refine)?;
    writeln!(output, "  FP8 + refinement: {:.3} us/token", report.full)?;
    writeln!(output, "  exact/full speedup: {:.3}x", report.exact / report.full)?;
    Ok(())
}

struct Report {
    exact: f64,
    fp8: f64,
    refine: f64,
    full: f64,
}

fn profile(context: &Context, stream: &Stream, pool: &MemoryPool) -> libmir_cuda::Result<Report> {
    let input = pool.allocate_zeroed::<bf16>(stream, INPUT)?;
    let exact_weight = pool.allocate_zeroed::<bf16>(stream, INPUT * OUTPUT)?;
    let mut logits = pool.allocate_zeroed::<bf16>(stream, OUTPUT)?;
    let cuda_home =
        env::var_os("CUDA_HOME").map_or_else(|| PathBuf::from("/usr/local/cuda"), PathBuf::from);
    let include = cuda_home.join("include");
    let compiler =
        Compiler::with_include_paths(context.clone(), [include.clone(), include.join("cccl")])?;
    let spec = BlockFp8LinearSpec::new(INPUT, OUTPUT)?;
    let kernels = BlockFp8LinearKernels::compile(&compiler, spec)?;
    let mut weight = pool.allocate::<u8>(stream, spec.weight_elements()?)?;
    let mut scales = pool.allocate::<f32>(stream, spec.scale_elements()?)?;
    kernels.quantize(stream, &exact_weight, &mut weight, &mut scales)?;
    let refinement =
        Fp8RefinementKernels::compile(&compiler, Fp8OutputSpec::new_refinement(INPUT, OUTPUT)?)?;
    let workspace = Fp8RefinementKernels::workspace_elements(OUTPUT)?;
    let mut first = pool.allocate::<u64>(stream, workspace)?;
    let mut second = pool.allocate::<u64>(stream, workspace)?;
    let mut exact = DenseVectorPlan::new(context, stream, DenseVectorSpec::new(OUTPUT, INPUT)?)?;
    for _ in 0..4 {
        kernels.project(stream, &input, &weight, &scales, &mut logits)?;
        refinement.execute(stream, &input, &exact_weight, &mut logits, &mut first, &mut second)?;
    }
    stream.synchronize()?;
    let exact_us = time(context, stream, || {
        Ok(exact.execute(stream, &input, &exact_weight, &mut logits, 1.0, 0.0)?)
    })?;
    let fp8_us = time(context, stream, || {
        kernels.project(stream, &input, &weight, &scales, &mut logits)
    })?;
    let refine_us = time(context, stream, || {
        refinement.execute(stream, &input, &exact_weight, &mut logits, &mut first, &mut second)
    })?;
    let full_us = time(context, stream, || {
        kernels.project(stream, &input, &weight, &scales, &mut logits)?;
        refinement.execute(stream, &input, &exact_weight, &mut logits, &mut first, &mut second)
    })?;
    Ok(Report {
        exact: exact_us,
        fp8: fp8_us,
        refine: refine_us,
        full: full_us,
    })
}

fn time(
    context: &Context,
    stream: &Stream,
    mut operation: impl FnMut() -> libmir_cuda::Result<()>,
) -> libmir_cuda::Result<f64> {
    let started = context.create_event(true)?;
    let completed = context.create_event(true)?;
    started.record(stream)?;
    for _ in 0..ITERATIONS {
        operation()?;
    }
    completed.record(stream)?;
    completed.synchronize()?;
    Ok(f64::from(started.elapsed_ms(&completed)?) * 1_000.0 / f64::from(ITERATIONS))
}