libmir-metal 0.2.0

Metal inference backend for libmir
use super::PagedAttentionScratch;
use crate::engine::{Array, Result, Stream};

#[derive(Debug, Clone, Copy)]
pub struct PagedAttention<'a> {
    pub key_pages: &'a Array,
    pub value_pages: &'a Array,
    pub key_scales: Option<&'a Array>,
    pub value_scales: Option<&'a Array>,
    pub page_table: &'a Array,
    pub page_dependency: &'a Array,
    pub page_size: usize,
    pub context_tokens: usize,
}

impl Array {
    pub fn scaled_dot_product_attention_with_sinks(
        &self,
        keys: &Self,
        values: &Self,
        scale: f32,
        causal: bool,
        sinks: &Self,
        stream: &Stream,
    ) -> Result<Self> {
        let mask = if causal {
            mirtal::AttentionMask::Causal
        } else {
            mirtal::AttentionMask::None
        };
        Self::from_native(stream.native().graph().scaled_dot_product_attention(
            self.native(),
            keys.native(),
            values.native(),
            mirtal::ScaledDotProductAttention { scale, mask, sinks: Some(sinks.native()) },
        )?)?
        .astype_like(self, stream)
    }

    pub fn masked_scaled_dot_product_attention_with_sinks(
        &self,
        keys: &Self,
        values: &Self,
        scale: f32,
        mask: &Self,
        sinks: &Self,
        stream: &Stream,
    ) -> Result<Self> {
        Self::from_native(stream.native().graph().scaled_dot_product_attention(
            self.native(),
            keys.native(),
            values.native(),
            mirtal::ScaledDotProductAttention {
                scale,
                mask: mirtal::AttentionMask::Array(mask.native()),
                sinks: Some(sinks.native()),
            },
        )?)?
        .astype_like(self, stream)
    }

    pub fn scaled_dot_product_attention(
        &self,
        keys: &Self,
        values: &Self,
        scale: f32,
        causal: bool,
        stream: &Stream,
    ) -> Result<Self> {
        let mask = if causal {
            mirtal::AttentionMask::Causal
        } else {
            mirtal::AttentionMask::None
        };
        Self::from_native(stream.native().graph().scaled_dot_product_attention(
            self.native(),
            keys.native(),
            values.native(),
            mirtal::ScaledDotProductAttention { scale, mask, sinks: None },
        )?)?
        .astype_like(self, stream)
    }

    pub fn masked_scaled_dot_product_attention(
        &self,
        keys: &Self,
        values: &Self,
        scale: f32,
        mask: &Self,
        stream: &Stream,
    ) -> Result<Self> {
        Self::from_native(stream.native().graph().scaled_dot_product_attention(
            self.native(),
            keys.native(),
            values.native(),
            mirtal::ScaledDotProductAttention {
                scale,
                mask: mirtal::AttentionMask::Array(mask.native()),
                sinks: None,
            },
        )?)?
        .astype_like(self, stream)
    }

    pub fn paged_scaled_dot_product_attention(
        &self,
        paged: PagedAttention<'_>,
        scale: f32,
        stream: &Stream,
    ) -> Result<Self> {
        self.paged_scaled_dot_product_attention_with_scratch(
            paged,
            &PagedAttentionScratch::default(),
            scale,
            stream,
        )
    }

    pub(crate) fn paged_scaled_dot_product_attention_with_scratch(
        &self,
        paged: PagedAttention<'_>,
        scratch: &PagedAttentionScratch,
        scale: f32,
        stream: &Stream,
    ) -> Result<Self> {
        let output = match (paged.key_scales, paged.value_scales) {
            (Some(key_scales), Some(value_scales)) => stream.quantized_paged_attention(
                [
                    self.native(),
                    paged.key_pages.native(),
                    paged.value_pages.native(),
                    key_scales.native(),
                    value_scales.native(),
                    paged.page_table.native(),
                    paged.page_dependency.native(),
                ],
                paged.page_size,
                paged.context_tokens,
                scale,
            )?,
            (None, None) => stream.paged_attention(
                [
                    self.native(),
                    paged.key_pages.native(),
                    paged.value_pages.native(),
                    paged.page_table.native(),
                    paged.page_dependency.native(),
                ],
                scratch,
                paged.page_size,
                paged.context_tokens,
                scale,
            )?,
            _ => {
                return Err(crate::engine::Error::InvalidModel(
                    "paged K/V scale arrays are incomplete".into(),
                ));
            },
        };
        Self::from_native(output)?.astype_like(self, stream)
    }
}