TurboVec Index Types Explained: TurboQuantIndex vs IdMapIndex Use Cases
TLDR: TurboVec offers two index types — TurboQuantIndex for raw speed with slot-based IDs and IdMapIndex for stable external IDs that survive deletions — each with eager or lazy constructors depending on your data pipeline.
TurboVec is a Rust library for high-performance vector indexing and approximate nearest-neighbor search. As implemented in the RyanCodrai/turbovec repository, it provides two distinct index types, each designed for a specific set of use cases. Understanding these index types in TurboVec is critical for choosing the right structure for your embeddings, whether you're dealing with high-dimensional vectors in production or a simple in-memory search. In this guide, we'll break down both TurboQuantIndex and IdMapIndex, their construction, and when to use each.
Overview of TurboVec Index Types
TurboVec's two index families are defined in the source code:
TurboQuantIndex— defined inturbovec/src/lib.rsat line 95. This is the core quantized index that packs vectors into 2–4 bits per coordinate and uses a blocked layout for SIMD-friendly search.IdMapIndex— re-exported fromturbovec/src/id_map.rs(the actual struct lives there) and imported inlib.rsat line 83. It wraps aTurboQuantIndexand adds a bidirectional mapping between external 64-bit IDs and internal slot numbers.
Both types support identical search and persistence operations, but they differ fundamentally in how they handle vector identity.
TurboQuantIndex: Fast Slot-Based Vector Search
What It Stores
TurboQuantIndex stores vectors in a blocked, packed bit-plane representation with a configurable bit width (2 to 4 bits per coordinate). The index also maintains a lazily-created SIMD-friendly cache that speeds up repeated searches on the same data.
Primary Use Case
Use TurboQuantIndex when:
- You need maximum search speed (both single-threaded and multi-threaded).
- Your vectors are high-dimensional embeddings (e.g., 128 to 1536+ dimensions).
- You only require the slot number (0-based index) as the identifier for each vector.
This is perfect for feature store lookups where you can keep your own external ID array outside the index and simply translate slot numbers back to your original keys.
Construction in Code
use turbovec::TurboQuantIndex;
// Eager construction — you know the dimension up front
let mut idx = TurboQuantIndex::new(1536, 4).expect("construct");
// Add 10 vectors (flat f32 slice)
let vectors = vec![0.0_f32; 1536 * 10];
idx.add(&vectors);
// Search with 2 queries, top-10 results each
let queries = vec![0.0_f32; 1536 * 2];
let results = idx.search(&queries, 10);
// results.indices are slot numbers (0-based)
println!("{:?}", results.indices);
The new(dim, bit_width) constructor is an eager builder; alternatively, TurboQuantIndex::new_lazy(bit_width) defers dimension inference until the first add call, which is useful when you don't know the vector size upfront.
IdMapIndex: Stable External IDs That Survive Updates
What It Stores
IdMapIndex internally wraps a TurboQuantIndex but also maintains a bidirectional map — a Vec<u64> for slot→external ID lookups and a HashMap<u64, usize> for external ID→slot lookups. This extra metadata ensures that external identifiers don't change, even when vectors are moved or removed.
Primary Use Case
Use IdMapIndex when you need persistent identifiers — e.g., database primary keys, user IDs, or document IDs — that must:
- Remain stable across
swap_removeoperations (deletions). - Be directly returned in search results, so you can immediately know which records are closest.
This index type is ideal for production deployments where you have to delete partial data without invalidating the rest of the index.
Construction in Code
use turbovec::{IdMapIndex, IdSearchResults};
// Create an index with stable external IDs (128-dim vectors, 3-bit quant)
let mut idx = IdMapIndex::new(128, 3).expect("Failed to create index");
// Insert a vector with a user-defined ID
let vec = vec![0.5_f32; 128];
let external_id = 42_u64;
idx.add_with_id(&vec, external_id).expect("Failed to add");
// Remove by external ID (slot gets swapped internally, but IDs stay stable)
idx.remove(external_id).expect("Failed to remove");
// Search; results contain the original external IDs
let queries = vec![0.5_f32; 128];
let results: IdSearchResults = idx.search(&queries, 5);
println!("{:?}", results.ids);
IdMapIndex also offers new(dim, bit_width) and new_lazy(bit_width) constructors, mirroring the behavior of TurboQuantIndex.
Serialization: Persisting and Loading Indexes
Both index types implement the same write and load methods, allowing you to persist and restore an index from disk:
// Save to disk
idx.write("my_index.tv").expect("Failed to write");
// Load later (type is inferred from the file)
let loaded = TurboQuantIndex::load("my_index.tv").expect("Failed to load");
The end-to-end example in turbovec/tests/write_path.rs demonstrates creating, writing, loading, and searching both index types, providing a complete reference for persistence.
Key Source Files in the TurboVec Repository
| File | What You’ll Find |
|---|---|
turbovec/src/lib.rs |
Public API, re-exports, and the core TurboQuantIndex definition |
turbovec/src/id_map.rs |
Implementation of IdMapIndex, the stable-ID mapping logic |
turbovec/tests/write_path.rs |
End-to-end example covering creation, persistence, and search for both index types |
turbovec/tests/id_map.rs |
Tests validating IdMapIndex behavior (addition, deletion, ID stability) |
These files together illustrate the two index families, their construction patterns, and typical workflows.
Summary
TurboQuantIndexis the base index, optimized for speed with slot-based (integer) identifiers. It supports eager (new) and lazy (new_lazy) construction and is ideal when external IDs are maintained elsewhere.IdMapIndexadds a stable 64-bit external ID layer on top ofTurboQuantIndex. It allowsadd_with_id,removeby external ID, and returns external IDs in search results — perfect for persistent primary keys that must survive deletions.- Both types support
writeandloadfor persistence, and tests inturbovec/testsshow complete workflows.
Choose TurboQuantIndex for raw speed and simple slot indexing, or IdMapIndex for stable external keys in production systems.
Frequently Asked Questions
Can you mix TurboQuantIndex and IdMapIndex in the same application?
Yes, you can. Both index types are independent structs. You might use a TurboQuantIndex for rapid throwaway searches and an IdMapIndex for persistent storage, as long as you keep the external IDs consistent manually. The source code even re-exports both from lib.rs.
How do the eager and lazy constructors differ in TurboVec?
The eager constructor (new(dim, bit_width)) requires you to know the vector dimension upfront, while the lazy new_lazy(bit_width) infers the dimension from the first add call. Lazy construction is convenient when your data source doesn't expose dimensions early, but it incurs a small first‑call overhead.
Does IdMapIndex support arbitrary external ID types?
The IdMapIndex implementation specifically uses u64 for external IDs (as seen in add_with_id and remove methods). For string or custom IDs, you would need to map them to u64 internally before using the index.
How much memory does IdMapIndex add compared to TurboQuantIndexBow index?
IdMapIndex stores a Vec<u64> and a HashMap<u64, usize> on top of the quantized vectors. For millions of entries, this adds about 16 bytes per vector for the slot→ID lookup plus extra hash map overhead. This is a reasonable trade‑off when stable IDs are essential.
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 →