11 points par GN⁺ 2024-10-30 | 1 commentaires | Partager sur WhatsApp
  • Le message qui hante les équipes d’ingénierie qui veulent construire des applications d’IA : « les embeddings ne sont plus synchronisés »
  • Une implémentation simple de recherche vectorielle évolue en une orchestration complexe de supervision, de synchronisation et de résolution de problèmes
  • Après avoir échangé avec des équipes d’ingénierie qui construisent des systèmes d’IA avec des bases de données vectorielles, l’auteur conclut que l’abstraction des bases de données vectorielles est mauvaise et que leur usage actuel est défaillant

« Cas courant lors de la construction d’un système RAG »

  • Utiliser Pinecone comme base de données vectorielle pour stocker et rechercher des embeddings
  • Comme les données textuelles s’intègrent mal dans les métadonnées de Pinecone, utiliser DynamoDB pour gérer les blobs et les données applicatives
  • Ajouter OpenSearch pour la recherche lexicale
  • Résultat : relier et synchroniser trois systèmes devient un cauchemar

Lorsqu’il faut supprimer un document source, il faut :

  1. exécuter boto3 pour supprimer l’enregistrement de DynamoDB
  2. mettre à jour Pinecone pour vérifier que les embeddings ont bien été supprimés
  3. envoyer une requête POST pour mettre à jour l’index de recherche lexicale
  • Il faut faire cela à chaque mise à jour, ajout ou suppression d’un document source
  • La gestion de configuration n’est pas seulement désordonnée, elle est aussi risquée
  • Certaines équipes ont payé 2 000 $ par mois pour des index qui auraient dû être supprimés quatre mois plus tôt
  • Avec le risque de renvoyer aux utilisateurs des données erronées ou obsolètes

Les bases de données vectorielles reposent sur une mauvaise abstraction

  • Les bases de données vectorielles traitent les embeddings comme des données indépendantes, et non comme des données dérivées
  • En les considérant comme indépendants, elles créent une complexité inutile

Une meilleure approche : l’abstraction « Vectorizer »

  • Proposition : traiter les embeddings comme un index de base de données via une abstraction de type « vectorizer »
  • Cette approche synchronise automatiquement les embeddings avec les données sources, ce qui supprime les coûts de maintenance
  • L’équivalent d’une commande CREATE INDEX pour un vectorizer ressemble à ceci :
SELECT ai.create_vectorizer(
    'public.blogs'::regclass,
    embedding => ai.embedding_openai('text-embedding-3-small', 1536),
    chunking => ai.chunking_recursive_character_text_splitter('content')
);
  • Cette commande génère les embeddings pour toutes les lignes de la table des blogs et continue de les mettre à jour lorsque les données changent

Le problème des bases de données vectorielles (et des types de données vectoriels)

  • Les bases de données vectorielles ont été développées pour traiter de grands volumes d’embeddings vectoriels pour du texte, des images et des données multimodales
  • Des bases de données généralistes comme PostgreSQL, MySQL, MongoDB et Oracle ont aussi ajouté la prise en charge de la recherche vectorielle
  • Mais l’abstraction des systèmes autonomes de recherche vectorielle ou des fonctions ajoutées à des bases existantes présente un défaut fondamental
  • Une fois insérés dans la base, les embeddings sont découplés des données non structurées qu’ils représentent
  • En l’absence de ce lien, les embeddings sont traités à tort comme des unités de données indépendantes que les développeurs doivent gérer, au lieu de données dérivées
  • Si l’on requalifie les embeddings en données dérivées, l’absurdité de l’abstraction actuelle des bases de données vectorielles devient évidente : les embeddings ne sont pas reliés à leurs données sources

Les équipes de développement doivent gérer :

  • des pipelines ETL complexes (extraction, chargement, transformation)

  • une base de données vectorielle pour les embeddings, une autre base pour les métadonnées et les données applicatives, ainsi qu’un index de recherche lexicale

  • des services de synchronisation de données

  • des systèmes de files d’attente pour les mises à jour et la synchronisation

  • des outils de supervision pour détecter la dérive des données et gérer, entre autres, les limitations de débit des services d’embedding

  • des systèmes d’alerte lorsque la recherche renvoie des résultats obsolètes

  • des vérifications de validation sur l’ensemble des systèmes

  • Passer à un nouveau modèle d’embedding ou tester une autre méthode de chunking impose d’écrire du code sur mesure et de coordonner les changements entre plusieurs services et bases de données

  • Cela fait peser sur les équipes la responsabilité de s’assurer que les embeddings sont générés au bon moment à mesure que les données sources évoluent

  • Sinon, les embeddings deviennent vite obsolètes, avec à la clé une expérience applicative dégradée pour les utilisateurs

