libmir-cuda 0.3.0

CUDA inference backend for libmir
use mircuda::{DeviceBuffer, MemoryPool, MxFp8Matmul, MxFp8Spec, MxFp8TensorCore, Stream, bf16};

use super::{MxFp8CheckpointWeight, dtype};
use crate::{CudaBackend, Error, Result, backend::tuning::MxFp8ProjectionExecution};

#[derive(Debug)]
pub(super) struct Candidate {
    pub(super) execution: MxFp8ProjectionExecution,
    operation: Operation,
}

#[derive(Debug)]
enum Operation {
    Portable(MxFp8Matmul),
    TensorCore(MxFp8TensorCore),
}

impl Candidate {
    pub(super) fn new(
        backend: &CudaBackend,
        spec: MxFp8Spec,
        weight: &MxFp8CheckpointWeight,
        execution: MxFp8ProjectionExecution,
    ) -> Result<Self> {
        if execution == MxFp8ProjectionExecution::TensorCore && !tensor_core_admitted(backend, spec)
        {
            return Err(Error::InvalidExecutionPlan("MXFP8 Tensor Core candidate is unavailable"));
        }
        let operation = match execution {
            MxFp8ProjectionExecution::Portable => {
                Operation::Portable(MxFp8Matmul::compile(&backend.inner.compiler, spec)?)
            },
            MxFp8ProjectionExecution::TensorCore => {
                let operation = MxFp8TensorCore::new_with_scratch(
                    &backend.inner.compiler,
                    &backend.inner.context,
                    &backend.inner.stream,
                    spec,
                    backend.mxfp8_tensor_core_scratch(spec)?,
                )?;
                let scales = tensor_core_scales(
                    &operation,
                    &backend.inner.pool,
                    &backend.inner.stream,
                    weight,
                )?;
                operation.prepare_weight_scales(&backend.inner.stream, scales)?;
                Operation::TensorCore(operation)
            },
        };
        Ok(Self { execution, operation })
    }

    pub(super) fn execute(
        &self,
        stream: &Stream,
        pool: &MemoryPool,
        input: &DeviceBuffer<bf16>,
        weight: &MxFp8CheckpointWeight,
        output: &mut DeviceBuffer<bf16>,
    ) -> Result<()> {
        let packed = weight.weight.as_u32().ok_or_else(|| dtype(&weight.weight, "U32"))?;
        match &self.operation {
            Operation::Portable(operation) => Ok(operation.execute(
                stream,
                input,
                packed,
                weight.scales.as_u8().ok_or_else(|| dtype(&weight.scales, "U8"))?,
                output,
            )?),
            Operation::TensorCore(operation) => Ok(operation.execute(
                stream,
                input,
                packed,
                tensor_core_scales(operation, pool, stream, weight)?,
                output,
            )?),
        }
    }
}

pub(super) fn tensor_core_admitted(backend: &CudaBackend, spec: MxFp8Spec) -> bool {
    spec.tokens() > 1
        && spec.input_features().is_multiple_of(128)
        && backend.inner.device.compute_capability.0 == 12
}

fn tensor_core_scales<'a>(
    operation: &MxFp8TensorCore,
    pool: &MemoryPool,
    stream: &Stream,
    weight: &'a MxFp8CheckpointWeight,
) -> Result<&'a DeviceBuffer<u8>> {
    if weight.swizzled_scales.get().is_none() {
        let scales = weight.scales.as_u8().ok_or_else(|| dtype(&weight.scales, "U8"))?;
        let candidate = operation.swizzle_weight_scales(pool, stream, scales)?;
        drop(weight.swizzled_scales.set(candidate));
    }
    weight
        .swizzled_scales
        .get()
        .ok_or(Error::InvalidExecutionPlan("MXFP8 swizzled checkpoint scales are missing"))
}