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
- Development & CI
- GPU Acceleration
- Contributing
- License
- Multiple Backends:
- Brute-force (exact) with SIMD acceleration
- HNSW (approximate) for large-scale datasets
- Multiple Distance Metrics: Euclidean, Cosine, Manhattan, Chebyshev
- Batch Queries for efficient processing
- Thread-safe indexes with concurrent access
- Zero-copy NumPy integration
- On-disk Persistence with serialization
- Filtered Search with custom Python callbacks
- GPU Acceleration for brute-force calculations
- Multi-platform support (Linux, Windows, macOS)
- Automated CI with performance tracking
# Stable release from PyPI:
pip install rust-annie
# Install with GPU support (requires CUDA):
pip install rust-annie[gpu]
# Or install from source:
git clone https://github.com/Programmers-Paradise/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)from rust_annie import PyHnswIndex
index = PyHnswIndex(dims=128)
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, _ = index.search(query, k=10)from rust_annie import AnnIndex, Distance
import numpy as np
# Create index
idx = AnnIndex(4, Distance.COSINE)
# Add data
data = np.random.rand(50, 4).astype(np.float32)
ids = np.arange(50, dtype=np.int64)
idx.add(data, ids)
# Search
labels, dists = idx.search(data[10], k=3)
print(labels, dists)from rust_annie import AnnIndex, Distance
import numpy as np
# Create index
idx = AnnIndex(16, Distance.EUCLIDEAN)
# Add data
data = np.random.rand(1000, 16).astype(np.float32)
ids = np.arange(1000, dtype=np.int64)
idx.add(data, ids)
# Batch search
queries = data[:32]
labels_batch, dists_batch = idx.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
idx = ThreadSafeAnnIndex(32, Distance.EUCLIDEAN)
# Add data
data = np.random.rand(500, 32).astype(np.float32)
ids = np.arange(500, dtype=np.int64)
idx.add(data, ids)
# Concurrent searches
def task(q):
return idx.search(q, k=5)
with ThreadPoolExecutor(max_workers=8) as executor:
futures = [executor.submit(task, data[i]) for i in range(8)]
for f in futures:
print(f.result())from rust_annie import AnnIndex, Distance
import numpy as np
# Create index
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)This section demonstrates a complete, beginner-friendly example of how to build and query a brute-force AnnIndex using Python.
A brute-force AnnIndex exhaustively compares the query vector with every vector in the dataset. Though it checks all vectors, it's extremely fast thanks to its underlying Rust + SIMD implementation.
- Initialize a
brute-force AnnIndexwith 128 dimensions and cosine distance. - Generate and add a batch of random vectors with unique IDs.
- Perform a top-5 nearest-neighbor search on a new query vector.
- Print the IDs and distances of the closest matches.
Make sure you’ve installed the library first:
pip install rust-annie # if not installed alreadyimport numpy as np
from rust_annie import AnnIndex, Distance
index = AnnIndex(dim=128, metric=Distance.COSINE)
vectors = np.random.rand(1000, 128).astype(np.float32)
ids = np.arange(1000, dtype=np.int64)
index.add(vectors, ids)
query = np.random.rand(128).astype(np.float32)
top_ids, distances = index.search(query, k=5)
print("Top 5 nearest neighbors:")
for i in range(5):
print(f"ID: {top_ids[i]}, Distance: {distances[i]}")Measured on a 6-core CPU:
| Setting | Pure Python | Rust (Annie) | Speedup |
|---|---|---|---|
N=5000, D=32, k=5 |
~0.31 ms | ~2.16 ms | 0.14× |
NOTE: Rust may appear slower on small single-query benchmarks. For larger workloads, use
.search_batchor multi-threaded execution to unleash its full power.
| Mode | Per-query Time |
|---|---|
| Pure-Python (NumPy - 𝑙2) | ~2.8 ms |
| Rust AnnIndex single query | ~0.7 ms |
| Rust AnnIndex batch (64 queries) | ~0.23 ms |
That’s a ~4× speedup vs. NumPy!
| Operation | Dataset Size | Time (ms) | Speedup vs Python |
|---|---|---|---|
| Single Query (Brute) | 10,000 × 64 | 0.7 | 4× |
| Batch Query (64) | 10,000 × 64 | 0.23 | 12× |
| HNSW Query | 100,000 × 128 | 0.05 | 56× |
You’ll find:
- Time-series plots for multiple configurations
- Speedup trends
- Auto-updating graphs on every push to
main
Create a new brute-force index.
add(data: np.ndarray[N×D], ids: np.ndarray[N]) -> Nonesearch(query: np.ndarray[D], k: int) -> (ids: np.ndarray[k], dists: np.ndarray[k])search_batch(data: np.ndarray[N×D], k: int) -> (ids: np.ndarray[N×k], dists: np.ndarray[N×k])remove(ids: Sequence[int]) -> Nonesave(path: str) -> Noneload(path: str) -> AnnIndex(static)
Enum: Distance.EUCLIDEAN, Distance.COSINE, Distance.MANHATTAN
Same API as AnnIndex, safe for concurrent use.
| Class | Description |
|---|---|
| AnnIndex | Brute-force exact search |
| PyHnswIndex | Approximate HNSW index |
| ThreadSafeAnnIndex | Thread-safe wrapper for AnnIndex |
| Distance | Distance metrics (Euclidean, Cosine, etc) |
| Method | Description |
|---|---|
| add(data, ids) | Add vectors to index |
| search(query, k) | Single query search |
| search_batch(queries, k) | Batch query search |
| search_filter_py(query, k, filter_fn) | Filtered search |
| save(path) | Save index to disk |
| load(path) | Load index from disk |
CI runs on GitHub Actions, building wheels on Linux, Windows, macOS, plus:
cargo testpytestbenchmark.py&batch_benchmark.py&compare_results.py
# Run tests
cargo test
pytest tests/
# Run benchmarks
python scripts/benchmark.py
python scripts/batch_benchmark.py
# Generate documentation
mkdocs buildCI pipeline includes:
- Cross-platform builds (Linux, Windows, macOS)
- Unit tests and integration tests
- Performance benchmarking
- Documentation generation
Benchmarks are tracked over time using:
scripts/benchmark.py— runs single-query performance testsdashboard.py— generates a Plotly dashboard + freshness badge- GitHub Actions auto-runs and updates benchmarks on every push to
main - Live Dashboard
from rust_annie import AnnIndex, MetadataType, MetadataValue
import numpy as np
# Define metadata schema
schema = {
"country": MetadataType.String,
"score": MetadataType.Float,
"tags": MetadataType.Tags,
}
idx = AnnIndex(dim=128, metric="cosine")
idx.py_set_metadata_schema(schema)
# Prepare data
vectors = np.random.rand(1000, 128).astype(np.float32)
ids = np.arange(1000, dtype=np.int64)
metadata = [
{
"country": MetadataValue.String("IN"),
"score": MetadataValue.Float(0.9),
"tags": MetadataValue.Tags(["sports", "trending"])
}
for _ in ids
]
# Add vectors with metadata
idx.py_add_with_metadata(vectors, ids, metadata)
# Query with predicate filtering
query = np.random.rand(128).astype(np.float32)
result_ids, result_dists = idx.py_search_filtered(query.tolist(), k=10, predicate='country=="IN" AND score>0.8')
print("Filtered IDs:", result_ids)
print("Distances:", result_dists)Annie optionally supports GPU-backed brute-force distance computation using cust (CUDA for Rust). It significantly accelerates batch queries and high-dimensional searches.
Supported:
- CUDA (NVIDIA GPUs, via
.ptx) - Batched L2 distance (prototype)
ROCm (AMD GPU) support is not yet available.
Enable CUDA support for brute-force calculations:
# Install with GPU support
pip install rust-annie[gpu]
# Or build from source with GPU features
maturin develop --release --features gpuSupported operations:
- Batch L2 distance calculations
- High-dimensional similarity search
Requirements:
- NVIDIA GPU with CUDA support
- CUDA Toolkit installed
Contributions are welcome! Please:
- Fork the repo
- Create a feature branch
- Add tests & docs
- Submit a Pull Request
See CONTRIBUTING.md for details.
This project is licensed under the MIT License. See LICENSE for details.
