Expand description
TurboQuant implementation for vector search.
Compresses high-dimensional vectors to 2-4 bits per coordinate with near-optimal distortion. Data-oblivious — no training required.
use turbovec::TurboQuantIndex;
// 1536-dim vectors compressed to 4 bits per coordinate.
let mut index = TurboQuantIndex::new(1536, 4).unwrap();
// `vectors` is a flat [f32] of length n * dim, `queries` likewise.
let vectors: Vec<f32> = vec![0.0; 1536 * 10];
let queries: Vec<f32> = vec![0.0; 1536 * 2];
index.add(&vectors);
let results = index.search(&queries, 10);
index.write("index.tv").unwrap();
let loaded = TurboQuantIndex::load("index.tv").unwrap();§Concurrent search
search takes &self and is safe to call from multiple threads
concurrently. Internally the rotation, the Lloyd-Max centroids
and the SIMD-blocked code layout are initialised lazily via
std::sync::OnceLock, so the first caller pays the one-time
initialisation cost and every subsequent caller reads the caches
without locking. TurboQuantIndex::prepare can be called once
after add/load to pay that cost up front.
Mutation still flows through &mut self, and the invariant it keeps
is stated in terms of what a reader can observe rather than in terms
of what any one mutator does: whenever the index is reachable
through &self, every populated cache describes exactly the
len() rows the index currently holds.
That holds by construction. The rotation, boundaries and centroids
are pure functions of dim and bit_width, neither of which ever
changes after the first add, so they can never go stale. The blocked
layout and the packed bit-plane rows are two encodings of the same
rows, each derivable from the other; a mutation holds &mut self for
its whole duration, so no concurrent reader exists while one of them
is being brought up to date, and by the time that borrow ends both
the row count and every populated cache describe the same rows.
Which of the two encodings a mutation updates is an implementation
detail that has changed more than once and is deliberately not
promised here. A TurboQuantIndex::loaded index may hold only the
blocked form until something needs the packed rows
(TurboQuantIndex::packed_ready reports which); elsewhere the
packed rows lead. Both give bit-identical search results.
Re-exports§
pub use error::AddError;pub use error::CalibrateError;pub use error::ConstructError;pub use error::FromPartsError;pub use error::SearchError;pub use id_map::IdMapIndex;pub use id_map::IdSearchResults;pub use warning::set_warning_hook;pub use warning::WarningHook;
Modules§
- codebook
- Lloyd-Max scalar quantizer for the Beta distribution.
- convert
- Convert an index file between every format turbovec has written.
- encode
- Encode vectors: normalize, rotate, calibrate, quantize, bit-pack, scale.
- error
- Errors returned by the user-facing construct, add and search paths.
- id_map
- Stable external IDs on top of
TurboQuantIndex. - io
- The shared write protocol behind TurboVec index files.
- pack
- Bit-plane to SIMD-blocked layout repacking.
- rotation
- Deterministic orthogonal rotation via a globally-permuted block-Hadamard transform.
- search
- SIMD-accelerated search pipeline.
- warning
- Non-fatal diagnostics: conditions a caller must be able to see but that are not failures of the operation that produced them.
Structs§
- Search
Results - Top-
kresults for a batch of queries, as returned byTurboQuantIndex::search/TurboQuantIndex::search_with_mask. - Turbo
Quant Index - Positional TurboQuant index.
Enums§
- Calibration
State - Whether an index has a TQ+ per-coordinate calibration.
Constants§
- MAX_DIM
- Upper bound on vector dimensionality. The block-Hadamard rotation and
the search-side query buffers scale linearly with
dim, but a loaded.tv/.tvimheader declaring a hugedimstill drives allocations (codebook, blocked layout, per-query rotate scratch) that are NOT bounded by the file’s own size — so an untrusted tiny file could otherwise request multi-gigabyte buffers (resource-exhaustion DoS). 16384 leaves >4x headroom over the largest embedding dimensions in common use (~4096; rare research models reach 8k-12k). Enforced identically at construction, first add, and load, so any index this build can create it can also load back. - MIN_
CALIBRATION_ ROWS - Fewest rows
TurboQuantIndex::calibratewill fit from. - MIN_
INPUT_ NORM - Norm at or below which a vector has no representable direction.
- RECOMMENDED_
CALIBRATION_ ROWS - Calibration sample size to aim for: see
encode::RECOMMENDED_CALIBRATION_ROWS.
Functions§
- expected_
codebook - The canonical Lloyd-Max codebook for
(bit_width, dim)—(boundaries, centroids). The codebook is a pure function of these two parameters; the v6 loader rejects a file whose embedded codebook is not the one this function returns (#320) — it checks the defining properties rather than re-deriving them, since the solve is far more expensive than the load (#357) — so callers serializing through the rawiowriters must embed exactly these arrays (or useTurboQuantIndex::codebook_for_write). - first_
invalid_ coord - Reject non-finite (NaN, +Inf, -Inf) or extremely-large input values.
Returns the first offending vector/coord/value tuple, or
Noneif the input is clean. - validation_
parallelizes - True when
first_invalid_coordonlenvalues splits into more than one rayon chunk, i.e. injects work into the current pool. Callers that must control which pool that is (the Python binding, whose global pool is a fork-unsafe sentinel — issue #288) gate on this.