Une meilleure approche : laisser la base de données gérer la complexité

  • Si l’on considère les embeddings comme des données dérivées, on peut confier au système de gestion de base de données la responsabilité de les créer et de les mettre à jour lorsque les données de base changent
  • Ce changement libère les développeurs de la charge consistant à maintenir manuellement la synchronisation entre embeddings et données sources
  • Cette distinction peut sembler peu importante pour des applications simples qui font une importation ponctuelle de données pour du RAG
  • Mais dans la plupart des applications réelles, les données changent en permanence
  • Pensez à une plateforme d’e-commerce qui utilise la recherche sémantique à base d’embeddings, ou à une application RAG d’assistant produit qui doit rester à jour avec les dernières informations produit
  • Suivre manuellement ces changements et régénérer les embeddings n’est pas seulement chronophage et source d’erreurs : cela détourne aussi les développeurs de leurs objectifs métier essentiels
  • Pourquoi gaspiller du temps de développement quand le système de base de données peut s’en charger automatiquement ?

Vectorizer : les embeddings vectoriels comme index

  • Une abstraction plus efficace consiste à concevoir les embeddings vectoriels comme un index spécialisé sur les données embarquées, et non comme une table ou un type de données indépendant
  • Les embeddings vectoriels ne sont pas un index au sens traditionnel
  • Ils fonctionnent plutôt comme un mécanisme d’indexation permettant de retrouver les parties les plus pertinentes des données sur la base des embeddings
  • Cette nouvelle abstraction proche d’un index peut être appelée « vectorizer »

Principaux avantages de l’abstraction vectorizer :

Synchronisation automatique

  • L’un des principaux bénéfices des index dans une base de données est qu’ils restent automatiquement synchronisés avec les données sous-jacentes
  • Quand les données d’une colonne changent, l’index est mis à jour en conséquence
  • En traitant les embeddings vectoriels comme une forme d’indexation, on peut bénéficier de cette même synchronisation automatique
  • Le système garantit que les embeddings restent toujours à jour avec les dernières données, supprimant le besoin de mises à jour manuelles et réduisant le risque d’erreur

Relation renforcée entre données et embeddings

  • Quand les vecteurs sont stockés indépendamment, il est facile de perdre leur lien avec les données d’origine
  • Ce vecteur a-t-il été généré à partir de la version la plus récente des données ? Ou s’agit-il d’un ancien vecteur créé avec un modèle d’embedding précédent ?
  • Ces questions sont cruciales, et toute confusion à ce niveau peut provoquer de graves erreurs
  • En reliant directement les embeddings vectoriels aux données sous forme d’index, cette relation devient claire et est maintenue automatiquement

Gestion des données simplifiée

  • Les développeurs rencontrent souvent des difficultés lorsqu’ils doivent gérer manuellement la synchronisation des données
  • Par exemple, si les données sous-jacentes sont supprimées mais que l’on oublie de supprimer les données générées avec un ancien modèle d’embedding, des incohérences apparaissent
  • L’abstraction vectorizer fait de la gestion de cette relation une responsabilité du système, ce qui réduit la charge cognitive des développeurs et limite les risques d’erreur

Le vectorizer, évolution naturelle des promesses fondamentales d’un DBMS

  • Le concept de vectorizer est une évolution naturelle des capacités des systèmes modernes de gestion de base de données (DBMS)
  • Les DBMS actuels savent déjà gérer la transformation et la synchronisation des données via des structures déclaratives comme les index, les triggers et les vues matérialisées
  • L’abstraction vectorizer s’inscrit bien dans ce paradigme en apportant un nouvel outil pour gérer une tâche de plus en plus importante : la gestion des embeddings vectoriels
  • En intégrant directement cette capacité au DBMS, on se rapproche de la promesse ultime des systèmes de base de données
  • Gérer les données de manière à abstraire la complexité, afin que les utilisateurs puissent se concentrer sur ce qu’ils font le mieux : construire des applications, analyser des données et innover

Implémentation d’un vectorizer pour PostgreSQL : pgai Vectorizer

  • Motivées par la volonté d’alléger la charge des développeurs, les équipes d’ingénierie IA de Timescale ont implémenté un vectorizer pour PostgreSQL
  • Il s’appelle pgai Vectorizer et est actuellement en Early Access
  • Il fait partie du projet PGAI, qui vise à rendre PostgreSQL plus adapté aux systèmes d’IA et à faciliter le développement IA pour les développeurs familiers de PostgreSQL
  • Pour voir comment pgai Vectorizer génère et met à jour automatiquement les embeddings vectoriels des données PostgreSQL, regardez la vidéo de démonstration

