A lightning-fast, Rust-powered Approximate Nearest Neighbor library for Python with multiple backends, thread-safety, and GPU acceleration.
- Features
- Installation
- Quick Start
- Examples
- Benchmark Results
- API Reference
- Performance
- GPU Acceleration
- License
- Multiple Backends:
- Brute-force (exact) with SIMD acceleration for guaranteed accuracy
- HNSW (approximate) for large-scale datasets with near-constant memory
- Multiple Distance Metrics: Euclidean, Cosine, Manhattan, Chebyshev
- Batch Queries for efficient processing of multiple vectors
- Thread-safe indexes with concurrent access patterns
- Zero-copy NumPy integration for minimal memory overhead
- On-disk Persistence with serialization/deserialization
- Filtered Search with custom Python callbacks and metadata
- GPU Acceleration for brute-force calculations on NVIDIA GPUs
- Multi-platform support (Linux, Windows, macOS)
- Automated CI/CD with benchmarking and performance tracking
# Stable release
pip install rust-annie
# With GPU support (requires CUDA Toolkit)
pip install rust-annie[gpu]git clone https://github.com/arnavk23/Annie.git
cd Annie
pip install maturin
maturin develop --releaseimport numpy as np
from rust_annie import AnnIndex, Distance
# Create index
index = AnnIndex(128, Distance.EUCLIDEAN)
# Add data
data = np.random.rand(1000, 128).astype(np.float32)
ids = np.arange(1000, dtype=np.int64)
index.add(data, ids)
# Search
query = np.random.rand(128).astype(np.float32)
neighbor_ids, distances = index.search(query, k=5)
print(f"Top 5 neighbors: {neighbor_ids}")
print(f"Distances: {distances}")from rust_annie import PyHnswIndex
import numpy as np
# Create index
index = PyHnswIndex(dims=128)
# Add data
data = np.random.rand(10000, 128).astype(np.float32)
ids = np.arange(10000, dtype=np.int64)
index.add(data, ids)
# Search
query = np.random.rand(128).astype(np.float32)
neighbor_ids, distances = index.search(query, k=10)
print(f"Approximate neighbors: {neighbor_ids}")from rust_annie import AnnIndex, Distance
import numpy as np
index = AnnIndex(16, Distance.EUCLIDEAN)
data = np.random.rand(1000, 16).astype(np.float32)
ids = np.arange(1000, dtype=np.int64)
index.add(data, ids)
# Batch search (32 queries at once)
queries = data[:32]
labels_batch, dists_batch = index.search_batch(queries, k=10)
print(labels_batch.shape) # (32, 10)from rust_annie import ThreadSafeAnnIndex, Distance
import numpy as np
from concurrent.futures import ThreadPoolExecutor
# Create thread-safe index
index = ThreadSafeAnnIndex(32, Distance.EUCLIDEAN)
data = np.random.rand(500, 32).astype(np.float32)
ids = np.arange(500, dtype=np.int64)
index.add(data, ids)
# Concurrent searches
def search_task(q):
return index.search(q, k=5)
with ThreadPoolExecutor(max_workers=8) as executor:
futures = [executor.submit(search_task, data[i]) for i in range(8)]
results = [f.result() for f in futures]from rust_annie import AnnIndex, Distance
import numpy as np
index = AnnIndex(3, Distance.EUCLIDEAN)
data = np.array([
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0]
], dtype=np.float32)
ids = np.array([10, 20, 30], dtype=np.int64)
index.add(data, ids)
# Filter function
def even_ids(id: int) -> bool:
return id % 2 == 0
# Filtered search
query = np.array([1.0, 2.0, 3.0], dtype=np.float32)
filtered_ids, filtered_dists = index.search_filter_py(query, k=3, filter_fn=even_ids)
print(filtered_ids) # [10, 30] (20 is filtered out)from rust_annie import AnnIndex, Distance
import numpy as np
# Create and populate index
index = AnnIndex(64, Distance.COSINE)
data = np.random.rand(5000, 64).astype(np.float32)
ids = np.arange(5000, dtype=np.int64)
index.add(data, ids)
# Save to disk
index.save("my_index.bin")
# Load later
loaded_index = AnnIndex.load("my_index.bin")
query = np.random.rand(64).astype(np.float32)
neighbors, distances = loaded_index.search(query, k=5)| Operation | Dataset | Time | Speedup |
|---|---|---|---|
| Single Query | 10k × 64 | 0.7 ms | 4× vs NumPy |
| Batch Query (64) | 10k × 64 | 0.23 ms per query | 12× vs NumPy |
| HNSW Query | 100k × 128 | 0.05 ms | 56× vs NumPy |
See the Live Benchmark Dashboard for continuous performance tracking across versions.
Brute-force exact nearest neighbor search.
AnnIndex(dim: int, metric: Distance)Methods:
add(data: np.ndarray[N×D], ids: np.ndarray[N]) -> None- Add vectors to indexsearch(query: np.ndarray[D], k: int) -> (ids, distances)- Single query searchsearch_batch(queries: np.ndarray[N×D], k: int) -> (ids, distances)- Batch searchsearch_filter_py(query: np.ndarray[D], k: int, filter_fn: Callable) -> (ids, distances)- Filtered searchremove(ids: Sequence[int]) -> None- Remove vectors by IDsave(path: str) -> None- Serialize to diskload(path: str) -> AnnIndex- Load from disk (static method)
Hierarchical Navigable Small World (HNSW) approximate search.
PyHnswIndex(dims: int, ef_construction: int = 200, M: int = 5)Methods:
add(data: np.ndarray[N×D], ids: np.ndarray[N]) -> None- Add vectorssearch(query: np.ndarray[D], k: int, ef: int = 200) -> (ids, distances)- Searchsave(path: str) -> None- Serializeload(path: str) -> PyHnswIndex- Load (static method)
Thread-safe wrapper for concurrent access.
ThreadSafeAnnIndex(dim: int, metric: Distance)Same API as AnnIndex, safe for multi-threaded use.
Enum for distance metrics:
Distance.EUCLIDEAN- L2 distanceDistance.COSINE- Cosine similarityDistance.MANHATTAN- L1 distanceDistance.CHEBYSHEV- L∞ distance
For small queries, Python function call overhead dominates. Use .search_batch() for multiple vectors.
Process 64+ queries together for near-optimal throughput:
# ✓ Efficient: amortizes overhead
ids, dists = index.search_batch(queries_1000, k=5)
# ✗ Inefficient: repeats overhead
for q in queries_1000:
ids, dists = index.search(q, k=5)- Brute-force: O(N·D) where N=vectors, D=dimensions
- HNSW: ~O(N·D + N·M) where M=connectivity parameter (~5-15)
- NVIDIA GPU with CUDA compute capability 5.0+
- CUDA Toolkit 11.0+ installed
- cuBLAS libraries available
# From source with GPU
maturin develop --release --features gpu
# Or install pre-built wheels with GPU
pip install rust-annie[gpu]Automatically used for:
- Batch L2 distance calculations
- High-dimensional searches (D > 256)
from rust_annie import AnnIndex, Distance
# GPU acceleration is automatic for large batches
index = AnnIndex(512, Distance.EUCLIDEAN)
data = np.random.rand(100000, 512).astype(np.float32)
index.add(data, np.arange(len(data), dtype=np.int64))
# This will use GPU if available and beneficial
neighbors, distances = index.search_batch(queries, k=10)git clone https://github.com/arnavk23/Annie.git
cd Annie
# Install development dependencies
pip install maturin
cargo install cargo-watch
# Build and test
maturin develop
pytest tests/# Rust tests
cargo test --all
# Python tests
pytest tests/ -v
# Benchmarks
python scripts/benchmark.py --dataset medium
python scripts/dashboard.pyThis project is licensed under the MIT License. See LICENSE for details.
