use crate::kernels::{MAX_DOT_I8_DIMENSION, dot_i8};
use super::QuantError;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Int8Vec<'a> {
pub codes: &'a [i8],
pub scale: f32,
pub offset: f32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Int8Query {
codes: Vec<i8>,
scale: f64,
code_sum: i32,
}
impl Int8Query {
#[must_use]
pub fn len(&self) -> usize {
self.codes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.codes.is_empty()
}
#[cfg(any(test, feature = "test-support"))]
#[doc(hidden)]
#[must_use]
pub fn observation_parts(&self) -> (&[i8], f64, i32) {
(&self.codes, self.scale, self.code_sum)
}
#[cfg(test)]
pub(crate) fn codes(&self) -> &[i8] {
&self.codes
}
pub(crate) fn score_integer_dot(
&self,
integer_dot: i32,
row_scale: f32,
row_offset: f32,
) -> f32 {
(self.scale
* (f64::from(row_scale) * f64::from(integer_dot)
+ f64::from(row_offset) * f64::from(self.code_sum))) as f32
}
}
pub fn quantize_int8(v: &[f32], out: &mut [i8]) -> Result<(f32, f32), QuantError> {
validate_vector(v)?;
if out.len() != v.len() {
return Err(QuantError::OutputLength {
expected: v.len(),
actual: out.len(),
});
}
let (minimum, maximum) = extrema(v);
if minimum == maximum {
out.fill(0);
return Ok((0.0, minimum));
}
let range = f64::from(maximum) - f64::from(minimum);
let mut scale = (range / 254.0) as f32;
if scale == 0.0 {
scale = f32::from_bits(1);
}
let offset = ((f64::from(maximum) + f64::from(minimum)) * 0.5) as f32;
let scale_f64 = f64::from(scale);
let offset_f64 = f64::from(offset);
for (&value, code) in v.iter().zip(out.iter_mut()) {
let mapped = ((f64::from(value) - offset_f64) / scale_f64)
.round()
.clamp(-127.0, 127.0);
*code = mapped as i8;
}
Ok((scale, offset))
}
pub fn dequantize_int8(encoded: Int8Vec<'_>, out: &mut [f32]) -> Result<(), QuantError> {
if out.len() != encoded.codes.len() {
return Err(QuantError::OutputLength {
expected: encoded.codes.len(),
actual: out.len(),
});
}
for (&code, value) in encoded.codes.iter().zip(out.iter_mut()) {
*value = (f64::from(encoded.scale) * f64::from(code) + f64::from(encoded.offset)) as f32;
}
Ok(())
}
pub fn prepare_int8_query(q: &[f32]) -> Result<Int8Query, QuantError> {
validate_vector(q)?;
#[cfg(any(test, feature = "test-support"))]
super::QUERY_PREPARATIONS.with(|calls| {
if let Some(calls) = calls.borrow_mut().as_mut() {
calls.int8.push(q.len());
}
});
let maximum = q.iter().map(|value| value.abs()).fold(0.0_f32, f32::max);
if maximum == 0.0 {
return Ok(Int8Query {
codes: vec![0_i8; q.len()],
scale: 0.0,
code_sum: 0,
});
}
let scale = f64::from(maximum) / 127.0;
let mut code_sum = 0_i32;
let codes = q
.iter()
.map(|&value| {
let code = (f64::from(value) / scale).round().clamp(-127.0, 127.0) as i8;
code_sum += i32::from(code);
code
})
.collect();
Ok(Int8Query {
codes,
scale,
code_sum,
})
}
pub fn dot_int8_query(query: &Int8Query, row: Int8Vec<'_>) -> Result<f32, QuantError> {
if row.codes.len() != query.codes.len() {
return Err(QuantError::CodeLength {
expected: query.codes.len(),
actual: row.codes.len(),
});
}
let integer_dot = dot_i8(&query.codes, row.codes);
Ok(query.score_integer_dot(integer_dot, row.scale, row.offset))
}
fn validate_vector(v: &[f32]) -> Result<(), QuantError> {
if v.is_empty() {
return Err(QuantError::EmptyVector);
}
if v.len() > MAX_DOT_I8_DIMENSION {
return Err(QuantError::DimensionTooLarge {
actual: v.len(),
maximum: MAX_DOT_I8_DIMENSION,
});
}
if let Some((index, _)) = v.iter().enumerate().find(|(_, value)| !value.is_finite()) {
return Err(QuantError::NonFinite { index });
}
Ok(())
}
fn extrema(v: &[f32]) -> (f32, f32) {
v.iter().copied().fold(
(f32::INFINITY, f32::NEG_INFINITY),
|(minimum, maximum), value| (minimum.min(value), maximum.max(value)),
)
}