libmir-cuda 0.2.0

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

use super::{CudaBackend, NvFp4ExpertBank, moe::ExpertBuckets};
use crate::{
    Error, Result,
    kernels::{BucketQuantize, NvFp4BucketPreparation, NvFp4Spec, scale_elements},
};

#[derive(Debug)]
pub(super) struct BucketedNvFp4Projection {
    pub(super) bank: NvFp4ExpertBank,
    pub(super) packed: DeviceBuffer<u8>,
    pub(super) scales: DeviceBuffer<u8>,
    pub(super) stream: Stream,
    spec: NvFp4Spec,
    assignments: usize,
    experts: usize,
    tokens: usize,
    selected: usize,
}

impl BucketedNvFp4Projection {
    pub(super) fn new(
        backend: &CudaBackend,
        tokens: usize,
        selected: usize,
        bank: NvFp4ExpertBank,
    ) -> Result<Self> {
        let assignments = tokens
            .checked_mul(selected)
            .ok_or(Error::InvalidNvFp4("bucketed assignment count overflow"))?;
        let experts = bank.config.experts;
        if tokens == 0 || selected == 0 || selected > experts {
            return Err(Error::InvalidNvFp4("invalid bucketed expert geometry"));
        }
        let spec = NvFp4Spec::new(bank.config.input_features, bank.config.output_features)?;
        let packed_elements = assignments
            .checked_mul(spec.input_features / 2)
            .ok_or(Error::InvalidNvFp4("bucketed packed input overflow"))?;
        let scale_count = experts
            .checked_mul(scale_elements(assignments, spec.input_features)?)
            .ok_or(Error::InvalidNvFp4("bucketed input scale overflow"))?;
        Ok(Self {
            bank,
            packed: backend.inner.pool.allocate(&backend.inner.stream, packed_elements)?,
            scales: backend.inner.pool.allocate_zeroed(&backend.inner.stream, scale_count)?,
            stream: backend.inner.stream.clone(),
            spec,
            assignments,
            experts,
            tokens,
            selected,
        })
    }

    pub(super) fn quantize_pair(
        preparation: &NvFp4BucketPreparation,
        buckets: &ExpertBuckets,
        input: &DeviceBuffer<bf16>,
        selected: &DeviceBuffer<u32>,
        left: &mut Self,
        right: &mut Self,
    ) -> Result<()> {
        left.require_compatible(right)?;
        let geometry = left.quantize_geometry(false);
        preparation.quantize_pair(
            &left.stream,
            input,
            selected,
            &buckets.order,
            &buckets.offsets,
            &left.bank.input_scales,
            &right.bank.input_scales,
            &mut left.packed,
            &mut right.packed,
            &mut left.scales,
            &mut right.scales,
            geometry,
        )
    }

    pub(super) fn quantize_ranked(
        &mut self,
        preparation: &NvFp4BucketPreparation,
        buckets: &ExpertBuckets,
        input: &DeviceBuffer<bf16>,
        selected: &DeviceBuffer<u32>,
    ) -> Result<()> {
        let geometry = self.quantize_geometry(true);
        preparation.quantize(
            &self.stream,
            input,
            selected,
            &buckets.order,
            &buckets.offsets,
            &self.bank.input_scales,
            &mut self.packed,
            &mut self.scales,
            geometry,
        )
    }

    pub(super) fn plan_spec(&self) -> Result<VariableGroupedFp4Spec> {
        Ok(VariableGroupedFp4Spec::new(
            self.experts,
            self.experts,
            self.assignments,
            self.spec.output_features,
            self.spec.input_features,
            self.assignments,
        )?)
    }

    pub(super) fn output_elements(&self) -> Result<usize> {
        self.assignments
            .checked_mul(self.spec.output_features)
            .ok_or(Error::InvalidNvFp4("bucketed expert output overflow"))
    }

    pub(super) const fn output_features(&self) -> usize {
        self.spec.output_features
    }

    pub(super) const fn input_features(&self) -> usize {
        self.spec.input_features
    }

    fn require_compatible(&self, other: &Self) -> Result<()> {
        if self.spec.input_features == other.spec.input_features
            && self.spec.output_features == other.spec.output_features
            && self.assignments == other.assignments
            && self.experts == other.experts
            && self.tokens == other.tokens
            && self.selected == other.selected
        {
            Ok(())
        } else {
            Err(Error::InvalidNvFp4("incompatible paired bucket projections"))
        }
    }

    const fn quantize_geometry(&self, ranked: bool) -> BucketQuantize {
        BucketQuantize {
            assignments: self.assignments,
            experts: self.experts,
            selected: self.selected,
            input_rows: if ranked {
                self.assignments
            } else {
                self.tokens
            },
            columns: self.spec.input_features,
            ranked,
        }
    }
}