use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Precision {
#[default]
F32,
F16,
I8,
}
impl Precision {
#[inline]
pub fn encoded_len(&self, dim: usize) -> usize {
match self {
Precision::F32 => dim * 4,
Precision::F16 => dim * 2,
Precision::I8 => 8 + dim, }
}
pub fn encode(&self, v: &[f32]) -> Vec<u8> {
match self {
Precision::F32 => {
let mut out = Vec::with_capacity(v.len() * 4);
for x in v {
out.extend_from_slice(&x.to_le_bytes());
}
out
}
Precision::F16 => {
let mut out = Vec::with_capacity(v.len() * 2);
for x in v {
out.extend_from_slice(&half::f16::from_f32(*x).to_le_bytes());
}
out
}
Precision::I8 => {
let min = v.iter().copied().fold(f32::INFINITY, f32::min);
let max = v.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let range = max - min;
let scale = if range > 0.0 { range / 255.0 } else { 0.0 };
let mut out = Vec::with_capacity(self.encoded_len(v.len()));
out.extend_from_slice(&min.to_le_bytes());
out.extend_from_slice(&scale.to_le_bytes());
for x in v {
let q = if scale > 0.0 {
(((x - min) / scale).round()).clamp(0.0, 255.0) as u8
} else {
0
};
out.push(q);
}
out
}
}
}
pub fn decode(&self, bytes: &[u8], dim: usize) -> Vec<f32> {
let mut out = Vec::with_capacity(dim);
self.decode_into(bytes, dim, &mut out);
out
}
pub fn decode_into(&self, bytes: &[u8], dim: usize, out: &mut Vec<f32>) {
out.clear();
out.reserve(dim);
match self {
Precision::F32 => {
for c in bytes.chunks_exact(4).take(dim) {
out.push(f32::from_le_bytes([c[0], c[1], c[2], c[3]]));
}
}
Precision::F16 => {
for c in bytes.chunks_exact(2).take(dim) {
out.push(half::f16::from_le_bytes([c[0], c[1]]).to_f32());
}
}
Precision::I8 => {
if bytes.len() < 8 {
out.resize(dim, 0.0);
return;
}
let min = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let scale = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
for &q in bytes[8..].iter().take(dim) {
out.push(min + q as f32 * scale);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> Vec<f32> {
vec![-1.0, -0.25, 0.0, 0.1, 0.5, 0.9, 1.0, 2.5]
}
#[test]
fn f32_round_trips_exactly() {
let v = sample();
let bytes = Precision::F32.encode(&v);
assert_eq!(bytes.len(), Precision::F32.encoded_len(v.len()));
assert_eq!(Precision::F32.decode(&bytes, v.len()), v);
}
#[test]
fn f16_round_trips_within_half_precision() {
let v = sample();
let bytes = Precision::F16.encode(&v);
assert_eq!(bytes.len(), Precision::F16.encoded_len(v.len()));
let back = Precision::F16.decode(&bytes, v.len());
for (a, b) in v.iter().zip(back.iter()) {
assert!((a - b).abs() <= a.abs() * 1e-2 + 1e-3, "{a} vs {b}");
}
}
#[test]
fn i8_round_trips_within_quantization_error() {
let v = sample();
let bytes = Precision::I8.encode(&v);
assert_eq!(bytes.len(), Precision::I8.encoded_len(v.len()));
let back = Precision::I8.decode(&bytes, v.len());
let range = 2.5 - (-1.0);
let step = range / 255.0;
for (a, b) in v.iter().zip(back.iter()) {
assert!((a - b).abs() <= step, "{a} vs {b}");
}
}
#[test]
fn i8_preserves_ordering() {
let v: Vec<f32> = (0..32).map(|i| i as f32).collect();
let back = Precision::I8.decode(&Precision::I8.encode(&v), v.len());
for w in back.windows(2) {
assert!(w[0] <= w[1]);
}
}
#[test]
fn constant_vector_is_stable_under_i8() {
let v = vec![3.0; 5];
let back = Precision::I8.decode(&Precision::I8.encode(&v), v.len());
for x in back {
assert!((x - 3.0).abs() < 1e-6);
}
}
}