libmir-metal 0.1.0

Metal inference backend for libmir
use mirtal::{DType, Graph, Shape};

use super::super::{Array, Error, Result, Stream};

#[derive(Debug, Clone, Copy)]
pub struct RopeOptions {
    pub dimensions: i32,
    pub traditional: bool,
    pub base: Option<f32>,
    pub scale: f32,
    pub offset: i32,
}

impl Array {
    pub(crate) fn proportional_rope_frequencies(
        head_dim: i32,
        rope_dimensions: i32,
        base: f32,
        stream: &Stream,
    ) -> Result<Self> {
        dimensions(head_dim)?;
        dimensions(rope_dimensions)?;
        if rope_dimensions > head_dim {
            return Err(invalid("RoPE dimensions exceed head dimensions"));
        }
        positive(base, "base")?;
        let graph = stream.native().graph();
        let exponents = graph.arange(0.0, as_f32(rope_dimensions)?, 2.0, DType::Float32)?;
        let exponents = graph.divide(&exponents, &scalar(graph, as_f32(head_dim)?)?)?;
        let frequencies = graph.power(&scalar(graph, base)?, &exponents)?;
        let remaining = usize::try_from(head_dim / 2 - rope_dimensions / 2)?;
        if remaining == 0 {
            return Self::from_native(frequencies);
        }
        let tail = graph.full(&Shape::new([remaining])?, f32::INFINITY, DType::Float32)?;
        Self::from_native(graph.concatenate(&[&frequencies, &tail], 0)?)
    }

    pub(crate) fn piecewise_rope_frequencies(
        rotary_dimensions: i32,
        base: f32,
        factor: f32,
        low_frequency_factor: f32,
        high_frequency_factor: f32,
        original_context_len: i32,
        stream: &Stream,
    ) -> Result<Self> {
        dimensions(rotary_dimensions)?;
        for (value, name) in [
            (base, "base"),
            (factor, "factor"),
            (low_frequency_factor, "low frequency factor"),
            (high_frequency_factor, "high frequency factor"),
        ] {
            positive(value, name)?;
        }
        if high_frequency_factor <= low_frequency_factor || original_context_len <= 0 {
            return Err(invalid("invalid piecewise RoPE interpolation range"));
        }
        let graph = stream.native().graph();
        let exponents = graph.arange(0.0, as_f32(rotary_dimensions)?, 2.0, DType::Float32)?;
        let exponents = graph.divide(&exponents, &scalar(graph, as_f32(rotary_dimensions)?)?)?;
        let frequencies = graph.power(&scalar(graph, base)?, &exponents)?;
        let inverse = graph.reciprocal(&frequencies)?;
        let wavelength = graph.multiply(&scalar(graph, std::f32::consts::TAU)?, &frequencies)?;
        let ratio = graph.divide(&scalar(graph, as_f32(original_context_len)?)?, &wavelength)?;
        let smooth = graph.subtract(&ratio, &scalar(graph, low_frequency_factor)?)?;
        let smooth =
            graph.divide(&smooth, &scalar(graph, high_frequency_factor - low_frequency_factor)?)?;
        let smooth = graph.maximum(&smooth, &scalar(graph, 0.0)?)?;
        let smooth = graph.minimum(&smooth, &scalar(graph, 1.0)?)?;
        let slowed = graph.divide(&inverse, &scalar(graph, factor)?)?;
        let inverse_smooth = graph.subtract(&scalar(graph, 1.0)?, &smooth)?;
        let adjusted = graph
            .add(&graph.multiply(&slowed, &inverse_smooth)?, &graph.multiply(&inverse, &smooth)?)?;
        Self::from_native(graph.reciprocal(&adjusted)?)
    }

    pub fn rope(&self, options: RopeOptions, stream: &Stream) -> Result<Self> {
        Self::from_native(stream.native().graph().rope(
            self.native(),
            mirtal::RopeOptions {
                dimensions: usize::try_from(options.dimensions)?,
                traditional: options.traditional,
                base: options.base,
                scale: options.scale,
                offset: usize::try_from(options.offset)?,
            },
        )?)
    }

    pub fn rope_with_frequencies(
        &self,
        dimensions: i32,
        traditional: bool,
        frequencies: &Self,
        offset: i32,
        stream: &Stream,
    ) -> Result<Self> {
        Self::from_native(stream.native().graph().rope_with_frequencies(
            self.native(),
            frequencies.native(),
            mirtal::FrequencyRopeOptions {
                dimensions: usize::try_from(dimensions)?,
                traditional,
                offset: usize::try_from(offset)?,
            },
        )?)?
        .astype_like(self, stream)
    }
}

fn scalar(graph: Graph<'_>, value: f32) -> Result<mirtal::Array> {
    Ok(graph.full(&Shape::new([])?, value, DType::Float32)?)
}

fn dimensions(value: i32) -> Result<()> {
    if value <= 0 || value % 2 != 0 {
        return Err(invalid("RoPE dimensions must be positive and even"));
    }
    Ok(())
}

fn positive(value: f32, name: &str) -> Result<()> {
    if !value.is_finite() || value <= 0.0 {
        return Err(invalid(format!("{name} must be finite and positive")));
    }
    Ok(())
}

fn as_f32(value: i32) -> Result<f32> {
    Ok(f32::from(u16::try_from(value)?))
}

fn invalid(message: impl Into<String>) -> Error {
    Error::InvalidModel(message.into())
}