Examples¶
Complete code examples demonstrating Vq usage patterns.
Binary Quantization with Hamming Distance¶
use vq::{BinaryQuantizer, Quantizer, VqResult};
/// Count the number of differing bits between two binary vectors
fn hamming_distance(a: &[u8], b: &[u8]) -> usize {
a.iter().zip(b.iter()).filter(|(x, y)| x != y).count()
}
fn main() -> VqResult<()> {
let bq = BinaryQuantizer::new(0.0, 0, 1)?;
// Sample embeddings
let embeddings = vec![
vec![0.5, -0.3, 0.1, -0.8, 0.2],
vec![0.4, -0.2, 0.0, -0.7, 0.3], // Similar to first
vec![-0.6, 0.4, -0.2, 0.9, -0.1], // Different
];
// Quantize all embeddings
let codes: Vec<_> = embeddings.iter()
.map(|e| bq.quantize(e))
.collect::<VqResult<_>>()?;
// Compare using Hamming distance
println!("Hamming(0, 1) = {}", hamming_distance(&codes[0], &codes[1]));
println!("Hamming(0, 2) = {}", hamming_distance(&codes[0], &codes[2]));
Ok(())
}
Scalar Quantization with Error Analysis¶
use vq::{ScalarQuantizer, Quantizer, VqResult};
fn main() -> VqResult<()> {
// Test different quantization levels
let levels_to_test = [4, 16, 64, 256];
let test_vector: Vec<f32> = (0..100)
.map(|i| (i as f32 / 50.0) - 1.0) // Values in [-1, 1]
.collect();
for levels in levels_to_test {
let sq = ScalarQuantizer::new(-1.0, 1.0, levels)?;
let quantized = sq.quantize(&test_vector)?;
let reconstructed = sq.dequantize(&quantized)?;
let mse: f32 = test_vector.iter()
.zip(reconstructed.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f32>() / test_vector.len() as f32;
let max_error: f32 = test_vector.iter()
.zip(reconstructed.iter())
.map(|(a, b)| (a - b).abs())
.fold(0.0, f32::max);
println!(
"Levels: {:3} | MSE: {:.6} | Max Error: {:.4}",
levels, mse, max_error
);
}
Ok(())
}
Product Quantization for Embedding Compression¶
use vq::{ProductQuantizer, Distance, Quantizer, VqResult};
fn main() -> VqResult<()> {
// Simulate 1000 embeddings of dimension 128
let embeddings: Vec<Vec<f32>> = (0..1000)
.map(|i| {
(0..128)
.map(|j| ((i * 7 + j * 13) % 1000) as f32 / 500.0 - 1.0)
.collect()
})
.collect();
let refs: Vec<&[f32]> = embeddings.iter().map(|v| v.as_slice()).collect();
// Train PQ: 16 subspaces (128/16 = 8 dims each), 256 centroids
println!("Training PQ...");
let pq = ProductQuantizer::new(&refs, 16, 256, 15, Distance::SquaredEuclidean, 42)?;
println!("PQ Configuration:");
println!(" Dimension: {}", pq.dim());
println!(" Subspaces: {}", pq.num_subspaces());
println!(" Sub-dimension: {}", pq.sub_dim());
// Quantize and measure error
let mut total_mse = 0.0;
for emb in &embeddings[..100] {
let quantized = pq.quantize(emb)?;
let reconstructed = pq.dequantize(&quantized)?;
let mse: f32 = emb.iter()
.zip(reconstructed.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f32>() / emb.len() as f32;
total_mse += mse;
}
println!("Average MSE: {:.6}", total_mse / 100.0);
// Storage comparison
let original_bytes = 128 * 4; // 128 floats * 4 bytes
let quantized_bytes = 128 * 2; // 128 f16 values * 2 bytes
println!(
"Compression: {} bytes -> {} bytes ({:.0}% reduction)",
original_bytes,
quantized_bytes,
(1.0 - quantized_bytes as f64 / original_bytes as f64) * 100.0
);
Ok(())
}
Distance Computation Comparison¶
use vq::{Distance, VqResult};
fn main() -> VqResult<()> {
// Create test vectors
let a: Vec<f32> = (0..100).map(|i| i as f32 / 100.0).collect();
let b: Vec<f32> = (0..100).map(|i| (i as f32 / 100.0) + 0.1).collect();
// Compare all distance metrics
let metrics = [
("Squared Euclidean", Distance::SquaredEuclidean),
("Euclidean", Distance::Euclidean),
("Manhattan", Distance::Manhattan),
("Cosine Distance", Distance::CosineDistance),
];
for (name, metric) in metrics {
let dist = metric.compute(&a, &b)?;
println!("{:20} = {:.6}", name, dist);
}
// Check SIMD backend (if enabled)
#[cfg(feature = "simd")]
{
println!("\nSIMD Backend: {}", vq::get_simd_backend());
}
Ok(())
}
Chaining Quantizers¶
use vq::{BinaryQuantizer, ScalarQuantizer, Quantizer, VqResult};
fn main() -> VqResult<()> {
let test_vector = vec![0.1, -0.5, 0.8, -0.2, 0.6];
// Chain quantizers: first SQ, then BQ on reconstructed
let sq = ScalarQuantizer::new(-1.0, 1.0, 256)?;
let bq = BinaryQuantizer::new(0.5, 0, 1)?;
// Step 1: Scalar quantization
let sq_quantized = sq.quantize(&test_vector)?;
let sq_reconstructed = sq.dequantize(&sq_quantized)?;
// Step 2: Binary quantization on SQ output
let bq_quantized = bq.quantize(&sq_reconstructed)?;
println!("Original: {:?}", test_vector);
println!("After SQ: {:?}", sq_reconstructed);
println!("After BQ: {:?}", bq_quantized);
Ok(())
}