libmir-metal 0.3.0

Metal inference backend for libmir
use models::weights::RoutedExpertBindings;

use super::{
    super::{
        expert_tuning,
        fused_gate_up::{split_interleaved_last, split_last},
    },
    Array, BoundLinear, Error, FusedExpertGateUp, ModelTensors, Result, Stream,
};

#[derive(Debug)]
pub(super) enum RoutedGateUp {
    Separate {
        gate: BoundLinear,
        up: BoundLinear,
        fused: Option<Box<FusedExpertGateUp>>,
    },
    Fused {
        projection: BoundLinear,
        width: usize,
        interleaved: bool,
    },
}

impl RoutedGateUp {
    pub(super) fn load(
        tensors: &ModelTensors,
        bindings: RoutedExpertBindings<'_>,
        stream: &Stream,
    ) -> Result<(Self, BoundLinear)> {
        match bindings {
            RoutedExpertBindings::SeparateGateUp { gate, up, down } => Ok((
                Self::Separate {
                    gate: BoundLinear::load(tensors, gate, stream)?,
                    up: BoundLinear::load(tensors, up, stream)?,
                    fused: None,
                },
                BoundLinear::load(tensors, down, stream)?,
            )),
            RoutedExpertBindings::InterleavedGateUp { gate_up, down } => {
                let output = gate_up.shape.get(1).copied().ok_or(Error::ShapeOverflow)?;
                if !output.is_multiple_of(2) {
                    return Err(Error::InvalidModel(
                        "fused expert gate/up width must be even".into(),
                    ));
                }
                Ok((
                    Self::Fused {
                        projection: BoundLinear::load(tensors, gate_up, stream)?,
                        width: output / 2,
                        interleaved: gate_up.transforms.contains(
                            &models::weights::BindingTransform::FusedGateUp { interleaved: true },
                        ),
                    },
                    BoundLinear::load(tensors, down, stream)?,
                ))
            },
            RoutedExpertBindings::Individual { .. } => Err(Error::InvalidModel(
                "shared routed Metal execution requires stacked expert tensors".into(),
            )),
        }
    }

    pub(super) fn enable(&mut self, stream: &Stream) -> Result<bool> {
        match self {
            Self::Separate { gate, up, fused } => {
                if fused.is_none() {
                    *fused = gate.fuse_expert_gate_up(up, stream)?.map(Box::new);
                }
                fused.as_deref().map_or(Ok(()), FusedExpertGateUp::warm)?;
                Ok(fused.is_some())
            },
            Self::Fused { .. } => Ok(true),
        }
    }

    pub(super) fn fused_bytes(&self) -> Result<Option<usize>> {
        match self {
            Self::Separate { gate, up, .. } => gate.fused_expert_gate_up_bytes(up),
            Self::Fused { .. } => Ok(Some(0)),
        }
    }

    pub(super) const fn is_fused(&self) -> bool {
        match self {
            Self::Separate { fused, .. } => fused.is_some(),
            Self::Fused { .. } => true,
        }
    }

    pub(super) fn tuning_format(&self) -> (i32, i32) {
        match self {
            Self::Separate { gate, .. } => gate.tuning_format(),
            Self::Fused { projection, .. } => projection.tuning_format(),
        }
    }

    pub(super) fn gather(
        &self,
        input: &Array,
        indices: &Array,
        sorted: bool,
        stream: &Stream,
    ) -> Result<(Array, Array)> {
        match self {
            Self::Separate { gate, up, fused } => {
                let fused = (!sorted).then_some(fused.as_deref()).flatten();
                fused.map_or_else(
                    || {
                        Ok((
                            gate.gather(input, indices, sorted, stream)?,
                            up.gather(input, indices, sorted, stream)?,
                        ))
                    },
                    |projection| {
                        expert_tuning::forward(gate, up, projection, input, indices, stream)
                    },
                )
            },
            Self::Fused { projection, width, interleaved } => {
                let output = projection.gather(input, indices, sorted, stream)?;
                if *interleaved {
                    split_interleaved_last(&output, *width, stream)
                } else {
                    split_last(&output, *width, stream)
                }
            },
        }
    }
}