libmir-cuda 0.1.0

CUDA inference backend for libmir
use mircuda::{DeviceBuffer, LaunchConfig, Stream, bf16};

use super::{BucketQuantize, NvFp4BucketPreparation};
use crate::{
    Error, Result,
    kernels::{
        geometry::{narrow, product, require},
        scale_elements,
    },
};

impl NvFp4BucketPreparation {
    #[allow(clippy::too_many_arguments)]
    pub fn quantize(
        &self,
        stream: &Stream,
        input: &DeviceBuffer<bf16>,
        selected: &DeviceBuffer<u32>,
        order: &DeviceBuffer<u32>,
        offsets: &DeviceBuffer<u32>,
        globals: &DeviceBuffer<f32>,
        packed: &mut DeviceBuffer<u8>,
        scales: &mut DeviceBuffer<u8>,
        geometry: BucketQuantize,
    ) -> Result<()> {
        geometry.validate(input, selected, order, offsets, globals, packed, scales)?;
        let (launch, scale_stride) = launch(geometry)?;
        Ok(self.quantize.launch(
            stream,
            launch,
            (
                input,
                selected,
                order,
                offsets,
                globals,
                packed,
                scales,
                narrow(geometry.assignments)?,
                narrow(geometry.selected)?,
                narrow(geometry.input_rows)?,
                narrow(geometry.columns)?,
                narrow(scale_stride)?,
                u32::from(geometry.ranked),
            ),
        )?)
    }

    #[allow(clippy::too_many_arguments)]
    pub fn quantize_pair(
        &self,
        stream: &Stream,
        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>,
        geometry: BucketQuantize,
    ) -> Result<()> {
        if geometry.ranked {
            return Err(Error::InvalidNvFp4("paired bucket quantization requires shared input"));
        }
        geometry
            .validate(input, selected, order, offsets, left_globals, left_packed, left_scales)?;
        geometry
            .validate(input, selected, order, offsets, right_globals, right_packed, right_scales)?;
        let (launch, scale_stride) = launch(geometry)?;
        Ok(self.quantize_pair.launch(
            stream,
            launch,
            (
                input,
                selected,
                order,
                offsets,
                left_globals,
                right_globals,
                left_packed,
                right_packed,
                left_scales,
                right_scales,
                narrow(geometry.assignments)?,
                narrow(geometry.selected)?,
                narrow(geometry.input_rows)?,
                narrow(geometry.columns)?,
                narrow(scale_stride)?,
            ),
        )?)
    }
}

impl BucketQuantize {
    #[allow(clippy::too_many_arguments)]
    fn validate(
        self,
        input: &DeviceBuffer<bf16>,
        selected: &DeviceBuffer<u32>,
        order: &DeviceBuffer<u32>,
        offsets: &DeviceBuffer<u32>,
        globals: &DeviceBuffer<f32>,
        packed: &DeviceBuffer<u8>,
        scales: &DeviceBuffer<u8>,
    ) -> Result<()> {
        if self.assignments == 0 || self.experts == 0 || !self.columns.is_multiple_of(64) {
            return Err(Error::InvalidNvFp4("invalid bucket quantization geometry"));
        }
        require("bucket input", product(self.input_rows, self.columns)?, input.len())?;
        require("bucket selections", self.assignments, selected.len())?;
        require("bucket order", self.assignments, order.len())?;
        require("bucket offsets", self.experts, offsets.len())?;
        require("bucket globals", self.experts, globals.len())?;
        require("bucket packed", product(self.assignments, self.columns / 2)?, packed.len())?;
        require(
            "bucket scales",
            product(self.experts, scale_elements(self.assignments, self.columns)?)?,
            scales.len(),
        )
    }
}

fn launch(geometry: BucketQuantize) -> Result<(LaunchConfig, usize)> {
    let blocks = product(geometry.assignments, geometry.columns / 16)?;
    Ok((
        LaunchConfig {
            grid: (narrow(blocks)?, 1, 1),
            block: (32, 1, 1),
            shared_memory_bytes: 0,
        },
        scale_elements(geometry.assignments, geometry.columns)?,
    ))
}