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 in turbovec/src/lib.rs at 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 from turbovec/src/id_map.rs (the actual struct lives there) and imported in lib.rs at line 83. It wraps a TurboQuantIndex and 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.

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_remove operations (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

  • TurboQuantIndex is 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.
  • IdMapIndex adds a stable 64-bit external ID layer on top of TurboQuantIndex. It allows add_with_id, remove by external ID, and returns external IDs in search results — perfect for persistent primary keys that must survive deletions.
  • Both types support write and load for persistence, and tests in turbovec/tests show 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →