Pure Rust implementation of advanced vector quantization and search algorithms:
- RaBitQ quantization with IVF (Inverted File) search - Production ready
- MSTG (Multi-Scale Tree Graph) index -
⚠️ Experimental (v0.5.0)
Up to 32× memory compression with significantly higher accuracy than traditional Product Quantization (PQ) or Scalar Quantization (SQ).
RaBitQ delivers exceptional compression-accuracy trade-offs for approximate nearest neighbor search:
- vs. Raw Vectors: Achieve up to 32× memory reduction with configurable compression ratios
- vs. PQ/SQ: Substantially higher accuracy, especially at high recall targets (90%+)
- How it Works: Combines 1-bit binary codes with multi-bit refinement codes for precise distance estimation
The quantization applies to residual vectors (data - centroid), enabling accurate reconstruction even at extreme compression rates.
This library provides a feature-complete RaBitQ + IVF search engine with all optimizations from the original C++ implementation:
- ✅ Faster Config Mode: 100-500× faster index building with <1% accuracy loss
- ✅ FHT Rotation: Fast Hadamard Transform for efficient orthonormal rotations
- ✅ SIMD Accelerated: Optimized distance computations on modern CPUs
- ✅ Memory Safe: No segfaults, no unsafe code in dependencies
- ✅ Complete IVF Pipeline: Training (built-in k-means or FAISS-compatible), search, persistence
New in v0.5.0: MSTG is an experimental hybrid index combining hierarchical clustering with graph navigation.
⚠️ Experimental Status: MSTG is under active development and lacks comprehensive testing and tuning. For production workloads, we recommend using the battle-tested IVF+RaBitQ index. MSTG is provided for research and experimentation purposes.
- Hierarchical Balanced Clustering: Adaptive multi-scale clustering with configurable branching factor
- Closure Assignment: Vectors can belong to multiple clusters (RNG rule) for better recall
- HNSW Graph Navigation: Fast approximate nearest centroid search
- Flexible Precision: FP32, BF16, FP16, or INT8 for centroids
- Dynamic Pruning: Adaptive cluster selection based on query-centroid distances
- Python Bindings: Seamless integration with NumPy and ann-benchmarks
| Feature | IVF+RaBitQ | MSTG (Experimental) |
|---|---|---|
| Maturity | ✅ Production Ready | |
| Testing | ✅ Comprehensive | |
| Best For | All use cases (10K-10M+) | Research & experimentation |
| Recall | 85-95% (well-tuned) | 90-98% (theoretical) |
| Build Time | Fast (k-means) | Moderate (hierarchical) |
| Query Speed | Fast | Very Fast (HNSW navigation) |
| Memory | Low | Low-Medium (configurable) |
| Stability | ✅ Stable | |
| Documentation | ✅ Complete | 🚧 In progress |
Recommendation: Use IVF+RaBitQ for production workloads. It's well-tested, documented, and optimized. Consider MSTG only if you're researching advanced indexing techniques or willing to help with testing and tuning.
x86_64 (Intel/AMD): ✅ Fully supported and tested
ARM64 (Apple Silicon, ARM servers):
ARM64 Notice: The current implementation has critical bugs on ARM64/AArch64 platforms that affect both L2 and Inner Product metrics. Distance calculations produce incorrect results, making the library unreliable on Apple Silicon Macs and ARM servers. We're actively investigating these issues. For now, production use on ARM64 is NOT recommended.
Add to your Cargo.toml:
[dependencies]
rabitq-rs = "0.5"Build an IVF+RaBitQ index and search in ~10 lines:
use rabitq_rs::{IvfRabitqIndex, SearchParams, Metric, RotatorType};
use rand::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut rng = StdRng::seed_from_u64(42);
let dim = 128;
// Generate 10,000 random vectors
let dataset: Vec<Vec<f32>> = (0..10_000)
.map(|_| (0..dim).map(|_| rng.gen::<f32>()).collect())
.collect();
// Train index with 256 clusters, 7-bit quantization
let index = IvfRabitqIndex::train(
&dataset,
256, // nlist (number of clusters)
7, // total_bits (1 sign + 6 magnitude)
Metric::L2,
RotatorType::FhtKacRotator, // Fast Hadamard Transform
42, // random seed
false, // use_faster_config
)?;
// Search: probe 32 clusters, return top 10 neighbors
let params = SearchParams::new(10, 32);
let results = index.search(&dataset[0], params)?;
println!("Top neighbor ID: {}, distance: {}", results[0].id, results[0].score);
Ok(())
}Build an MSTG index and search in ~10 lines:
use rabitq_rs::mstg::{MstgIndex, MstgConfig, SearchParams};
use rand::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut rng = StdRng::seed_from_u64(42);
let dim = 128;
// Generate 10,000 random vectors
let dataset: Vec<Vec<f32>> = (0..10_000)
.map(|_| (0..dim).map(|_| rng.gen::<f32>()).collect())
.collect();
// Build index with default configuration
let index = MstgIndex::build(&dataset, MstgConfig::default())?;
// Search: balanced mode with top 10 results
let params = SearchParams::balanced(10);
let results = index.search(&dataset[0], ¶ms);
println!("Top neighbor ID: {}, distance: {:.6}", results[0].vector_id, results[0].distance);
Ok(())
}
⚠️ Note: MSTG is experimental and not recommended for production use. Use IVF+RaBitQ for stable, well-tested performance.
use rabitq_rs::{IvfRabitqIndex, Metric, RotatorType};
let index = IvfRabitqIndex::train(
&vectors,
1024, // nlist: number of IVF clusters
7, // total_bits: quantization precision
Metric::L2, // or Metric::InnerProduct
RotatorType::FhtKacRotator,
12345, // seed for reproducibility
false, // use_faster_config
)?;If you already have centroids and cluster assignments from FAISS or another tool:
use rabitq_rs::{IvfRabitqIndex, Metric, RotatorType};
let index = IvfRabitqIndex::train_with_clusters(
&vectors,
¢roids, // shape: [nlist, dim]
&assignments, // shape: [num_vectors], cluster ID per vector
7, // total_bits
Metric::L2,
RotatorType::FhtKacRotator,
42, // seed
false, // use_faster_config
)?;For datasets >100K vectors, enable faster_config to accelerate training by 100-500× with minimal accuracy loss (<1%):
let index = IvfRabitqIndex::train(
&vectors,
4096,
7,
Metric::L2,
RotatorType::FhtKacRotator,
42,
true, // use_faster_config = true
)?;use rabitq_rs::SearchParams;
let params = SearchParams::new(
100, // top_k: return 100 nearest neighbors
64, // nprobe: search 64 nearest clusters
);
let results = index.search(&query_vector, params)?;
for result in results.iter().take(10) {
println!("ID: {}, Distance: {}", result.id, result.score);
}Parameter Tuning:
nprobe: Higher = better recall, slower search (typical: 10-1024)top_k: Number of neighbors to returntotal_bits: More bits = higher accuracy, larger index (typical: 3-7)
// Save index to disk (with CRC32 checksum)
index.save("my_index.bin")?;
// Load index from disk
let loaded_index = IvfRabitqIndex::load("my_index.bin")?;use rabitq_rs::mstg::{MstgIndex, MstgConfig, ScalarPrecision};
use rabitq_rs::Metric;
let mut config = MstgConfig::default();
// Configure clustering
config.max_posting_size = 5000; // Max vectors per cluster
config.branching_factor = 10; // Hierarchical branching
config.balance_weight = 1.0; // Balance vs purity trade-off
// Configure closure assignment (multi-assignment)
config.closure_epsilon = 0.15; // Distance threshold
config.max_replicas = 8; // Max assignments per vector
// Configure quantization
config.rabitq_bits = 7; // 3-8 bits (7 recommended)
config.faster_config = true; // Fast mode
config.metric = Metric::L2; // or Metric::InnerProduct
// Configure HNSW graph
config.hnsw_m = 32; // Connectivity
config.hnsw_ef_construction = 200; // Build quality
config.centroid_precision = ScalarPrecision::BF16; // FP32/BF16/FP16/INT8
let index = MstgIndex::build(&vectors, config)?;use rabitq_rs::mstg::SearchParams;
// Option 1: Pre-defined modes
let params = SearchParams::high_recall(100); // 95%+ recall
let params = SearchParams::balanced(100); // ~90% recall (default)
let params = SearchParams::low_latency(100); // ~80% recall, fastest
// Option 2: Custom parameters
let params = SearchParams::new(
150, // ef_search: candidates to explore
0.6, // pruning_epsilon: dynamic pruning threshold
100, // top_k: number of results
);
let results = index.search(&query_vector, ¶ms);
for result in results.iter().take(10) {
println!("ID: {}, Distance: {:.6}", result.vector_id, result.distance);
}Parameter Tuning:
ef_search: Higher = better recall, slower (typical: 50-300)pruning_epsilon: Higher = more clusters searched (typical: 0.4-0.8)max_posting_size: Smaller = more clusters, better balance (typical: 3000-10000)rabitq_bits: More bits = higher accuracy (typical: 5-7)
⚠️ Reminder: MSTG is experimental. For production use, refer to the IVF+RaBitQ documentation above.
The ivf_rabitq binary lets you evaluate RaBitQ on standard datasets like GIST-1M:
mkdir -p data/gist
wget -P data/gist ftp://ftp.irisa.fr/local/texmex/corpus/gist.tar.gz
tar -xzvf data/gist/gist.tar.gz -C data/gistcargo run --release --bin ivf_rabitq -- \
--base data/gist/gist_base.fvecs \
--nlist 4096 \
--bits 3 \
--faster-config \
--save data/gist/index.bincargo run --release --bin ivf_rabitq -- \
--load data/gist/index.bin \
--queries data/gist/gist_query.fvecs \
--gt data/gist/gist_groundtruth.ivecs \
--benchmarkThis performs an automatic nprobe sweep and reports:
nprobe | QPS | Recall@100
-------|---------|------------
5 | 12500 | 45.2%
10 | 8200 | 62.8%
20 | 5100 | 75.4%
...
For C++ library compatibility, generate clusters using the provided Python helper:
python python/ivf.py \
data/gist/gist_base.fvecs \
4096 \
data/gist/gist_centroids_4096.fvecs \
data/gist/gist_clusterids_4096.ivecs \
l2
cargo run --release --bin ivf_rabitq -- \
--base data/gist/gist_base.fvecs \
--centroids data/gist/gist_centroids_4096.fvecs \
--assignments data/gist/gist_clusterids_4096.ivecs \
--bits 3 \
--save data/gist/index.binRaBitQ-RS provides Python bindings for the MSTG (Multi-Scale Tree Graph) index, enabling seamless integration with Python machine learning workflows and ann-benchmarks.
# Install dependencies
pip install maturin numpy
# Build and install from source
maturin develop --release --features pythonimport numpy as np
from rabitq_rs import MstgIndex
# Generate sample data
data = np.random.randn(10000, 128).astype(np.float32)
query = np.random.randn(128).astype(np.float32)
# Create and build index
index = MstgIndex(
dimension=128,
metric="euclidean", # or "angular"
max_posting_size=5000,
rabitq_bits=7, # 3-8 bits
faster_config=True, # Fast quantization mode
centroid_precision="bf16" # "fp32", "bf16", "fp16", or "int8"
)
index.fit(data)
# Search
index.set_query_arguments(ef_search=150, pruning_epsilon=0.6)
results = index.query(query, k=10)
# Results: numpy array of shape (k, 2) with [id, distance]
for neighbor_id, distance in results:
print(f"ID: {int(neighbor_id)}, Distance: {distance:.6f}")Index Parameters (Build Time):
dimension: Vector dimensionality (required)metric: Distance metric - "euclidean" (L2) or "angular" (inner product)max_posting_size: Maximum vectors per cluster (default: 5000)rabitq_bits: Quantization bits, 3-8 (default: 7)faster_config: Fast quantization mode (default: True)centroid_precision: Centroid storage - "fp32", "bf16", "fp16", "int8" (default: "bf16")hnsw_m: HNSW connectivity (default: 32)hnsw_ef_construction: HNSW build quality (default: 200)
Query Parameters (Search Time):
ef_search: Centroid candidates to explore (default: 150)pruning_epsilon: Dynamic pruning threshold (default: 0.6)k: Number of neighbors to return
# Query multiple vectors efficiently
queries = np.random.randn(100, 128).astype(np.float32)
results_list = index.batch_query(queries, k=10)
# Returns list of numpy arrays, one per query
for i, results in enumerate(results_list):
print(f"Query {i}: Top neighbor ID = {int(results[0, 0])}")memory_bytes = index.get_memory_usage()
print(f"Index size: {memory_bytes / 1024**2:.2f} MB")RaBitQ-RS includes a complete ann-benchmarks wrapper for standardized evaluation:
# Run benchmarks
cd ann_benchmarks
python run.py --algorithm rabitq-mstg --dataset fashion-mnist-784-euclideanSee ann_benchmarks/README.md for detailed benchmark configuration and usage.
Run the test suite before committing changes:
# Rust tests
cargo fmt
cargo clippy --all-targets --all-features
cargo test
# Python tests
python test_python_bindings.pyAll tests use seeded RNGs for reproducible results.
If you use RaBitQ in your research, please cite the original paper:
@inproceedings{guo2024rabitq,
title={RaBitQ: Quantizing High-Dimensional Vectors with a Theoretical Error Bound for Approximate Nearest Neighbor Search},
author={Guo, Jianyang and Wang, Zihan and Tang, Yun and Wang, Jihao and Cai, Ruihang and Cai, Peng and Zhang, Xiyuan and Wang, Jianye and Zhao, Jun},
booktitle={Proceedings of the ACM on Management of Data},
year={2024}
}Original C++ Implementation: VectorDB-NTU/RaBitQ-Library
Licensed under the Apache License, Version 2.0. See LICENSE for details.