Aller au contenu
Développement medium

FAISS : recherche vectorielle haute performance

18 min de lecture

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.

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éristiqueCe que FAISS offre
VitesseRecherche sur 1M vecteurs < 1ms (index optimisé)
ÉchelleSupporte des milliards de vecteurs
GPUAccélération x10-100 avec CUDA
CompressionProduct 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
Fenêtre de terminal
# Python 3.10+ requis
python3 --version
# Créer un environnement dédié
python3 -m venv faiss-env
source faiss-env/bin/activate # Linux/Mac
# Version CPU (recommandée pour débuter)
pip install faiss-cpu
# Version GPU (CUDA requis)
pip install faiss-gpu

Vérifiez l’installation :

1.13.2
import faiss
print(f"FAISS version: {faiss.__version__}")

FAISS propose différents index selon vos besoins en vitesse, précision et mémoire.

IndexMétriqueUsage
IndexFlatL2Distance euclidienneRéférence, petits datasets
IndexFlatIPProduit scalaireCosine similarity (après normalisation)
import faiss
import numpy as np
d = 384 # dimension des embeddings
index = faiss.IndexFlatL2(d) # ou IndexFlatIP(d)
print(f"Index entraîné: {index.is_trained}") # True (pas d'entraînement requis)
IndexEntraînementParamètres clésUsage
IndexIVFFlatOuinlist, nprobe1M-100M vecteurs
IndexHNSWNonM, efSearchFaible latence CPU
IndexIVFPQOuinlist, m, nbits>100M, mémoire limitée

L’index le plus simple pour commencer. Il effectue une recherche exhaustive (brute-force) et retourne des résultats exacts.

import faiss
import numpy as np
# Paramètres
d = 128 # dimension des vecteurs
nb = 1000 # nombre de vecteurs dans la base
nq = 5 # nombre de requêtes
# Générer des données aléatoires
np.random.seed(42)
xb = np.random.random((nb, d)).astype('float32') # base
xq = np.random.random((nq, d)).astype('float32') # requêtes
# Créer et remplir l'index
index = faiss.IndexFlatL2(d)
index.add(xb)
print(f"Vecteurs indexés: {index.ntotal}")
# Vecteurs indexés: 1000
# Recherche des 4 plus proches voisins
k = 4
D, I = index.search(xq, k)
# D = distances, I = indices
print(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]

Pour la similarité cosinus, normalisez vos vecteurs puis utilisez IndexFlatIP (Inner Product) :

import faiss
import numpy as np
d = 384
xb = 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 scalaire
index = faiss.IndexFlatIP(d)
index.add(xb)
# Recherche
D, 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 faiss
import numpy as np
d = 128
nb = 10000
nlist = 100 # nombre de cellules
# Quantizer = index utilisé pour trouver les cellules
quantizer = faiss.IndexFlatL2(d)
index = faiss.IndexIVFFlat(quantizer, d, nlist)
# Générer des données d'entraînement
xb = 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 vecteurs
index.add(xb)
print(f"Vecteurs indexés: {index.ntotal}")
# Recherche avec nprobe cellules explorées
index.nprobe = 10 # plus de cellules = meilleur rappel, plus lent
xq = np.random.random((5, d)).astype('float32')
D, I = index.search(xq, k=4)
print(f"Top-4: {I[0]}")

Compromis nprobe :

nprobeRappelVitesse
1~50%⚡ Très rapide
10~90%⚡ Rapide
50~98%Moyen
nlist100%🐢 Comme Flat

HNSW (Hierarchical Navigable Small World) construit un graphe de proximité. Excellent pour la latence mais consomme plus de mémoire.

import faiss
import numpy as np
d = 128
M = 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 direct
xb = np.random.random((10000, d)).astype('float32')
index.add(xb)
# Paramètre de recherche
index.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ètreEffet
MConnexions par nœud. Plus élevé = meilleur rappel, plus de mémoire
efSearchVoisins explorés. Plus élevé = meilleur rappel, plus lent
efConstructionQualité du graphe à la construction

Product Quantization compresse les vecteurs pour économiser la mémoire. Idéal pour des milliards de vecteurs.

