libmir-metal 0.2.0

Metal inference backend for libmir
use super::{dimension, rope::VisionRope, slice_axis};
use crate::engine::{Array, DenseLinear, ModelTensors, Result, Stream};

#[derive(Debug)]
pub(super) struct VisionAttention {
    qkv: DenseLinear,
    output: DenseLinear,
    rope: VisionRope,
    heads: usize,
    head_dim: usize,
    scale: f32,
}

impl VisionAttention {
    pub(super) fn load(
        tensors: &ModelTensors,
        prefix: &str,
        heads: usize,
        head_dim: usize,
        stream: &Stream,
    ) -> Result<Self> {
        Ok(Self {
            qkv: DenseLinear::load(tensors, &format!("{prefix}.qkv"), stream)?,
            output: DenseLinear::load(tensors, &format!("{prefix}.proj"), stream)?,
            rope: VisionRope::new(head_dim)?,
            heads,
            head_dim,
            scale: 1.0 / head_dim.to_string().parse::<f32>()?.sqrt(),
        })
    }

    pub(super) fn forward(
        &self,
        input: &Array,
        positions: &Array,
        stream: &Stream,
    ) -> Result<Array> {
        let shape = input.shape()?;
        let sequence = shape[1];
        let heads = dimension(self.heads, "attention heads")?;
        let head_dim = dimension(self.head_dim, "attention head width")?;
        let qkv = self
            .qkv
            .forward(input, stream)?
            .reshape(&[1, sequence, 3, heads, head_dim], stream)?;
        let query =
            slice_axis(&qkv, 2, 0, 1, stream)?.reshape(&[1, sequence, heads, head_dim], stream)?;
        let key =
            slice_axis(&qkv, 2, 1, 2, stream)?.reshape(&[1, sequence, heads, head_dim], stream)?;
        let value = slice_axis(&qkv, 2, 2, 3, stream)?
            .reshape(&[1, sequence, heads, head_dim], stream)?
            .transpose(&[0, 2, 1, 3], stream)?;
        let (query, key) = self.rope.apply(&query, &key, positions, stream)?;
        let query = query.transpose(&[0, 2, 1, 3], stream)?;
        let key = key.transpose(&[0, 2, 1, 3], stream)?;
        let attention =
            query.scaled_dot_product_attention(&key, &value, self.scale, false, stream)?;
        self.output.forward(
            &attention.transpose(&[0, 2, 1, 3], stream)?.reshape(
                &[1, sequence, dimension(self.heads * self.head_dim, "attention width")?],
                stream,
            )?,
            stream,
        )
    }
}