libmir-cuda 0.1.0

CUDA inference backend for libmir
use std::path::Path;

use models::{layout::ModelLayout, weights::TensorCatalog};
use runtime::kv::{BlockId, BlockTable, KvWritePlan};
use uuid::Uuid;

use super::{CudaBackend, tests::*};
use crate::{CudaConfig, Result};

#[test]
fn checkpoint_batched_attention_matches_independent_decode_rows()
-> std::result::Result<(), Box<dyn std::error::Error>> {
    let Some(root) = std::env::var_os("LIBMIR_CUDA_NVFP4_MODEL") else {
        return Ok(());
    };
    let layout = ModelLayout::inspect(Path::new(&root))?;
    let catalog = TensorCatalog::from_layout(&layout)?;
    let names = names();
    let infos = names.iter().map(|name| required(&catalog, name)).collect::<Result<Vec<_>>>()?;
    let backend = CudaBackend::new(CudaConfig::default())?;
    let tensors = upload(&backend, &infos)?;
    let qkv = qkv(&backend, &tensors, &names)?;
    let weights = weights(&tensors, &names, &qkv)?;
    let config = config();
    let first_table = table(0, 1);
    let second_table = table(1, 3);

    let mut first_scalar = backend.prepare_decode_attention_bf16(config)?;
    let mut second_scalar = backend.prepare_decode_attention_bf16(config)?;
    let mut first_output = backend.inner.pool.allocate(&backend.inner.stream, 2_816)?;
    let mut second_output = backend.inner.pool.allocate(&backend.inner.stream, 2_816)?;
    first_scalar.execute(
        &input(&backend, 0)?,
        weights,
        &KvWritePlan::prefill(Uuid::nil(), 0, &first_table, 0, 1)?,
        &first_table,
        &mut first_output,
    )?;
    second_scalar.execute(
        &input(&backend, 1)?,
        weights,
        &KvWritePlan::prefill(Uuid::nil(), 0, &second_table, 2, 1)?,
        &second_table,
        &mut second_output,
    )?;

    let mut batch = backend.prepare_paged_decode_batch(config.cache, 2, 2)?;
    batch.prepare(&[&first_table, &second_table])?;
    let mut operation = backend.prepare_batched_decode_attention_bf16(config, 2)?;
    let mut actual = backend.inner.pool.allocate(&backend.inner.stream, 5_632)?;
    operation.execute(&input_batch(&backend, 2)?, weights, &batch, &mut actual)?;
    let actual = read(&backend, &actual)?;
    assert_close(&actual[..2_816], &read(&backend, &first_output)?);
    assert_close(&actual[2_816..], &read(&backend, &second_output)?);
    Ok(())
}

fn table(block: u32, tokens: usize) -> BlockTable {
    let mut table = BlockTable::with_block_size(16);
    table.push(BlockId(block));
    table.set_token_len(tokens);
    table
}