use crate::optimization::quantization::config::*;
use std::vec::Vec;
use wasm_bindgen::prelude::*;
fn affine_quantize_dequantize(data: &[f32], precision: QuantizationPrecision) -> Vec<f32> {
if data.is_empty() {
return Vec::new();
}
let bits = precision.bits();
let levels: u64 = 1u64 << bits; let qmax = (levels - 1) as f32;
let (min_val, max_val) =
data.iter().fold((f32::INFINITY, f32::NEG_INFINITY), |(lo, hi), &x| {
(lo.min(x), hi.max(x))
});
if max_val <= min_val {
return vec![min_val; data.len()];
}
let scale = (max_val - min_val) / qmax;
let zero_point = (-min_val / scale).round().clamp(0.0, qmax);
data.iter()
.map(|&x| {
let q = (x / scale + zero_point).round().clamp(0.0, qmax);
(q - zero_point) * scale
})
.collect()
}
pub fn apply_dynamic_quantization(
data: &[f32],
precision: QuantizationPrecision,
) -> Result<Vec<f32>, JsValue> {
Ok(affine_quantize_dequantize(data, precision))
}
pub fn apply_static_quantization(
data: &[f32],
precision: QuantizationPrecision,
) -> Result<Vec<f32>, JsValue> {
Ok(affine_quantize_dequantize(data, precision))
}
pub fn apply_post_training_quantization(
data: &[f32],
precision: QuantizationPrecision,
) -> Result<Vec<f32>, JsValue> {
Ok(affine_quantize_dequantize(data, precision))
}
#[cfg(test)]
mod tests {
use super::*;
fn distinct_sorted(mut v: Vec<f32>) -> Vec<f32> {
v.sort_by(|a, b| a.partial_cmp(b).expect("no NaNs in test data"));
v.dedup();
v
}
#[test]
fn test_dynamic_quantization_int4_collapses_to_at_most_16_levels() {
let data: Vec<f32> = (0..100).map(|i| i as f32 / 10.0).collect();
let result = apply_dynamic_quantization(&data, QuantizationPrecision::INT4)
.expect("quantization should not fail");
assert_eq!(result.len(), data.len());
let levels = distinct_sorted(result);
assert!(
levels.len() <= 16,
"expected <= 16 distinct INT4 levels, got {}",
levels.len()
);
}
#[test]
fn test_static_quantization_int8_collapses_to_at_most_256_levels() {
let data: Vec<f32> = (0..1000).map(|i| (i as f32).sin() * 50.0).collect();
let result = apply_static_quantization(&data, QuantizationPrecision::INT8)
.expect("quantization should not fail");
let levels = distinct_sorted(result);
assert!(levels.len() <= 256);
}
#[test]
fn test_post_training_quantization_int1_collapses_to_at_most_2_levels() {
let data = vec![-3.0, -1.0, 0.0, 2.0, 5.0, 10.0];
let result = apply_post_training_quantization(&data, QuantizationPrecision::INT1)
.expect("quantization should not fail");
let levels = distinct_sorted(result);
assert!(
levels.len() <= 2,
"INT1 must collapse to at most 2 levels, got {}",
levels.len()
);
}
#[test]
fn test_constant_input_round_trips_exactly() {
let data = vec![7.0f32; 10];
let result = apply_dynamic_quantization(&data, QuantizationPrecision::INT4)
.expect("quantization should not fail");
assert_eq!(result.len(), 10);
assert!(result.iter().all(|&v| v == 7.0));
}
#[test]
fn test_output_is_not_the_old_fixed_constant_multiplier() {
let data = vec![1.0, 2.0, 3.0, 4.0, 100.0];
let result = apply_dynamic_quantization(&data, QuantizationPrecision::INT8)
.expect("quantization should not fail");
let old_placeholder: Vec<f32> = data.iter().map(|&x| x * 0.5).collect();
assert_ne!(result, old_placeholder);
}
#[test]
fn test_empty_input_returns_empty_output() {
let data: Vec<f32> = Vec::new();
let result = apply_dynamic_quantization(&data, QuantizationPrecision::INT8)
.expect("quantization should not fail");
assert!(result.is_empty());
}
#[test]
fn test_output_length_matches_input_length_for_all_public_functions() {
let data = vec![-10.0, -5.0, 0.0, 5.0, 10.0, 42.0, -42.0];
let dynamic = apply_dynamic_quantization(&data, QuantizationPrecision::INT4)
.expect("quantization should not fail");
let static_q = apply_static_quantization(&data, QuantizationPrecision::INT4)
.expect("quantization should not fail");
let post_training = apply_post_training_quantization(&data, QuantizationPrecision::INT4)
.expect("quantization should not fail");
assert_eq!(dynamic.len(), data.len());
assert_eq!(static_q.len(), data.len());
assert_eq!(post_training.len(), data.len());
}
}