libmir-metal 0.2.0

Metal inference backend for libmir
use models::weights::RoutedDecoderLayerBindings;

use super::{config::ClampedRoutedConfig, projection::BoundLinear};
use crate::engine::{
    Array, KvCache, ModelTensors, NormWeight, PagedContextMode, Result, Stream,
    paged_attention_min_context,
};

#[derive(Debug)]
pub(super) struct ClampedRoutedAttention {
    norm: NormWeight,
    query: BoundLinear,
    key: BoundLinear,
    value: BoundLinear,
    output: BoundLinear,
    sinks: Array,
    frequencies: Array,
    config: ClampedRoutedConfig,
}

impl ClampedRoutedAttention {
    pub fn load(
        tensors: &ModelTensors,
        bindings: RoutedDecoderLayerBindings<'_>,
        config: ClampedRoutedConfig,
        stream: &Stream,
    ) -> Result<Self> {
        Ok(Self {
            norm: NormWeight::load_name(tensors, &bindings.input_norm.source)?,
            query: BoundLinear::load_binding(tensors, bindings.query, stream)?,
            key: BoundLinear::load_binding(tensors, bindings.key, stream)?,
            value: BoundLinear::load_binding(tensors, bindings.value, stream)?,
            output: BoundLinear::load_binding(tensors, bindings.attention_output, stream)?,
            sinks: tensors.get(&bindings.attention_sinks.source)?,
            frequencies: Array::yarn_rope_frequencies(
                config.head_dim,
                config.rope_base,
                config.rope_factor,
                config.beta_fast,
                config.beta_slow,
                config.original_context,
                stream,
            )?,
            config,
        })
    }

    pub fn forward(
        &self,
        input: &Array,
        cache: &mut KvCache,
        position: i32,
        causal: bool,
        stream: &Stream,
    ) -> Result<Array> {
        let sequence = *input.shape()?.get(1).ok_or_else(|| {
            crate::engine::Error::InvalidModel(
                "clamped-routed attention input has no sequence axis".into(),
            )
        })?;
        let hidden = self.norm.apply(input, self.config.epsilon, stream)?;
        let queries = self
            .query
            .forward(&hidden, stream)?
            .reshape(&[1, sequence, self.config.heads, self.config.head_dim], stream)?;
        let keys = self
            .key
            .forward(&hidden, stream)?
            .reshape(&[1, sequence, self.config.kv_heads, self.config.head_dim], stream)?;
        let values = self
            .value
            .forward(&hidden, stream)?
            .reshape(&[1, sequence, self.config.kv_heads, self.config.head_dim], stream)?;
        let queries = self.rope(&queries, position, stream)?;
        let keys = self.rope(&keys, position, stream)?;
        let values = values.transpose(&[0, 2, 1, 3], stream)?;
        let context = cache.update_for_attention_mode(
            &keys,
            &values,
            stream,
            paged_attention_min_context(stream),
            PagedContextMode::View,
        )?;
        let attended = match context.mask.as_ref() {
            Some(mask) => queries.masked_scaled_dot_product_attention_with_sinks(
                &context.keys,
                &context.values,
                self.config.scale,
                mask,
                &self.sinks,
                stream,
            )?,
            None => queries.scaled_dot_product_attention_with_sinks(
                &context.keys,
                &context.values,
                self.config.scale,
                causal,
                &self.sinks,
                stream,
            )?,
        };
        let output = attended
            .transpose(&[0, 2, 1, 3], stream)?
            .reshape(&[1, sequence, self.config.heads * self.config.head_dim], stream)?;
        self.output.forward(&output, stream)
    }

    fn rope(&self, input: &Array, position: i32, stream: &Stream) -> Result<Array> {
        input
            .transpose(&[0, 2, 1, 3], stream)?
            .rope_with_frequencies(
                self.config.head_dim,
                false,
                &self.frequencies,
                position,
                stream,
            )?
            .multiply_scalar(self.config.rope_concentration, stream)
    }
}