use mirtal_sys::ffi;
use crate::{Array, Error, Graph, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Quantization {
group_size: i32,
bits: i32,
}
impl Quantization {
pub fn new(group_size: i32, bits: i32) -> Result<Self> {
if group_size <= 0 {
return Err(Error::InvalidQuantization("group size must be positive".into()));
}
if !matches!(bits, 2 | 3 | 4 | 5 | 6 | 8) {
return Err(Error::InvalidQuantization("unsupported affine bit width".into()));
}
Ok(Self { group_size, bits })
}
}
#[derive(Debug, Clone, Copy)]
pub struct Quantized<'array> {
pub weight: &'array Array,
pub scales: &'array Array,
pub biases: &'array Array,
pub format: Quantization,
}
#[derive(Debug, Clone)]
pub struct QuantizedArrays {
pub weight: Array,
pub scales: Array,
pub biases: Array,
pub format: Quantization,
}
impl QuantizedArrays {
#[must_use]
pub const fn as_ref(&self) -> Quantized<'_> {
Quantized {
weight: &self.weight,
scales: &self.scales,
biases: &self.biases,
format: self.format,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct GatherQmmOptions {
pub transpose: bool,
pub sorted_indices: bool,
}
impl Graph<'_> {
pub fn quantize(self, input: &Array, format: Quantization) -> Result<QuantizedArrays> {
let outputs =
ffi::quantize(input.native()?, format.group_size, format.bits, self.native()?)?;
let outputs = outputs.as_ref().ok_or(Error::NullHandle("quantized arrays"))?;
let actual = ffi::arrays_len(outputs);
if actual != 3 {
return Err(Error::Arity {
operation: "quantize",
expected: 3,
actual,
});
}
Ok(QuantizedArrays {
weight: array(outputs, 0, "quantized weight")?,
scales: array(outputs, 1, "quantized scales")?,
biases: array(outputs, 2, "quantized biases")?,
format,
})
}
pub fn quantized_matmul(
self,
input: &Array,
quantized: Quantized<'_>,
transpose: bool,
) -> Result<Array> {
let format = quantized.format;
let options = native_options(format, transpose, false);
Array::from_raw(
ffi::quantized_matmul(
input.native()?,
quantized.weight.native()?,
quantized.scales.native()?,
quantized.biases.native()?,
&options,
self.native()?,
)?,
"quantized matmul",
)
}
pub fn gather_qmm(
self,
input: &Array,
quantized: Quantized<'_>,
rhs_indices: &Array,
options: GatherQmmOptions,
) -> Result<Array> {
let format = quantized.format;
let options = native_options(format, options.transpose, options.sorted_indices);
Array::from_raw(
ffi::gather_qmm(
input.native()?,
quantized.weight.native()?,
quantized.scales.native()?,
quantized.biases.native()?,
rhs_indices.native()?,
&options,
self.native()?,
)?,
"gather qmm",
)
}
pub fn dequantize(self, quantized: Quantized<'_>) -> Result<Array> {
let options = native_options(quantized.format, false, false);
Array::from_raw(
ffi::dequantize(
quantized.weight.native()?,
quantized.scales.native()?,
quantized.biases.native()?,
&options,
self.native()?,
)?,
"dequantize",
)
}
}
fn array(values: &ffi::Arrays, index: usize, name: &'static str) -> Result<Array> {
Array::from_raw(ffi::arrays_get(values, index)?, name)
}
const fn native_options(
format: Quantization,
transpose: bool,
sorted_indices: bool,
) -> ffi::QuantizationOptions {
ffi::QuantizationOptions {
group_size: format.group_size,
bits: format.bits,
transpose,
sorted_indices,
}
}