libmir-metal 0.1.0

Metal inference backend for libmir
use mirtal::{Array as NativeArray, DType, Error as NativeError, Graph};

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

#[derive(Debug)]
pub struct RouterOutput {
    pub indices: Array,
    pub weights: Array,
}

impl Array {
    pub fn router_top_k(
        &self,
        expert_scale: &Self,
        top_k: i32,
        stream: &Stream,
    ) -> Result<RouterOutput> {
        let [indices, weights] = route_top_k(
            stream.native().graph(),
            self.native(),
            Some(expert_scale.native()),
            usize::try_from(top_k)?,
        )?;
        Ok(RouterOutput {
            indices: Self::from_native(indices)?,
            weights: Self::from_native(weights)?,
        })
    }

    pub fn router_top_k_unit(&self, top_k: i32, stream: &Stream) -> Result<RouterOutput> {
        let [indices, weights] =
            route_top_k(stream.native().graph(), self.native(), None, usize::try_from(top_k)?)?;
        Ok(RouterOutput {
            indices: Self::from_native(indices)?,
            weights: Self::from_native(weights)?,
        })
    }
}

pub(in crate::engine) fn route_top_k(
    graph: Graph<'_>,
    scores: &NativeArray,
    scale: Option<&NativeArray>,
    top_k: usize,
) -> mirtal::Result<[NativeArray; 2]> {
    let shape = scores.shape()?;
    let experts = shape
        .dimensions()
        .last()
        .copied()
        .ok_or_else(|| invalid("router scores must have rank"))?;
    if top_k == 0 || top_k > experts {
        return Err(invalid("router top-k is outside score dimensions"));
    }
    if let Some(scale) = scale
        && scale.shape()?.dimensions() != [experts]
    {
        return Err(invalid("router scale is incompatible with score dimensions"));
    }
    let kth = i32::try_from(experts - top_k)?;
    let indices = graph.argpartition(scores, kth, -1)?;
    let mut start = vec![0; shape.dimensions().len()];
    let mut stop = shape.dimensions().to_vec();
    let last = start.len() - 1;
    start[last] = experts - top_k;
    stop[last] = experts;
    let indices = graph.slice(&indices, &start, &stop)?;
    let selected = graph.take_along_axis(scores, &indices, -1)?;
    let mut weights = graph.softmax(&selected, -1, false)?;
    let indices = graph.astype(&indices, DType::Uint32)?;
    if let Some(scale) = scale {
        let scale = graph.astype(scale, scores.dtype()?)?;
        weights = graph.multiply(&graph.take(&scale, &indices, 0)?, &weights)?;
    }
    Ok([indices, weights])
}

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