Guide
Quantification des embeddings : int8 et binaire
Diviser par 4 ou par 32 la taille de vos vecteurs : quantification int8 et binaire, retri par vecteurs complets, et ce que proposent les bases.
Mis à jour le
Quantifier un embedding, c'est stocker chaque valeur avec moins de bits. Un vecteur classique utilise 32 bits par valeur (float32). En int8, chaque valeur tient sur 8 bits : la taille est divisée par 4. En binaire, chaque valeur tient sur 1 bit : la taille est divisée par 32. On perd un peu de qualité, qu'on regagne en grande partie en retriant les meilleurs candidats avec des vecteurs plus précis.
Les deux techniques
D'après l'article de Hugging Face consacré au sujet :
| Technique | Principe | Gain de mémoire | Distance utilisée |
|---|---|---|---|
| Scalaire (int8) | Chaque valeur est ramenée à 256 niveaux (-128 à 127), d'après les valeurs minimales et maximales observées sur un jeu d'étalonnage | × 4 | la même qu'avant |
| Binaire | Sur des vecteurs normalisés, toute valeur positive devient 1, toute valeur nulle ou négative devient 0 | × 32 | distance de Hamming (nombre de bits différents) |
Ce que coûte la compression en qualité
Les mesures de l'article portent sur un modèle (mxbai-embed-large-v1) et sur la partie recherche du banc d'essai MTEB (voir Classement MTEB). Elles donnent un ordre de grandeur, pas une garantie :
| Variante | Performance conservée | Vitesse moyenne mesurée |
|---|---|---|
| Binaire, sans retri | environ 92,5 % | 24,76 fois plus rapide que float32 |
| Binaire, avec retri | 96,45 % | — |
| int8, sans retri | 97 % | 3,66 fois plus rapide |
| int8, avec retri (multiplicateur de 4 à 5) | environ 99 % | — |
Le retri, ou comment récupérer la qualité
Le principe, décrit dans l'article : on cherche avec les vecteurs compressés un nombre de candidats plus grand que nécessaire (par exemple 4 fois plus), puis on recalcule le score de ces seuls candidats avec le vecteur complet de la question. Le gros de la recherche se fait sur des données légères, et la précision revient pour le classement final.
Avec pgvector, cela tient en un index sur l'expression binaire, puis une requête en deux temps (exemple de la documentation, avec des vecteurs de 3 dimensions) :
CREATE INDEX ON items USING hnsw ((binary_quantize(embedding)::bit(3)) bit_hamming_ops);
SELECT * FROM (
SELECT * FROM items ORDER BY binary_quantize(embedding)::bit(3) <~> binary_quantize('[1,-2,3]') LIMIT 20
) ORDER BY embedding <=> '[1,-2,3]' LIMIT 5;
La sous-requête prend 20 candidats par distance de Hamming (<~>), la requête extérieure les retrie par distance cosinus (<=>) sur les vecteurs complets. Remplacez 3 par la dimension de votre modèle.
pgvector propose aussi une solution intermédiaire, la demi-précision (halfvec) : 2 × dimensions + 8 octets par vecteur au lieu de 4 × dimensions + 8, et jusqu'à 4 000 dimensions indexables au lieu de 2 000.
Ce que proposent les autres bases
- Weaviate : sa documentation décrit quatre méthodes, par produit (PQ), binaire (BQ, 32 fois moins de place), scalaire (SQ, 4 fois moins) et par rotation (RQ, en 8, 4 ou 1 bit). Avec SQ, RQ ou BQ, Weaviate prend plus de candidats puis les retrie avec les vecteurs non compressés. Aucune quantification n'est appliquée par défaut tant que vous ne la configurez pas.
- Elasticsearch : d'après la documentation du type
dense_vector, l'index par défaut des vecteursfloatest quantifié depuis la version 9.0 (int8_hnsw), et depuis la 9.1 il passe en quantification binaire (bbq_hnsw) à partir de 384 dimensions. - Qdrant et Milvus : l'article de Hugging Face indique que Qdrant propose une quantification binaire et scalaire, et que Milvus accepte les vecteurs binaires directement.
Quantifier dès la sortie du modèle
Certains fournisseurs renvoient directement des vecteurs compressés, ce qui évite de convertir soi-même : Voyage AI (output_dtype : int8, uint8, binary, ubinary, d'après sa documentation) et Cohere (embedding_types, mêmes valeurs, d'après sa référence d'API). Avec Sentence Transformers, la fonction quantize_embeddings(embeddings, precision="binary") fait la conversion.
Quand s'en servir
- Petit corpus, dont les vecteurs tiennent sans peine en mémoire : souvent inutile ; faites le calcul avec la formule de taille ci-dessus avant de compliquer la chaîne.
- Gros corpus, ou serveur à mémoire limitée : la quantification et le retri sont un levier fort, à combiner avec la réduction des dimensions.
- Dans tous les cas, mesurez le rappel avant et après sur vos propres questions, comme le recommande la documentation de pgvector pour tout index approximatif.
Questions fréquentes
La quantification binaire fait-elle perdre beaucoup de qualité ?
Dans l'essai publié par Hugging Face sur un modèle donné, la recherche binaire conservait environ 92,5 % de la performance en recherche, et 96,45 % avec un retri par le vecteur complet de la question. À vérifier sur votre corpus.
Faut-il garder les vecteurs complets après quantification ?
C'est conseillé si vous voulez retrier les candidats : la recherche se fait sur les vecteurs compressés, puis les meilleurs résultats sont recalculés avec plus de précision.
Sources
Pages consultées pour écrire cet article :
- https://huggingface.co/blog/embedding-quantization
- https://github.com/pgvector/pgvector
- https://docs.weaviate.io/weaviate/concepts/vector-quantization
- https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector
- https://docs.voyageai.com/docs/embeddings
- https://docs.cohere.com/reference/embed