Faiss : guide pratique recherche vectorielle
Mise à jour :
Le Retrieval-Augmented Generation (RAG) est une approche qui combine la recherche d’information (retrieval) et la génération de texte (generation) pour enrichir les réponses produites par les modèles de langage. Contrairement à un modèle qui se limite à ses connaissances internes, le RAG interroge une base externe de documents ou de vecteurs avant de générer une réponse.
Dans mes guides précédents, j’ai déjà présenté les fondements du RAG : comment relier un LLM à une source de données (textes, bases vectorielles, API) et comment structurer ce flux en plusieurs étapes — indexation, recherche, puis génération.
Dans cette partie, je vous propose de découvrir Faiss, une bibliothèque python open source.
Qu’est-ce que Faiss ?
Faiss (Facebook AI Similarity Search) est une bibliothèque open source développée par Meta AI Research. Elle est spécialisée dans la recherche de similarité et le clustering de vecteurs de grande dimension. Son objectif : permettre la recherche rapide et efficace dans des bases contenant des millions, voire des milliards de vecteurs.
Contrairement aux bases de données relationnelles classiques, Faiss n’est pas conçu pour stocker des textes ou des images entières. Il fonctionne avec des représentations vectorielles (aussi appelées embeddings), obtenues grâce à des modèles de machine learning ou de traitement du langage naturel (NLP). Ces vecteurs traduisent la signification d’un objet (un mot, une image, un document) en coordonnées numériques dans un espace multidimensionnel.
Point important : Faiss ne stocke pas sur disque les vecteurs. Il se concentre sur la recherche en mémoire. Pour gérer des ensembles de données volumineux, il utilise des techniques d’indexation avancées et des algorithmes optimisés. Il est tout de même possible de sauvegarder et recharger les index Faiss sur disque.
Un peu d’histoire
Meta a publié Faiss en 2017, dans un article intitulé Faiss: A library for efficient similarity search and clustering of dense vectors ↗. À l’origine, le défi consistait à accélérer la recherche d’images similaires dans des catalogues massifs, puis à étendre cette approche à d’autres domaines :
- Systèmes de recommandation : trouver des articles proches d’un profil utilisateur.
- Recherche sémantique : retrouver des textes similaires à une requête.
- Vision par ordinateur : comparer des images en fonction de leur contenu visuel.
- Détection d’anomalies : repérer des points de données atypiques dans de grands volumes.
Concepts clés de Faiss
Avant de plonger dans l’installation, il est essentiel de comprendre les concepts fondamentaux qui sous-tendent Faiss. Ces notions vous permettront de choisir la bonne approche selon votre cas d’usage.
Vecteurs et embeddings
Les vecteurs (ou embeddings) sont des représentations numériques d’objets complexes dans un espace multidimensionnel. Chaque dimension capture une caractéristique spécifique :
- Texte : Un document devient un vecteur de 384, 512 ou 768 dimensions via des modèles comme BERT ou Sentence-Transformers
- Images : Une photo se transforme en vecteur de 2048 dimensions avec ResNet ou 512 avec CLIP
- Audio : Un fichier sonore peut être représenté par un vecteur de 1024 dimensions
# Exemple : vecteur de dimension 4vecteur_exemple = [0.2, -0.7, 1.3, 0.8]
Index : structures de données optimisées
Un index est une structure de données qui organise les vecteurs pour accélérer la recherche. Faiss propose plusieurs types d’index selon vos contraintes :
- Index exacts :
IndexFlatL2
: Distance euclidienne, recherche exhaustiveIndexFlatIP
: Produit scalaire (cosine similarity après normalisation)
- Index approximatifs :
IndexIVFFlat
: Partitionnement par k-means (Inverted File)IndexHNSW
: Graphe hiérarchique navigableIndexLSH
: Hachage sensible à la localité
- Index avec compression :
IndexIVFPQ
: Product Quantization pour réduire la mémoireIndexIVFOPQ
: Optimized Product Quantization
Tableau comparatif des principaux types d’index :
Index | Métrique | Mémoire/vec | Entraînement | Réglages clés | Usage |
---|---|---|---|---|---|
IndexFlatL2/IP | L2/IP | élevé | non | – | base < 1M, ground truth |
IVF Flat | L2/IP | moyen | oui | nlist , nprobe | 1M–100M |
IVFPQ/IVFOPQ | L2/IP | faible | oui | m , nbits , nlist , nprobe | >100M |
HNSW | L2/IP | élevé | non | M , efSearch | faible latence CPU |
Métriques de distance
Faiss supporte plusieurs métriques de similarité selon le type de données :
- Distance euclidienne (L2) :
√∑(ai - bi)²
- Adaptée aux vecteurs d’images, coordonnées géographiques
- Plus la distance est faible, plus les vecteurs sont similaires
- Produit scalaire (Inner Product) :
∑(ai × bi)
- Utilisé pour la similarité cosinus après normalisation
- Plus le produit est élevé, plus les vecteurs sont similaires
import faissimport numpy as np
# Distance L2d = 128 # dimension des vecteursindex_l2 = faiss.IndexFlatL2(d)
# Produit scalaireindex_ip = faiss.IndexFlatIP(d)
Compromis performance-précision
Faiss permet d’ajuster le compromis entre vitesse, mémoire et précision selon vos besoins :
Type d’index | Vitesse | Mémoire | Précision | Cas d’usage |
---|---|---|---|---|
IndexFlat | Lente | Élevée | 100% | Benchmarks, petits datasets |
IndexIVF | Rapide | Moyenne | 95-99% | Production, datasets moyens |
IndexIVFPQ | Très rapide | Faible | 85-95% | Très grandes bases |
IndexHNSW | Rapide | Élevée | 98-99% | Latence critique |
Phases d’utilisation
Travailler avec Faiss suit généralement ces étapes :
- Préparation : Générer les embeddings via un modèle ML
- Création : Instancier l’index approprié
- Entraînement : Former l’index sur un échantillon (si nécessaire)
- Ajout : Insérer tous les vecteurs dans l’index
- Recherche : Interroger l’index avec de nouveaux vecteurs
- Optimisation : Ajuster les paramètres selon les performances
# Flux typiqueimport faiss
d = 512index = faiss.IndexIVFFlat(faiss.IndexFlatL2(d), d, 100)index.train(training_vectors) # Étape 3index.add(all_vectors) # Étape 4D, I = index.search(query, k=5) # Étape 5
Ces concepts fondamentaux vous guideront dans le choix de la stratégie optimale pour votre projet.
Comment installer Faiss ?
L’installation de Faiss dépend de votre environnement et de vos besoins en performance. Deux variantes principales existent : une version optimisée pour le CPU et une version exploitant le GPU grâce à CUDA :
-
faiss-cpu
: Convient pour des jeux de données de taille moyenne ou lorsque vous ne disposez pas de carte graphique compatible CUDA. Plus simple à installer et compatible avec la plupart des environnements Python. -
faiss-gpu
: Permet d’accélérer la recherche et l’indexation sur des millions à milliards de vecteurs. Cette version exploite CUDA et nécessite une carte graphique NVIDIA. Indispensable pour des cas d’usage intensifs (recherche d’images, NLP à grande échelle, etc.).
Installation avec pip
La méthode la plus simple reste pip, disponible sur
PyPI ↗. Comme pour beaucoup de bibliothèques
scientifiques, il est recommandé d’utiliser un environnement virtuel (via venv
ou virtualenv
) pour éviter les conflits de dépendances.
python -m venv faiss-envsource faiss-env/bin/activate # Sur Windows : faiss-env\Scripts\activate
Pour la version CPU uniquement :
pip install faiss-cpu
Pour la version GPU (avec CUDA déjà installé) :
pip install faiss-gpu
Attention : selon la version de CUDA installée, il peut être nécessaire de préciser une variante (ex.
faiss-gpu-cu118
pour CUDA 11.8).
Vérification de l’installation
Une fois l’installation terminée, testez le module dans Python :
import faissprint(f"Faiss version: {faiss.__version__}")
Si aucune erreur n’apparaît et que la version est affichée, Faiss est correctement installé.
# Exemple de sortieFaiss version: 1.12.0
Exemples pratiques
L’un des points forts de Faiss est son intégration simple avec Python. Cela permet de combiner des modèles d’embeddings (pour transformer du texte, des images ou des documents en vecteurs) et des index Faiss pour exécuter des recherches de similarité à grande échelle.
Exemple 1 : Recherche textuelle avec Sentence-Transformers + IndexFlatIP
Un cas d’usage courant est la recherche sémantique : retrouver les documents les plus proches d’une requête textuelle. On utilise des embeddings issus de modèles comme Sentence-Transformers ↗, puis Faiss pour indexer et interroger.
Commençons par installer les dépendances nécessaires :
pip install sentence-transformers faiss-cpu
Puis créons un exemple simple :
from sentence_transformers import SentenceTransformerimport faissimport numpy as np
# 1. Générer des embeddingsmodel = SentenceTransformer('all-MiniLM-L6-v2')corpus = [ "Faiss est une librairie de recherche vectorielle.", "Le GPU accélère la recherche sur des milliards de vecteurs.", "Sentence Transformers produit des embeddings sémantiques.", "La recherche d’images peut utiliser CLIP et Faiss."]corpus_embeddings = model.encode(corpus, convert_to_numpy=True)
# 2. Normaliser pour utiliser IndexFlatIP (cosine similarity)corpus_embeddings = corpus_embeddings / np.linalg.norm(corpus_embeddings, axis=1, keepdims=True)
# 3. Créer et remplir l’indexd = corpus_embeddings.shape[1]index = faiss.IndexFlatIP(d)index.add(corpus_embeddings)
# 4. Interrogerquery = "Comment accélérer la recherche vectorielle ?"query_vec = model.encode([query], convert_to_numpy=True)query_vec = query_vec / np.linalg.norm(query_vec, axis=1, keepdims=True)
D, I = index.search(query_vec, k=2) # top-2 résultatsprint([corpus[i] for i in I[0]])
Ici, Faiss retourne les phrases les plus proches sémantiquement de la requête.
python test.pymodules.json: 100%|██████████████████████████████████████████| 349/349 [00:00<00:00, 3.99MB/s]config_sentence_transformers.json: 100%|█████████████████████| 116/116 [00:00<00:00, 1.24MB/s]README.md: 10.5kB [00:00, 46.4MB/s]sentence_bert_config.json: 100%|████████████████████████████| 53.0/53.0 [00:00<00:00, 670kB/s]config.json: 100%|███████████████████████████████████████████| 612/612 [00:00<00:00, 7.95MB/s]model.safetensors: 100%|█████████████████████████████████| 90.9M/90.9M [00:02<00:00, 40.6MB/s]tokenizer_config.json: 100%|█████████████████████████████████| 350/350 [00:00<00:00, 4.26MB/s]vocab.txt: 232kB [00:00, 5.63MB/s]tokenizer.json: 466kB [00:00, 11.9MB/s]special_tokens_map.json: 100%|███████████████████████████████| 112/112 [00:00<00:00, 1.47MB/s]config.json: 100%|███████████████████████████████████████████| 190/190 [00:00<00:00, 2.51MB/s]['Faiss est une librairie de recherche vectorielle.', 'Le GPU accélère la recherche sur des milliards de vecteurs.']
Exemple 2 : Recherche d’images avec CLIP + Faiss
Pour la recherche d’images, on peut combiner CLIP ↗ (texte ↔ image) avec Faiss. Chaque image est transformée en vecteur, puis indexée.
import torchimport clipfrom PIL import Imageimport faissimport numpy as np
# Charger CLIPdevice = "cuda" if torch.cuda.is_available() else "cpu"model, preprocess = clip.load("ViT-B/32", device=device)
# Jeu d’imagesimages = ["chat.jpg", "chien.jpg", "voiture.jpg"]image_embeddings = []
for img_path in images: image = preprocess(Image.open(img_path)).unsqueeze(0).to(device) with torch.no_grad(): vec = model.encode_image(image) vec = vec.cpu().numpy() vec = vec / np.linalg.norm(vec, axis=1, keepdims=True) image_embeddings.append(vec)
image_embeddings = np.vstack(image_embeddings)
# Index Faissd = image_embeddings.shape[1]index = faiss.IndexFlatIP(d)index.add(image_embeddings)
# Requête textetext = clip.tokenize(["un animal domestique"]).to(device)with torch.no_grad(): query_vec = model.encode_text(text).cpu().numpy()query_vec = query_vec / np.linalg.norm(query_vec, axis=1, keepdims=True)
D, I = index.search(query_vec, k=2)print("Résultats :", [images[i] for i in I[0]])
Faiss permet ici de retrouver les images correspondant le mieux à une requête textuelle.
Exemple 3 : Combinaison avec IVF pour de grandes bases
Sur des collections massives, IndexFlat
devient trop coûteux. On utilise
IVF pour accélérer la recherche.
# Index inversé IVFnlist = 100 # nb de cellulesquantizer = faiss.IndexFlatL2(d)index_ivf = faiss.IndexIVFFlat(quantizer, d, nlist, faiss.METRIC_L2)
# Entraînement sur un sous-échantillonindex_ivf.train(corpus_embeddings)index_ivf.add(corpus_embeddings)
# Paramètre de recherche (plus de cellules sondées = meilleure précision)index_ivf.nprobe = 10D, I = index_ivf.search(query_vec, k=2)print("Résultats (IVF) :", [corpus[i] for i in I[0]])
Ces exemples montrent que Faiss est polyvalent : il peut gérer aussi bien des projets de prototypage (textes, petites bases) que des systèmes à grande échelle (images, milliards de vecteurs).
Optimisation de performance
La performance de Faiss dépend directement du choix de l’index, de la méthode d’entraînement, des paramètres de recherche et de l’utilisation du GPU. Optimiser ces points est essentiel pour obtenir un bon compromis entre rapidité, mémoire et précision.
Entraînement des index
Certains index comme IVF, PQ ou OPQ nécessitent un entraînement préalable. Cet entraînement est effectué sur un échantillon représentatif des données, souvent par k-means :
nlist = 4096quantizer = faiss.IndexFlatL2(d)index = faiss.IndexIVFFlat(quantizer, d, nlist)
# Entraînementxtrain = xb[np.random.choice(len(xb), 200000, replace=False)]index.train(xtrain)index.add(xb)
⚠️ Mauvais entraînement = chute de précision (recall). Toujours vérifier que l’échantillon couvre la distribution réelle des données.
Paramètres de recherche
-
nprobe
(IVF) : nombre de cellules sondées.- Petit = plus rapide, rappel faible.
- Grand = plus lent, rappel meilleur.
index.nprobe = 32 # valeur typique : 8 à 64
-
efSearch
(HNSW) : nombre de voisins explorés pendant la recherche.- Plus grand = meilleur rappel, latence plus élevée.
index.hnsw.efSearch = 128
- Quantization (PQ/OPQ) : réduit la mémoire (stockage compressé), mais peut baisser la précision.
Accélération GPU
Faiss propose une version GPU pour accélérer recherche et entraînement :
res = faiss.StandardGpuResources()index_cpu = faiss.IndexIVFFlat(faiss.IndexFlatL2(d), d, 8192)index_cpu.train(xtrain)
# Migration CPU → GPUindex_gpu = faiss.index_cpu_to_gpu(res, 0, index_cpu)index_gpu.add(xb)
Points forts du GPU :
- Accélération x10 à x100 pour les grandes bases.
- Support du multi-GPU via sharding (répartition des données) ou replication (copie complète sur chaque GPU).
Multi-threading et batch
- CPU : Faiss utilise OpenMP pour paralléliser la recherche. On peut régler le nombre de threads :
faiss.omp_set_num_threads(8)
- Batching : interroger plusieurs requêtes à la fois est beaucoup plus efficace que de les traiter une par une.
Trade-offs : vitesse vs mémoire vs précision
- IndexFlat : précision maximale, mais RAM et temps de calcul explosifs si > 1M vecteurs.
- IVF : bon compromis. Plus
nlist
est élevé, plus l’index est fin, mais il faut aussi ajusternprobe
. - IVFPQ/OPQ : réduit drastiquement la mémoire, utile pour milliards de vecteurs, mais rappel inférieur.
- HNSW : bon rappel sur CPU, mais mémoire élevée.
Exemple de configuration typique :
- 10M vecteurs → IVF avec
nlist=4096
,nprobe=16
. - 100M vecteurs → IVFPQ avec compression (
m=64, nbits=8
). - >1B vecteurs → IVFPQ/OPQ sur GPU multi-cartes.
Bonnes pratiques
- Toujours évaluer la qualité avec un index exact (
IndexFlat
) pour mesurer le recall@k. - Ajuster
nlist
≈√N
(N = taille du dataset) comme point de départ. - Vérifier l’impact de
nprobe
ouefSearch
avant mise en production. - Sauvegarder les index entraînés pour éviter de refaire le processus coûteux :
faiss.write_index(index, "faiss.index")index = faiss.read_index("faiss.index")