libmir-metal 0.1.0

Metal inference backend for libmir
mod attention;
mod batch;
mod feed_forward;
mod layer;
mod model;
mod weights;

pub use layer::HybridMoeLayer;
pub use model::HybridMoeModel;
use models::layout::{AttentionLayerType, DecoderConfig};

use super::{Error, Result};

#[derive(Debug, Clone, Copy)]
pub struct HybridMoeLayerConfig {
    pub layer_index: usize,
    pub hidden_size: i32,
    pub attention_heads: i32,
    pub kv_heads: i32,
    pub head_dim: i32,
    pub rope_dimensions: i32,
    pub rope_base: f32,
    pub proportional_rope: bool,
    pub use_k_eq_v: bool,
    pub rms_norm_eps: f32,
    pub top_k: i32,
    pub group_size: i32,
    pub router_norm_scale: f32,
    pub max_context: Option<usize>,
}

impl HybridMoeLayerConfig {
    pub fn from_decoder(
        layer_index: usize,
        decoder: &DecoderConfig,
        group_size: usize,
    ) -> Result<Self> {
        let layer_type = decoder.layer_type(layer_index);
        let head_dim = decoder.layer_head_dim(layer_index);
        let partial = decoder.partial_rotary_factor_for_layer(layer_index).unwrap_or(1.0);
        let head_dim_f64 = head_dim.to_string().parse::<f64>()?;
        let rope_dimensions = float_to_i32((head_dim_f64 * partial).round())?;
        let hidden_size = i32::try_from(decoder.hidden_size)?;
        let router_norm_scale = 1.0 / hidden_size.to_string().parse::<f32>()?.sqrt();
        Ok(Self {
            layer_index,
            hidden_size,
            attention_heads: i32::try_from(decoder.num_attention_heads)?,
            kv_heads: i32::try_from(decoder.layer_key_value_heads(layer_index))?,
            head_dim: i32::try_from(head_dim)?,
            rope_dimensions,
            rope_base: decoder
                .rope_theta_for_layer(layer_index)
                .unwrap_or(10_000.0)
                .to_string()
                .parse()?,
            proportional_rope: decoder.rope_type_for_layer(layer_index) == Some("proportional"),
            use_k_eq_v: layer_type == AttentionLayerType::Full && decoder.attention_k_eq_v,
            rms_norm_eps: decoder.rms_norm_eps.to_string().parse()?,
            top_k: i32::try_from(decoder.top_k_experts.unwrap_or(1))?,
            group_size: i32::try_from(group_size)?,
            router_norm_scale,
            max_context: decoder.layer_sliding_window(layer_index),
        })
    }

    pub(super) fn validate(self) -> Result<Self> {
        let dimensions = [
            self.hidden_size,
            self.attention_heads,
            self.kv_heads,
            self.head_dim,
            self.rope_dimensions,
            self.top_k,
            self.group_size,
        ];
        if dimensions.into_iter().any(|dimension| dimension <= 0) {
            return Err(Error::InvalidModel(format!("non-positive dimensions: {self:?}")));
        }
        if !self.rope_base.is_finite()
            || !self.rms_norm_eps.is_finite()
            || !self.router_norm_scale.is_finite()
        {
            return Err(Error::InvalidModel(format!("non-finite parameters: {self:?}")));
        }
        Ok(self)
    }
}

fn float_to_i32(value: f64) -> Result<i32> {
    Ok(value.to_string().parse::<i32>()?)
}