Qu'est-ce que TurboVec ?
TurboVec est une bibliothèque d'indexation vectorielle haute performance écrite en Rust avec des bindings Python, construite sur l'algorithme de quantification TurboQuant de Google Research. Son objectif principal est de résoudre deux problèmes majeurs des systèmes RAG (Retrieval-Augmented Generation) : l'occupation mémoire et la vitesse de recherche.
Pourquoi TurboVec est-il nécessaire ?
Dans les scénarios de recherche vectorielle classiques, si vous avez un corpus de 10 millions de documents stockés sous forme de vecteurs float32, vous avez besoin d'environ 31 Go de RAM. TurboVec utilise la quantification indépendante des données (data-oblivious quantization) pour compresser le même jeu de données à seulement 4 Go — soit une réduction de 87 % de l'utilisation mémoire — tout en étant plus rapide que FAISS.
TurboVec vs FAISS
| Caractéristique | FAISS | TurboVec |
|---|---|---|
| Occupation mémoire | Élevée (float32 ou quantification PQ) | Très faible (quantification TurboQuant) |
| Entraînement nécessaire | ✅ Nécessite une phase d'entraînement | ❌ Aucun entraînement, prêt à l'emploi |
| Ajout de vecteurs en ligne | ⚠️ Reconstruire l'index requis | ✅ Ajout en temps réel, sans reconstruction |
| Recherche filtrée | Post-traitement requis | ✅ Support au niveau noyau, sans perte de performance |
| Optimisation SIMD | Oui | Écriture manuelle NEON (ARM) + AVX-512BW (x86) |
| Exécution 100 % locale | ✅ | ✅ |
| Bindings Python | ✅ | ✅ |
| Support natif Rust | ❌ | ✅ |
Technologie centrale : l'algorithme TurboQuant
TurboQuant est un nouvel algorithme de quantification proposé par Google Research en 2025, dont les caractéristiques clés sont :
- Indépendant des données (Data-Oblivious) : pas besoin d'entraîner un codebook sur un jeu de données spécifique, ce qui évite le surcoût d'entraînement de la quantification PQ (Product Quantization) traditionnelle
- Proche de la limite de Shannon : la distorsion est proche de l'optimum théorique
- Largeur de bits flexible : prend en charge la quantification en 2-bit, 4-bit et 8-bit pour équilibrer le rappel (recall) et l'occupation mémoire
Dans les benchmarks, TurboVec est 12 à 20 % plus rapide que FAISS IndexPQFastScan sur architecture ARM, et équivalent ou légèrement meilleur sur x86.
Installation de TurboVec
TurboVec propose des interfaces en Python et en Rust.
Installation Python
pip install turbovec
Pour une intégration avec LangChain, LlamaIndex ou d'autres frameworks, installez les dépendances supplémentaires :
# Intégration LangChain
pip install turbovec[langchain]
# Intégration LlamaIndex
pip install turbovec[llama-index]
# Intégration Haystack
pip install turbovec[haystack]
# Intégration Agno
pip install turbovec[agno]
Installation Rust
Ajoutez la dépendance dans votre Cargo.toml :
[dependencies]
turbovec = "0.1"
Prise en main rapide : utilisation Python de base
Créer un index et ajouter des vecteurs
import numpy as np
from turbovec import TurboQuantIndex
# Création de l'index : dimension 1536 (dimension par défaut d'OpenAI embedding), quantification 4-bit
index = TurboQuantIndex(dim=1536, bit_width=4)
# Génération de vecteurs d'exemple (remplacez par vos vrais embeddings)
vectors = np.random.rand(10000, 1536).astype(np.float32)
# Ajout des vecteurs à l'index
index.add(vectors)
# Vous pouvez continuer à ajouter des vecteurs sans reconstruire l'index
more_vectors = np.random.rand(5000, 1536).astype(np.float32)
index.add(more_vectors)
print(f"L'index contient {len(index)} vecteurs")
Effectuer une recherche
# Génération d'un vecteur de requête
query = np.random.rand(1536).astype(np.float32)
# Recherche des 10 vecteurs les plus similaires
scores, indices = index.search(query, k=10)
print("Scores de similarité :", scores)
print("Index des vecteurs :", indices)
Sauvegarde et chargement persistants
# Sauvegarde de l'index sur le disque
index.write("my_index.tq")
# Chargement de l'index depuis le disque
loaded_index = TurboQuantIndex.load("my_index.tq")
# Vérification du chargement
scores, indices = loaded_index.search(query, k=10)
Fonctionnalité avancée n°1 : mappage d'ID externes
Dans les applications réelles, vous avez généralement besoin d'associer les index vectoriels aux identifiants de vos documents en base de données. TurboVec fournit IdMapIndex pour répondre à ce besoin.
Ajout de vecteurs avec ID
import numpy as np
from turbovec import IdMapIndex
# Création d'un index prenant en charge les ID externes
index = IdMapIndex(dim=1536, bit_width=4)
# Supposons que vous ayez 3 vecteurs avec les ID externes 1001, 1002, 1003
vectors = np.random.rand(3, 1536).astype(np.float32)
external_ids = np.array([1001, 1002, 1003], dtype=np.uint64)
# Ajout des vecteurs avec leurs ID externes
index.add_with_ids(vectors, external_ids)
# La recherche retourne les ID externes, pas les index internes
query = np.random.rand(1536).astype(np.float32)
scores, ids = index.search(query, k=10)
print("ID externes retournés :", ids) # [1001, 1003, 1002, ...]
Suppression de vecteurs
IdMapIndex permet de supprimer directement des vecteurs via leur ID externe, avec une complexité temporelle de O(1) :
# Suppression du vecteur avec l'ID 1002
index.remove(1002)
# Nouvelle recherche : 1002 n'apparaît plus dans les résultats
scores, ids = index.search(query, k=10)
print("ID après suppression :", ids) # 1002 n'est plus présent
Persistance de l'index avec ID
# Sauvegarde
index.write("my_index.tvim")
# Chargement
loaded_index = IdMapIndex.load("my_index.tvim")
Fonctionnalité avancée n°2 : recherche filtrée (Filter-at-Search)
C'est l'un des points forts de TurboVec. Dans les bases de données vectorielles traditionnelles, si vous voulez limiter les résultats de recherche à un locataire ou à une plage temporelle spécifique, vous devez d'abord trouver un grand nombre de candidats, puis filtrer au niveau applicatif — ce qui entraîne une baisse du rappel et un gaspillage de performances.
TurboVec prend en charge le filtrage au niveau du noyau via le paramètre allowlist, qui transmet la liste des ID autorisés. Le noyau SIMD ignore directement les emplacements non autorisés pendant le calcul.
Scénario : système RAG multi-locataires
Imaginez un système RAG multi-locataires où chaque locataire ne peut accéder qu'à ses propres documents :
import numpy as np
from turbovec import IdMapIndex
# Création de l'index
idx = IdMapIndex(dim=1536, bit_width=4)
# Supposons 10 000 vecteurs, chacun correspondant à un ID de document
vectors = np.random.rand(10000, 1536).astype(np.float32)
doc_ids = np.arange(1, 10001, dtype=np.uint64)
idx.add_with_ids(vectors, doc_ids)
# Simulation d'une requête base de données : récupération des ID des documents du locataire A
# Dans un cas réel, cela viendrait de PostgreSQL / MySQL
tenant_a_docs = np.array([1, 5, 10, 15, 20, 25, 30, 35, 40, 45], dtype=np.uint64)
# Recherche limitée aux documents du locataire A
query = np.random.rand(1536).astype(np.float32)
scores, ids = idx.search(query, k=5, allowlist=tenant_a_docs)
print("Documents les plus pertinents pour le locataire A :", ids)
# Les résultats ne contiendront que les ID présents dans tenant_a_docs
Avantages en termes de performances
Le filtrage a lieu à l'intérieur du noyau SIMD, avec un mécanisme de court-circuit par blocs de 32 vecteurs :
- Si un bloc ne contient aucun emplacement autorisé, tout le bloc est sauté (pas de recherche LUT ni de calcul de score)
- Si un bloc contient des emplacements autorisés, seuls ceux-ci sont évalués
- Pour des filtres très sélectifs (peu d'ID autorisés par rapport au total), cela évite la majeure partie des calculs SIMD
Le résultat a une longueur de min(k, len(allowlist)). Si la liste autorisée est plus petite que k, le résultat contient exactement len(allowlist) éléments, sans être rempli de résultats non pertinents.
Intégration avec les frameworks RAG populaires
TurboVec s'intègre parfaitement à LangChain, LlamaIndex, Haystack et Agno — il suffit de modifier les imports.
Intégration LangChain
from langchain_community.vectorstores import TurboVec
from langchain_openai import OpenAIEmbeddings
# Initialisation des embeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# Création du store vectoriel TurboVec
vector_store = TurboVec.from_documents(
documents=documents, # votre liste de documents
embedding=embeddings,
bit_width=4 # quantification 4-bit
)
# Recherche par similarité
results = vector_store.similarity_search("Votre question", k=5)
# Persistance
vector_store.save_local("turbovec_index")
# Chargement
loaded_store = TurboVec.load_local("turbovec_index", embeddings)
Intégration LlamaIndex
from llama_index.vector_stores.turbovec import TurboVecVectorStore
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
# Chargement des documents
documents = SimpleDirectoryReader("./data").load_data()
# Création du store vectoriel TurboVec
vector_store = TurboVecVectorStore(dim=1536, bit_width=4)
# Création de l'index
index = VectorStoreIndex.from_documents(
documents,
vector_store=vector_store
)
# Moteur de requête
query_engine = index.as_query_engine()
response = query_engine.query("Votre question")
print(response)
Intégration Haystack
from haystack_integrations.document_stores.turbovec import TurboVecDocumentStore
from haystack.components.embedders import SentenceTransformersDocumentEmbedder
from haystack import Pipeline
# Création du store de documents
document_store = TurboVecDocumentStore(dim=768, bit_width=4)
# Embedder
embedder = SentenceTransformersDocumentEmbedder(model="sentence-transformers/all-MiniLM-L6-v2")
# Construction du pipeline
pipeline = Pipeline()
pipeline.add_component("embedder", embedder)
# ... ajoutez d'autres composants
Utilisation native en Rust
Si vous construisez un service backend haute performance en Rust, vous pouvez utiliser directement l'API Rust native de TurboVec.
Utilisation de base
use turbovec::TurboQuantIndex;
use ndarray::Array2;
fn main() {
// Création de l'index : 1536 dimensions, quantification 4-bit
let mut index = TurboQuantIndex::new(1536, 4);
// Préparation des données vectorielles (exemple avec des nombres aléatoires)
let vectors = Array2::<f32>::random((10000, 1536), &mut rand::thread_rng());
// Ajout des vecteurs
index.add(&vectors);
// Préparation du vecteur de requête
let query = Array1::<f32>::random(1536, &mut rand::thread_rng());
// Recherche des 10 vecteurs les plus similaires
let results = index.search(&query, 10);
println!("Top 10 résultats : {:?}", results);
// Persistance
index.write("index.tv").unwrap();
// Chargement
let loaded = TurboQuantIndex::load("index.tv").unwrap();
}
Index avec ID externes
use turbovec::IdMapIndex;
fn main() {
let mut index = IdMapIndex::new(1536, 4);
let vectors = Array2::<f32>::random((100, 1536), &mut rand::thread_rng());
let ids = vec![1001u64, 1002, 1003, /* ... */];
index.add_with_ids(&vectors, &ids);
let query = Array1::<f32>::random(1536, &mut rand::thread_rng());
let (scores, returned_ids) = index.search(&query, 10);
println!("ID externes retournés : {:?}", returned_ids);
// Suppression
index.remove(1002);
// Persistance
index.write("index.tvim").unwrap();
let loaded = IdMapIndex::load("index.tvim").unwrap();
}
Benchmarks de performance
D'après les données de benchmark officielles, voici les performances de TurboVec sur différents jeux de données et largeurs de bits :
Comparaison du rappel (TurboQuant vs FAISS IndexPQ)
Conditions de test : 100 000 vecteurs, k=64
| Jeu de données | Largeur de bit | TurboVec R@1 | FAISS R@1 | Avantage |
|---|---|---|---|---|
| GloVe d=200 | 4-bit | +0,3 pts | Référence | TurboVec supérieur |
| OpenAI d=1536 | 2-bit | +0,4 pts | Référence | TurboVec supérieur |
| OpenAI d=1536 | 4-bit | +1,2 pts | Référence | TurboVec supérieur |
| OpenAI d=3072 | 4-bit | +3,4 pts | Référence | TurboVec significativement supérieur |
Dans tous les tests, les deux convergent vers un rappel de 1,0 pour k=4.
Comparaison de vitesse
- ARM (NEON) : TurboVec est 12 à 20 % plus rapide que FAISS IndexPQFastScan
- x86 (AVX-512BW) : TurboVec est équivalent ou légèrement meilleur que FAISS
Occupation mémoire
| Nombre de vecteurs | Dimension | Mémoire float32 | Mémoire TurboVec (4-bit) | Économie |
|---|---|---|---|---|
| 10 millions | 1536 | ~31 Go | ~4 Go | 87 % |
| 1 million | 1536 | ~3,1 Go | ~400 Mo | 87 % |
| 100 000 | 1536 | ~310 Mo | ~40 Mo | 87 % |
Cas pratique : construction d'un système RAG local
Voici un exemple complet montrant comment construire un système RAG entièrement local avec TurboVec, sans aucun service cloud.
Préparation de l'environnement
pip install turbovec sentence-transformers langchain-community langchain-openai
Code complet
import numpy as np
from turbovec import IdMapIndex
from sentence_transformers import SentenceTransformer
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 1. Chargement du modèle d'embedding (local, sans clé API)
print("Chargement du modèle d'embedding...")
model = SentenceTransformer('all-MiniLM-L6-v2') # 384 dimensions
dim = 384
# 2. Chargement et découpage des documents
print("Chargement des documents...")
loader = TextLoader('./data/my_documents.txt', encoding='utf-8')
documents = loader.load()
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
chunks = splitter.split_documents(documents)
print(f"Découpé en {len(chunks)} blocs de texte")
# 3. Génération des vecteurs et construction de l'index
print("Génération des vecteurs et construction de l'index...")
index = IdMapIndex(dim=dim, bit_width=4)
texts = [chunk.page_content for chunk in chunks]
metadata_list = [chunk.metadata for chunk in chunks]
# Génération par lots des vecteurs
embeddings = model.encode(texts, show_progress_bar=True)
# Ajout des vecteurs avec leurs métadonnées (utilisation de l'index comme ID externe)
external_ids = np.arange(len(texts), dtype=np.uint64)
index.add_with_ids(embeddings.astype(np.float32), external_ids)
# Sauvegarde de l'index
index.write("rag_index.tvim")
print("Index sauvegardé")
# 4. Fonction de recherche
def search(query: str, k: int = 5):
"""Recherche les blocs de texte les plus pertinents"""
# Chargement de l'index
idx = IdMapIndex.load("rag_index.tvim")
# Génération du vecteur de requête
query_embedding = model.encode([query])[0].astype(np.float32)
# Recherche
scores, ids = idx.search(query_embedding, k=k)
# Retour des résultats
results = []
for score, doc_id in zip(scores, ids):
doc_id = int(doc_id)
results.append({
'text': texts[doc_id],
'score': float(score),
'metadata': metadata_list[doc_id]
})
return results
# 5. Test de la recherche
if __name__ == "__main__":
query = "Qu'est-ce que TurboVec ?"
results = search(query, k=3)
print("\n=== Résultats de la recherche ===")
for i, result in enumerate(results, 1):
print(f"\n[{i}] Similarité : {result['score']:.4f}")
print(f"Contenu : {result['text'][:200]}...")
Ajout du filtrage
Si vos documents ont des étiquettes de catégorie, vous pouvez filtrer lors de la recherche :
def search_with_filter(query: str, category: str, k: int = 5):
"""Recherche avec filtrage par catégorie"""
idx = IdMapIndex.load("rag_index.tvim")
# Récupération des ID de documents appartenant à la catégorie spécifiée
allowed_ids = np.array([
i for i, meta in enumerate(metadata_list)
if meta.get('category') == category
], dtype=np.uint64)
if len(allowed_ids) == 0:
return []
# Génération du vecteur de requête
query_embedding = model.encode([query])[0].astype(np.float32)
# Recherche avec filtrage
scores, ids = idx.search(query_embedding, k=k, allowlist=allowed_ids)
results = []
for score, doc_id in zip(scores, ids):
doc_id = int(doc_id)
results.append({
'text': texts[doc_id],
'score': float(score),
'metadata': metadata_list[doc_id]
})
return results
# Recherche uniquement dans les documents de la catégorie "technique"
results = search_with_filter("Comment installer ?", category="technique", k=3)
Questions fréquentes
Q1 : À quels scénarios TurboVec est-il adapté ?
- Environnements à mémoire limitée : stocker de grands index vectoriels dans une RAM restreinte
- Scénarios sensibles à la vie privée : les données ne peuvent pas quitter le réseau local ou le VPC
- Corpus en croissance dynamique : nécessité d'ajouter fréquemment de nouveaux vecteurs sans pouvoir reconstruire l'index
- RAG multi-locataires : filtrage fin des permissions lors de la recherche
Q2 : À quels scénarios TurboVec n'est-il pas adapté ?
- Recherche distribuée à très grande échelle : si vous devez répartir l'index sur plusieurs machines, envisagez Milvus, Weaviate ou d'autres bases vectorielles distribuées
- Filtrage complexe par métadonnées : TurboVec ne prend en charge que le filtrage par ID ; les requêtes complexes sur métadonnées doivent être traitées au niveau applicatif
Q3 : Comment choisir le bit_width ?
- 4-bit : valeur recommandée par défaut, bon équilibre entre rappel et occupation mémoire
- 2-bit : compression extrême, rappel légèrement réduit, adapté aux environnements avec une mémoire très limitée
- 8-bit : précision maximale, occupation mémoire environ deux fois supérieure au 4-bit, rappel proche du float32
Q4 : Peut-on utiliser TurboVec et FAISS ensemble ?
Oui. Vous pouvez utiliser FAISS pour une recherche grossière (coarse search) et TurboVec pour un reclassement fin (rerank), ou l'inverse. Les deux bibliothèques ont des conceptions d'API différentes et peuvent être utilisées de manière complémentaire.
Résumé
TurboVec est une bibliothèque de recherche vectorielle émergente qui mérite l'attention. Grâce à l'algorithme TurboQuant, elle atteint un excellent équilibre entre occupation mémoire, vitesse de recherche et facilité d'utilisation :
- ✅ Réduction mémoire de 87 % : 10 millions de vecteurs passent de 31 Go à 4 Go
- ✅ Vitesse supérieure à FAISS : 12 à 20 % plus rapide sur ARM, équivalente sur x86
- ✅ Aucun entraînement nécessaire : prêt à l'emploi, ajout de vecteurs en ligne
- ✅ Filtrage au niveau noyau : idéal pour le RAG multi-locataires
- ✅ Intégration écosystème : compatible avec LangChain, LlamaIndex, Haystack
Si votre projet RAG rencontre des goulots d'étranglement mémoire ou nécessite un déploiement 100 % local, TurboVec mérite d'être essayé.
Adresse du projet : https://github.com/RyanCodrai/turbovec
Article de recherche : TurboQuant: Data-Oblivious Vector Quantization