Fonctionnement de pgai Vectorizer

  • Le vectorizer est défini et créé en SQL
  • La requête suivante crée un vectorizer et précise la table concernée, la colonne à vectoriser, le modèle d’embedding à utiliser ainsi qu’un formatage additionnel des informations à inclure dans les données sources à embarquer
-- Créer un vectorizer qui embarque automatiquement les données de la table blogs
SELECT ai.create_vectorizer(
   'public.blogs'::regclass
   -- Utiliser le modèle OpenAI text-embedding-3-small
 , embedding=>ai.embedding_openai('text-embedding-3-small', 1536, api_key_name=>'OPENAI_API_KEY')
   -- Créer automatiquement un index StreamingDiskANN lorsque la table atteint 100k lignes
 , indexing => ai.indexing_diskann(min_rows => 100000, storage_layout => 'memory_optimized'),
   -- Appliquer un chunking récursif sur la colonne content
 , chunking=>ai.chunking_recursive_character_text_splitter('content')
   -- Ajouter des métadonnées d’autres colonnes dans les embeddings pour améliorer la recherche
 , formatting=>ai.formatting_python_template('Blog title: $title url: $url blog chunk: $chunk')
);
-- Le vectorizer met à jour les embeddings quand la table source change
-- Aucune autre action utilisateur n’est nécessaire
  • Il définit aussi des fonctions de chunking par défaut, car les longs textes doivent être découpés en plusieurs petits segments compatibles avec les limites de tokens des modèles d’embedding

Suivi des changements dans les données sources

  • En interne, pgai Vectorizer détecte les modifications de la table source (insertions, mises à jour, suppressions) et génère ou met à jour les embeddings vectoriels de manière asynchrone
  • pgai Vectorizer a été conçu pour deux modes de déploiement : auto-hébergé et entièrement managé sur Timescale Cloud
  • Dans l’implémentation hébergée dans le cloud, pgai Vectorizer utilise les fonctions cloud de la plateforme Timescale Cloud pour générer les embeddings
  • Dans la version open source de pgai Vectorizer, la génération d’embeddings repose sur l’exécution d’un worker externe
  • pgai Vectorizer stocke les informations de configuration et de catalogue dans la base, avec les principales données internes de suivi

Où les embeddings sont-ils réellement générés ?

  • Le processus réel de génération d’embeddings se déroule dans un processus externe, hors de la base de données
  • Cela réduit la charge sur le serveur de base de données et évite que le vectorizer n’affecte la capacité de la base à traiter les requêtes applicatives
  • Cela permet aussi de faire évoluer facilement le traitement des embeddings indépendamment des autres opérations de base de données
  • Le processus commence par lire la base pour vérifier s’il y a du travail à effectuer
  • Si c’est le cas, il lit les données depuis la base, effectue le chunking et le formatage, appelle un fournisseur de modèle d’embedding comme OpenAI pour générer les embeddings, puis réécrit les résultats dans la base

Personnalisation du processus

  • pgai Vectorizer est flexible : il permet de définir les règles de chunking et de formatage utilisées pour produire les embeddings
  • En particulier, il est possible de configurer les colonnes de la table source à vectoriser ainsi que les règles de chunking et de formatage pour que les données restent dans les limites de tokens du modèle tout en incluant les informations pertinentes dans chaque embedding
  • Dans sa version Early Access, pgai Vectorizer permet de personnaliser le choix du modèle d’embedding OpenAI, la stratégie de chunking pour découper le texte, les options de formatage pour injecter du contexte supplémentaire dans chaque chunk, ainsi que la configuration d’indexation pour la création automatique d’index et le tuning des performances
  • L’équipe prévoit bientôt d’aller plus loin en permettant aux utilisateurs de fournir leur propre code Python pour personnaliser entièrement le chunking, l’embedding et le formatage

Par exemple, voici un vectorizer configuré pour découper récursivement des fichiers source HTML et créer des embeddings OpenAI à partir des données sources. Le chunking et le formatage peuvent être adaptés aux données de l’application : code, documentation, Markdown, etc.

-- Configuration avancée d’un vectorizer
SELECT ai.create_vectorizer(
   'public.blogs'::regclass,
   destination => 'blogs_embedding_recursive',
   embedding => ai.embedding_openai('text-embedding-3-small', 1536),
   -- Appliquer un chunking récursif avec les paramètres spécifiés sur du contenu HTML
   chunking => ai.chunking_recursive_character_text_splitter(
       'content',
       chunk_size => 800,
       chunk_overlap => 400,
       -- Séparateurs adaptés au HTML, triés du plus prioritaire au moins prioritaire
       separator => array[
           E'

', -- découper aux grandes sections du document
           E'

',    -- découper aux limites des div
           E'

',
           E'

',      -- découper aux paragraphes
           E'
',      -- découper aux retours à la ligne
           E'
',     -- découper aux éléments de liste
           E'. ',        -- repli sur les frontières de phrase
           ' '          -- ultime recours : découper sur les espaces
       ]
   ),
   formatting => ai.formatting_python_template('title: $title url: $url $chunk')
);

