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 super::super::geometry::{narrow, product, require};
use crate::{Error, Result};

cuda_export!(
    InterpolateKernel = "libmir_cuda_spatial_merge_interpolate_bf16"(
        table: &DeviceBuffer<bf16>, output: &mut DeviceBuffer<bf16>,
        grid_height: u32, grid_width: u32, source_side: u32, merge: u32, hidden: u32,
    )
);
cuda_export!(
    QkvSplitKernel = "libmir_cuda_spatial_merge_qkv_split_bf16"(
        input: &DeviceBuffer<bf16>, query: &mut DeviceBuffer<bf16>,
        key: &mut DeviceBuffer<bf16>, value: &mut DeviceBuffer<bf16>,
        tokens: u32, hidden: u32,
    )
);
cuda_export!(
    RopeKernel = "libmir_cuda_spatial_merge_rope_bf16"(
        input: &DeviceBuffer<bf16>, positions: &DeviceBuffer<u32>,
        output: &mut DeviceBuffer<bf16>, tokens: u32, heads: u32, head_dim: u32,
    )
);

#[derive(Clone, Debug)]
pub struct SpatialMergeKernels {
    interpolate: TypedKernel<InterpolateKernel>,
    split: TypedKernel<QkvSplitKernel>,
    rope: TypedKernel<RopeKernel>,
}

impl SpatialMergeKernels {
    pub fn compile(compiler: &Compiler) -> Result<Self> {
        let source = cuda_kernel_file!("../../../kernels/vision_spatial_merge_bf16.cu");
        let module = compiler.compile(source, &CompileOptions::default())?;
        Ok(Self {
            interpolate: module.kernel()?,
            split: module.kernel()?,
            rope: module.kernel()?,
        })
    }

    #[allow(clippy::too_many_arguments)]
    pub fn interpolate(
        &self,
        stream: &Stream,
        table: &DeviceBuffer<bf16>,
        output: &mut DeviceBuffer<bf16>,
        grid_height: usize,
        grid_width: usize,
        source_side: usize,
        merge: usize,
        hidden: usize,
    ) -> Result<()> {
        let tokens = product(grid_height, grid_width)?;
        require(
            "spatial position table",
            product(product(source_side, source_side)?, hidden)?,
            table.len(),
        )?;
        require("spatial interpolated positions", product(tokens, hidden)?, output.len())?;
        if source_side == 0
            || merge == 0
            || !grid_height.is_multiple_of(merge)
            || !grid_width.is_multiple_of(merge)
        {
            return Err(Error::InvalidVisionKernel("invalid spatial interpolation geometry"));
        }
        Ok(self.interpolate.launch(
            stream,
            launch(product(tokens, hidden)?)?,
            (
                table,
                output,
                narrow(grid_height)?,
                narrow(grid_width)?,
                narrow(source_side)?,
                narrow(merge)?,
                narrow(hidden)?,
            ),
        )?)
    }

    #[allow(clippy::too_many_arguments)]
    pub fn split_qkv(
        &self,
        stream: &Stream,
        input: &DeviceBuffer<bf16>,
        query: &mut DeviceBuffer<bf16>,
        key: &mut DeviceBuffer<bf16>,
        value: &mut DeviceBuffer<bf16>,
        tokens: usize,
        hidden: usize,
    ) -> Result<()> {
        let elements = product(tokens, hidden)?;
        require("spatial qkv", product(elements, 3)?, input.len())?;
        require("spatial query", elements, query.len())?;
        require("spatial key", elements, key.len())?;
        require("spatial value", elements, value.len())?;
        Ok(self.split.launch(
            stream,
            launch(elements)?,
            (input, query, key, value, narrow(tokens)?, narrow(hidden)?),
        )?)
    }

    #[allow(clippy::too_many_arguments)]
    pub fn rope(
        &self,
        stream: &Stream,
        input: &DeviceBuffer<bf16>,
        positions: &DeviceBuffer<u32>,
        output: &mut DeviceBuffer<bf16>,
        tokens: usize,
        heads: usize,
        head_dim: usize,
    ) -> Result<()> {
        if !head_dim.is_multiple_of(8) || head_dim > 256 {
            return Err(Error::InvalidVisionKernel("invalid spatial-merge RoPE geometry"));
        }
        let elements = product(product(tokens, heads)?, head_dim)?;
        require("spatial RoPE input", elements, input.len())?;
        require("spatial RoPE positions", product(tokens, 2)?, positions.len())?;
        require("spatial RoPE output", elements, output.len())?;
        Ok(self.rope.launch(
            stream,
            launch(elements)?,
            (input, positions, output, narrow(tokens)?, narrow(heads)?, narrow(head_dim)?),
        )?)
    }
}

fn launch(elements: usize) -> Result<LaunchConfig> {
    Ok(LaunchConfig {
        grid: (narrow(elements.div_ceil(256))?, 1, 1),
        block: (256, 1, 1),
        shared_memory_bytes: 0,
    })
}