RyanCodrai /turbovec

A vector index built on TurboQuant, written in Rust with Python bindings

★ 17,486⑂ 1,482
RustCreated 2026-03-26pypi.org/project/turbovec/
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

Snapshot: 2026-11-10T09:35:25Z · Data license Apache-2.0 · JSON