libmir-metal 0.3.0

Metal inference backend for libmir
mod attention;

use self::attention::packed_attention;
use super::{
    layer::DenseSwiGluLayer,
    model::{DenseSwiGluModel, OutputProjection},
};
use crate::engine::{
    Array, DecoderCache, KvCache, Result, Stream, decode_graph, decoder::forward_packed_layers,
};

impl DenseSwiGluModel {
    pub(crate) fn forward_packed_decode(
        &self,
        token_ids: &Array,
        caches: &mut [&mut DecoderCache],
        positions: &[i32],
        stream: &Stream,
    ) -> Result<Array> {
        let hidden = self.embedding.lookup(token_ids, stream)?;
        let hidden = forward_packed_layers(&self.layers, hidden, caches, positions, stream)?;
        let hidden = self.final_norm.apply(&hidden, 1.0e-6, stream)?;
        let logits = match &self.output_projection {
            OutputProjection::TiedEmbedding => self.embedding.project(&hidden, stream)?,
            OutputProjection::Linear(output) => output.forward(&hidden, stream)?,
        };
        decode_graph::export_once(&logits, stream)?;
        Ok(logits)
    }

    pub(crate) fn forward_packed_prefill_state(
        &self,
        token_ids: &Array,
        caches: &mut [&mut DecoderCache],
        positions: &[i32],
        stream: &Stream,
    ) -> Result<Array> {
        let mut hidden = self.embedding.lookup(token_ids, stream)?;
        let shape = token_ids.shape()?;
        let position = positions.iter().copied().min().map_or(Ok(0), usize::try_from)?;
        let evaluation_step = crate::engine::decoder::prefill_evaluation_step(
            caches.len(),
            usize::try_from(shape[1])?,
            position,
            self.layers.len(),
        );
        for (index, layer) in self.layers.iter().enumerate() {
            let mut layer_caches = caches
                .iter_mut()
                .map(|cache| {
                    cache.attention_caches_mut()?.get_mut(index).ok_or_else(|| {
                        crate::engine::Error::InvalidModel(format!(
                            "missing cache for layer {index}"
                        ))
                    })
                })
                .collect::<Result<Vec<_>>>()?;
            hidden = layer.mix_packed_prefill(&hidden, &mut layer_caches, positions, stream)?;
            hidden = layer.feed_forward_packed(&hidden, caches.len(), stream)?;
            if evaluation_step
                .is_some_and(|step| (index + 1) % step == 0 || index + 1 == self.layers.len())
            {
                hidden.async_eval()?;
                stream.synchronize()?;
            }
        }
        Ok(hidden)
    }
}

impl DenseSwiGluLayer {
    pub(super) fn mix_packed(
        &self,
        input: &Array,
        caches: &mut [&mut KvCache],
        positions: &[i32],
        stream: &Stream,
    ) -> Result<Array> {
        let normalized = self.weights.input_norm.apply(input, self.config.rms_norm_eps, stream)?;
        let attention = packed_attention(
            &normalized,
            &self.weights.attention,
            self.fused_attention.as_ref(),
            self.config,
            caches,
            positions,
            false,
            stream,
        )?;
        input.add(&attention, stream)
    }

    pub(super) fn mix_packed_prefill(
        &self,
        input: &Array,
        caches: &mut [&mut KvCache],
        positions: &[i32],
        stream: &Stream,
    ) -> Result<Array> {
        let normalized = self.weights.input_norm.apply(input, self.config.rms_norm_eps, stream)?;
        let attention = packed_attention(
            &normalized,
            &self.weights.attention,
            self.fused_attention.as_ref(),
            self.config,
            caches,
            positions,
            true,
            stream,
        )?;
        input.add(&attention, stream)
    }

    pub(super) fn feed_forward_packed(
        &self,
        input: &Array,
        batch_size: usize,
        stream: &Stream,
    ) -> Result<Array> {
        let normalized =
            self.weights
                .post_attention_norm
                .apply(input, self.config.rms_norm_eps, stream)?;
        let fused = (batch_size <= 2).then_some(self.fused_gate_up.as_ref()).flatten();
        let (gate, up) = fused.map_or_else(
            || {
                Ok((
                    self.weights.mlp.gate.forward(&normalized, stream)?,
                    self.weights.mlp.up.forward(&normalized, stream)?,
                ))
            },
            |fused| fused.forward_pair(&normalized, stream),
        )?;
        let activated = gate.silu_mul(&up, stream)?;
        input.add(&self.weights.mlp.down.forward(&activated, stream)?, stream)
    }
}