libmir-cuda 0.1.0

CUDA inference backend for libmir
mod quantize;

use mircuda::{
    CompileOptions, Compiler, DeviceBuffer, LaunchConfig, Stream, TypedKernel, bf16, cuda_export,
    cuda_kernel_file,
};

use super::geometry::{narrow, product, require};
use crate::{Error, Result};

cuda_export!(PrepareBucketsKernel = "libmir_cuda_nvfp4_prepare_buckets"(
    selected: &DeviceBuffer<u32>, counts: &mut DeviceBuffer<u32>,
    offsets: &mut DeviceBuffer<u32>, order: &mut DeviceBuffer<u32>,
    positions: &mut DeviceBuffer<u32>, indices: &mut DeviceBuffer<u32>,
    assignments: u32, experts: u32,
));
cuda_export!(QuantizeBucketsKernel = "libmir_cuda_nvfp4_quantize_buckets_bf16"(
    input: &DeviceBuffer<bf16>, selected: &DeviceBuffer<u32>,
    order: &DeviceBuffer<u32>, offsets: &DeviceBuffer<u32>,
    global_scales: &DeviceBuffer<f32>, packed: &mut DeviceBuffer<u8>,
    scales: &mut DeviceBuffer<u8>, assignments: u32, selected_count: u32,
    input_rows: u32, columns: u32, scale_stride: u32, ranked: u32,
));
cuda_export!(QuantizeBucketPairKernel = "libmir_cuda_nvfp4_quantize_bucket_pair_bf16"(
    input: &DeviceBuffer<bf16>, selected: &DeviceBuffer<u32>,
    order: &DeviceBuffer<u32>, offsets: &DeviceBuffer<u32>,
    left_globals: &DeviceBuffer<f32>, right_globals: &DeviceBuffer<f32>,
    left_packed: &mut DeviceBuffer<u8>, right_packed: &mut DeviceBuffer<u8>,
    left_scales: &mut DeviceBuffer<u8>, right_scales: &mut DeviceBuffer<u8>,
    assignments: u32, selected_count: u32, input_rows: u32,
    columns: u32, scale_stride: u32,
));

#[derive(Clone, Debug)]
pub struct NvFp4BucketPreparation {
    prepare: TypedKernel<PrepareBucketsKernel>,
    quantize: TypedKernel<QuantizeBucketsKernel>,
    quantize_pair: TypedKernel<QuantizeBucketPairKernel>,
}

impl NvFp4BucketPreparation {
    pub fn compile(compiler: &Compiler) -> Result<Self> {
        let source = cuda_kernel_file!("../../../kernels/nvfp4_buckets.cu");
        let options = CompileOptions { fast_math: false, ..Default::default() };
        let module = compiler.compile(source, &options)?;
        Ok(Self {
            prepare: module.kernel()?,
            quantize: module.kernel()?,
            quantize_pair: module.kernel()?,
        })
    }

    #[allow(clippy::too_many_arguments)]
    pub fn prepare(
        &self,
        stream: &Stream,
        selected: &DeviceBuffer<u32>,
        counts: &mut DeviceBuffer<u32>,
        offsets: &mut DeviceBuffer<u32>,
        order: &mut DeviceBuffer<u32>,
        positions: &mut DeviceBuffer<u32>,
        indices: &mut DeviceBuffer<u32>,
        geometry: BucketGeometry,
    ) -> Result<()> {
        geometry.validate(selected, counts, offsets, order, positions, indices)?;
        Ok(self.prepare.launch(
            stream,
            LaunchConfig {
                grid: (1, 1, 1),
                block: (256, 1, 1),
                shared_memory_bytes: narrow(product(geometry.experts, 2 * size_of::<u32>())?)?,
            },
            (
                selected,
                counts,
                offsets,
                order,
                positions,
                indices,
                narrow(geometry.assignments)?,
                narrow(geometry.experts)?,
            ),
        )?)
    }
}

#[derive(Clone, Copy, Debug)]
pub struct BucketGeometry {
    pub assignments: usize,
    pub experts: usize,
}

impl BucketGeometry {
    fn validate(
        self,
        selected: &DeviceBuffer<u32>,
        counts: &DeviceBuffer<u32>,
        offsets: &DeviceBuffer<u32>,
        order: &DeviceBuffer<u32>,
        positions: &DeviceBuffer<u32>,
        indices: &DeviceBuffer<u32>,
    ) -> Result<()> {
        if self.assignments == 0 || self.experts == 0 {
            return Err(Error::InvalidNvFp4("invalid bucket geometry"));
        }
        require("bucket selections", self.assignments, selected.len())?;
        require("bucket counts", self.experts, counts.len())?;
        require("bucket offsets", self.experts, offsets.len())?;
        require("bucket order", self.assignments, order.len())?;
        require("bucket positions", self.assignments, positions.len())?;
        require("bucket indices", self.experts, indices.len())
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BucketQuantize {
    pub assignments: usize,
    pub experts: usize,
    pub selected: usize,
    pub input_rows: usize,
    pub columns: usize,
    pub ranked: bool,
}