Composants — Encoders, Embeddings & Vector DB
De l’encoder (texte/vision/code/audio) au vecteur normalisé, puis à l’index vectoriel (ANN) enrichi de métadonnées filtrables. Usages : RAG, recherche sémantique, clustering, détection d’anomalies.
Cosine (L2-norm) • d=384–3072 ANN : HNSW / IVF / PQ RLS, filtres, TTL, hash delta
1) Encoders / Modèles d’embeddings
Rôle & principes
- Encoder une séquence → vecteur dense v ∈ ℝd (d=384–3072).
- Objectifs : contrastif (paires +/−), supervisé (STS/NLI), multimodal (CLIP).
- Pooling : mean, [CLS], attention pooling selon modèle.
Catalogue rapide
| Type | Exemples | Dimension | Notes |
|---|---|---|---|
| Texte | text-embedding-3-small/large, bge-m3, e5-large-v2 | 384–3072 | multilingue, coût/latence variés |
| Multimodal | CLIP, SigLIP, JinaCLIP | 256–1024 | image↔texte; VQA, recherche produits |
| Code | code-embedding, StarCoder emb. | 768–1536 | structure & symboles |
| Audio | whisper-embeddings | 256–1024 | ASR features, mots-clés audio |
Choisir le modèle
- Langues et domaine (juridique, biomédical, code).
- Fenêtre d’entrée (docs longs → chunking requis).
- Licences (open vs SaaS), SLA et coûts.
Règle d’or : évaluer sur vos données (Recall@k, P95) avant de généraliser.
2) Vectorisation & Normalisation
Pipeline
- Découper doc → chunks (cf. RAG) + métadonnées (title/section/lang/permissions/hash).
- Encoder chaque chunk → vecteur v puis L2-norm : v̂ = v / ||v||.
- Option multi-vecteurs (ex. titre & corps) pour améliorer le rappel.
Métriques & mémoire
- cosine standard après normalisation ; dot ≡ cosine si ||v||=1.
- Mémoire ≈ N × d × bytes (fp32=4, fp16=2, int8=1).
| Ex. | N | d | DType | Empreinte |
|---|---|---|---|---|
| Petit | 100k | 768 | fp16 | ~150 MB |
| Moyen | 5 M | 768 | fp16 | ~7.2 GB |
| Très grand | 200 M | 1024 | int8 | ~200 GB |
Optimisations
- Quantization mémoire : fp16/int8 (vecteurs) ; PQ pour x10–x30.
- Cache d’embeddings (clé =
hash(text)). - Déduplication (simhash/minhash) avant indexation.
# Normaliser & quantifier
v = embed(chunk) # float32
v = v / np.linalg.norm(v) # L2
v8 = (v * 127).astype('int8') # int8 (stockage compact)Toujours enregistrer le hash du chunk : MAJ incrémentales (upsert/delete) garanties.
3) Index vectoriel (Vector DB)
Rôle & features
- ANN (Approx NN) pour top-k à faible latence.
- Filtres : lang, tags, permissions, date, TTL.
- Upsert, delete, snapshots, réplication, RLS.
Familles
- Local : FAISS, Chroma.
- On-prem : Weaviate, Milvus, Qdrant.
- Cloud : Pinecone, Elastic Vector Search.
Paramètres clés
- Métrique : cosine/dot/L2 (alignée au preprocessing).
- Gestion de la fraîcheur : TTL + re-ingest delta.
- Observabilité : P50/P95, nprobe/efSearch, rebuild time.
Choisir selon volume, SLA, coût, ops & features (RLS, filtres, TTL, snapshots).
4) Structures d’index : IVF, HNSW, PQ/OPQ
| Structure | Paramètres | Forces | Limites | Cas d’usage |
|---|---|---|---|---|
| IVF | nlist (centroïdes), nprobe (listes scannées) | Très rapide, scalable | Approx; tuning fin | Clusters massifs, latence stricte |
| HNSW | M (degré), efConstruction, efSearch | Rappel élevé, recherche rapide | Build long, RAM ↑ | Qualité top-k prioritaire |
| PQ/OPQ | m (sous-espaces), bits/code | Compression ×10–×30 | Précision ↓; calibration | Très grands corpus (RAM limitée) |
# Choix empirique
- < 5M vecteurs : HNSW par défaut (efSearch 32–128)
- 5M–200M : IVF (nlist 4k–64k, nprobe 8–64) ou HNSW si RAM ok
- > 200M : IVF+PQ (OPQ si possible) ; quantizer = IndexFlatIP5) Stockage des métadonnées
Champs conseillés
{
"id": "...", "text": "...",
"title": "...", "section": "H2/H3",
"source_type": "pdf|html|wiki|sql",
"source_id": "...", "url": "...",
"lang": "fr|en|...", "tags": ["rgpd","sso"],
"permissions": ["hr","legal"], // RBAC
"updated_at": "2025-09-09T10:00Z",
"hash": "sha256:..."
}- Filtres : lang/tags/permissions/dates.
- Identifiants stables (source_id) pour citations & audit.
DDL PostgreSQL (exemple)
CREATE TABLE doc_chunks(
id TEXT PRIMARY KEY,
text TEXT, title TEXT, section TEXT,
source_type TEXT, source_id TEXT, url TEXT,
lang TEXT, tags TEXT[], permissions TEXT[],
updated_at TIMESTAMPTZ, hash TEXT
);
-- Index
CREATE INDEX idx_doc_lang ON doc_chunks(lang);
CREATE INDEX idx_doc_perm ON doc_chunks USING GIN(permissions);
CREATE INDEX idx_doc_tags ON doc_chunks USING GIN(tags);
-- RLS
ALTER TABLE doc_chunks ENABLE ROW LEVEL SECURITY;Règle : jamais de vecteur sans métadonnées complètes → sinon impossible de filtrer/auditer.
6) ⚙️ Stratégies clés (A→E)
A. Dimension
- 300–800 : recherche générale, compact/rapide.
- 1024–3072 : domaines spécialisés, vocabulaire rare.
B. Normalisation & métrique
- L2 + cosine = standard; dot si déjà normalisé.
- Mix métrique (cosine/L2) = résultats incohérents.
C. Indexation incrémentale
- Hash par chunk → upsert/delete précis.
- Batch nocturne + triggers à l’événement (Git/file-watcher).
D. Scalabilité
- Sharding (hash
source_idou date); réplication. - Cache des top-requêtes; pré-calcul de requêtes « chaudes ».
E. Usages avancés
- Clustering K-means → catégories auto.
- Anomalies : distance au centroïde & seuil de densité.
- Re-ranking : BM25 ∪ vecteur → cross-encoder.
🚫 Anti-patterns
- Vecteurs non normalisés avec cosine.
- Dimension XXL sans gain mesuré → coûts/latence ↑.
- Rebuild complet à chaque update (pas de delta/hash).
- Métadonnées pauvres (pas de permissions/lang/dates).
- Mélanger langues & domaines sans tagging.
7) 🧪 Qualité & Évaluation
KPIs
- Recall@k (doc attendu ∈ top-k).
- Precision@k sur requêtes bruitées.
- P95 Latency (SLA de recherche).
- Footprint mémoire (vecteurs + index).
- Drift après upgrade d’encoder/index.
Jeux & diff-tests
- Dataset (query → expected_id/sources).
- Comparer avant/après (encoder, index, params).
- Échantillons multilingues & OOD (out-of-domain).
Observabilité
- Histogrammes distances top-k; seuils anomalies.
- Logs masqués (PII) + dashboard latence/recall.
# Recall@10 (simple)
ok = 0
for q in dataset:
D, I = index.search(embed(q.text).reshape(1,-1), k=10)
ok += int(q.expected_id in I[0].tolist())
recall_at_10 = ok / len(dataset)8) 🧰 Recette d’implémentation (Python, FAISS)
# 0) Pré-requis : pip install faiss-cpu numpy
import faiss, numpy as np
# 1) Ingestion (simplifiée)
def iter_chunks(docs):
for doc in docs:
text, meta = clean_extract(doc) # OCR/tables→md, lang detect
for ch in smart_chunk(text, meta.kind):
yield ch.text, {"title": meta.title, "section": ch.title,
"source_type": meta.type, "source_id": meta.id,
"url": meta.url, "lang": meta.lang,
"permissions": meta.perms, "hash": sha256(ch.text)}
# 2) Embeddings normalisés (d=768)
X = []
META = []
for t, m in iter_chunks(docs):
v = embed(t).astype("float32")
v /= np.linalg.norm(v) + 1e-12
X.append(v); META.append(m)
X = np.vstack(X)
# 3) Index HNSW (cosine ~ dot après L2)
d = X.shape[1]
index = faiss.IndexHNSWFlat(d, 32) # M=32
index.hnsw.efConstruction = 200
index.hnsw.efSearch = 64
index.add(X)
# 4) Recherche
def search(q, k=10):
v = embed(q).astype("float32")
v /= np.linalg.norm(v) + 1e-12
D, I = index.search(v.reshape(1,-1), k)
return [(META[i], float(D[0][j])) for j,i in enumerate(I[0])]
Variante IVF+PQ (gros volume)
quantizer = faiss.IndexFlatIP(d)
ivfpq = faiss.IndexIVFPQ(quantizer, d, nlist=4096, M=16, nbits=8)
ivfpq.train(X); ivfpq.add(X); ivfpq.nprobe = 16Bonnes pratiques
- Normaliser avant
add()et avantsearch(). - Comparer HNSW vs IVF (P95, recall@k, mémoire).
- Stocker métadonnées côté DB et faire les filtres (lang, perms, dates).
- Snapshots réguliers + tests de restauration.
