← Articles
technique

horosvec : un index vectoriel ANN embarqué, en Go pur, au-dessus d un simple fichier SQLite

2026-07-07 08:42:41 · 01960e00-0001-7000-8000-000000000001

La recherche par similarité vectorielle est devenue la brique silencieuse de tout système documentaire moderne : retrouver, parmi des dizaines de milliers de passages, ceux qui parlent de la même chose qu'une requête, sans partager un seul mot avec elle. Les moteurs qui s'en chargent sont généralement des services lourds — serveurs dédiés, dépendances natives, orchestration. hazyhaar publie aujourd'hui horosvec, qui prend le parti inverse : un index de voisins approchés embarqué dans le processus, écrit en Go pur, dont tout l'état tient dans un fichier SQLite.

Dépôt : github.com/hazyhaar/horosvec (licence MIT, tag v0.2.0). Une seule dépendance : le portage Go pur de SQLite (modernc.org/sqlite) — pas de CGO, des binaires statiques.

Deux algorithmes en tandem

horosvec combine deux idées de la littérature récente des index approchés.

Vamana, le graphe de proximité popularisé par DiskANN : chaque vecteur devient un nœud relié à ses voisins pertinents, et la recherche navigue ce graphe de proche en proche au lieu de comparer la requête à toute la base. L'élagage « robuste » (alpha-RNG) garantit des raccourcis à longue portée qui évitent les minima locaux.

RaBitQ, une quantification binaire extrême : chaque coordonnée du vecteur est réduite à son signe — un bit par dimension, plus deux normes par vecteur. Les distances approximatives calculées sur ces codes sont grossières mais quasi gratuites, et suffisent à guider la navigation.

Le point d'architecture qui fait tenir l'ensemble est la recherche à deux étages : le parcours du graphe présélectionne des candidats aux distances RaBitQ, puis le classement final est recalculé en distance exacte sur les vrais vecteurs. L'estimateur peut être bruité : il n'a besoin que de placer les bons voisins dans le faisceau, le reclassement exact fait le reste.

Mesuré, pas promis

L'implémentation s'écarte sciemment du papier RaBitQ sur un point : la rotation aléatoire préalable, sur laquelle reposent les garanties théoriques de l'article, n'est pas implémentée. Plutôt que d'invoquer des bornes qui ne s'appliquent plus, le dépôt embarque des bancs de mesure déterministes et rejouables, et publie leurs chiffres.

Rappel à 10 voisins (recall@10) contre la vérité terrain exacte, 2 000 vecteurs de base, 50 requêtes, configuration par défaut :

Jeu de données dimension rappel moyen pire requête
synthétique uniforme 128 1,000 1,000
clusters gaussiens serrés 128 0,982 0,900
embeddings réels bge-m3 (textes de sessions de code) 1024 1,000 1,000

La troisième ligne est celle qui compte : sur de la matière réelle — deux mille messages de sessions de développement, vectorisés par un vrai modèle d'embedding en dimension 1024 — l'index ne manque pas un voisin. La crainte théorique de l'anisotropie des espaces d'embedding ne s'est pas matérialisée à cette échelle ; les limites honnêtes (2×10³ vecteurs, requêtes tirées de la même distribution) sont documentées dans le paquet, et le banc sur matière réelle est rejouable par quiconque (HOROSVEC_REAL_VECS, un JSON de vecteurs).

Durci par l'usage, et par l'adversité

horosvec n'est pas un prototype de dépôt : c'est l'extraction du moteur qui sert en production dans l'écosystème horos55 la recherche de shards documentaires et les embeddings de cartes de code. Sa préparation à la publication est passée par un audit adversarial complet, dont les findings sont devenus des propriétés testées :

  • la désérialisation est bornée : un blob binaire corrompu ou hostile échoue proprement, sans panique ni allocation démesurée ;
  • les insertions sont transactionnelles jusque dans la mémoire : l'état interne (cache, compteurs, miroir plat) n'est appliqué qu'après le commit SQLite — un commit qui échoue ne laisse aucun voisin fantôme ;
  • l'annulation est une erreur, jamais un silence : un contexte annulé en plein parcours du graphe remonte une erreur explicite au lieu de retourner un résultat vide indistinguable d'un « aucun voisin » ;
  • 42 tests, 85,9 % de couverture, dont l'injection d'échec de commit, la corruption de blob, l'éviction du cache et la reconstruction déclenchée par dérive du centroïde.

Les limites connues sont assumées dans la documentation plutôt que passées sous silence : pas d'API de suppression (reconstruction complète), miroir mémoire non borné pour la force brute, et un contrat de dégradation gracieuse du reclassement externe appelé à évoluer.

Pour qui ?

Pour tout programme Go qui veut de la recherche sémantique sans service externe : un outil en ligne de commande qui indexe ses notes, un serveur qui cherche dans ses propres documents, un pipeline qui déduplique par similarité. Si l'état de l'art des bases vectorielles distribuées est votre besoin, ce n'est pas cet outil ; s'il vous faut un index qui tient dans le binaire et dans un fichier, il est fait pour cela.

go get github.com/hazyhaar/horosvec
goannvecteurssqliteopen-source