libmir-cuda 0.1.0

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

use super::{
    DecodeDenseSwiGlu, DenseScratch, DenseSwiGluConfig, DenseSwiGluWeights, DownProjection,
    GateUpBuffers, GateUpProjection, validate,
};
use crate::{
    CudaBackend, Error, PrefillAttentionBf16, Result, RmsNormBf16,
    kernels::{ElementwiseBf16, PackedGatedBf16},
};

pub struct PrefillDenseSwiGlu {
    attention: PrefillAttentionBf16,
    post_attention_norm: RmsNormBf16,
    gate_up: GateUpProjection,
    down: DownProjection,
    activation: PackedGatedBf16,
    hidden_ops: ElementwiseBf16,
    scratch: DenseScratch,
    stream: Stream,
    config: DenseSwiGluConfig,
    tokens: usize,
}

impl PrefillDenseSwiGlu {
    pub(in crate::backend) fn new(
        backend: &CudaBackend,
        config: DenseSwiGluConfig,
        tokens: usize,
        weights: DenseSwiGluWeights<'_>,
    ) -> Result<Self> {
        validate(config)?;
        if tokens == 0 {
            return Err(Error::InvalidDecoderKernel("dense prefill chunk is empty"));
        }
        let hidden = config.attention.hidden_size;
        let intermediate = config.intermediate_size;
        Ok(Self {
            attention: PrefillAttentionBf16::new(
                backend,
                config.attention,
                tokens,
                Some(weights.attention),
            )?,
            post_attention_norm: RmsNormBf16::new(
                backend,
                tokens,
                hidden,
                config.attention.rms_norm_epsilon,
            )?,
            gate_up: GateUpProjection::new(backend, config, tokens, Some(weights.gate_up))?,
            down: DownProjection::new(backend, config, tokens, Some(weights.down))?,
            activation: PackedGatedBf16::compile(&backend.inner.compiler, tokens, intermediate)?,
            hidden_ops: ElementwiseBf16::compile(
                &backend.inner.compiler,
                super::scratch::elements(tokens, hidden)?,
            )?,
            scratch: DenseScratch::new(backend, config, tokens)?,
            stream: backend.inner.stream.clone(),
            config,
            tokens,
        })
    }

    #[allow(clippy::too_many_arguments)]
    pub fn execute(
        &mut self,
        state: &mut DecodeDenseSwiGlu,
        input: &DeviceBuffer<bf16>,
        weights: DenseSwiGluWeights<'_>,
        write_plan: &KvWritePlan,
        table: &BlockTable,
        start_position: usize,
        output: &mut DeviceBuffer<bf16>,
    ) -> Result<()> {
        self.attention.execute(
            &mut state.attention,
            input,
            weights.attention,
            write_plan,
            table,
            start_position,
            &mut self.scratch.attention,
        )?;
        self.hidden_ops.add(
            &self.stream,
            input,
            &self.scratch.attention,
            &mut self.scratch.residual,
        )?;
        let separate = self.gate_up.execute(
            &self.scratch.residual,
            &self.post_attention_norm,
            weights.post_attention_norm,
            weights.gate_up,
            &mut GateUpBuffers {
                normalized: &mut self.scratch.normalized,
                packed: &mut self.scratch.gate_up,
                separate: &mut self.scratch.gate_up_separate,
            },
        )?;
        let fused_down = separate
            && self.down.execute_gated(
                &self.scratch.gate_up_separate[0],
                &self.scratch.gate_up_separate[1],
                self.config.activation,
                weights.down,
                &mut self.scratch.mlp,
            )?;
        if separate && !fused_down {
            self.activation.execute_separate(
                &self.stream,
                &self.scratch.gate_up_separate[0],
                &self.scratch.gate_up_separate[1],
                &mut self.scratch.activated,
                self.config.activation.into(),
            )?;
        } else if !fused_down {
            self.activation.execute(
                &self.stream,
                &self.scratch.gate_up,
                &mut self.scratch.activated,
                self.config.activation.into(),
            )?;
        }
        if !fused_down {
            self.down
                .execute(&self.scratch.activated, weights.down, &mut self.scratch.mlp)?;
        }
        self.hidden_ops
            .add(&self.stream, &self.scratch.residual, &self.scratch.mlp, output)
    }

    #[must_use]
    pub const fn tokens(&self) -> usize {
        self.tokens
    }
}