libmir-metal 0.3.0

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

mirtal::metal_kernel! {
    fn nvfp4_convert {
        name: "mirmir_nvfp4_convert",
        templates: [T: dtype = bf16],
        inputs: [weight: u8, scales: u8, global_scale: f32],
        outputs: [output: T],
        source: file "kernels/nvfp4_convert.metal",
        header: inline r"
            inline float mirmir_e4m3_to_float(uchar encoded) {
              uint magnitude = uint(encoded & 0x7fu);
              uint exponent = magnitude >> 3;
              uint mantissa = magnitude & 7u;
              float value = exponent == 0u
                  ? ldexp(float(mantissa), -9)
                  : ldexp(float(8u + mantissa), int(exponent) - 10);
              return (encoded & 0x80u) == 0u ? value : -value;
            }
        ",
        row_contiguous: true,
        atomic_outputs: false,
    }
}

#[derive(Debug)]
pub(super) struct NvFp4ConvertKernel {
    kernel: mirtal::MetalKernel<3, 1>,
}

impl NvFp4ConvertKernel {
    pub(super) fn new() -> Result<Self> {
        Ok(Self { kernel: nvfp4_convert()? })
    }

    fn execute(
        &self,
        weight: &Array,
        scales: &Array,
        global_scale: &Array,
        shape: [usize; 2],
        stream: &Stream,
    ) -> Result<Array> {
        let [output, input] = shape;
        let elements = output.checked_mul(input).ok_or(crate::engine::Error::ShapeOverflow)?;
        let output = mirtal::OutputSpec::new(mirtal::Shape::new(shape)?, mirtal::DType::Bfloat16);
        let [output] = self.kernel.dispatch(
            stream.native(),
            [weight.native(), scales.native(), global_scale.native()],
            &[output],
            &mirtal::Dispatch::new([elements, 1, 1], [256, 1, 1])
                .templates([mirtal::TemplateArg::dtype("T", mirtal::DType::Bfloat16)]),
        )?;
        Array::from_native(output)
    }
}

impl Kernels {
    pub(crate) fn nvfp4_convert(
        &self,
        weight: &Array,
        scales: &Array,
        global_scale: &Array,
        shape: [usize; 2],
        stream: &Stream,
    ) -> Result<Array> {
        self.nvfp4_convert.execute(weight, scales, global_scale, shape, stream)
    }
}