libmir-metal 0.1.0

Metal inference backend for libmir
use super::{Error, Result, Stream};

#[derive(Debug)]
pub struct Array {
    native: mirtal::Array,
}

impl Array {
    pub fn from_f32(data: &[f32], shape: &[i32]) -> Result<Self> {
        validate_shape(data.len(), shape)?;
        let shape = native_shape(shape)?;
        Self::from_native(mirtal::Array::from_shape(data, &shape)?)
    }

    pub fn from_u32(data: &[u32], shape: &[i32]) -> Result<Self> {
        validate_shape(data.len(), shape)?;
        let shape = native_shape(shape)?;
        Self::from_native(mirtal::Array::from_shape(data, &shape)?)
    }

    pub fn add(&self, right: &Self, stream: &Stream) -> Result<Self> {
        Self::from_native(stream.native().graph().add(&self.native, &right.native)?)
    }

    pub fn rms_norm_unit(&self, eps: f32, stream: &Stream) -> Result<Self> {
        let graph = stream.native().graph();
        let output = graph.rms_norm_unit(&self.native, eps)?;
        Self::from_native(graph.astype(&output, self.native.dtype()?)?)
    }

    pub fn async_eval(&self) -> Result<()> {
        Ok(self.native.async_eval()?)
    }

    pub fn to_vec_f32(&self) -> Result<Vec<f32>> {
        Ok(self.native.to_vec_f32()?)
    }

    pub fn to_vec_f32_on_stream(&self, stream: &Stream) -> Result<Vec<f32>> {
        Ok(stream.native().read::<f32>(&self.native)?)
    }

    pub fn to_vec_u32_on_stream(&self, stream: &Stream) -> Result<Vec<u32>> {
        Ok(stream.native().read::<u32>(&self.native)?)
    }

    pub fn item_u32(&self) -> Result<u32> {
        Ok(self.native.item_u32()?)
    }

    pub(crate) fn concatenate(inputs: &[&Self], axis: i32, stream: &Stream) -> Result<Self> {
        let inputs = inputs.iter().map(|input| input.native()).collect::<Vec<_>>();
        Self::from_native(stream.native().graph().concatenate(&inputs, axis)?)
    }

    pub(crate) fn slice(&self, start: &[usize], stop: &[usize], stream: &Stream) -> Result<Self> {
        Self::from_native(stream.native().graph().slice(self.native(), start, stop)?)
    }

    pub(super) const fn native(&self) -> &mirtal::Array {
        &self.native
    }

    #[allow(clippy::unnecessary_wraps)]
    pub(super) fn from_native(native: mirtal::Array) -> Result<Self> {
        Ok(Self { native })
    }
}

fn validate_shape(data: usize, shape: &[i32]) -> Result<()> {
    let elements = shape.iter().try_fold(1_usize, |total, dimension| {
        let dimension = usize::try_from(*dimension)?;
        total.checked_mul(dimension).ok_or(Error::ShapeOverflow)
    })?;
    if elements == data {
        return Ok(());
    }
    Err(Error::Shape { shape: shape.to_vec(), elements, data })
}

pub(super) fn native_shape(shape: &[i32]) -> Result<mirtal::Shape> {
    let dimensions = shape
        .iter()
        .copied()
        .map(usize::try_from)
        .collect::<std::result::Result<Vec<_>, _>>()?;
    Ok(mirtal::Shape::new(dimensions)?)
}