libmir-metal 0.1.0

Metal inference backend for libmir
use super::{Kernels, template};
use crate::engine::{Error, Result};

impl Kernels {
    pub(crate) fn gated_delta_gates(
        &self,
        stream: &mirtal::Stream,
        inputs: [&mirtal::Array; 4],
    ) -> Result<[mirtal::Array; 2]> {
        let [alpha, beta, a_log, dt_bias] = inputs;
        let shape = alpha.shape()?;
        let dimensions = shape.dimensions().to_vec();
        if dimensions.len() != 3
            || beta.shape()? != shape
            || a_log.shape()? != dt_bias.shape()?
            || a_log.shape()?.dimensions() != [dimensions[2]]
        {
            return Err(Error::InvalidModel("Gated Delta gate shapes are incompatible".into()));
        }
        Ok(self.gated_delta_gates.dispatch(
            stream,
            inputs,
            &[
                mirtal::OutputSpec::new(shape.clone(), mirtal::DType::Float32),
                mirtal::OutputSpec::new(shape, mirtal::DType::Float32),
            ],
            &mirtal::Dispatch::new([alpha.len(), 1, 1], [256, 1, 1])
                .templates([template("HV", dimensions[2])?]),
        )?)
    }

    pub(crate) fn gated_delta_recurrence(
        &self,
        stream: &mirtal::Stream,
        inputs: [&mirtal::Array; 6],
    ) -> Result<[mirtal::Array; 2]> {
        let [query, key, value, .., state] = inputs;
        let key_shape = key.shape()?;
        let value_shape = value.shape()?;
        let key_dimensions = key_shape.dimensions().to_vec();
        let value_dimensions = value_shape.dimensions().to_vec();
        validate_recurrence(inputs, &key_dimensions, &value_dimensions)?;
        Ok(self.gated_delta_recurrence.dispatch(
            stream,
            inputs,
            &[
                mirtal::OutputSpec::new(value_shape, query.dtype()?),
                mirtal::OutputSpec::new(state.shape()?, state.dtype()?),
            ],
            &mirtal::Dispatch::new(
                [32, value_dimensions[3], value_dimensions[0] * value_dimensions[2]],
                [32, 4, 1],
            )
            .templates([
                mirtal::TemplateArg::dtype("InT", query.dtype()?),
                mirtal::TemplateArg::dtype("StT", state.dtype()?),
                template("DK", key_dimensions[3])?,
                template("DV", value_dimensions[3])?,
                template("HK", key_dimensions[2])?,
                template("HV", value_dimensions[2])?,
                template("STEPS", value_dimensions[1])?,
            ]),
        )?)
    }

    pub(crate) fn gated_delta_decode(
        &self,
        stream: &mirtal::Stream,
        inputs: [&mirtal::Array; 8],
        normalize: bool,
    ) -> Result<[mirtal::Array; 2]> {
        let [query, _, value, .., state] = inputs;
        let query_shape = query.shape()?;
        let value_shape = value.shape()?;
        let query_dimensions = query_shape.dimensions().to_vec();
        let value_dimensions = value_shape.dimensions().to_vec();
        validate_decode(inputs, &query_dimensions, &value_dimensions)?;
        Ok(self.gated_delta_decode.dispatch(
            stream,
            inputs,
            &[
                mirtal::OutputSpec::new(value_shape, query.dtype()?),
                mirtal::OutputSpec::new(state.shape()?, mirtal::DType::Float32),
            ],
            &mirtal::Dispatch::new(
                [256, 1, value_dimensions[0] * value_dimensions[2]],
                [256, 1, 1],
            )
            .templates([
                mirtal::TemplateArg::dtype("InT", query.dtype()?),
                mirtal::TemplateArg::dtype("StT", mirtal::DType::Float32),
                template("DK", query_dimensions[3])?,
                template("DV", value_dimensions[3])?,
                template("HK", query_dimensions[2])?,
                template("HV", value_dimensions[2])?,
                mirtal::TemplateArg::bool("NORMALIZE", normalize),
            ]),
        )?)
    }
}

fn validate_recurrence(inputs: [&mirtal::Array; 6], key: &[usize], value: &[usize]) -> Result<()> {
    let [query, keys, _, decay, update, state] = inputs;
    let ranks = key.len() == 4 && value.len() == 4;
    let expected_state = ranks.then(|| [value[0], value[2], value[3], key[3]]);
    let state_shape = state.shape()?;
    if query.shape()? != keys.shape()?
        || !ranks
        || key[0..2] != value[0..2]
        || !key[3].is_multiple_of(32)
        || decay.shape()? != update.shape()?
        || decay.shape()?.dimensions() != [value[0], value[1], value[2]]
        || expected_state.as_ref().is_none_or(|shape| state_shape.dimensions() != shape)
    {
        return Err(Error::InvalidModel("Gated Delta recurrence shapes are incompatible".into()));
    }
    Ok(())
}

fn validate_decode(inputs: [&mirtal::Array; 8], query: &[usize], value: &[usize]) -> Result<()> {
    let [queries, keys, _, alpha, beta, a_log, dt_bias, state] = inputs;
    let ranks = query.len() == 4 && value.len() == 4;
    let expected_state = ranks.then(|| [value[0], value[2], value[3], query[3]]);
    let state_shape = state.shape()?;
    if queries.shape()? != keys.shape()?
        || !ranks
        || query[1] != 1
        || value[1] != 1
        || !query[3].is_multiple_of(32)
        || alpha.shape()? != beta.shape()?
        || alpha.shape()?.dimensions() != [value[0], 1, value[2]]
        || a_log.shape()? != dt_bias.shape()?
        || a_log.shape()?.dimensions() != [value[2]]
        || state.dtype()? != mirtal::DType::Float32
        || expected_state.as_ref().is_none_or(|shape| state_shape.dimensions() != shape)
    {
        return Err(Error::InvalidModel("Gated Delta decode shapes are incompatible".into()));
    }
    Ok(())
}