libmir-cuda 0.3.0

CUDA inference backend for libmir
use models::weights::{
    BindingTransform, BlockQuantization, ExpertProjectionRole, LayerTensorRole, LogicalTensorRole,
    TensorBinding, TensorPacking, TensorStorage,
};

use super::*;
use crate::MxFp8CheckpointWeight;

#[test]
fn loads_and_executes_gathered_mxfp8_bank() -> Result<()> {
    let path =
        std::env::temp_dir().join(format!("libmir-cuda-mxfp8-gathered-{}.bin", std::process::id()));
    let mut bytes = Vec::new();
    for word in [0x3838_3838_u32; 8]
        .into_iter()
        .chain([0xb0b0_b0b0; 8])
        .chain([0x3838_3838; 8])
        .chain([0xb0b0_b0b0; 8])
    {
        bytes.extend_from_slice(&word.to_le_bytes());
    }
    let weight_end = u64::try_from(bytes.len())?;
    bytes.extend([127_u8, 127, 128, 128]);
    let end = u64::try_from(bytes.len())?;
    fs::write(&path, bytes)?;
    let infos = [
        info("weight", &path, "U32", vec![2, 2, 8], 0, weight_end),
        info("scales", &path, "U8", vec![2, 2, 1], weight_end, end),
    ];
    let backend = CudaBackend::new(CudaConfig::default())?;
    let tensors = upload(&backend, &infos)?;
    let weight = MxFp8CheckpointWeight::load_binding(&tensors, &binding())?;
    let operation = weight.prepare_gathered_routed(&backend, 1, 2)?;
    let input = copy(&backend, &[bf16::ONE; 32])?;
    let selected = copy(&backend, &[1_u32, 0])?;
    let mut output = backend.inner.pool.allocate_zeroed(&backend.inner.stream, 4)?;

    operation.execute(&input, &selected, &weight, &mut output)?;
    assert_eq!(
        read(&backend, &output)?,
        [bf16::from_f32(64.0), bf16::from_f32(-32.0), bf16::from_f32(32.0), bf16::from_f32(-16.0),]
    );
    fs::remove_file(path)?;
    Ok(())
}

fn binding() -> TensorBinding {
    TensorBinding {
        role: LogicalTensorRole::Layer {
            index: 0,
            tensor: LayerTensorRole::ExpertProjection {
                expert: None,
                projection: ExpertProjectionRole::Gate,
            },
        },
        source: "weight".into(),
        shape: vec![2, 2, 8],
        logical_shape: Some(vec![2, 2, 32]),
        transforms: vec![BindingTransform::StackedExperts { count: 2 }],
        storage: TensorStorage::BlockQuantized {
            format: BlockQuantization::MXFP8,
            scales: "scales".into(),
            global_scale: None,
            input_scale: None,
            bias: None,
            packing: TensorPacking::Separate,
        },
    }
}