Skip to main content

Crate turbovec

Crate turbovec 

Source
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();

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§

SearchResults
Top-k results for a batch of queries, as returned by TurboQuantIndex::search / TurboQuantIndex::search_with_mask.
TurboQuantIndex
Positional TurboQuant index.

Enums§

CalibrationState
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/.tvim header declaring a huge dim still 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::calibrate will 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 raw io writers must embed exactly these arrays (or use TurboQuantIndex::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 None if the input is clean.
validation_parallelizes
True when first_invalid_coord on len values 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.