Développement de `sqlite-vec`, une nouvelle extension de recherche vectorielle pour SQLite
(alexgarcia.xyz)- Pour réduire les limites de
sqlite-vss,sqlite-vecest en cours de développement comme extension embarquée de recherche vectorielle en pur C, avec pour objectif tous les environnements où SQLite peut s’exécuter - Le flux d’utilisation SQL est simplifié autour de
CREATE VIRTUAL TABLE,INSERT INTOetSELECT, avec prise en charge de la recherche de style KNN ainsi que des entrées vectorielles en JSON et en binaire compact - En supprimant la dépendance à Faiss, le projet vise non seulement Linux et macOS, mais aussi Windows, WebAssembly, le mobile et le Raspberry Pi, avec une taille binaire attendue de quelques centaines de Ko, contre 3 à 5 Mo auparavant
- Les vecteurs sont stockés par chunks dans une shadow table, ce qui réduit le besoin de charger l’ensemble en RAM, et
PRAGMA mmap_sizepeut accélérer les recherches en mémoire - La première version ne proposera qu’un full-scan exhaustif, sans ANN, et la sortie de
v0.1.0est prévue une fois les 246 TODO desqlite-vec.cterminés
Ce que sqlite-vec veut changer dans la recherche vectorielle de SQLite
sqlite-vecest une nouvelle extension SQLite écrite en pur C, pensée comme un remplaçant desqlite-vss, publié en février 2023- Le périmètre visé inclut des fonctions SQL personnalisées pour la recherche vectorielle rapide, des tables virtuelles, ainsi que des outils et utilitaires pour travailler avec les vecteurs
- quantification
- conversion JSON/BLOB/numpy
- arithmétique vectorielle
- Les utilisateurs peuvent créer et interroger un stockage vectoriel en pur SQL
- création d’une table virtuelle pour les vecteurs avec
CREATE VIRTUAL TABLE - insertion des vecteurs avec
INSERT INTO - recherche de style KNN sous la forme
SELECT ... WHERE sample_embedding MATCH ... ORDER BY distance LIMIT ...
- création d’une table virtuelle pour les vecteurs avec
- Les vecteurs en entrée peuvent être fournis sous forme de chaîne JSON ou au format binaire compact
Le pur C et l’absence de dépendances élargissent les environnements d’exécution
sqlite-vecvise une extension en pur C sans dépendance, un choix clé pour élargir les plateformes prises en charge- L’ancien
sqlite-vss, à cause de sa dépendance au C++, ne fonctionnait de façon stable que sur Linux et macOS, avec une taille binaire comprise entre 3 et 5 Mo - La nouvelle extension vise une exécution dans les environnements suivants
- Linux
- macOS
- Windows
- WebAssembly dans le navigateur
- appareils mobiles
- petits appareils comme le Raspberry Pi
- La taille binaire attendue se situe dans la plage de quelques centaines de Ko
Contrôle de l’usage mémoire et de la vitesse de recherche
sqlite-veccontrôle l’usage mémoire en stockant les vecteurs dans des chunks au sein d’une shadow table- lors d’une recherche KNN, tous les vecteurs ne sont pas chargés en RAM en une seule fois, mais lus chunk par chunk
- il n’est donc pas nécessaire de garder tous les vecteurs résidents en mémoire
- Si de meilleures performances en mémoire sont nécessaires, on peut utiliser
PRAGMA mmap_sizede SQLite pour accélérer la recherche KNN
Nouvelles fonctions vectorielles et limites initiales
sqlite-veccherche à mieux prendre en charge les fonctions suivantes, dans la continuité des outils récents de recherche vectorielle et des avancées de la recherche- embeddings à longueur adaptative, c’est-à-dire les Matryoshka embeddings
- vecteurs
int8etbit - quantification binaire et scalaire
- Ces fonctions servent de base pour ajuster plus finement la vitesse, la précision et l’usage disque des vecteurs
- La première version de
sqlite-vecne prendra en charge que la recherche vectorielle exhaustive par full-scan- il n’y aura pas d’option “approximate nearest neighbors” au départ
- IVF et HNSW sont envisagés pour plus tard
Configuration de la démo dans le navigateur
- La démo utilise
sqlite-vecexécuté dans le navigateur - La configuration visible dans les outils de développement est la suivante
- un
sqlite3.wasmnon optimisé de 5,9 Mo - le build WASM officiel de SQLite compilé avec
sqlite-vec - une base de données SQLite
movies.bit.dbde 2,6 Mo
- un
movies.bit.dbcontient dans la tablearticlesles synopsis de 4 800 films fondés sur les métadonnées de films TMDB- Une table virtuelle distincte
vec_moviessert d’index vectoriel pour les embeddings de ces synopsis- les embeddings utilisent le modèle Nomic 1.5 embeddings
- les vecteurs sont quantifiés en vecteurs binaires
Données de démo et déroulement de la recherche KNN
- La table
articlescontient des colonnes commetitle,release_dateetoverview - La colonne
overviewcontient un bref résumé du film, utilisé dans la démo comme base pour les embeddings - La table virtuelle
vec_moviesstocke les embeddings dearticles.overviewdans la colonneoverview_embeddings- les vecteurs sont des vecteurs binaires à 768 dimensions
- la taille de stockage est de
768 / 8 = 96, soit 96 octets
- Quand l’utilisateur sélectionne un film via un bouton radio, l’identifiant du film sélectionné est injecté dans le paramètre
:selected_moviede la requête SQL KNN - Les résultats de recherche sont les 10 films les plus proches du film sélectionné
- comme il s’agit d’un vecteur binaire, le calcul de distance utilise la distance de Hamming
- le résultat le plus proche est toujours le même film, avec une distance de 0
- Les embeddings produits à partir de très courts résumés et d’un petit jeu de données de films n’offrent pas la meilleure qualité possible, et la quantification binaire dégrade encore cette qualité, mais l’objectif est de montrer une recherche vectorielle rapide et « suffisamment bonne » dans le navigateur
- Pour voir le fonctionnement interne, il suffit de préfixer
SELECTavecEXPLAIN QUERY PLAN, ce qui permet de voir l’“index”0:knnutilisé parvec_movies
Les limites de sqlite-vss et la dépendance à Faiss
- Le développement et l’adoption de
sqlite-vssse sont heurtés à plusieurs obstacles- il ne fonctionne que sur Linux et macOS, sans prise en charge de Windows, du WASM ou des appareils mobiles
- tous les vecteurs sont stockés en mémoire
- il existe des bugs et problèmes liés aux transactions
- la compilation est très difficile et prend beaucoup de temps
- des opérations vectorielles courantes comme la quantification scalaire ou binaire sont absentes
- Presque tous ces problèmes proviennent de la dépendance à
Faiss - Une partie pourrait être résolue avec beaucoup de temps et d’efforts, mais nombre d’entre eux risquent de rester bloqués à cause de Faiss
- C’est ce qui a conduit au lancement de
sqlite-vec, dans l’idée qu’une solution bas niveau sans dépendances était une option séduisante et que la recherche vectorielle elle-même n’était pas si complexe
État de la release et appel au sponsoring
- Les fonctions essentielles de
sqlite-vecfonctionnent, mais la gestion des erreurs et les tests restent très insuffisants - Le fichier
sqlite-vec.ccomporte encore 246 TODOtodo_assert(): 191- commentaires
// TODO: 41 todo panic: 14- la progression globale affichée pour
sqlite-vec v0est de 0/246, soit 0 %
- Une fois les 246 TODO terminés, la première release
v0.1.0est prévue- documentation
- démo
- bindings
- autres composants
- L’objectif est d’y parvenir d’ici environ un mois, mais aucun calendrier n’est confirmé
- Le projet recherche le sponsoring d’entreprises intéressées par le succès de
sqlite-vec, avec possibilité de contact par e-mail
1 commentaires
Commentaires sur Hacker News
C’est l’auteur — je répondrai aux questions s’il y en a. Il s’agit moins d’une release officielle que d’un « je travaille sur un nouveau projet », et l’extension elle-même est encore en cours de développement. Lien du projet : https://github.com/asg017/sqlite-vec
J’ai déjà une idée assez précise de ce à quoi ressemblera la v0.1.0 de cette extension, mais il faudra sans doute encore quelques semaines pour y arriver. Ce billet visait surtout à informer les utilisateurs de sqlite-vss, mon extension précédente de recherche vectorielle pour SQLite, de ce qui arrive ensuite, et une release plus importante est prévue quand ce sera prêt
Globalement, je suis très enthousiaste à l’idée d’avoir une alternative de recherche vectorielle facile à embarquer. Le fait que cela fonctionne sur tous les systèmes d’exploitation, en WASM, sur mobile, sur Raspberry Pi, etc., est particulièrement appréciable, et personnellement je trouve amusant d’essayer de faire tourner une petite appli de recherche sémantique sur Beepy
[0] https://beepy.sqfmi.com/
J’aimerais aussi connaître les performances par rapport à sqlite-vss. Je suis curieux de voir des chiffres de profilage à la fois sur la vitesse des requêtes et sur l’utilisation mémoire
Dans l’ensemble, ça a vraiment l’air excellent, et j’aime beaucoup cette direction
Le fait que sqlite-vec ne prenne au départ en charge que la recherche vectorielle par scan exhaustif, sans option de plus proches voisins approximatifs (ANN), mais avec l’idée d’ajouter plus tard IVF et HNSW, me semble être le bon choix à 1000 %. C’est bien de ne pas rendre les choses trop complexes dès le début
J’ai déjà lancé de la recherche vectorielle on-device, et avec une combinaison de vecteurs binaires 128 bits et de distance de Hamming, c’était assez rapide pour exécuter une recherche de distance totalement exhaustive à chaque frame caméra, même avec plus de 200 000 entrées dans la base de données. On dépassait les 10 fps même sur des téléphones bas de gamme, et c’était très fluide sur les bons téléphones. Il y a étonnamment beaucoup de cas où la force brute suffit
Cela dit, au moment d’implémenter des algorithmes ANN comme HNSW, ce serait chouette de pouvoir les gérer selon le paradigme des index de table. Ainsi, passer d’une recherche par force brute à de l’ANN deviendrait aussi simple que créer un index sur une table, et expérimenter plusieurs algorithmes ANN et leurs paramètres reviendrait à ajuster les paramètres de création de l’index. C’est peut-être déjà la direction prise, mais je le mentionne au cas où
Je me demande aussi si cela se compile avec sqlite-httpvfs. Cela semblerait bien correspondre à ce projet : https://github.com/phiresky/sql.js-httpvfs
Je me demande aussi si vous avez envisagé une compatibilité syntaxique avec pgvector pour un DSL SQL vectoriel commun. J’imagine que les inconvénients ne sont pas forcément bien plus importants que les avantages, mais je me demande si c’est possible
https://observablehq.com/@asg017/introducing-sqlite-loadable...
sqlite-vss est déjà bien utilisé dans plusieurs projets
Le passage « un vecteur binaire de 768 dimensions occupe 96 octets (768 / 8 = 96) » me perturbe. C’est justement ce genre de problème que la plupart des vector stores rencontrent, à savoir la malédiction de la dimensionnalité, et à mon sens c’est un problème antérieur à l’indexation
Je me suis dit qu’il s’agissait peut-être plutôt de 768 dimensions * 8 octets (f64), soit 6144 octets. En général, on réduit ça en f32, f16, ou dans une représentation encore plus compacte, au prix d’une légère perte
S’il existe une méthode pour faire tenir 768 dimensions dans 96 octets, via de la compression ou un schéma d’encodage proche d’un trie, j’aimerais vraiment en lire davantage dans un billet séparé. Si chaque dimension est traitée comme 1 bit, je comprends, mais dans ce cas je me pose encore des questions sur la qualité de recherche
Certains modèles d’embedding, comme nomic v1.5[0] ou le nouveau modèle de mixedbread[1], sont entraînés spécifiquement pour conserver leur qualité même après quantification binaire. Ce n’est pas le cas de tous les modèles, donc les résultats peuvent varier. En général, avec de très grands vecteurs comme le grand modèle d’embedding 3072 dimensions d’OpenAI, cela semble fonctionner dans une certaine mesure même sans entraînement spécifique pour cet usage
[0] https://twitter.com/nomic_ai/status/1769837800793243687
[1] https://www.mixedbread.ai/blog/binary-mrl
Si on utilise un index FAISS sur les données et qu’on applique de la quantification par produit (Product Quantization), on peut aussi tester des vecteurs binaires avec un schéma comme PQ768x1 pour des caractéristiques binaires, ou comparer avec des approches où chaque paire de vecteurs est quantifiée vers l’une de quatre valeurs : https://github.com/facebookresearch/faiss/wiki/The-index-fac...
En général, dans les bases de données vectorielles, on compresse ou on projette les données dans un espace de dimension plus faible avant le stockage, ce qui améliore plutôt la situation
Grâce à sqlite-vss, j’ai pu comprendre comment fonctionne le RAG et l’implémenter dans un projet jouet. Le débogage était un peu difficile, mais une fois bien configuré, ça tournait sans accroc sur Ubuntu, et je l’utilise encore aujourd’hui.
Ravi d’apprendre qu’une nouvelle meilleure version est en préparation, sans dépendances contraignantes.
Je me demande si l’objectif est d’utiliser uniquement l’API SQLite publique, ou si vous envisagez quelque chose qui s’attache à l’amalgamation SQLite.
Cette fonctionnalité m’intéresse clairement, mais avec des bindings Go basés sur Wasm, il faut réfléchir à la manière de la distribuer séparément de SQLite. Jusqu’à présent, j’ai toujours embarqué tout le code C, car c’est bien plus simple qu’un « lien dynamique » en Wasm.
Vous avez aussi mentionné les E/S BLOB incrémentales ; vous le savez peut-être déjà, mais il faut garder à l’esprit que les gros BLOB sont stockés sous forme de liste chaînée de pages, donc les E/S BLOB ne sont absolument pas à accès aléatoire.
J’aime vraiment beaucoup les bindings SQLite pour wazero. Je prévois d’ailleurs de proposer 1) des bindings CGO pour sqlite-vec et 2) un build WASI personnalisé de sqlite-vec utilisable directement dans go-sqlite3. À l’origine, je pensais utiliser les scripts de build de ce dépôt pour générer le fichier sqlite3.wasm. Si vous voulez le prendre en charge directement dans le projet, il devrait suffire de placer les fichiers sqlite-vec.c/h dans go-sqlite3/sqlite3.
J’ai appris à mes dépens que les E/S BLOB incrémentales sont compliquées. C’est clairement un facteur limitant pour la vitesse des requêtes de sqlite-vec. En gardant des tailles de chunk relativement petites, de l’ordre de quelques Mo, et en augmentant
page_size, on obtenait un compromis correct, maispage_sizea notamment des effets secondaires.PRAGMA mmap_sizeaide aussi beaucoup, car cela semble garder les pages en mémoire et accélérer les lectures de débordement, mais bien sûr cela augmente fortement l’usage mémoire. C’est un équilibre difficile.DuckDB a annoncé aujourd’hui son extension « Vector Similarity Search in DuckDB ».
https://duckdb.org/2024/05/03/vector-similarity-search-vss.h...
Avec DuckDB VSS, on pourrait encoder les embeddings, les stocker au format DuckDB, puis exécuter du SQL à l’intérieur du CDN.
J’aime ce genre de projet. C’est un projet open source qui vise un problème très spécifique, et c’est ce qui me plaît.
Je continue à me demander si on pourrait créer quelque chose de très utile pour une niche technique dans l’écosystème TypeScript/Next.js/React, mais l’inspiration ne m’est pas encore venue.
J’ai utilisé Qdrant vector DB pour l’automatisation de bout en bout dans l’appli AI RAG https://github.com/rnadigital/agentcloud, donc je suis impatient de voir la suite. Je me demande quand ce sera prêt à l’emploi et s’il y aura un guide de démarrage rapide.
Je pourrais peut-être aussi aider pour la rédaction d’articles de blog.
Il existe aussi un paquet pip
sqlite-vecnon documenté, donc si vous voulez l’appeler directement depuis un « Agent Backend » Python, vous pouvez probablement déjà l’essayer.C’est assez proche de ce que j’imaginais pour du « README-driven development ». Je me demande si l’auteur a commencé par la documentation.
Mais on en est au stade du « 80 % réalisés avec 20 % d’effort », donc les 20 % restants — gestion des erreurs, fuzz testing, tests de correction — vont probablement prendre 80 % du temps. Cela dit, comme les gens posent déjà des questions sur l’état actuel de
sqlite-vss, je me suis dit que ce billet de blog « work in progress » pourrait répondre à quelques-unes d’entre elles.J’aime aussi l’idée de commencer par la documentation. Surtout pour une extension SQLite, l’API SQL — à quoi ressemblent les fonctions scalaires, les tables virtuelles, etc. — est vraiment essentielle. Avant même d’écrire la majeure partie du code, j’ai beaucoup esquissé ce à quoi la partie SQL de sqlite-vec devait ressembler.
[0] https://github.com/asg017/sqlite-vec/blob/main/sqlite-vec.c
On dirait presque une réponse à une issue GitHub que j’avais ouverte sur SQLite-vss il y a quelques mois. Pas strictement une réponse à cette issue, mais presque.
https://github.com/asg017/sqlite-vss/issues/124
En réalité, c’est en lisant ce ticket pour la première fois que je suis tombé dans le terrier du lapin « comment rendre sqlite-vss meilleur », ce qui m’a finalement conduit à « il faut que je crée sqlite-vec ». Merci de m’avoir mis sur cette voie.
Avec la quantification binaire intégrée de sqlite-vec, on peut faire à peu près ceci :
CREATE VIRTUAL TABLE vec_files USING vec0 ( contents_embedding bit[1536] );INSERT INTO vec_files(rowid,contents_embedding) VALUES ((1, vec_quantize_binary( /* 1536-dimension float vector here*/)))Je me demande si, lorsqu’il s’exécute dans le navigateur, sqlite-vec peut persister les données dans IndexedDB natif du navigateur. Ou bien faut-il que l’utilisateur gère lui-même cette partie ?
Même si la réponse est que vous n’y avez pas encore réfléchi, je vous serais reconnaissant de partager votre réflexion dans cette direction.
Je ne suis pas certain qu’IndexedDB soit pris en charge spécifiquement, mais les VFS localStorage/OPFS peuvent être utilisés.
[0] https://sqlite.org/wasm/doc/trunk/persistence.md#kvvfs