libmir-cuda 0.2.0

CUDA inference backend for libmir
use runtime::{
    backend::SamplingLogits,
    kv::{BlockId, BlockTable, CacheConfig},
};
use uuid::Uuid;

use super::CudaMoeModelSession;
use crate::{Error, Result};

impl CudaMoeModelSession {
    pub(crate) fn warmup(
        &mut self,
        session_id: Uuid,
        cache: CacheConfig,
        context_len: usize,
    ) -> Result<()> {
        let capacity = warmup_capacity(self.prefill_tokens.capacity(), cache, context_len)?;
        let geometries = canonical_geometries(capacity);
        for layer in &mut self.layers {
            for tokens in &geometries {
                layer.prepare_prefill(*tokens)?;
            }
        }
        for tokens in geometries {
            let prompt = vec![0; tokens];
            let table = block_table(tokens, cache)?;
            self.prefill_from_for_sampling(session_id, &prompt, 0, &table, SamplingLogits::None)?;
        }
        let mut table = block_table(capacity + 2, cache)?;
        table.set_token_len(capacity + 1);
        self.sample(SamplingLogits::None)?;
        self.decode_sampled_for_sampling(session_id, &table, SamplingLogits::None)?;
        table.set_token_len(capacity + 2);
        self.sample(SamplingLogits::None)?;
        self.decode_sampled_for_sampling(session_id, &table, SamplingLogits::None)?;
        self.sample(SamplingLogits::None)?;
        Ok(())
    }
}

fn warmup_capacity(chunk: usize, cache: CacheConfig, context: usize) -> Result<usize> {
    let cache_tokens = usize::try_from(cache.block_count)?
        .checked_mul(cache.block_size)
        .ok_or(Error::InvalidPagedKv("CUDA warmup cache capacity overflow"))?;
    let capacity = chunk.min(context.saturating_sub(2)).min(cache_tokens.saturating_sub(2));
    if capacity == 0 {
        Err(Error::InvalidPagedKv("CUDA warmup requires at least three context slots"))
    } else {
        Ok(capacity)
    }
}

fn canonical_geometries(capacity: usize) -> Vec<usize> {
    let mut geometries = Vec::new();
    let mut tokens = 1;
    while tokens < capacity {
        geometries.push(tokens);
        tokens = tokens.saturating_mul(2);
    }
    if geometries.last().copied() != Some(capacity) {
        geometries.push(capacity);
    }
    geometries
}

fn block_table(tokens: usize, cache: CacheConfig) -> Result<BlockTable> {
    let blocks = tokens.div_ceil(cache.block_size);
    if blocks > usize::try_from(cache.block_count)? {
        return Err(Error::InvalidPagedKv("CUDA warmup exceeds KV block arena"));
    }
    let mut table = BlockTable::with_block_size(cache.block_size);
    for block in 0..blocks {
        table.push(BlockId(u32::try_from(block)?));
    }
    table.set_token_len(tokens);
    Ok(table)
}

#[cfg(test)]
mod tests {
    use super::canonical_geometries;

    #[test]
    fn covers_every_tail_with_prepared_geometries() {
        assert_eq!(canonical_geometries(256), [1, 2, 4, 8, 16, 32, 64, 128, 256]);
        assert_eq!(canonical_geometries(192), [1, 2, 4, 8, 16, 32, 64, 128, 192]);
    }
}