FAISS (Facebook AI Similarity Search) est une bibliothèque C++/Python développée par Meta qui permet de rechercher parmi des millions de vecteurs en quelques millisecondes. Dans ce guide, vous allez créer vos premiers index, comprendre quand utiliser IVF, HNSW ou PQ, et intégrer FAISS dans un pipeline RAG — le tout avec du code testé sur la version 1.13.2.
Pourquoi FAISS
Section intitulée « Pourquoi FAISS »FAISS se distingue par ses performances brutes : là où une recherche naïve prendrait des minutes sur 10 millions de vecteurs, FAISS répond en millisecondes grâce à des structures d’index optimisées.
| Caractéristique | Ce que FAISS offre |
|---|---|
| Vitesse | Recherche sur 1M vecteurs < 1ms (index optimisé) |
| Échelle | Supporte des milliards de vecteurs |
| GPU | Accélération x10-100 avec CUDA |
| Compression | Product Quantization réduit la mémoire 10-100x |
| Flexibilité | 15+ types d’index selon les compromis |
Cas d’usage typiques :
- RAG : retrouver les chunks pertinents parmi des millions
- Recherche sémantique : moteur de recherche intelligent
- Recommandation : produits ou contenus similaires
- Déduplication : détecter les doublons dans de grands corpus
Installation
Section intitulée « Installation »# Python 3.10+ requispython3 --version
# Créer un environnement dédiépython3 -m venv faiss-envsource faiss-env/bin/activate # Linux/Mac
# Version CPU (recommandée pour débuter)pip install faiss-cpu
# Version GPU (CUDA requis)pip install faiss-gpuVérifiez l’installation :
import faissprint(f"FAISS version: {faiss.__version__}")Les types d’index
Section intitulée « Les types d’index »FAISS propose différents index selon vos besoins en vitesse, précision et mémoire.
Index exacts (précision 100%)
Section intitulée « Index exacts (précision 100%) »| Index | Métrique | Usage |
|---|---|---|
IndexFlatL2 | Distance euclidienne | Référence, petits datasets |
IndexFlatIP | Produit scalaire | Cosine similarity (après normalisation) |
import faissimport numpy as np
d = 384 # dimension des embeddingsindex = faiss.IndexFlatL2(d) # ou IndexFlatIP(d)print(f"Index entraîné: {index.is_trained}") # True (pas d'entraînement requis)Index approximatifs (recherche rapide)
Section intitulée « Index approximatifs (recherche rapide) »| Index | Entraînement | Paramètres clés | Usage |
|---|---|---|---|
IndexIVFFlat | Oui | nlist, nprobe | 1M-100M vecteurs |
IndexHNSW | Non | M, efSearch | Faible latence CPU |
IndexIVFPQ | Oui | nlist, m, nbits | >100M, mémoire limitée |
Votre premier index : IndexFlatL2
Section intitulée « Votre premier index : IndexFlatL2 »L’index le plus simple pour commencer. Il effectue une recherche exhaustive (brute-force) et retourne des résultats exacts.
import faissimport numpy as np
# Paramètresd = 128 # dimension des vecteursnb = 1000 # nombre de vecteurs dans la basenq = 5 # nombre de requêtes
# Générer des données aléatoiresnp.random.seed(42)xb = np.random.random((nb, d)).astype('float32') # basexq = np.random.random((nq, d)).astype('float32') # requêtes
# Créer et remplir l'indexindex = faiss.IndexFlatL2(d)index.add(xb)print(f"Vecteurs indexés: {index.ntotal}")# Vecteurs indexés: 1000
# Recherche des 4 plus proches voisinsk = 4D, I = index.search(xq, k)
# D = distances, I = indicesprint(f"Indices top-{k}: {I[0]}")print(f"Distances: {D[0]}")Sortie :
Indices top-4: [644 560 373 158]Distances: [11.088875 11.401318 11.640604 11.681519]Cosine similarity avec IndexFlatIP
Section intitulée « Cosine similarity avec IndexFlatIP »Pour la similarité cosinus, normalisez vos vecteurs puis utilisez IndexFlatIP (Inner Product) :
import faissimport numpy as np
d = 384xb = np.random.random((1000, d)).astype('float32')xq = np.random.random((5, d)).astype('float32')
# Normalisation L2 (transforme en vecteurs unitaires)faiss.normalize_L2(xb)faiss.normalize_L2(xq)
# Index avec produit scalaireindex = faiss.IndexFlatIP(d)index.add(xb)
# RechercheD, I = index.search(xq, k=3)print(f"Scores cosine (plus élevé = plus similaire): {D[0]}")IndexIVFFlat : recherche rapide pour grandes bases
Section intitulée « IndexIVFFlat : recherche rapide pour grandes bases »Pour des millions de vecteurs, IndexFlat devient trop lent. IndexIVFFlat partitionne l’espace en cellules (Voronoi) et ne cherche que dans les cellules proches.
import faissimport numpy as np
d = 128nb = 10000nlist = 100 # nombre de cellules
# Quantizer = index utilisé pour trouver les cellulesquantizer = faiss.IndexFlatL2(d)index = faiss.IndexIVFFlat(quantizer, d, nlist)
# Générer des données d'entraînementxb = np.random.random((nb, d)).astype('float32')
# Entraînement OBLIGATOIRE avant add()print(f"Avant train(): is_trained = {index.is_trained}")index.train(xb)print(f"Après train(): is_trained = {index.is_trained}")
# Ajouter les vecteursindex.add(xb)print(f"Vecteurs indexés: {index.ntotal}")
# Recherche avec nprobe cellules exploréesindex.nprobe = 10 # plus de cellules = meilleur rappel, plus lentxq = np.random.random((5, d)).astype('float32')D, I = index.search(xq, k=4)print(f"Top-4: {I[0]}")Compromis nprobe :
| nprobe | Rappel | Vitesse |
|---|---|---|
| 1 | ~50% | ⚡ Très rapide |
| 10 | ~90% | ⚡ Rapide |
| 50 | ~98% | Moyen |
| nlist | 100% | 🐢 Comme Flat |
IndexHNSW : graphe hiérarchique
Section intitulée « IndexHNSW : graphe hiérarchique »HNSW (Hierarchical Navigable Small World) construit un graphe de proximité. Excellent pour la latence mais consomme plus de mémoire.
import faissimport numpy as np
d = 128M = 32 # nombre de connexions par nœud (16-64 typique)
index = faiss.IndexHNSWFlat(d, M)print(f"Entraîné par défaut: {index.is_trained}") # True
# Pas d'entraînement requis, ajout directxb = np.random.random((10000, d)).astype('float32')index.add(xb)
# Paramètre de rechercheindex.hnsw.efSearch = 64 # plus élevé = meilleur rappel
xq = np.random.random((5, d)).astype('float32')D, I = index.search(xq, k=4)print(f"Top-4: {I[0]}")Paramètres HNSW :
| Paramètre | Effet |
|---|---|
M | Connexions par nœud. Plus élevé = meilleur rappel, plus de mémoire |
efSearch | Voisins explorés. Plus élevé = meilleur rappel, plus lent |
efConstruction | Qualité du graphe à la construction |
IndexIVFPQ : compression pour très grandes bases
Section intitulée « IndexIVFPQ : compression pour très grandes bases »Product Quantization compresse les vecteurs pour économiser la mémoire. Idéal pour des milliards de vecteurs.
import faissimport numpy as np
d = 128nb = 10000nlist = 100m = 8 # nombre de sous-quantizers (doit diviser d)nbits = 8 # bits par sous-vecteur
quantizer = faiss.IndexFlatL2(d)index = faiss.IndexIVFPQ(quantizer, d, nlist, m, nbits)
xb = np.random.random((nb, d)).astype('float32')
# Entraînementindex.train(xb)index.add(xb)
print(f"Taille du code: {index.code_size} bytes par vecteur")# Taille du code: 8 bytes par vecteur (vs 512 bytes en float32)
index.nprobe = 10D, I = index.search(np.random.random((1, d)).astype('float32'), k=4)Économie mémoire :
| Configuration | Mémoire/vecteur | Compression |
|---|---|---|
| Float32 (d=128) | 512 bytes | 1x |
| PQ (m=8, nbits=8) | 8 bytes | 64x |
| PQ (m=16, nbits=8) | 16 bytes | 32x |
index_factory : création simplifiée
Section intitulée « index_factory : création simplifiée »La fonction index_factory crée des index complexes à partir d’une chaîne descriptive :
import faissimport numpy as np
d = 128xb = np.random.random((10000, d)).astype('float32')
# Différentes configurationsconfigs = { "Flat": "Recherche exacte", "IVF100,Flat": "IVF avec 100 cellules", "IVF100,PQ8": "IVF + Product Quantization", "HNSW32": "Graphe HNSW (M=32)", "IVF100,SQ8": "IVF + Scalar Quantization 8-bit",}
for factory_string, description in configs.items(): index = faiss.index_factory(d, factory_string) if not index.is_trained: index.train(xb) index.add(xb) print(f"{factory_string:15} | {description}")Syntaxe index_factory :
| Composant | Signification |
|---|---|
Flat | Index exact |
IVF{n} | Index inversé avec n cellules |
PQ{m} | Product Quantization avec m sous-vecteurs |
SQ{bits} | Scalar Quantization |
HNSW{M} | Graphe HNSW avec M connexions |
IDs personnalisés avec IndexIDMap
Section intitulée « IDs personnalisés avec IndexIDMap »Par défaut, FAISS assigne des IDs séquentiels (0, 1, 2…). Pour des IDs personnalisés :
import faissimport numpy as np
d = 128vectors = np.random.random((5, d)).astype('float32')custom_ids = np.array([1001, 1002, 1003, 1004, 1005], dtype=np.int64)
# Wrapper IndexIDMapindex_flat = faiss.IndexFlatL2(d)index = faiss.IndexIDMap(index_flat)
# Ajouter avec IDsindex.add_with_ids(vectors, custom_ids)print(f"Vecteurs: {index.ntotal}")
# La recherche retourne vos IDsD, I = index.search(vectors[:1], k=3)print(f"Top-3 IDs: {I[0]}")# Top-3 IDs: [1001 1002 1003]IndexIDMap2 : reconstruction
Section intitulée « IndexIDMap2 : reconstruction »IndexIDMap2 permet de reconstruire un vecteur à partir de son ID :
index_flat = faiss.IndexFlatL2(d)index = faiss.IndexIDMap2(index_flat)index.add_with_ids(vectors, custom_ids)
# Reconstruire le vecteur avec ID 1001reconstructed = index.reconstruct(1001)print(f"Vecteur original: {vectors[0][:5]}")print(f"Reconstruit: {reconstructed[:5]}")Sauvegarde et chargement
Section intitulée « Sauvegarde et chargement »import faiss
# Sauvegarderfaiss.write_index(index, "mon_index.faiss")
# Chargerindex = faiss.read_index("mon_index.faiss")print(f"Index rechargé: {index.ntotal} vecteurs")Suppression de vecteurs
Section intitulée « Suppression de vecteurs »La suppression fonctionne avec les index IVF et nécessite un sélecteur :
import faissimport numpy as np
d = 128nb = 1000
# Créer un index avec IDMapquantizer = faiss.IndexFlatL2(d)index_ivf = faiss.IndexIVFFlat(quantizer, d, 20)xb = np.random.random((nb, d)).astype('float32')index_ivf.train(xb)
index = faiss.IndexIDMap(index_ivf)ids = np.arange(nb, dtype=np.int64)index.add_with_ids(xb, ids)
print(f"Avant suppression: {index.ntotal}")
# Supprimer les IDs 0, 1, 2, 3, 4ids_to_remove = np.array([0, 1, 2, 3, 4], dtype=np.int64)selector = faiss.IDSelectorBatch(ids_to_remove)n_removed = index.remove_ids(selector)
print(f"Supprimés: {n_removed}")print(f"Après suppression: {index.ntotal}")Recherche sémantique avec Sentence Transformers
Section intitulée « Recherche sémantique avec Sentence Transformers »Intégration FAISS pour la recherche textuelle :
from sentence_transformers import SentenceTransformerimport faissimport numpy as np
# Charger le modèlemodel = SentenceTransformer('all-MiniLM-L6-v2')
# Corpuscorpus = [ "Kubernetes orchestre les conteneurs à grande échelle", "Docker permet de créer des images de conteneurs", "Ansible automatise la configuration des serveurs", "Terraform provisionne l'infrastructure cloud", "Prometheus collecte les métriques",]
# Générer les embeddingsembeddings = model.encode(corpus, convert_to_numpy=True)d = embeddings.shape[1]
# Normaliser pour cosine similarityfaiss.normalize_L2(embeddings)
# Créer l'indexindex = faiss.IndexFlatIP(d)index.add(embeddings)
# Recherchequery = "Comment déployer une application ?"query_vec = model.encode([query], convert_to_numpy=True)faiss.normalize_L2(query_vec)
D, I = index.search(query_vec, k=3)
print(f"Query: {query}")for rank, (idx, score) in enumerate(zip(I[0], D[0])): print(f" {rank+1}. (score={score:.4f}) {corpus[idx]}")Sortie :
Query: Comment déployer une application ? 1. (score=0.3626) Kubernetes orchestre les conteneurs à grande échelle 2. (score=0.3029) Prometheus collecte les métriques 3. (score=0.2891) Docker permet de créer des images de conteneursExemple RAG complet
Section intitulée « Exemple RAG complet »"""RAG avec FAISS."""from sentence_transformers import SentenceTransformerimport faissimport numpy as np
def creer_index_rag(documents: list[dict], model): """ Crée un index FAISS à partir de documents.
Chaque document doit avoir : id, content, source """ contents = [d["content"] for d in documents] embeddings = model.encode(contents, convert_to_numpy=True) faiss.normalize_L2(embeddings)
d = embeddings.shape[1] index_flat = faiss.IndexFlatIP(d) index = faiss.IndexIDMap(index_flat)
ids = np.arange(len(documents), dtype=np.int64) index.add_with_ids(embeddings, ids)
return index
def rechercher(question: str, index, documents: list[dict], model, top_k: int = 3): """Recherche les documents pertinents.""" query_vec = model.encode([question], convert_to_numpy=True) faiss.normalize_L2(query_vec)
D, I = index.search(query_vec, top_k)
results = [] for idx, score in zip(I[0], D[0]): if idx >= 0: doc = documents[idx] results.append({ "content": doc["content"], "source": doc["source"], "score": float(score) }) return results
# Utilisationif __name__ == "__main__": model = SentenceTransformer('all-MiniLM-L6-v2')
documents = [ {"id": 1, "content": "kubectl apply -f déploie une application", "source": "k8s.md"}, {"id": 2, "content": "Les Secrets Kubernetes sont encodés en base64", "source": "k8s.md"}, {"id": 3, "content": "docker build construit une image", "source": "docker.md"}, ]
index = creer_index_rag(documents, model)
question = "Comment sécuriser mes secrets ?" results = rechercher(question, index, documents, model)
print(f"Question: {question}\n") for r in results: print(f"[{r['source']}] (score={r['score']:.4f})") print(f" {r['content']}\n")Optimisation des performances
Section intitulée « Optimisation des performances »Multi-threading
Section intitulée « Multi-threading »import faiss
# Voir le nombre de threadsprint(f"Threads: {faiss.omp_get_max_threads()}")
# Configurerfaiss.omp_set_num_threads(8)Conseils généraux
Section intitulée « Conseils généraux »| Situation | Recommandation |
|---|---|
| < 100K vecteurs | IndexFlatL2 ou HNSW |
| 100K - 10M vecteurs | IVF{nlist},Flat avec nlist ≈ √N |
| > 10M vecteurs | IVF{nlist},PQ{m} |
| Latence critique | HNSW avec efSearch élevé |
| Mémoire limitée | IVFPQ ou IVFSQ |
| GPU disponible | faiss-gpu avec index_cpu_to_gpu |
Tableau de référence des index
Section intitulée « Tableau de référence des index »| Index | Entraînement | Mémoire | Vitesse | Précision | Usage |
|---|---|---|---|---|---|
Flat | Non | Élevée | 🐢 | 100% | Référence, < 1M |
IVFFlat | Oui | Moyenne | ⚡ | 95-99% | 1M-100M |
IVFPQ | Oui | Faible | ⚡⚡ | 85-95% | > 100M |
HNSW | Non | Élevée | ⚡⚡ | 98%+ | Latence critique |
IVFSQ | Oui | Moyenne | ⚡ | 95%+ | Compromis mémoire |
À retenir
Section intitulée « À retenir »-
IndexFlatL2/IP pour débuter et comme référence de précision
-
Normaliser + IndexFlatIP pour la similarité cosinus
-
IVF pour les grandes bases (train + nprobe pour le compromis rappel/vitesse)
-
HNSW pour la latence critique sans entraînement
-
PQ pour économiser la mémoire sur très grandes bases
-
IndexIDMap pour des IDs personnalisés
-
write_index/read_index pour persister les index