L’avis de GN⁺

  • pgai Vectorizer semble être un outil puissant et innovant, capable de simplifier fortement la gestion des embeddings. L’abstraction vectorizer allège la charge des développeurs en évitant une gestion manuelle des embeddings tout en garantissant leur synchronisation avec les données sources.
  • Cela paraît particulièrement utile lorsqu’il faut appliquer des changements comme une mise à niveau du modèle d’embedding ou une modification de la stratégie de chunking. Avec une base de données vectorielle classique, ce type d’évolution peut imposer un processus complexe impliquant du code sur mesure et une coordination entre plusieurs systèmes, alors qu’avec pgai Vectorizer, il suffit de mettre à jour la configuration du vectorizer.
  • Par ailleurs, gérer les embeddings dans une base généraliste comme PostgreSQL permet d’éviter les problèmes liés à l’orchestration de plusieurs systèmes spécialisés. Cela peut simplifier considérablement le développement applicatif.
  • Un point à prendre en compte : les embeddings sont effectivement générés dans un processus Python externe. C’est un bon choix de conception pour ne pas affecter les performances de la base de données, mais cela implique aussi de superviser et gérer séparément le processus de génération des embeddings.
  • Au final, pgai Vectorizer représente une avancée importante dans la manière de gérer les embeddings pour les applications d’IA. À mesure que davantage d’équipes l’adopteront et partageront leurs retours, cet outil puissant devrait encore progresser. Intégrer la gestion des embeddings dans un outil familier comme Postgres pourrait permettre à davantage de développeurs d’exploiter des capacités d’IA avancées.

1 commentaires

 
GN⁺ 2024-10-30
Avis Hacker News
  • Le surcoût de la synchronisation des données est surestimé, et la plupart des workflows basés sur des embeddings n’impliquent pas beaucoup de mises à jour ni de suppressions. Même sur de petits jeux de données, les problèmes de cohérence sont difficiles à percevoir. Mais le fait de ne pas avoir à se soucier de la synchronisation des données reste très appréciable

    • Le plus gros inconvénient du stockage des embeddings dans une base de données Postgres est que les charges de travail sont très différentes. Les index HNSW consomment beaucoup de ressources et peuvent provoquer des problèmes de contention. Si l’on déplace la base de données, les problèmes de cohérence réapparaissent
    • Une question est posée sur l’interaction avec le filtrage. Il se demande s’il est possible d’utiliser des index partiels, et si les limitations de l’implémentation HNSW de pgvector existent toujours
  • En tant qu’employé d’Elastic, il mentionne qu’Elasticsearch a récemment ajouté un type de données appelé semantic_text. Celui-ci découpe automatiquement le texte en chunks, calcule les embeddings et les stocke. Les requêtes sont aussi simplifiées, ce qui réduit les E/S et allège le code côté client

  • Présentation d’un outil PostgreSQL qui repense les embeddings vectoriels comme des index de base de données. Seul OpenAI est pris en charge pour le moment, mais la prise en charge des modèles locaux et OSS est prévue prochainement. Il attend des retours et des réactions

  • Il remet en question l’idée d’utiliser FAISS comme base de données unique. Ce serait l’équivalent de sqlite pour les embeddings vectoriels, en stockant ensemble les métadonnées et les vecteurs afin de préserver leurs relations

  • Avis positif sur l’utilisation de vecteurs dans Postgres, avec une question sur l’ordre du filtrage lorsqu’on inclut à la fois une recherche vectorielle et de la logique dans une requête SQL. Il apprécie la DX de pg_vector, mais le filtrage après la recherche vectorielle peut ralentir les performances

  • Il mentionne que stocker des embeddings bruts dans une base de données vectorielle revient à stocker les n-grammes bruts d’un texte dans une base de données. Il serait plus logique de stocker les documents

  • Il indique utiliser sqlite-vec et FTS5 dans SQLite, et les trouver très utiles

  • Il a construit un ORM PostgreSQL en Node.js permettant d’écrire du code avec des champs vectoriels. Cela permet d’interroger les données ou le contenu des embeddings, et de définir comment stocker les champs du modèle sous forme d’embeddings

  • Il mentionne que les Materialized Views sont bien

  • Il affirme que les applications d’IA utilisant des chunks basés sur les caractères n’ont pas dépassé le stade du PoC