import faiss
import numpy as np
d = 128
nb = 10000
nlist = 100
m = 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înement
index.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 = 10
D, I = index.search(np.random.random((1, d)).astype('float32'), k=4)

Économie mémoire :

ConfigurationMémoire/vecteurCompression
Float32 (d=128)512 bytes1x
PQ (m=8, nbits=8)8 bytes64x
PQ (m=16, nbits=8)16 bytes32x

La fonction index_factory crée des index complexes à partir d’une chaîne descriptive :

import faiss
import numpy as np
d = 128
xb = np.random.random((10000, d)).astype('float32')
# Différentes configurations
configs = {
"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 :

ComposantSignification
FlatIndex 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

Par défaut, FAISS assigne des IDs séquentiels (0, 1, 2…). Pour des IDs personnalisés :

import faiss
import numpy as np
d = 128
vectors = np.random.random((5, d)).astype('float32')
custom_ids = np.array([1001, 1002, 1003, 1004, 1005], dtype=np.int64)
# Wrapper IndexIDMap
index_flat = faiss.IndexFlatL2(d)
index = faiss.IndexIDMap(index_flat)
# Ajouter avec IDs
index.add_with_ids(vectors, custom_ids)
print(f"Vecteurs: {index.ntotal}")
# La recherche retourne vos IDs
D, I = index.search(vectors[:1], k=3)
print(f"Top-3 IDs: {I[0]}")
# Top-3 IDs: [1001 1002 1003]

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 1001
reconstructed = index.reconstruct(1001)
print(f"Vecteur original: {vectors[0][:5]}")
print(f"Reconstruit: {reconstructed[:5]}")
import faiss
# Sauvegarder
faiss.write_index(index, "mon_index.faiss")
# Charger
index = faiss.read_index("mon_index.faiss")
print(f"Index rechargé: {index.ntotal} vecteurs")

La suppression fonctionne avec les index IVF et nécessite un sélecteur :

import faiss
import numpy as np
d = 128
nb = 1000
# Créer un index avec IDMap
quantizer = 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, 4
ids_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}")

Intégration FAISS pour la recherche textuelle :

from sentence_transformers import SentenceTransformer
import faiss
import numpy as np
# Charger le modèle
model = SentenceTransformer('all-MiniLM-L6-v2')
# Corpus
corpus = [
"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 embeddings
embeddings = model.encode(corpus, convert_to_numpy=True)
d = embeddings.shape[1]
# Normaliser pour cosine similarity
faiss.normalize_L2(embeddings)
# Créer l'index
index = faiss.IndexFlatIP(d)
index.add(embeddings)
# Recherche
query = "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 conteneurs
"""RAG avec FAISS."""
from sentence_transformers import SentenceTransformer
import faiss
import 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
# Utilisation
if __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")
import faiss
# Voir le nombre de threads
print(f"Threads: {faiss.omp_get_max_threads()}")
# Configurer
faiss.omp_set_num_threads(8)
SituationRecommandation
< 100K vecteursIndexFlatL2 ou HNSW
100K - 10M vecteursIVF{nlist},Flat avec nlist ≈ √N
> 10M vecteursIVF{nlist},PQ{m}
Latence critiqueHNSW avec efSearch élevé
Mémoire limitéeIVFPQ ou IVFSQ
GPU disponiblefaiss-gpu avec index_cpu_to_gpu
IndexEntraînementMémoireVitessePrécisionUsage
FlatNonÉlevée🐢100%Référence, < 1M
IVFFlatOuiMoyenne95-99%1M-100M
IVFPQOuiFaible⚡⚡85-95%> 100M
HNSWNonÉlevée⚡⚡98%+Latence critique
IVFSQOuiMoyenne95%+Compromis mémoire
  1. IndexFlatL2/IP pour débuter et comme référence de précision

  2. Normaliser + IndexFlatIP pour la similarité cosinus

  3. IVF pour les grandes bases (train + nprobe pour le compromis rappel/vitesse)

  4. HNSW pour la latence critique sans entraînement

  5. PQ pour économiser la mémoire sur très grandes bases

  6. IndexIDMap pour des IDs personnalisés

  7. write_index/read_index pour persister les index

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn