libmir-cuda 0.2.0

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

use super::{Bf16Projection, CudaBackend, DecodeAttentionConfig, ProjectionFormat};
use crate::{
    BlockFp8LinearWeight, CudaTensor, DenseExecution, DensePlanRequest, DenseRole, Error,
    ExecutionPhase, Fp8ResidualLinearWeight, NvFp4Bf16Linear, NvFp4LinearWeight, Result,
};

#[derive(Clone, Copy)]
pub enum DecodeAttentionOutputWeight<'a> {
    Bf16(&'a CudaTensor),
    NvFp4(&'a NvFp4LinearWeight),
    BlockFp8 {
        exact: &'a CudaTensor,
        quantized: &'a BlockFp8LinearWeight,
    },
    Fp8Int4 {
        exact: &'a CudaTensor,
        quantized: &'a Fp8ResidualLinearWeight,
    },
}

impl<'a> DecodeAttentionOutputWeight<'a> {
    #[must_use]
    pub const fn bf16(self) -> Option<&'a CudaTensor> {
        match self {
            Self::Bf16(weight)
            | Self::BlockFp8 { exact: weight, .. }
            | Self::Fp8Int4 { exact: weight, .. } => Some(weight),
            Self::NvFp4(_) => None,
        }
    }
}

#[derive(Debug)]
pub(super) enum AttentionOutputProjection {
    Bf16(Bf16Projection),
    NvFp4(NvFp4Bf16Linear),
    BlockFp8,
    Fp8Int4,
}

impl AttentionOutputProjection {
    pub(super) fn new(
        backend: &CudaBackend,
        config: DecodeAttentionConfig,
        tokens: usize,
        weight: Option<DecodeAttentionOutputWeight<'_>>,
    ) -> Result<Self> {
        let input_features = config.query_heads * config.cache.value_head_dim;
        let request = DensePlanRequest {
            phase: if tokens == 1 {
                ExecutionPhase::Decode
            } else {
                ExecutionPhase::Prefill
            },
            role: DenseRole::AttentionOutput,
            tokens,
            input_features,
            output_features: config.hidden_size,
        };
        match config.projection_format {
            ProjectionFormat::NvFp4 => {
                let Some(DecodeAttentionOutputWeight::NvFp4(weight)) = weight else {
                    return Err(Error::InvalidExecutionPlan(
                        "NVFP4 attention requires prepared output weight",
                    ));
                };
                Ok(Self::NvFp4(NvFp4Bf16Linear::from_weight(backend, tokens, weight.clone())?))
            },
            ProjectionFormat::Bf16 => {
                Ok(match backend.execution_planner().plan_dense(request)?.execution() {
                    DenseExecution::BlockFp8Vector => Self::BlockFp8,
                    DenseExecution::Fp8Int4Vector => Self::Fp8Int4,
                    DenseExecution::Matrix | DenseExecution::Vector => {
                        Self::Bf16(backend.prepare_bf16_projection(request)?)
                    },
                })
            },
        }
    }

    pub(super) fn execute(
        &mut self,
        stream: &Stream,
        input: &DeviceBuffer<bf16>,
        weight: DecodeAttentionOutputWeight<'_>,
        output: &mut DeviceBuffer<bf16>,
    ) -> Result<()> {
        match (self, weight) {
            (Self::NvFp4(operation), DecodeAttentionOutputWeight::NvFp4(_)) => {
                operation.execute(input, output)
            },
            (Self::NvFp4(_), _) => {
                Err(Error::InvalidExecutionPlan("NVFP4 attention output received non-NVFP4 weight"))
            },
            (Self::Bf16(operation), weight) => operation.execute(
                input,
                weight
                    .bf16()
                    .ok_or(Error::InvalidExecutionPlan("BF16 attention lacks BF16 weight"))?,
                output,
            ),
            (Self::BlockFp8, DecodeAttentionOutputWeight::BlockFp8 { quantized, .. }) => {
                quantized.execute(stream, input, output)
            },
            (Self::BlockFp8, DecodeAttentionOutputWeight::Bf16(_)) => {
                Err(Error::InvalidExecutionPlan("block FP8 attention plan lacks quantized weight"))
            },
            (Self::Fp8Int4, DecodeAttentionOutputWeight::Fp8Int4 { quantized, .. }) => {
                quantized.execute(stream, input, output)
            },
            (Self::Fp8Int4, _) => Err(Error::InvalidExecutionPlan(
                "FP8 plus INT4 attention plan lacks quantized weight",
            )),
            (Self::BlockFp8, DecodeAttentionOutputWeight::Fp8Int4 { .. }) => Err(
                Error::InvalidExecutionPlan("block FP8 attention plan received residual weight"),
            ),
            (_, DecodeAttentionOutputWeight::NvFp4(_)) => {
                Err(Error::InvalidExecutionPlan("NVFP4 attention output execution is not prepared"))
            },
        }
    }
}