Turbovec .tv and .tvim File Format Structure: Quantized Vector Storage Explained
The .tv and .tvim formats are versioned binary containers—defined in turbovec/src/io.rs—that store packed quantization codes, per-vector scales, optional TQ+ calibration data, and an ID-map side table in the case of .tvim, with all fields validated before allocation.
Turbovec, as implemented in RyanCodrai/turbovec, is a Rust library for fast quantized vector search that persists indexes to disk using the .tv and .tvim file formats. The .tv file stores the core positional index, while the .tvim file extends it with a slot_to_id mapping for vector identification. Both formats share a common binary layout for the quantized payload and are governed by the serialization logic in turbovec/src/io.rs.
Magic Number and Version Byte
Every file begins with a 4-byte magic identifier followed by a single version byte. The .tv format uses the magic sequence TVPI, and the .tvim format uses the magic sequence TVIM, as declared in turbovec/src/io.rs. Both formats currently emit version 4 when writing, while the loader explicitly accepts versions 2, 3, and 4 to preserve backward-compatible reads.
Core Header Layout in Version 4
After the magic and version, a v4 header stores the metadata required to reconstruct the index. The exact layout is controlled by the constant V4_HEADER_SIZE in turbovec/src/io.rs.
- 1 byte:
bit_width— the quantization width, which must be 2, 3, or 4. - 4 bytes:
dim— the vector dimensionality, stored as a little-endianu32. - 8 bytes:
n_vectors— the total number of vectors, stored as a little-endianu64. - 8 bytes: rotation fingerprint hash — a
u64hash that protects against rotation drift on load. N_PROBES× 4 bytes: fingerprint probes — stored asf32values.
This header upgrades prior versions by expanding n_vectors from u32 to u64 and adding the rotation fingerprint field for integrity verification.
Shared Core Payload
Following the header, both .tv and .tvim files write the same core payload in three sequential blocks.
Packed Quantization Codes
The first block contains the quantized vector data. It occupies exactly bit_width × (dim / 8) × n_vectors bytes, tightly packing the low-bit codes for every dimension of every vector. This byte stream is read directly into the engine’s distance-computation buffers.
Per-Vector Scales
The second block stores one 32-bit float per vector, consuming n_vectors × 4 bytes. These scales are applied during dequantization to rescale the packed integer codes back to their original approximate magnitudes.
TQ+ Calibration Trailer
The final shared block is the optional TQ+ calibration trailer. It begins with a n_calib count stored as a 4-byte u32, which is either 0 or exactly equal to dim. When present, the trailer continues with:
tqplus_shift:n_calib × 4bytes off32shift values.tqplus_scale:n_calib × 4bytes off32scale values.
The helper function assert_tqplus_calibration validates these arrays, while read_tqplus_trailer handles their deserialization in turbovec/src/io.rs.
.tvim-Specific ID-Map Extension
The .tvim format appends an extra table after the core payload. This block is a slot-to-ID map consisting of n_vectors entries, each an 8-byte little-endian u64. The writer enforces that the length of this table exactly matches n_vectors, ensuring a one-to-one correspondence between quantized slots and external identifiers. When loading, IdMapIndex::load_id_map returns this vector alongside the core quantized data.
Version Differences and Backward Compatibility
Turbovec maintains a small but meaningful version lineage inside turbovec/src/io.rs.
- Version 2: Uses a
u32forn_vectorsand omits the TQ+ calibration trailer entirely. - Version 3: Retains the v2 header but adds the TQ+ calibration trailer.
- Version 4: Upgrades
n_vectorstou64, introduces the rotation fingerprint hash, and keeps the TQ+ trailer.
The loader inspects the version byte and dispatches to the appropriate deserialization path, allowing modern Turbovec builds to read indexes produced by earlier releases.
Validation and Safety Guards
Before any memory is allocated, Turbovec performs strict validation on every field in turbovec/src/io.rs.
- Header sanity checks ensure that
bit_width,dim, andn_vectorsfall within plausible ranges. - Buffer sizes are computed using overflow-checked arithmetic to prevent out-of-memory crashes on malformed headers.
- Per-vector scales and every TQ+ calibration value must be finite; non-finite floats trigger an error and abort loading.
These checks prevent corrupted or malicious files from causing silent data corruption or memory exhaustion.
Reading and Writing .tv and .tvim Files
The public API in turbovec/src/lib.rs exposes safe methods for persisting and restoring indexes.
use turbovec::TurboQuantIndex;
use std::path::Path;
// Write a positional-only .tv index (version 4)
let idx: TurboQuantIndex = /* ... */;
idx.write(Path::new("my_index.tv"))?;
To load a positional index later:
use turbovec::TurboQuantIndex;
let idx = TurboQuantIndex::load("my_index.tv")?;
For indexes that require an ID mapping, use the .tvim helpers:
use turbovec::IdMapIndex;
use std::path::Path;
// Write the index plus slot_to_id table
let id_map_idx: IdMapIndex = /* ... */;
id_map_idx.write_id_map(Path::new("my_index.tvim"))?;
Loading returns all runtime components explicitly:
use turbovec::IdMapIndex;
let (bit_width, dim, n_vectors, packed, scales, tq_shift, tq_scale, slot_to_id) =
IdMapIndex::load_id_map("my_index.tvim")?;
Summary
- The
.tvand.tvimformats are little-endian binary containers defined inturbovec/src/io.rs. - Both start with a 4-byte magic (
TVPIorTVIM), a version byte, and a v4 header containingbit_width,dim,n_vectors, a rotation fingerprint, and probe values. - After the header, the core payload stores packed codes, per-vector
f32scales, and an optional TQ+ calibration trailer. - The
.tvimfile appends aslot_to_idtable ofu64values to map internal slots to external IDs. - Versions 2–4 are supported for reading, with v4 expanding
n_vectorsto 64 bits and adding drift detection. - All inputs are validated for plausibility, arithmetic overflow, and finite floating-point values before allocation.
Frequently Asked Questions
What is the difference between .tv and .tvim?
The .tv file stores only the positional quantized index, including header, packed codes, scales, and optional TQ+ calibration. The .tvim file contains the identical core payload but appends a slot_to_id mapping table, making it suitable when each vector must be associated with an external identifier.
Which Turbovec versions can read older file formats?
The loader in turbovec/src/io.rs accepts versions 2, 3, and 4. Version 4 is emitted by default during writes, ensuring that new files benefit from 64-bit vector counts and rotation fingerprinting while legacy files remain readable.
What does the rotation fingerprint in v4 prevent?
The rotation fingerprint is an 8-byte hash stored in the v4 header. When a file is loaded, Turbovec verifies this fingerprint against the current transform state to detect rotation drift, which would otherwise produce incorrect distance calculations if the quantization rotation matrix had changed.
How does Turbovec validate a file before loading?
All fields are checked for semantic plausibility, buffer sizes are computed with overflow-checked arithmetic, and every floating-point scale or calibration parameter is verified to be finite. These guards run in turbovec/src/io.rs before any heap allocation occurs, eliminating common vectors for malformed-file exploits.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →