libmir-metal 0.3.0

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

mirtal::metal_kernel! {
    fn awq_repack {
        name: "mirmir_awq_repack",
        templates: [INPUT: int = 1024, OUTPUT: int = 2048, GROUP: int = 128],
        inputs: [qweight: u32, qzeros: u32, scales: f16],
        outputs: [weight: u32, native_scales: f16, biases: f16],
        source: file "kernels/awq_repack.metal",
        header: inline "",
        row_contiguous: true,
        atomic_outputs: false,
    }
}

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

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

    fn repack(
        &self,
        stream: &Stream,
        [qweight, qzeros, scales]: [&Array; 3],
        input: usize,
        output: usize,
        group: usize,
    ) -> Result<QuantizedArrays> {
        validate(input, output, group)?;
        let words = input / 8;
        let groups = input / group;
        let outputs = [
            output_spec([output, words], mirtal::DType::Uint32)?,
            output_spec([output, groups], mirtal::DType::Float16)?,
            output_spec([output, groups], mirtal::DType::Float16)?,
        ];
        let [weight, scales, biases] = self.kernel.dispatch(
            stream.native(),
            [qweight.native(), qzeros.native(), scales.native()],
            &outputs,
            &mirtal::Dispatch::new([words.max(groups), output, 1], [32.min(words), 1, 1])
                .templates([
                    super::template("INPUT", input)?,
                    super::template("OUTPUT", output)?,
                    super::template("GROUP", group)?,
                ]),
        )?;
        QuantizedArrays::new(
            Array::from_native(weight)?,
            Array::from_native(scales)?,
            Array::from_native(biases)?,
            i32::try_from(group)?,
            4,
        )
    }
}

impl Kernels {
    pub(crate) fn awq_repack(
        &self,
        stream: &Stream,
        inputs: [&Array; 3],
        input: usize,
        output: usize,
        group: usize,
    ) -> Result<QuantizedArrays> {
        self.awq_repack.repack(stream, inputs, input, output, group)
    }
}

fn output_spec(shape: [usize; 2], dtype: mirtal::DType) -> Result<mirtal::OutputSpec> {
    Ok(mirtal::OutputSpec::new(mirtal::Shape::new(shape)?, dtype))
}

fn validate(input: usize, output: usize, group: usize) -> Result<()> {
    if input == 0
        || output == 0
        || group == 0
        || !input.is_multiple_of(group)
        || !input.is_multiple_of(8)
        || !output.is_multiple_of(8)
    {
        Err(Error::InvalidQuantization("invalid AWQ repack geometry".into()))
    } else {
        Ok(())
    }
}