lnmp_quant/
decode.rs

1use crate::error::QuantError;
2use crate::scheme::QuantScheme;
3use crate::vector::QuantizedVector;
4use lnmp_embedding::Vector;
5
6/// Dequantizes a quantized vector back to approximate F32 representation
7///
8/// # Arguments
9/// * `q` - The quantized vector to dequantize
10///
11/// # Returns
12/// * `Ok(Vector)` - Successfully dequantized vector (F32 type)
13/// * `Err(QuantError)` - If dequantization fails
14///
15/// # Example
16/// ```
17/// use lnmp_quant::{quantize_embedding, dequantize_embedding, QuantScheme};
18/// use lnmp_embedding::Vector;
19///
20/// let original = Vector::from_f32(vec![0.12, -0.45, 0.33]);
21/// let quantized = quantize_embedding(&original, QuantScheme::QInt8).unwrap();
22/// let restored = dequantize_embedding(&quantized).unwrap();
23/// ```
24pub fn dequantize_embedding(q: &QuantizedVector) -> Result<Vector, QuantError> {
25    if q.dim == 0 {
26        return Err(QuantError::InvalidDimension(
27            "Cannot dequantize zero-dimensional vector".to_string(),
28        ));
29    }
30
31    match q.scheme {
32        QuantScheme::QInt8 => dequantize_qint8(q),
33        QuantScheme::QInt4 => crate::qint4::dequantize_qint4(q),
34        QuantScheme::Binary => crate::binary::dequantize_binary(q),
35        QuantScheme::FP16Passthrough => crate::fp16::dequantize_fp16(q),
36    }
37}
38
39/// Dequantizes a QInt8 quantized vector
40fn dequantize_qint8(q: &QuantizedVector) -> Result<Vector, QuantError> {
41    // Validate data length
42    if q.data.len() != q.dim as usize {
43        return Err(QuantError::DataCorrupted(format!(
44            "Data length mismatch: expected {} bytes, got {}",
45            q.dim,
46            q.data.len()
47        )));
48    }
49
50    // Dequantize each value using the stored min_val
51    let mut values = Vec::with_capacity(q.dim as usize);
52
53    for &quantized_byte in &q.data {
54        let quantized = quantized_byte as i8;
55        // Reverse the quantization: value = (quantized + 128) * scale + min_val
56        let value = ((quantized as i32 + 128) as f32 * q.scale) + q.min_val;
57        values.push(value);
58    }
59
60    // Convert to embedding vector
61    Ok(Vector::from_f32(values))
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use crate::encode::quantize_embedding;
68    use lnmp_embedding::{SimilarityMetric, Vector};
69
70    #[test]
71    fn test_dequantize_simple() {
72        let original = Vector::from_f32(vec![0.12, -0.45, 0.33]);
73        let quantized = quantize_embedding(&original, QuantScheme::QInt8).unwrap();
74        let result = dequantize_embedding(&quantized);
75
76        assert!(result.is_ok());
77        let restored = result.unwrap();
78        assert_eq!(restored.dim, 3);
79        // Verify it's F32 by successfully converting
80        assert!(restored.as_f32().is_ok());
81    }
82
83    #[test]
84    fn test_roundtrip_accuracy() {
85        let original =
86            Vector::from_f32(vec![0.1, -0.2, 0.3, -0.4, 0.5, -0.6, 0.7, -0.8, 0.9, -1.0]);
87        let quantized = quantize_embedding(&original, QuantScheme::QInt8).unwrap();
88        let restored = dequantize_embedding(&quantized).unwrap();
89
90        // Check cosine similarity
91        let similarity = original
92            .similarity(&restored, SimilarityMetric::Cosine)
93            .unwrap();
94
95        // Should be very close to 1.0 (near-perfect similarity)
96        assert!(similarity > 0.98, "Cosine similarity: {}", similarity);
97    }
98
99    #[test]
100    fn test_roundtrip_large_vector() {
101        let values: Vec<f32> = (0..512).map(|i| (i as f32 / 512.0) - 0.5).collect();
102        let original = Vector::from_f32(values);
103
104        let quantized = quantize_embedding(&original, QuantScheme::QInt8).unwrap();
105        let restored = dequantize_embedding(&quantized).unwrap();
106
107        let similarity = original
108            .similarity(&restored, SimilarityMetric::Cosine)
109            .unwrap();
110
111        assert!(similarity > 0.99, "Cosine similarity: {}", similarity);
112    }
113
114    #[test]
115    fn test_dequantize_corrupted_data() {
116        // Create a quantized vector with mismatched dimensions
117        let qv = QuantizedVector::new(10, QuantScheme::QInt8, 0.01, 0, 0.0, vec![0u8; 5]);
118        let result = dequantize_embedding(&qv);
119        assert!(result.is_err());
120        assert!(matches!(result.unwrap_err(), QuantError::DataCorrupted(_)));
121    }
122}