libmir-metal 0.1.0

Metal inference backend for libmir
use super::Kernels;
use crate::engine::{Error, Result};

#[derive(Debug, Clone, Copy)]
pub struct PageWriteOptions {
    pub(crate) sequence: usize,
    pub(crate) offset: usize,
    pub(crate) kv_heads: usize,
    pub(crate) page_capacity: usize,
    pub(crate) page_size: usize,
    pub(crate) head_dim: usize,
}

#[derive(Debug, Default)]
pub struct PreparedPageWrite {
    kernel: Option<(mirtal::DType, mirtal::PreparedAliasing<5, 2>)>,
}

impl PreparedPageWrite {
    fn bind(
        &mut self,
        library: &mirtal::MetalLibrary,
        dtype: mirtal::DType,
        function: &'static str,
        options: PageWriteOptions,
    ) -> Result<&mut mirtal::PreparedAliasing<5, 2>> {
        let elements = options
            .sequence
            .checked_mul(options.kv_heads)
            .and_then(|value| value.checked_mul(options.head_dim))
            .ok_or(Error::ShapeOverflow)?;
        let constants = [
            u32::try_from(options.sequence)?,
            u32::try_from(options.offset)?,
            u32::try_from(options.kv_heads)?,
            u32::try_from(options.page_capacity)?,
            u32::try_from(options.page_size)?,
            u32::try_from(options.head_dim)?,
        ];
        let grid = [elements, 1, 1];
        let threadgroup = [elements.min(256), 1, 1];
        match self.kernel.as_mut() {
            Some((prepared_dtype, kernel)) if *prepared_dtype == dtype => {
                kernel.rebind(&constants, grid, threadgroup)?;
            },
            Some(_) => {
                return Err(Error::InvalidModel(
                    "paged K/V dtype changed after preparing page-write".into(),
                ));
            },
            None => {
                let dispatch = mirtal::AliasingDispatch::new([2, 3])
                    .constants(constants)
                    .strides([
                        mirtal::StrideBinding { input: 0, axis: 1 },
                        mirtal::StrideBinding { input: 0, axis: 2 },
                        mirtal::StrideBinding { input: 0, axis: 3 },
                        mirtal::StrideBinding { input: 1, axis: 1 },
                        mirtal::StrideBinding { input: 1, axis: 2 },
                        mirtal::StrideBinding { input: 1, axis: 3 },
                    ])
                    .grid(grid)
                    .threadgroup(threadgroup);
                let kernel = library.export(function)?.prepare_aliasing(dispatch)?;
                self.kernel = Some((dtype, kernel));
            },
        }
        self.kernel
            .as_mut()
            .map(|(_, kernel)| kernel)
            .ok_or(Error::NullHandle("prepared page-write kernel"))
    }
}

impl Kernels {
    pub(crate) fn page_write(
        &self,
        stream: &mirtal::Stream,
        inputs: [&mirtal::Array; 5],
        options: PageWriteOptions,
        prepared: &mut PreparedPageWrite,
    ) -> Result<[mirtal::Array; 2]> {
        let dtype = inputs[0].dtype()?;
        let function = match dtype {
            mirtal::DType::Float32 => "mirmir_page_write_f32",
            mirtal::DType::Float16 => "mirmir_page_write_f16",
            mirtal::DType::Bfloat16 => "mirmir_page_write_bf16",
            _ => return Err(Error::InvalidModel("paged K/V dtype is unsupported".into())),
        };
        Ok(prepared
            .bind(&self.paged_kv, dtype, function, options)?
            .dispatch(stream, inputs)?)
    }
}