libmir-cuda 0.2.0

CUDA inference backend for libmir
use mircuda::{DeviceBuffer, bf16};
use runtime::kv::{BlockTable, KvWritePlan};

use super::{AffineGatedFullAttentionMoeLayerConfig, scratch::FullAttentionLayerScratch};
use crate::{
    CudaAffineGatedFullAttention, CudaAffineGatedFullAttentionExecution,
    CudaAffineGatedFullAttentionState, CudaAffineSharedExpertMoe,
    CudaAffineSharedExpertMoeExecution, CudaBackend, CudaTensor, Error, Result,
    kernels::{ElementwiseBf16, ShiftedRmsNorm},
};

#[derive(Debug)]
pub struct CudaAffineGatedFullAttentionMoeExecution {
    backend: CudaBackend,
    attention: CudaAffineGatedFullAttentionExecution,
    moe: CudaAffineSharedExpertMoeExecution,
    input_norm: ShiftedRmsNorm,
    post_attention_norm: ShiftedRmsNorm,
    residual: ElementwiseBf16,
    input_norm_weight: CudaTensor,
    post_attention_norm_weight: CudaTensor,
    scratch: FullAttentionLayerScratch,
}

impl CudaAffineGatedFullAttentionMoeExecution {
    #[allow(clippy::too_many_arguments)]
    pub(super) fn new(
        backend: &CudaBackend,
        config: AffineGatedFullAttentionMoeLayerConfig,
        attention: &CudaAffineGatedFullAttention,
        moe: &CudaAffineSharedExpertMoe,
        input_norm_weight: &CudaTensor,
        post_attention_norm_weight: &CudaTensor,
        tokens: usize,
    ) -> Result<Self> {
        let hidden = config.attention.hidden_size;
        let norm = || {
            ShiftedRmsNorm::compile(
                &backend.inner.compiler,
                tokens,
                hidden,
                config.rms_norm_epsilon,
                config.norm_weight_shift,
            )
        };
        let elements = tokens
            .checked_mul(hidden)
            .ok_or(Error::InvalidDecoderKernel("gated full-attention residual shape overflow"))?;
        Ok(Self {
            backend: backend.clone(),
            attention: attention.prepare(tokens)?,
            moe: moe.prepare(tokens)?,
            input_norm: norm()?,
            post_attention_norm: norm()?,
            residual: ElementwiseBf16::compile(&backend.inner.compiler, elements)?,
            input_norm_weight: input_norm_weight.clone(),
            post_attention_norm_weight: post_attention_norm_weight.clone(),
            scratch: FullAttentionLayerScratch::new(backend, tokens, hidden)?,
        })
    }

    #[allow(clippy::too_many_arguments)]
    pub fn execute(
        &mut self,
        input: &DeviceBuffer<bf16>,
        positions: &DeviceBuffer<u32>,
        state: &mut CudaAffineGatedFullAttentionState,
        write_plan: &KvWritePlan,
        table: &BlockTable,
        start_position: usize,
        window: Option<usize>,
        output: &mut DeviceBuffer<bf16>,
    ) -> Result<()> {
        self.execute_with_image_span(
            input, positions, state, write_plan, table, start_position, window, None, output,
        )
    }

    #[allow(clippy::too_many_arguments)]
    pub fn execute_with_image_span(
        &mut self,
        input: &DeviceBuffer<bf16>,
        positions: &DeviceBuffer<u32>,
        state: &mut CudaAffineGatedFullAttentionState,
        write_plan: &KvWritePlan,
        table: &BlockTable,
        start_position: usize,
        window: Option<usize>,
        image_span: Option<(usize, usize)>,
        output: &mut DeviceBuffer<bf16>,
    ) -> Result<()> {
        self.validate(input, output)?;
        let stream = &self.backend.inner.stream;
        self.input_norm.execute(
            stream,
            input,
            bf16_tensor(&self.input_norm_weight)?,
            &mut self.scratch.normalized,
        )?;
        self.attention.execute_with_image_span(
            &self.scratch.normalized,
            positions,
            state,
            write_plan,
            table,
            start_position,
            window,
            image_span,
            &mut self.scratch.attention,
        )?;
        self.residual
            .add(stream, input, &self.scratch.attention, &mut self.scratch.residual)?;
        self.post_attention_norm.execute(
            stream,
            &self.scratch.residual,
            bf16_tensor(&self.post_attention_norm_weight)?,
            &mut self.scratch.normalized,
        )?;
        self.moe.execute(&self.scratch.normalized, &mut self.scratch.moe)?;
        self.residual.add(stream, &self.scratch.residual, &self.scratch.moe, output)
    }

    fn validate(&self, input: &DeviceBuffer<bf16>, output: &DeviceBuffer<bf16>) -> Result<()> {
        let expected = self.scratch.residual.len();
        if input.len() != expected || output.len() != expected {
            return Err(Error::InvalidDecoderKernel(
                "gated full-attention MoE layer buffer mismatch",
            ));
        }
        Ok(())
    }
}

fn bf16_tensor(tensor: &CudaTensor) -> Result<&DeviceBuffer<bf16>> {
    tensor.as_bf16().ok_or_else(|| Error::DTypeMismatch {
        name: tensor.name().into(),
        expected: "BF16",
    })
}