libmir-cuda 0.2.0

CUDA inference backend for libmir
use mircuda::{
    CompileOptions, Compiler, DeviceBuffer, LaunchConfig, Stream, TypedKernel, bf16, cuda_export,
    cuda_kernel_file,
};

use crate::{Error, Result};

cuda_export!(
    TextAttentionKernel = "libmir_cuda_text_attention_bf16"(
        query: &DeviceBuffer<bf16>, key: &DeviceBuffer<bf16>,
        value: &DeviceBuffer<bf16>, output: &mut DeviceBuffer<bf16>,
        tokens: u32, query_heads: u32, kv_heads: u32, head_dim: u32,
        scale: f32, causal: u32,
    )
);

#[derive(Clone, Copy, Debug)]
pub struct TextAttentionSpec {
    pub tokens: usize,
    pub query_heads: usize,
    pub kv_heads: usize,
    pub head_dim: usize,
    pub scale: f32,
    pub causal: bool,
}

#[derive(Clone, Debug)]
pub struct TextAttention {
    kernel: TypedKernel<TextAttentionKernel>,
    spec: TextAttentionSpec,
}

impl TextAttention {
    pub fn compile(compiler: &Compiler, spec: TextAttentionSpec) -> Result<Self> {
        if spec.tokens == 0
            || spec.query_heads == 0
            || spec.kv_heads == 0
            || !spec.query_heads.is_multiple_of(spec.kv_heads)
            || spec.head_dim == 0
            || spec.head_dim > 256
            || !spec.scale.is_finite()
            || spec.scale <= 0.0
        {
            return Err(Error::InvalidDecoderKernel("invalid text attention geometry"));
        }
        let source = cuda_kernel_file!("../../../kernels/text/attention_bf16.cu");
        let module = compiler.compile(source, &CompileOptions::default())?;
        Ok(Self { kernel: module.kernel()?, spec })
    }

    pub fn execute(
        &self,
        stream: &Stream,
        query: &DeviceBuffer<bf16>,
        key: &DeviceBuffer<bf16>,
        value: &DeviceBuffer<bf16>,
        output: &mut DeviceBuffer<bf16>,
    ) -> Result<()> {
        let query_elements = self.spec.tokens * self.spec.query_heads * self.spec.head_dim;
        let kv_elements = self.spec.tokens * self.spec.kv_heads * self.spec.head_dim;
        if query.len() != query_elements
            || output.len() != query_elements
            || key.len() != kv_elements
            || value.len() != kv_elements
        {
            return Err(Error::InvalidDecoderKernel("text attention buffer geometry differs"));
        }
        Ok(self.kernel.launch(
            stream,
            LaunchConfig {
                grid: (u32::try_from(self.spec.tokens)?, u32::try_from(self.spec.query_heads)?, 1),
                block: (256, 1, 1),
                shared_memory_bytes: 0,
            },
            (
                query,
                key,
                value,
                output,
                u32::try_from(self.spec.tokens)?,
                u32::try_from(self.spec.query_heads)?,
                u32::try_from(self.spec.kv_heads)?,
                u32::try_from(self.spec.head_dim)?,
                self.spec.scale,
                u32::from(self.spec.causal),
            ),
        )?)
    }
}