Recherche vectorielle Milvus : implémenter un moteur de similarité
La recherche vectorielle Milvus résout l’inefficacité des recherches de similarité sur des datasets massifs. Une recherche brute (flat search) sur un million de vecteurs présente une complexité algorithmique en O(N), ce qui devient prohibitif dès que l’échelle augmente.
Le passage à une recherche approximative (ANN) permet de réduire cette complexité à O(log N). Milvus structure ses données pour supporter des milliards de vecteurs avec des latences de l’ordre de la milliseconde.
Ce guide détaille la mise en place d’un pipeline complet, de l’instanciation du cluster Docker à l’exécution de requêtes de similarité cosinus avec Python 3.12.
🛠️ Prérequis
L’environnement doit être configuré avec les versions suivantes pour garantir la compatibilité des types de données :
- Python 3.12+ (utilisation des generics et du typage statique avancé).
- Docker et Docker Compose (pour le déploiement de Milvus Standalone).
- PyMilvus 2.4.x (le driver client officiel).
- Sentence-Transformers 2.7.0 (pour la génération d’embeddings).
📚 Comprendre recherche vectorielle Milvus
La recherche vectorielle Milvus repose sur la transformation de données non structurées (texte, images) en vecteurs de dimension fixe. Ces vecteurs résident dans un espace hyper-dimensionnel où la proximité géométrique traduit une proximité sémantique.
Le cœur du moteur utilise des algorithmes de type HNSW (Hierarchical Navigable Small World). Voici le principe de fonctionnement simplifié :
Graphe de niveau 0 (densité maximale) -> Recherche locale
Graphe de niveau 1 (densité réduite) -> Saut de grappes
...
Graphe de niveau N (densité minimale) -> Point d'entrée
Contra’à une base de données relationnelle classique qui compare des valeurs scalaires, Milvus calcule des distances métriques : la distance Euclidienne (L2) ou le produit scalaire (IP). Le choix de la métrique dépend de la fonction de perte utilisée lors de l’entraînement de votre modèle d’embedding.
🐍 Le code — recherche vectorielle Milvus
📖 Explication
Dans le premier snippet, l’utilisation de DataType.INT64 pour la clé primaire est cruciale pour éviter les débordements d’entiers sur de gros datasets. L’argument auto_id=True délègue la gestion des IDs au moteur Milvus, ce qui simplifie l’insertion.
Dans le second snippet, l’utilisation de numpy.ndarray assure une compatibilité directe avec les buffers mémoire de pymilvus. Attention au choix du modèle : all-MiniLM-L6-v2 est rapide mais moins précis que les modèles de la famille BGE. Si vous utilisez un modèle avec une dimension différente de celle définie dans le schéma, le driver Python lèvera une ValueError lors de l’appel à insert.
Documentation officielle Python
🔄 Second exemple
Tutoriel pas-à-pas
La mise en place d’un système de recherche vectorielle Milvus suit un processus rigoureux. Ne négligez pas la phase d’indexation, car un index mal configuré rendra vos recherches lentes malgré la puissance de l’outil.
1. Déploiement de l’infrastructure
Milvus ne s’installe pas comme un simple package Python. Utilisez Docker Compose pour lancer l’instance standalone. Téléchargez le fichier YAML officiel de Milvus. Lancez la commande docker-compose up -d. Vérifiez que le port 19530 est bien exposé et accessible.
2. Définition du schéma de données
La recherche vectorielle Milvus exige une définition stricte des dimensions. Si votre modèle d’embedding produit des vecteurs de 384 dimensions, votre champ FLOAT_VECTOR doit impérativement être configuré avec dim=384. Une erreur de dimension lors de l’insertion provoquera une exception immédiate du serveur.
3. Chargement des données et indexation
Une fois la collection créée, vous devez injecter vos vecteurs. L’insertion se fait par lots (batches). Évitez les insertions ligne par ligne qui satureent le log d’écriture. Après l’insertion, l’étape cruciale est la création de l’index. Pour la recherche vectorielle Milvus, l’index HNSW est le standard. Il nécessite de définir deux paramètres : M (nombre de connexions par nœud) et efConstruction (taille de la liste de recherche lors de la construction). Un M élevé améliore la précision mais augmente l’empreinte mémoire.
4. Exécution de la recherche
Pour interroger la base, vous devez d’abord charger la collection en mémoire via collection.load(). La recherche s’effectue en passant un vecteur de requête (query vector) et en spécifiant le nombre de voisins les plus proches (top-k). Le résultat renvoyé contient les identifiants et les distances calculées.
▶️ Exemple d’utilisation
Voici comment orchestrer le processus complet de recherche vectorielle Milvus dans un script unique.
import numpy as np
from pymilvus import connections, FieldSchema, CollectionSchema, DataType, Collection
from sentence_transformers import SentenceTransformer
# 1. Setup
connections.connect("default", host="localhost", port="19530")
model = SentenceTransformer("all-MiniLM-L6-v2")
DIM = 384
# 2. Schema & Collection
fields = [
FieldSchema(name="pk", dtype=DataType.INT64, is_primary=mode=True, auto_id=True),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=DIM)
]
schema = CollectionSchema(fields, "Demo Search")
col = Collection("demo_collection", schema)
# 3. Data
docs = ["Le Python est un langage de programmation.", "Milvus est une base de données vectorielle."]
vectors = model.encode(docs)
col.insert([vectors.tolist()])
# 4. Indexing
index_params = {"metric_type": "L2", "params": {"M": 8, "efConstruction": 64}, "index_type": "HNSW"}
col.create_index("embedding", index_params)
col.load()
# 5. Search
query_vec = model.encode(["parle de base de données"])
search_params = {"metric_type": "L2", "params": {"nprobe": 10}}
results = col.search(query_vec, "embedding", search_params, limit=1)
for hits in results:
for hit in hits:
print(f"ID: {hit.id}, Distance: {hit.distance}")
Sortie attendue :
ID: 1, Distance: 0.842312
🚀 Cas d’usage avancés
L’intégration de la recherche vectorielle Milvus dans un pipeline RAG (Retrieval-Augmented Generation) permet de fournir un contexte pertinent aux LLM. En stockant les chunks de documents, vous pouvez injecter les segments les plus proches de la question utilisateur dans le prompt.
Un autre cas est la recherche d’images multimodale. En utilisant un modèle type CLIP, vous pouvez transformer des images et du texte dans le même espace latent. La recherche vectorielle Milvus permet alors de trouver des images à partir d’une description textuelle avec une latence minimale.
Enfin, pour la détection de fraude, les signatures de transactions peuvent être vectorisées. Un écart important dans l’espace vectoriel par rapport aux transactions habituelles peut déclencher une alerte de sécurité en temps réel.
🐛 Erreurs courantes
⚠️ Dimension mismatch
L’embedding généré a une dimension différente de celle définie dans le schéma de la collection.
col.insert([vectors_384_dim]) # si schema.dim=512
col.insert([vectors_51le_dim])
⚠️
Tentative de recherche sur une collection dont l’index n’est pas encore construit ou chargé en mémoire.
col.search(query_vec, ...)
col.create_index("vec", params); col.load(); col.search(...)
⚠️ Type de données incorrect
Passage d’une liste de listes de Python au lieu d’un format compatible numpy/float pour les vecteurs.
col.insert([[[0.1, 0.2]]])
col.insert([vectors.tolist()])
⚠️
L’index HNSW est trop grand pour la RAM allouée au conteneur Milvus.
M=1024, efConstruction=512 sur 2Go RAM
M=8, efConstruction=64 sur 2Go RAM
✅ Bonnes pratiques
Pour maintenir une production stable avec la recherche vectorielle Milvus, suivez ces directives techniques :
- Utilisez toujours le typage statique (mypy) pour vos fonctions de transformation de vecteurs afin d’éviter les erreurs de structure de liste.
- Implémentez une stratégie de batching pour les insertions : des blocs de 500 à 1000 vecteurs sont généralement optimaux pour le throughput.
- Précisez explicitement la métrique de distance (L2 ou IP) lors de la création de l’index et lors de la requête pour éviter des résultats incohérents.
- Surveillez le paramètre
nprobelors de la recherche : unnprobetrop faible accélère la recherche mais dégrade la précision (recall). - Ne stockez pas de métadonnées textuelles massives directement dans Milvus ; stockez les IDs et utilisez une base relationnelle ou un S3 pour le texte brut.
- La recherche vectorielle Milvus utilise des index ANN pour une complexité O(log N).
- Le schéma de la collection doit correspondre strictement à la dimension des embeddings.
- L'index HNSW est le choix privilégié pour l'équilibre performance/précision.
- L'étape col.load() est obligatoire avant toute opération de recherche.
- Le batching des insertions est crucial pour éviter la saturation du log d'écriture.
- Le choix de la métrique (L2 vs IP) dépend du modèle d'embedding utilisé.
- Docker est la méthode de déploiement la plus simple pour les tests locaux.
- La gestion de la mémoire RAM est le facteur limitant principal pour l'indexation.
❓ Questions fréquentes
Est-ce que Milvus remplace PostgreSQL ?
Non. Milvus est spécialisé pour les vecteurs haute dimension. Pour des données structurées et des jointures complexes, restez sur PostgreSQL avec l’extension pgvector.
Quelle est la limite de taille d'un vecteur dans Milvus ?
La limite dépend de la configuration mémoire, mais Milvus supporte des dimensions allant jusqu’à plusieurs milliers sans dégradation majeure de performance.
Pourquoi utiliser HNSW plutôt que IVF_FLAT ?
HNSW offre des temps de recherche plus rapides (latence plus faible) au prix d’une utilisation mémoire plus importante que l’approche IVF.
Comment gérer la mise à jour des vecteurs ?
Milvus gère les suppressions et insertions. Cependant, pour de gros changements, il est souvent plus efficace de reconstruire l’index pour maintenir la performance.
📚 Sur le même blog
🔗 Le même sujet sur nos autres blogs
📝 Conclusion
La mise en œuvre de la recherche vectorielle Milvus nécessite une attention particulière à la configuration des index et à la gestion de la mémoire. Un pipeline bien dimensionné permet de passer de la recherche brute à une recherche sémantique à grande échelle sans friction. Pour approfondir la gestion des types de données en Python, consultez la documentation Python officielle. Surveillez la consommation mémoire de vos QueryNodes lors de l’augmentation du paramètre M de l’index HNSW.