libmir-cuda 0.1.0

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

use crate::{
    Bf16LinearPackWeights, BlockFp8LinearWeight, CudaBackend, CudaTensor,
    DecodeAttentionOutputWeight, DecodeAttentionWeights, DecodeQkvWeights, DenseExecution,
    DensePlanRequest, DenseRole, Error, ExecutionPhase, Fp8ResidualLinearWeight, NvFp4LinearWeight,
    Result,
};

#[derive(Clone)]
enum CapturedOutputWeight {
    Bf16(CudaTensor),
    NvFp4(NvFp4LinearWeight),
    BlockFp8 {
        exact: CudaTensor,
        quantized: BlockFp8LinearWeight,
    },
    Fp8Int4 {
        exact: CudaTensor,
        quantized: Fp8ResidualLinearWeight,
    },
}

#[derive(Clone)]
enum CapturedQkvWeights {
    Bf16(Bf16LinearPackWeights<3>),
    NvFp4([NvFp4LinearWeight; 3]),
}

#[derive(Clone)]
pub(in crate::backend) struct CapturedAttentionWeights {
    input_norm: CudaTensor,
    qkv: CapturedQkvWeights,
    query_norm: DeviceBuffer<bf16>,
    key_norm: DeviceBuffer<bf16>,
    output: CapturedOutputWeight,
}

impl CapturedAttentionWeights {
    pub(in crate::backend) fn prepare(
        backend: &CudaBackend,
        weights: DecodeAttentionWeights<'_>,
    ) -> Result<Self> {
        let output = weights.output.bf16().ok_or(Error::InvalidExecutionPlan(
            "optimized BF16 graph preparation received NVFP4 output weight",
        ))?;
        let [output_features, input_features] = output.shape() else {
            return Err(Error::InvalidLinearWeight {
                name: output.name().into(),
                expected: [0, 0],
                actual: output.shape().to_vec(),
            });
        };
        let request = DensePlanRequest {
            phase: ExecutionPhase::Decode,
            role: DenseRole::AttentionOutput,
            tokens: 1,
            input_features: *input_features,
            output_features: *output_features,
        };
        let output = match backend.execution_planner().plan_dense(request)?.execution() {
            DenseExecution::BlockFp8Vector => CapturedOutputWeight::BlockFp8 {
                exact: output.clone(),
                quantized: backend.prepare_block_fp8_linear_weight(output)?,
            },
            DenseExecution::Fp8Int4Vector => CapturedOutputWeight::Fp8Int4 {
                exact: output.clone(),
                quantized: backend.prepare_fp8_residual_linear_weight(output)?,
            },
            DenseExecution::Matrix | DenseExecution::Vector => {
                CapturedOutputWeight::Bf16(output.clone())
            },
        };
        Ok(Self {
            input_norm: weights.input_norm.clone(),
            qkv: CapturedQkvWeights::from(weights.qkv),
            query_norm: weights.query_norm.clone(),
            key_norm: weights.key_norm.clone(),
            output,
        })
    }

    pub(in crate::backend) fn borrow(&self) -> DecodeAttentionWeights<'_> {
        DecodeAttentionWeights {
            input_norm: &self.input_norm,
            qkv: self.qkv.borrow(),
            query_norm: &self.query_norm,
            key_norm: &self.key_norm,
            output: match &self.output {
                CapturedOutputWeight::Bf16(weight) => DecodeAttentionOutputWeight::Bf16(weight),
                CapturedOutputWeight::NvFp4(weight) => DecodeAttentionOutputWeight::NvFp4(weight),
                CapturedOutputWeight::BlockFp8 { exact, quantized } => {
                    DecodeAttentionOutputWeight::BlockFp8 { exact, quantized }
                },
                CapturedOutputWeight::Fp8Int4 { exact, quantized } => {
                    DecodeAttentionOutputWeight::Fp8Int4 { exact, quantized }
                },
            },
        }
    }
}

impl From<DecodeAttentionWeights<'_>> for CapturedAttentionWeights {
    fn from(weights: DecodeAttentionWeights<'_>) -> Self {
        Self {
            input_norm: weights.input_norm.clone(),
            qkv: CapturedQkvWeights::from(weights.qkv),
            query_norm: weights.query_norm.clone(),
            key_norm: weights.key_norm.clone(),
            output: CapturedOutputWeight::from(weights.output),
        }
    }
}

impl From<DecodeQkvWeights<'_>> for CapturedQkvWeights {
    fn from(weights: DecodeQkvWeights<'_>) -> Self {
        match weights {
            DecodeQkvWeights::Bf16(weights) => Self::Bf16(weights.clone()),
            DecodeQkvWeights::NvFp4(weights) => Self::NvFp4(weights.map(Clone::clone)),
        }
    }
}

impl CapturedQkvWeights {
    fn borrow(&self) -> DecodeQkvWeights<'_> {
        match self {
            Self::Bf16(weights) => DecodeQkvWeights::Bf16(weights),
            Self::NvFp4(weights) => {
                DecodeQkvWeights::NvFp4([&weights[0], &weights[1], &weights[2]])
            },
        }
    }
}

impl From<DecodeAttentionOutputWeight<'_>> for CapturedOutputWeight {
    fn from(weight: DecodeAttentionOutputWeight<'_>) -> Self {
        match weight {
            DecodeAttentionOutputWeight::Bf16(weight) => Self::Bf16(weight.clone()),
            DecodeAttentionOutputWeight::NvFp4(weight) => Self::NvFp4(weight.clone()),
            DecodeAttentionOutputWeight::BlockFp8 { exact, quantized } => Self::BlockFp8 {
                exact: exact.clone(),
                quantized: quantized.clone(),
            },
            DecodeAttentionOutputWeight::Fp8Int4 { exact, quantized } => Self::Fp8Int4 {
                exact: exact.clone(),
                quantized: quantized.clone(),
            },
        }
    }
}