Aller au contenu

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 4
vecteur_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 exhaustive
    • IndexFlatIP : Produit scalaire (cosine similarity après normalisation)
  • Index approximatifs :
    • IndexIVFFlat : Partitionnement par k-means (Inverted File)
    • IndexHNSW : Graphe hiérarchique navigable
    • IndexLSH : Hachage sensible à la localité
  • Index avec compression :
    • IndexIVFPQ : Product Quantization pour réduire la mémoire
    • IndexIVFOPQ : Optimized Product Quantization

Tableau comparatif des principaux types d’index :

IndexMétriqueMémoire/vecEntraînementRéglages clésUsage
IndexFlatL2/IPL2/IPélevénonbase < 1M, ground truth
IVF FlatL2/IPmoyenouinlist, nprobe1M–100M
IVFPQ/IVFOPQL2/IPfaibleouim, nbits, nlist, nprobe>100M
HNSWL2/IPélevénonM, efSearchfaible 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 faiss
import numpy as np
# Distance L2
d = 128 # dimension des vecteurs
index_l2 = faiss.IndexFlatL2(d)
# Produit scalaire
index_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’indexVitesseMémoirePrécisionCas d’usage
IndexFlatLenteÉlevée100%Benchmarks, petits datasets
IndexIVFRapideMoyenne95-99%Production, datasets moyens
IndexIVFPQTrès rapideFaible85-95%Très grandes bases
IndexHNSWRapideÉlevée98-99%Latence critique

Phases d’utilisation

Travailler avec Faiss suit généralement ces étapes :

  1. Préparation : Générer les embeddings via un modèle ML
  2. Création : Instancier l’index approprié
  3. Entraînement : Former l’index sur un échantillon (si nécessaire)
  4. Ajout : Insérer tous les vecteurs dans l’index
  5. Recherche : Interroger l’index avec de nouveaux vecteurs
  6. Optimisation : Ajuster les paramètres selon les performances
# Flux typique
import faiss
d = 512
index = faiss.IndexIVFFlat(faiss.IndexFlatL2(d), d, 100)
index.train(training_vectors) # Étape 3
index.add(all_vectors) # Étape 4
D, 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.

Terminal window
python -m venv faiss-env
source faiss-env/bin/activate # Sur Windows : faiss-env\Scripts\activate

Pour la version CPU uniquement :

Terminal window
pip install faiss-cpu

Pour la version GPU (avec CUDA déjà installé) :

Terminal window
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 faiss
print(f"Faiss version: {faiss.__version__}")

Si aucune erreur n’apparaît et que la version est affichée, Faiss est correctement installé.

Terminal window
# Exemple de sortie
Faiss 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 :

Terminal window
pip install sentence-transformers faiss-cpu

Puis créons un exemple simple :

from sentence_transformers import SentenceTransformer
import faiss
import numpy as np
# 1. Générer des embeddings
model = 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’index
d = corpus_embeddings.shape[1]
index = faiss.IndexFlatIP(d)
index.add(corpus_embeddings)
# 4. Interroger
query = "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ésultats
print([corpus[i] for i in I[0]])

Ici, Faiss retourne les phrases les plus proches sémantiquement de la requête.

Terminal window
python test.py
modules.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 torch
import clip
from PIL import Image
import faiss
import numpy as np
# Charger CLIP
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load("ViT-B/32", device=device)
# Jeu d’images
images = ["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 Faiss
d = image_embeddings.shape[1]
index = faiss.IndexFlatIP(d)
index.add(image_embeddings)
# Requête texte
text = 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é IVF
nlist = 100 # nb de cellules
quantizer = faiss.IndexFlatL2(d)
index_ivf = faiss.IndexIVFFlat(quantizer, d, nlist, faiss.METRIC_L2)
# Entraînement sur un sous-échantillon
index_ivf.train(corpus_embeddings)
index_ivf.add(corpus_embeddings)
# Paramètre de recherche (plus de cellules sondées = meilleure précision)
index_ivf.nprobe = 10
D, 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 = 4096
quantizer = faiss.IndexFlatL2(d)
index = faiss.IndexIVFFlat(quantizer, d, nlist)
# Entraînement
xtrain = 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 → GPU
index_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 ajuster nprobe.
  • 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 ou efSearch 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")