mircuda 0.3.0

Native, explicit-stream Rust gateway to NVIDIA CUDA
use mircuda::{DenseMatmulPlan, DenseMatmulSpec, DenseVectorPlan, DenseVectorSpec, bf16};

use super::{copy_device, environment, read_device};

#[test]
fn matches_tensor_core_gemm() -> mircuda::Result<()> {
    const OUTPUTS: usize = 257;
    const FEATURES: usize = 2_816;
    let input = (0..FEATURES)
        .map(|index| Ok(bf16::from_f32(f32::from(u8::try_from(index % 31)?) / 32.0 - 0.5)))
        .collect::<mircuda::Result<Vec<_>>>()?;
    let weight = (0..OUTPUTS * FEATURES)
        .map(|index| Ok(bf16::from_f32(f32::from(u8::try_from(index % 17)?) / 64.0 - 0.125)))
        .collect::<mircuda::Result<Vec<_>>>()?;
    compare(OUTPUTS, FEATURES, &input, &weight, 0.125)
}

#[test]
#[ignore = "allocates the full Qwen2 output-head geometry"]
fn matches_tensor_core_gemm_for_qwen2_output_head() -> mircuda::Result<()> {
    const OUTPUTS: usize = 151_936;
    const FEATURES: usize = 896;
    let input = (0..FEATURES)
        .map(|index| Ok(bf16::from_f32(f32::from(u8::try_from(index % 29)?) / 32.0 - 0.4375)))
        .collect::<mircuda::Result<Vec<_>>>()?;
    let row = (0..FEATURES)
        .map(|index| Ok(bf16::from_f32(f32::from(u8::try_from(index % 23)?) / 64.0 - 0.171_875)))
        .collect::<mircuda::Result<Vec<_>>>()?;
    let mut weight = row.repeat(OUTPUTS);
    for (index, values) in weight.as_chunks_mut::<FEATURES>().0.iter_mut().enumerate() {
        values[0] = bf16::from_f32(f32::from(u8::try_from(index % 127)?) / 256.0);
    }
    compare(OUTPUTS, FEATURES, &input, &weight, 0.125)
}

fn compare(
    outputs: usize,
    features: usize,
    input: &[bf16],
    weight: &[bf16],
    tolerance: f32,
) -> mircuda::Result<()> {
    let (context, stream, pool) = environment()?;
    let input = copy_device(&context, &stream, &pool, input)?;
    let weight = copy_device(&context, &stream, &pool, weight)?;
    let mut expected = copy_device(&context, &stream, &pool, &vec![bf16::ZERO; outputs])?;
    let mut actual = copy_device(&context, &stream, &pool, &vec![bf16::NAN; outputs])?;
    DenseMatmulPlan::new(&context, &stream, DenseMatmulSpec::new(1, outputs, features)?)?
        .execute(&stream, &input, &weight, &mut expected, 1.0, 0.0)?;
    DenseVectorPlan::new(&context, &stream, DenseVectorSpec::new(outputs, features)?)?
        .execute(&stream, &input, &weight, &mut actual, 1.0, 0.0)?;
    let expected = read_device(&context, &stream, &expected)?;
    let actual = read_device(&context, &stream, &actual)?;
    assert!(actual.iter().all(|value| value.to_f32().is_finite()));
    assert_eq!(maximum(&actual), maximum(&expected));
    let error = expected
        .iter()
        .zip(&actual)
        .map(|(left, right)| (left.to_f32() - right.to_f32()).abs())
        .fold(0.0_f32, f32::max);
    assert!(error <= tolerance, "maximum BF16 GEMV difference: {error}");
    Ok(())
}

fn maximum(values: &[bf16]) -> Option<usize> {
    values
        .iter()
        .enumerate()
        .max_by(|left, right| left.1.to_f32().total_cmp(&right.1.to_f32()))
        .map(|(index, _)| index)
}