Différences entre modèles HuggingFace, SentenceTransformer et modèles génériques
Tout d’abord, il faut considérer que :
- le modèle d’embedding DOIT être celui utilisé par le LLM,
- dans une architecture RAG, il doit être le même pour l’ingestion, la sélection et la génération.
Les modèles d’embeddings utilisés dans un pipeline RAG ou agentique n’appartiennent pas tous à la même famille. Ils diffèrent par leur format, leur API, leur manière de produire les embeddings et leur niveau de spécialisation. Comprendre ces différences aide à choisir le bon modèle et à structurer une architecture propre et stable.
Note : la factory peut également gérer de façon similaire des modèles ’causal LM’ ( notamment des LLM ), on se concentre ici sur les modèles d’embedding.
Modèles HuggingFace (HF Encoders)
Les modèles HF de type encoder (comme BERT, RoBERTa, MPNet, BGE, E5, GTE, Jina) sont des modèles Transformers polyvalents. Ils ne sont pas conçus spécifiquement pour produire des embeddings, mais ils peuvent le faire.
Ils utilisent le format standard HuggingFace, avec des fichiers comme config.json, model.safetensors et tokenizer.json. Leur API repose sur l’appel direct du modèle avec un tokenizer, ce qui renvoie un tenseur PyTorch contenant les représentations de chaque token. Pour obtenir un embedding final, il faut appliquer un pooling manuel (par exemple une moyenne des tokens ou l’utilisation du token CLS).
Ces modèles sont très flexibles et peuvent servir à d’autres tâches que les embeddings, mais ils demandent plus de travail pour produire un vecteur cohérent et stable. Ils peuvent aussi être plus lourds en mémoire et en temps d’inférence.
Modèles SentenceTransformer (ST)
Les modèles SentenceTransformer sont construits à partir de modèles HF, mais ils sont spécialement adaptés pour produire des embeddings de phrases. Ils incluent un pipeline complet : tokenizer, modèle Transformer et couche de pooling intégrée.
Leur format est différent de celui de HuggingFace : ils utilisent des fichiers comme modules.json et des sous-dossiers tels que 0_Transformer/ et 1_Pooling/. Leur API est simple : une seule méthode encode(text) renvoie directement un vecteur numpy prêt à l’emploi.
Ces modèles sont optimisés pour la similarité sémantique, ce qui les rend particulièrement adaptés aux systèmes RAG, aux moteurs de recherche et aux tâches de clustering. Ils sont plus simples à utiliser que les modèles HF, mais moins flexibles pour d’autres usages.
Note : BAAI fournit ses modèles ( comme BAAI/bge-small-en-v1.5" ) sous les deux formats HF et ST.
Modèles génériques ou personnalisés
Il existe aussi des modèles qui ne suivent ni le format HF ni celui de SentenceTransformer. Ils peuvent être très simples, comme un modèle maison exposant une méthode embed(text), ou très spécialisés, comme un modèle ONNX ou quantisé avec une API minimale.
Ces modèles n’ont pas de format standard et leur API varie selon l’implémentation. Ils peuvent renvoyer des listes Python, des tenseurs ou d’autres structures. Ils sont souvent utilisés pour les tests, les prototypes ou les environnements très contraints.
Ils sont faciles à intégrer, mais nécessitent une couche d’abstraction pour garantir une sortie cohérente et éviter les comportements imprévisibles.
Pourquoi une architecture unifiée est nécessaire
Les différences entre HF, ST et les modèles génériques rendent indispensable une couche d’unification. Sans cela, chaque composant du système devrait gérer des formats différents, des API différentes et des comportements différents.
L’ architecture décrite par la suite résout ce problème en trois étapes :
- la factory installe et valide le modèle ;
- le wrapper normalise les sorties et fournit une API interne stable ;
- l’adapter expose une API compatible LlamaIndex.
Cette approche garantit que tous les modèles, quelle que soit leur origine, produisent des embeddings cohérents, normalisés et utilisables dans l’ensemble du pipeline.
Architecture de la création des embeddings locaux
Objectifs
- Déterminisme total : aucun fallback, aucune heuristique.
- Installation automatique au premier appel de la factory.
- Compatibilité offline : tout fonctionne ensuite sans réseau.
- Normalisation stricte : toujours retourner des
list[float]. - Séparation nette des responsabilités :
- Factory = installation/validation
- Wrapper = normalisation
- Adapter = API LlamaIndex
- Assurer un point d’accès à l’API unique et global : Settings
1. Vue d’ensemble
Le système d’embeddings repose sur trois couches clairement séparées :
-
ModelLoaderFactory
- Installe, valide et instancie les modèles HF/ST.
- Garantit un fonctionnement déterministe, sans fallback ni heuristique.
-
UniversalEmbeddingWrapper Normalisation et abstraction du backend :
- Unifie tous les backends (HF, ST, générique).
- Fournit une API interne stable :
embed(str) -> list[float].
-
LlamaIndexEmbeddingAdapter
- Expose l’API LlamaIndex (
get_text_embedding,get_query_embedding). - Utilise le wrapper pour garantir la normalisation.
- Expose l’API LlamaIndex (
Le résultat est stocké dans Settings.embed_model
qui devient le singleton global pour tout le système.
2. Chargement du modèle via la factory
factory = ModelLoaderFactory()
embedding = factory.create_embedding(
model_path= "/home/iadnc/.models/bge-small,
model_name= "BAAI/bge-small-en-v1.5",
model_type= "sentence_transformer",
device= "cuda"
)
Ce que fait la factory :
- Sélectionne l’installateur approprié (
HFInstaller,STInstaller). - Installe ou valide le modèle dans
model_path. - Détecte le type réel du modèle (HF, ST, causal LM).
- Vérifie la cohérence entre type déclaré et type détecté.
- Instancie l’embedding RAM (
HFEmbeddingouSTEmbedding).
Résultat : un embedding brut, non encore adapté à LlamaIndex.
3. Construction du singleton AppSettings.embed_model
AppSettings.embed_model = create_llamaindex_embedding(embedding)
Cette fonction construit la chaîne API complète :
AppSettings.embed_model
├── wrapper (UniversalEmbeddingWrapper)
│ └── embedding (HFEmbedding ou STEmbedding)
└── get_*_embedding() (API LlamaIndex BaseEmbedding)
Architecture
┌──────────────────────────┐
│ ModelLoaderFactory │
│ (installation + RAM) │
└─────────────┬────────────┘
│
▼
Embedding RAM instancié
HFEmbedding (HF) | STEmbedding (ST) | Generic
- tensor output | - numpy output | - list
- pooling manuel | - pooling intégré | - API custom
│
▼
┌────────────────────────────────────┐
│ UniversalEmbeddingWrapper │
│ embed(str) -> list[float] │
│ backend = HF / ST / generic │
└─────────────────┬──────────────────┘
│
▼
┌──────────────────────────────────────┐
│ LlamaIndexEmbeddingAdapter │
│ get_text_embedding() │
│ get_query_embedding() │
└──────────────────┬───────────────────┘
│
▼
┌────────────────────────────────┐
│ AppSettings.embed_model │
│ (singleton global complet) │
│ │
│ ├── wrapper │
│ │ └── embedding (HF/ST) │
│ └── get_*_embedding() │
└────────────────────────────────┘Le singleton AppSettings permet d’accéder à trois niveaux d’API, chacun avec un rôle clair :
-
API interne :
AppSettings.embed_model.wrapper.embed("texte")
→ utilisée par les compresseurs, le RAG, les agents. -
API LlamaIndex :
AppSettings.embed_model.get_text_embedding("texte")
→ utilisée par les index, retrievers, query engines. -
API modèle brut :
AppSettings.embed_model.wrapper.embedding.model
→ utile pour debug, tests, opérations avancées.
4. Exemples d’usage
API interne (wrapper)
vec = AppSettings.embed_model.wrapper.embed("Bonjour")
API LlamaIndex
vec = AppSettings.embed_model.get_text_embedding("Bonjour")
API modèle HF/ST brut
hf_model = AppSettings.embed_model.wrapper.embedding.model
En savoir plus :
– AppSettings : un Singleton Global pour stabiliser les modèles face aux limites du singleton LlamaIndex