libmir-metal 0.2.0

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

#[derive(Debug)]
pub(super) struct PatchEmbedding {
    projection: DenseLinear,
    position_table: Array,
}

impl PatchEmbedding {
    pub(super) fn load(tensors: &ModelTensors, stream: &Stream) -> Result<Self> {
        Ok(Self {
            projection: DenseLinear::load(
                tensors,
                "model.vision_tower.patch_embedder.input_proj",
                stream,
            )?,
            position_table: tensors
                .get("model.vision_tower.patch_embedder.position_embedding_table")?,
        })
    }

    pub(super) fn forward(
        &self,
        patches: &Array,
        positions: &Array,
        stream: &Stream,
    ) -> Result<Array> {
        validate_positions(patches, positions)?;
        let normalized = patches.multiply_scalar(2.0, stream)?.add_scalar(-1.0, stream)?;
        let projected = self.projection.forward(&normalized, stream)?;
        let x = position_axis(positions, 0, stream)?;
        let y = position_axis(positions, 1, stream)?;
        let x_table = slice_axis(&self.position_table, 0, 0, 1, stream)?.squeeze_axis(0, stream)?;
        let y_table = slice_axis(&self.position_table, 0, 1, 2, stream)?.squeeze_axis(0, stream)?;
        let position = x_table.take(&x, 0, stream)?.add(&y_table.take(&y, 0, stream)?, stream)?;
        projected.add(&position, stream)
    }
}

pub(super) fn position_axis(positions: &Array, axis: usize, stream: &Stream) -> Result<Array> {
    slice_axis(positions, 2, axis, axis + 1, stream)?.squeeze_axis(2, stream)
}

fn validate_positions(patches: &Array, positions: &Array) -> Result<()> {
    let patch_shape = patches.shape()?;
    let position_shape = positions.shape()?;
    if patch_shape.len() == 3
        && position_shape.len() == 3
        && patch_shape[..2] == position_shape[..2]
        && position_shape[2] == 2
    {
        return Ok(());
    }
    Err(Error::InvalidModel(format!(
        "pooled vision patches {patch_shape:?} require position IDs [batch, sequence, 2], got {position_shape:?}"
    )))
}