RyanCodrai /turbovec
A vector index built on TurboQuant, written in Rust with Python bindings
★ 17,486⑂ 1,482
17,486
stars
+28+29+174
1,482
forks
–
watchers
–
prs
–
issues
–
releases
–
discussions
README
Python ·
Rust ·
How it compares ·
When to use something else ·
API reference ·
Benchmarks
---
Ten million 768-dimensional embeddings take 31 GB of RAM as float32. turbovec holds them in 4 GB and searches them 4.4× faster than FAISS, at matching recall.
turbovec is an in-process vector index for Python and Rust. It stores each vector at 2 or 4 bits per dimension using Google Research's TurboQuant and searches that compressed form directly with agent-optimised SIMD kernels.
- Fast. 4.4× FAISS FastScan at 4-bit and 2.2× at 2-bit, on ARM and x86. A staged search scans one bit plane and rescores the shortlist exactly.
- Low memory. 7.8× smaller than float32 at 4-bit, 15.5× at 2-bit, at the same recall as FAISS.
- Online. No training step, no rebuilds: the codebook comes from the maths, not your data. Add a vector and it is searchable; remove one in microseconds.
- Incremental. sync(path) writes only what changed since the last call — one fsync, crash-safe at any byte, milliseconds however large the index.
- Local. A library, not a service: nothing leaves your process. Pair it with an open-source embedding model for a fully offline stack.
- Filtered. Pass an allowlist of ids or a bitmask; the kernel skips what the filter excludes and returns up to k results from the allowed set.
Python
bash
pip install turbovec
python
import numpy as np
from turbovec import TurboQuantIndex
vectors = np.load("embeddings.npy").astype(np.float32) shape (n, 1536)
index = TurboQuantIndex(dim=1536, bit_width=4)
index.add(vectors)
scores, indices = index.search(vectors[:1], k=5)
print(indices) [[ 0 86544 3385 84492 24967]] int64 slot positions
print(scores.shape) (1, 5) float32 inner products
index.sync("index.tv") first sync writes the file
index.add(more_vectors)
index.sync("index.tv") later syncs write just the change, durably
index = TurboQuantIndex.load("index.tv") loads either a sync'd or a write() file
Inputs are 2-D float32 arrays of shape (n, dim); other dtypes raise rather than silently convert. Scores are inner products, so normalise your vectors if you want cosine similarity.
Need ids that survive deletes? IdMapIndex keys everything by your own uint64 ids:
python
from turbovec import IdMapIndex
index = IdMapIndex(dim=1536, bit_width=4)
index.add_with_ids(vectors, ids) ids: uint64 array, one per row
scores, ids = index.search(query, k=10) your ids, not slot positions
index.remove(1002) O(1)
Filter: only ids another system approved — a SQL predicate, an ACL, a time window.
allowed = np.array([1001, 1003, 1042], dtype=np.uint64)
scores, ids = index.search(query, k=10, allowlist=allowed) shape (1, min(k, 3))
Framework integrations are drop-in replacements for each framework's in-memory store — same public surface, same persistence, same retriever wiring:
LangChain (pip install turbovec[langch