Skip to content

Latest commit

 

History

History
124 lines (88 loc) · 5.56 KB

File metadata and controls

124 lines (88 loc) · 5.56 KB

API Reference

turbovec exposes two index types and one serialization format per type.

All examples below are Python. The Rust API mirrors it — see each type's rustdoc for the exact signatures.


TurboQuantIndex

Positional index. Each vector is identified by its insertion slot (0..n). Fast and small, but external references to slots are invalidated by swap_remove. If you need stable ids, use IdMapIndex.

from turbovec import TurboQuantIndex

idx = TurboQuantIndex(dim=1536, bit_width=4)
idx.add(vectors)                        # np.ndarray of shape (n, dim), float32
scores, indices = idx.search(queries, k=10)

idx.swap_remove(5)                      # O(1); the previously-last vector moves into slot 5

idx.write("index.tv")                   # .tv format
loaded = TurboQuantIndex.load("index.tv")

Methods

Method Notes
TurboQuantIndex(dim, bit_width) bit_width ∈ {2, 4}
add(vectors) vectors must be contiguous float32 (n, dim).
search(queries, k) Returns (scores, indices), both shape (nq, k). Indices are int64 slot positions.
swap_remove(idx) O(1). Moves the last vector into idx; returns the previous position of that moved vector (so external refs can be updated if needed).
prepare() Optional. Eagerly builds the rotation matrix, Lloyd-Max centroids and SIMD-blocked layout so the first search call doesn't pay the one-time cost.
write(path) / load(path) .tv format.
len(idx) / idx.dim / idx.bit_width Introspection.

swap_remove semantics

swap_remove(i) is named to match Rust's Vec::swap_remove: the last element moves into slot i, and the vector is truncated by one. It is not a shift (FAISS's IndexPQ::remove_ids behaviour). Order is not preserved; slot indices of vectors you didn't delete may now point at different vectors than before.

Use IdMapIndex if external references have to stay stable across deletes.


IdMapIndex

Stable-id wrapper around TurboQuantIndex. Roughly equivalent to FAISS's IndexIDMap2 — hash-table backed, O(1) remove(id).

import numpy as np
from turbovec import IdMapIndex

idx = IdMapIndex(dim=1536, bit_width=4)
idx.add_with_ids(vectors, np.array([1001, 1002, 1003], dtype=np.uint64))

scores, ids = idx.search(queries, k=10)   # ids are uint64 external ids

idx.remove(1002)                           # O(1) by id
assert 1003 in idx                         # __contains__ sugar

idx.write("index.tvim")                    # .tvim format
loaded = IdMapIndex.load("index.tvim")

Methods

Method Notes
IdMapIndex(dim, bit_width)
add_with_ids(vectors, ids) ids is a uint64 array with length vectors.shape[0]. Rejects duplicate ids (raises).
remove(id) -> bool True if the id was present and removed, False otherwise. O(1).
search(queries, k) Returns (scores, ids)ids are uint64 external ids.
contains(id) / id in idx Membership.
write(path) / load(path) .tvim format.
len(idx) / idx.dim / idx.bit_width / prepare() Same as TurboQuantIndex.

When to use which

  • TurboQuantIndex — you never delete, or you're fine with positional ids.
  • IdMapIndex — you need stable external ids (e.g. string-id → vector mapping maintained by the caller).

All the framework integrations (LangChain, LlamaIndex, Haystack) use IdMapIndex internally for exactly this reason.


File formats

.tvTurboQuantIndex

┌──────────────────────────────────────┐
│ 9-byte header                         │
│   bit_width  (u8)                     │
│   dim        (u32 LE)                 │
│   n_vectors  (u32 LE)                 │
├──────────────────────────────────────┤
│ packed codes                          │
│   (dim / 8) * bit_width * n_vectors   │
├──────────────────────────────────────┤
│ norms  (n_vectors × f32 LE)           │
└──────────────────────────────────────┘

.tvimIdMapIndex

┌──────────────────────────────────────┐
│ magic   "TVIM"  (4 bytes)             │
│ version  u8   = 1                     │
├──────────────────────────────────────┤
│ core payload (same as .tv)            │
├──────────────────────────────────────┤
│ slot_to_id  (n_vectors × u64 LE)      │
└──────────────────────────────────────┘

On load, the reverse id → slot map is rebuilt in memory. Duplicate ids in the slot_to_id table are rejected as corrupt.

Both formats are stable across minor versions. Breaking changes bump the file-format version byte (.tvim) or the header length (.tv).