Optimiser les bases de données vectorielles, améliorer l'IA générative pilotée par RAG
Cet article a été publié à l'origine sur le canal Medium d'Intel et est repris ici avec autorisation.
Deux méthodes pour optimiser votre base de données vectorielle lorsque vous utilisez RAG
Photo par Ilya Pavlov sur Unsplash
Par Cathy Zhang et Dr. Malini Bhandaru Contributeurs : Lin Yang et Changyan Liu
Les modèles d'IA générative (GenAI), qui connaissent une adoption exponentielle dans notre vie quotidienne, sont améliorés par la génération augmentée par récupération (RAG), une technique utilisée pour améliorer la précision et la fiabilité des réponses en récupérant des faits à partir de sources externes. La RAG aide un grand modèle de langage ordinaire (LLM ) à comprendre le contexte et à réduire les hallucinations en exploitant une base de données géante de données non structurées stockées sous forme de vecteurs - une présentation mathématique qui aide à capturer le contexte et les relations entre les données.
Les RAG permettent de récupérer plus d'informations contextuelles et donc de générer de meilleures réponses, mais les bases de données vectorielles sur lesquelles ils s'appuient deviennent de plus en plus grandes pour fournir un contenu riche à exploiter. Tout comme les LLM à mille milliards de paramètres, les bases de données vectorielles contenant des milliards de vecteurs ne sont pas loin derrière. En tant qu'ingénieurs en optimisation, nous étions curieux de voir si nous pouvions rendre les bases de données vectorielles plus performantes, charger les données plus rapidement et créer des index plus rapidement pour garantir la rapidité de la recherche, même lorsque de nouvelles données sont ajoutées. Cela permettrait non seulement de réduire le temps d'attente des utilisateurs, mais aussi de rendre les solutions d'IA basées sur RAG un peu plus durables.
Dans cet article, vous en apprendrez davantage sur les bases de données vectorielles et leurs cadres d'analyse comparative, sur les ensembles de données permettant d'aborder différents aspects et sur les outils utilisés pour l'analyse des performances - tout ce dont vous avez besoin pour commencer à optimiser les bases de données vectorielles. Nous partagerons également nos réalisations en matière d'optimisation de deux solutions de bases de données vectorielles populaires afin de vous inspirer dans votre démarche d'optimisation des performances et de l'impact sur le développement durable.
Comprendre les bases de données vectorielles
Contrairement aux bases de données relationnelles ou non relationnelles traditionnelles où les données sont stockées de manière structurée, une base de données vectorielle contient une représentation mathématique d'éléments de données individuels, appelée vecteur, construite à l'aide d'une fonction d'intégration ou de transformation. Le vecteur représente généralement des caractéristiques ou des significations sémantiques et peut être court ou long. Les bases de données vectorielles permettent la recherche de vecteurs par similarité à l'aide d'une métrique de distance (où la proximité signifie que les résultats sont plus similaires) telle que la similarité euclidienne, le produit de point ou la similarité cosinus.
Pour accélérer le processus de recherche, les données vectorielles sont organisées à l'aide d'un mécanisme d'indexation. Parmi les exemples de ces méthodes d'organisation, on peut citer les structures plates, les fichiers inversés (IVF), les petits mondes hiérarchiques navigables (HNSW ) et le hachage sensible à la localité (LSH), entre autres. Chacune de ces méthodes contribue à l'efficacité de la récupération de vecteurs similaires en cas de besoin.
Voyons comment utiliser une base de données vectorielles dans un système de GenAI. La figure 1 illustre à la fois le chargement des données dans une base de données vectorielles et leur utilisation dans le contexte d'une application GenAI. Lorsque vous saisissez votre invite, elle subit un processus de transformation identique à celui utilisé pour générer des vecteurs dans la base de données. Cette invite transformée est ensuite utilisée pour récupérer des vecteurs similaires dans la base de données vectorielles. Ces éléments récupérés servent essentiellement de mémoire conversationnelle, fournissant un historique contextuel pour les invites, un peu comme le font les LLM. Cette fonction s'avère particulièrement avantageuse dans le traitement du langage naturel, la vision par ordinateur, les systèmes de recommandation et d'autres domaines nécessitant une compréhension sémantique et une mise en correspondance des données. Votre invite initiale est ensuite "fusionnée" avec les éléments récupérés, fournissant un contexte et aidant le LLM à formuler des réponses basées sur le contexte fourni plutôt que de s'appuyer uniquement sur ses données de formation d'origine.
Figure 1. Architecture d'une application RAG.
Les vecteurs sont stockés et indexés pour une recherche rapide. Les bases de données vectorielles se présentent sous deux formes principales : les bases de données traditionnelles qui ont été étendues pour stocker des vecteurs et les bases de données vectorielles spécialement conçues. Redis, pgvector, Elasticsearch et OpenSearch sont des exemples de bases de données traditionnelles qui prennent en charge les vecteurs. Parmi les exemples de bases de données vectorielles spécifiques, on peut citer les solutions propriétaires Zilliz et Pinecone, ainsi que les projets open source Milvus, Weaviate, Qdrant, Faiss et Chroma. Vous pouvez en savoir plus sur les bases de données vectorielles sur GitHub via LangChain et OpenAI Cookbook.
Nous allons examiner de plus près une base de données de chaque catégorie, Milvus et Redis.
Améliorer les performances
Avant de plonger dans les optimisations, examinons comment les bases de données vectorielles sont évaluées, quelques cadres d'évaluation et les outils d'analyse des performances disponibles.
Mesures de performance
Examinons les principales mesures qui peuvent vous aider à évaluer les performances des bases de données vectorielles.
- Lalatence de chargement mesure le temps nécessaire pour charger les données dans la mémoire de la base de données vectorielle et construire un index. Un index est une structure de données utilisée pour organiser et récupérer efficacement des données vectorielles en fonction de leur similarité ou de leur distance. Les types d'index en mémoire comprennent l'index plat, IVF_FLAT, IVF_PQ, HNSW, ScaNN (scalable nearest neighbors) et DiskANN.
- Lerappel est la proportion de vraies correspondances, ou d'éléments pertinents, trouvés dans les K premiers résultats récupérés par l'algorithme de recherche. Des valeurs de rappel plus élevées indiquent une meilleure récupération des éléments pertinents.
- Le nombre derequêtes par seconde (QPS ) est la vitesse à laquelle la base de données vectorielle peut traiter les requêtes entrantes. Des valeurs de QPS plus élevées impliquent une meilleure capacité de traitement des requêtes et un meilleur débit du système.
Cadres d'évaluation comparative
La figure 2 présente le cadre d'évaluation des bases de données vectorielles. Le cadre d'évaluation comparative des bases de données vectorielles.
L'évaluation comparative d'une base de données vectorielle nécessite un serveur de base de données vectorielle et des clients. Pour nos tests de performance, nous avons utilisé deux outils open source populaires.
- VectorDBBench: Développé par Zilliz, VectorDBBench permet de tester différentes bases de données vectorielles avec différents types d'index et fournit une interface web pratique.
- vector-db-benchmark: Développé par Qdrant, vector-db-benchmark permet de tester plusieurs bases de données vectorielles typiques pour le type d'index HNSW. Il exécute les tests en ligne de commande et fournit un fichier Docker Compose __file pour simplifier le démarrage des composants du serveur.
Figure 3. Exemple de commande vector-db-benchmark utilisée pour exécuter le test de référence.
Mais le cadre de référence n'est qu'une partie de l'équation. Nous avons besoin de données pour tester différents aspects de la solution de base de données vectorielle elle-même, comme sa capacité à gérer de grands volumes de données, différentes tailles de vecteurs et la rapidité de la recherche.
Jeux de données ouverts pour tester les bases de données vectorielles
Les grands ensembles de données sont de bons candidats pour tester la latence de la charge et l'allocation des ressources. Certains ensembles de données ont des dimensions élevées et sont parfaits pour tester la vitesse de calcul de la similarité.
Les ensembles de données vont d'une dimension de 25 à une dimension de 2048. L'ensemble de données LAION, une collection d'images ouverte, a été utilisé pour entraîner de très grands modèles neuronaux profonds visuels et linguistiques, tels que des modèles génératifs de diffusion stables. L'ensemble de données OpenAI de 5 millions de vecteurs, chacun ayant une dimension de 1536, a été créé par VectorDBBench en exécutant OpenAI sur des données brutes. Étant donné que chaque élément vectoriel est de type FLOAT, la sauvegarde des vecteurs nécessite environ 29 Go (5M * 1536 * 4) de mémoire, plus une quantité similaire pour contenir les indices et autres métadonnées, soit un total de 58 Go de mémoire pour les tests. Lorsque vous utilisez l'outil vector-db-benchmark, veillez à disposer d'un espace de stockage adéquat sur le disque pour sauvegarder les résultats.
Pour tester la latence de chargement, nous avions besoin d'une grande collection de vecteurs, ce qu'offre deep-image-96-angular. Pour tester les performances de la génération d'index et du calcul de similarité, les vecteurs de haute dimension sont plus sollicités. À cette fin, nous avons choisi l'ensemble de données 500K composé de vecteurs de 1536 dimensions.
Outils de performance
Nous avons abordé les moyens de stresser le système afin d'identifier les mesures intéressantes, mais examinons ce qui se passe à un niveau inférieur : quel est le niveau d'occupation de l'unité de calcul, la consommation de mémoire, les temps d'attente sur les verrous, etc. Ces éléments fournissent des indices sur le comportement de la base de données, particulièrement utiles pour identifier les zones problématiques.
L'utilitaire top de Linux fournit des informations sur les performances du système. Cependant, l'outil perf de Linux fournit un ensemble d'informations plus approfondies. Pour en savoir plus, nous vous recommandons également de lire les exemples de perf Linux et la méthode d'analyse descendante de la microarchitecture d'Intel. Un autre outil encore est l'Intel® vTune™ Profiler, qui est utile pour optimiser non seulement les performances des applications, mais aussi celles du système et la configuration pour une variété de charges de travail couvrant le HPC, le cloud, l'IoT, les médias, le stockage, et plus encore.
Optimisations de la base de données vectorielle Milvus
Passons en revue quelques exemples de la manière dont nous avons tenté d'améliorer les performances de la base de données vectorielle Milvus.
Réduction des frais généraux de déplacement de la mémoire dans l'écriture du tampon du datanode
Le chemin d'écriture de Milvus permet aux mandataires d'écrire des données dans un courtier de journaux via MsgStream. Les nœuds de données consomment ensuite les données, en les convertissant et en les stockant dans des segments. Les segments fusionnent les données nouvellement insérées. La logique de fusion alloue un nouveau tampon pour contenir/déplacer les anciennes données et les nouvelles données à insérer, puis renvoie le nouveau tampon en tant qu'anciennes données pour la prochaine fusion de données. Les anciennes données deviennent ainsi de plus en plus volumineuses, ce qui ralentit le mouvement des données. Les profils de performance ont montré un surcoût élevé pour cette logique.
Figure 4. La fusion et le déplacement des données dans la base de données vectorielle génèrent une surcharge de performance importante.
Nous avons modifié la logique du tampon de fusion pour ajouter directement les nouvelles données à insérer dans les anciennes, ce qui évite d'allouer un nouveau tampon et de déplacer les anciennes données volumineuses. Les profils de performance confirment que cette logique ne génère pas de surcoût. Les mesures du microcode metric_CPU operating frequency et metric_CPU utilization indiquent une amélioration qui est cohérente avec le fait que le système n'a plus besoin d'attendre le long mouvement de la mémoire. La latence de chargement s'est améliorée de plus de 60 %. L'amélioration est capturée sur GitHub.
Figure 5. Avec moins de copies, nous constatons une amélioration des performances de plus de 50 % en ce qui concerne la latence de chargement.
Construction d'un index inversé avec réduction des frais généraux d'allocation de mémoire
Le moteur de recherche de Milvus, Knowhere, utilise l'algorithme Elkan k-means pour former des données en grappes afin de créer des index de fichiers inversés (IVF). Chaque cycle de formation des données définit un nombre d'itérations. Plus ce nombre est élevé, meilleurs sont les résultats de la formation. Cependant, cela implique également que l'algorithme d'Elkan sera appelé plus fréquemment.
L'algorithme d'Elkan gère l'allocation et la désallocation de la mémoire à chaque fois qu'il est exécuté. Plus précisément, il alloue de la mémoire pour stocker la moitié de la taille des données de la matrice symétrique, à l'exclusion des éléments diagonaux. Dans Knowhere, la dimension de la matrice symétrique utilisée par l'algorithme d'Elkan est fixée à 1024, ce qui se traduit par une taille de mémoire d'environ 2 Mo. Cela signifie que pour chaque cycle d'entraînement, Elkan alloue et désalloue de manière répétée 2 Mo de mémoire.
Les données de profilage Perf ont indiqué une activité fréquente d'allocation de mémoire importante. En fait, elles ont déclenché l'allocation de zones de mémoire virtuelle (VMA), l'allocation de pages physiques, la configuration de cartes de pages et la mise à jour des statistiques de cgroupes de mémoire dans le noyau. Ce modèle d'activité d'allocation/désallocation de mémoire importante peut, dans certaines situations, aggraver la fragmentation de la mémoire. Il s'agit d'une taxe importante.
La structure IndexFlatElkan est spécifiquement conçue et construite pour soutenir l'algorithme d'Elkan. Une instance IndexFlatElkan sera initialisée pour chaque processus d'apprentissage des données. Pour atténuer l'impact sur les performances résultant de l'allocation et de la désallocation fréquentes de la mémoire dans l'algorithme d'Elkan, nous avons remanié la logique du code, en déplaçant la gestion de la mémoire en dehors de la fonction de l'algorithme d'Elkan, dans le processus de construction d'IndexFlatElkan. Cela permet d'allouer la mémoire une seule fois au cours de la phase d'initialisation tout en servant tous les appels de fonction ultérieurs de l'algorithme d'Elkan à partir du processus d'apprentissage des données en cours et contribue à améliorer la latence de chargement d'environ 3 %. Le correctif Knowhere est disponible ici.
Accélération de la recherche vectorielle Redis grâce au Software Prefetch
Redis, un magasin de données clé-valeur traditionnel en mémoire, a récemment commencé à prendre en charge la recherche vectorielle. Pour aller au-delà d'un magasin de données clé-valeur typique, il offre des modules d'extensibilité ; le module RediSearch facilite le stockage et la recherche de vecteurs directement dans Redis.
Pour la recherche de similarités vectorielles, Redis prend en charge deux algorithmes, à savoir la force brute et HNSW. L'algorithme HNSW est spécialement conçu pour localiser efficacement les plus proches voisins approximatifs dans les espaces à haute dimension. Il utilise une file d'attente prioritaire nommée candidate_set pour gérer tous les vecteurs candidats pour le calcul de la distance.
Chaque vecteur candidat contient des métadonnées substantielles en plus des données vectorielles. Par conséquent, le chargement d'un candidat à partir de la mémoire peut entraîner l'absence de données dans le cache, ce qui entraîne des retards de traitement. Notre optimisation introduit la prélecture logicielle pour charger de manière proactive le candidat suivant tout en traitant le candidat en cours. Cette amélioration a permis d'améliorer le débit de 2 à 3 % pour les recherches de similarités vectorielles dans une configuration Redis à instance unique. Le correctif est en cours de remontée.
Changement de comportement par défaut de GCC pour éviter les pénalités liées au code assembleur mixte
Afin de maximiser les performances, les sections de code fréquemment utilisées sont souvent écrites à la main en assembleur. Cependant, lorsque différents segments de code sont écrits par différentes personnes ou à différents moments, les instructions utilisées peuvent provenir de jeux d'instructions assembleurs incompatibles tels que Intel® Advanced Vector Extensions 512 (Intel® AVX-512) et Streaming SIMD Extensions (SSE). S'il n'est pas compilé de manière appropriée, le code mixte entraîne une baisse des performances. Pour en savoir plus sur le mélange des instructions Intel AVX et SSE, cliquez ici.
Vous pouvez facilement déterminer si vous utilisez du code assembleur en mode mixte et si vous n'avez pas compilé le code avec VZEROUPPER, ce qui entraîne une pénalité en termes de performances. Cela peut être observé à l'aide d'une commande perf comme sudo perf stat -e 'assists.sse_avx_mix/event/event=0xc1,umask=0x10/' <workload>. Si votre système d'exploitation ne supporte pas l'événement, utilisez cpu/event=0xc1,umask=0x10,name=assists_sse_avx_mix/.
Le compilateur Clang insère par défaut VZEROUPPER, évitant ainsi toute pénalité en mode mixte. Mais le compilateur GCC n'insère VZEROUPPER que lorsque les drapeaux de compilateur -O2 ou -O3 sont spécifiés. Nous avons contacté l'équipe GCC et expliqué le problème. Désormais, le compilateur GCC gère correctement le code assembleur en mode mixte par défaut.
Commencez à optimiser vos bases de données vectorielles
Les bases de données vectorielles jouent un rôle essentiel dans la GenAI, et elles sont de plus en plus volumineuses afin de générer des réponses de meilleure qualité. En ce qui concerne l'optimisation, les applications d'IA ne sont pas différentes des autres applications logicielles en ce sens qu'elles révèlent leurs secrets lorsque l'on utilise des outils standard d'analyse des performances ainsi que des cadres de référence et des données de stress.
À l'aide de ces outils, nous avons découvert des pièges de performance liés à l'allocation inutile de mémoire, à l'absence de préemption des instructions et à l'utilisation d'options de compilateur incorrectes. Sur la base de nos conclusions, nous avons apporté des améliorations à Milvus, Knowhere, Redis et au compilateur GCC afin de rendre l'IA un peu plus performante et durable. Les bases de données vectorielles constituent une classe importante d'applications qui méritent vos efforts d'optimisation. Nous espérons que cet article vous aidera à démarrer.
- Comprendre les bases de données vectorielles
- Améliorer les performances
- Optimisations de la base de données vectorielle Milvus
- Accélération de la recherche vectorielle Redis grâce au Software Prefetch
- Changement de comportement par défaut de GCC pour éviter les pénalités liées au code assembleur mixte
- Commencez à optimiser vos bases de données vectorielles
On This Page
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word