La tokenization et l'embedding constituent les deux piliers fondamentaux sur lesquels repose l'ensemble de l'intelligence artificielle moderne appliquee au langage naturel. Chaque mot que vous tapez dans ChatGPT, chaque requete que vous soumettez a un moteur de recherche semantique, chaque document que vous indexez dans un systeme RAG passe inevitablement par ces deux etapes de transformation. Pourtant, la confusion entre ces concepts reste repandue, meme parmi les praticiens de l'IA. La tokenization decoupe le texte brut en unites discretes que le modele peut manipuler ; l'embedding transforme ces unites en vecteurs numeriques dans un espace multidimensionnel ou la proximite geometrique reflete la similarite semantique. Comprendre cette distinction, ainsi que la relation profonde entre ces deux processus, est indispensable pour optimiser les systemes de RAG (Retrieval-Augmented Generation), maitriser les couts d'utilisation des API, gerer efficacement les fenetres de contexte et construire des pipelines NLP performants. Cet article explore en profondeur les algorithmes de tokenization (BPE, WordPiece, SentencePiece, Unigram), les modeles d'embedding (OpenAI, Cohere, BGE, E5), le comptage de tokens avec tiktoken, l'impact sur le RAG et la tarification, et les defis specifiques de la tokenization multilingue.

A retenir : La tokenization est le decoupage du texte en unites (tokens). L'embedding est la transformation de ces tokens ou de sequences de tokens en vecteurs numeriques. La tokenization precede toujours l'embedding dans le pipeline. Le choix du tokenizer affecte directement la qualite des embeddings et le cout d'utilisation des modeles.

Qu'est-ce que la tokenization et pourquoi est-elle necessaire ?

Les modeles de langage, qu'ils soient des LLM generatifs ou des modeles d'embedding, ne peuvent pas traiter du texte brut. Un ordinateur ne comprend que des nombres. La tokenization est le processus qui convertit une chaine de caracteres en une sequence d'entiers, chacun correspondant a une entree dans un vocabulaire predetermine. C'est la premiere etape de tout pipeline de traitement du langage naturel.

L'histoire de la tokenization en NLP a connu plusieurs approches successives. La tokenization par mots (word-level) decoupe le texte aux espaces et a la ponctuation. Simple mais problematique : le vocabulaire explose rapidement (des centaines de milliers de mots dans une langue comme le francais), les mots rares ou inconnus ne peuvent pas etre representes (probleme de OOV, Out-Of-Vocabulary), et les variations morphologiques (manger, mangeons, mangerait) sont traitees comme des entites completement differentes.

La tokenization par caracteres (character-level) resout le probleme de vocabulaire (seuls quelques centaines de caracteres suffisent) mais perd toute notion de sens au niveau du token et produit des sequences tres longues, ce qui est problematique pour les modeles Transformer dont la complexite est quadratique en fonction de la longueur de la sequence.

La tokenization en sous-mots (subword tokenization) est le compromis qui a domine depuis l'avenement des Transformers. Elle decoupe les mots frequents en tokens complets et les mots rares en sous-unites (morphemes, syllabes, fragments). Par exemple, "anticonstitutionnellement" pourrait etre decoupe en ["anti", "constitu", "tion", "nelle", "ment"]. Cette approche offre un vocabulaire de taille geree (typiquement 32 000 a 128 000 tokens), une couverture complete de tout texte possible et une preservation partielle de l'information morphologique.

Le vocabulaire et son impact

Le vocabulaire du tokenizer est un dictionnaire fixe qui associe chaque token (sous-mot ou caractere) a un identifiant entier. La taille du vocabulaire est un hyperparametre crucial. Un vocabulaire trop petit force le decoupage de nombreux mots en fragments, allongeant les sequences et consommant plus de contexte. Un vocabulaire trop grand augmente la taille du modele (la couche d'embedding est proportionnelle a la taille du vocabulaire) et peut inclure des tokens trop rares pour etre bien appris.

Le choix de la taille du vocabulaire affecte directement le cout d'utilisation des API. Si un tokenizer decoupe "developpement" en 3 tokens au lieu de 1, chaque occurrence de ce mot coute trois fois plus cher. Pour les textes en francais, les tokenizers entraines principalement sur l'anglais tendent a produire plus de tokens, ce qui rend le traitement du francais proportionnellement plus couteux.

ModeleTaille vocabulaireTokenizerTokens pour "developpement"Tokens pour "anticonstitutionnellement"
GPT-4o200 000o200k_base (BPE)13
GPT-4100 000cl100k_base (BPE)24
GPT-3.5100 000cl100k_base (BPE)24
BERT30 522WordPiece36
Llama 3128 000BPE (tiktoken)13
Claude~100 000BPE1-23-4
Mistral32 000SentencePiece (BPE)25

L'algorithme BPE (Byte Pair Encoding) en detail

Le Byte Pair Encoding est l'algorithme de tokenization le plus utilise dans les LLM modernes. Il est utilise par GPT-4, GPT-4o, Llama 3, Claude et la plupart des grands modeles generatifs. Comprendre BPE en profondeur est essentiel pour tout praticien de l'IA.

Principe de base du BPE

BPE a ete initialement developpe comme algorithme de compression de donnees par Philip Gage en 1994, puis adapte au NLP par Sennrich et al. en 2016. Le principe est iteratif et elegant. On commence avec un vocabulaire initial compose de tous les caracteres individuels presents dans le corpus d'entrainement (ou de tous les octets, pour la variante Byte-Level BPE). A chaque iteration, on identifie la paire de tokens adjacents la plus frequente dans le corpus et on la fusionne en un nouveau token. Ce processus est repete jusqu'a atteindre la taille de vocabulaire souhaitee.

Prenons un exemple concret. Soit le corpus simplifie contenant les mots : "bas", "bas", "bat", "tab", "tab", "tab". Le vocabulaire initial est {b, a, s, t}. Les frequences des paires sont : (t,a) = 5, (a,b) = 3, (b,a) = 4, (a,s) = 2, (a,t) = 2. La paire la plus frequente est (t,a), qui est fusionnee en "ta". Apres fusion, on recalcule les frequences et on continue. En quelques iterations, on obtient des tokens comme "ta", "ba", "tab", "bas".

Byte-Level BPE

La variante Byte-Level BPE, utilisee par GPT-2 et tous ses successeurs, commence avec un vocabulaire de 256 tokens correspondant aux 256 valeurs d'octets possibles (0-255). Cela garantit que tout texte, dans n'importe quelle langue ou encodage, peut etre tokenise sans token inconnu (OOV). Les caracteres UTF-8 multi-octets (comme les accents francais, les caracteres chinois ou les emojis) sont initialement decomposes en leurs octets constitutifs, puis les paires frequentes sont fusionnees par BPE.

L'avantage du Byte-Level BPE est l'universalite : aucun pre-traitement specifique a la langue n'est necessaire, et tout texte valide peut etre encode. L'inconvenient est que les langues utilisant des caracteres multi-octets (chinois, japonais, coreen, arabe, etc.) peuvent produire plus de tokens par mot, car chaque caractere necessite 2 a 4 octets de base avant que les fusions BPE ne les compactent.

import tiktoken

# Charger le tokenizer de GPT-4o
enc = tiktoken.get_encoding("o200k_base")

# Tokenizer un texte francais
texte = "Le developpement de l'intelligence artificielle transforme nos methodes de travail."
tokens = enc.encode(texte)
print(f"Nombre de tokens : {len(tokens)}")
print(f"Tokens : {tokens}")

# Decoder chaque token individuellement pour voir le decoupage
for token_id in tokens:
    print(f"  {token_id} -> '{enc.decode([token_id])}'")

# Comparer avec l'anglais
texte_en = "The development of artificial intelligence transforms our work methods."
tokens_en = enc.encode(texte_en)
print(f"\nFrancais : {len(tokens)} tokens")
print(f"Anglais  : {len(tokens_en)} tokens")
print(f"Ratio FR/EN : {len(tokens)/len(tokens_en):.2f}")

Entrainement du tokenizer BPE

L'entrainement du tokenizer est une etape distincte de l'entrainement du modele de langage. Il se fait sur un large corpus de texte et determine le vocabulaire qui sera utilise pendant toute la vie du modele. Un tokenizer entraine sur un corpus principalement anglais aura un vocabulaire optimise pour l'anglais : les mots anglais courants seront des tokens uniques, tandis que les mots francais seront decoupes en plus de fragments.

C'est pourquoi les modeles recents (GPT-4o, Llama 3) utilisent des vocabulaires plus grands (100k-200k tokens) entraines sur des corpus multilingues equilibres. L'augmentation de la taille du vocabulaire ameliore la "densite informationnelle" par token pour les langues non anglophones, ce qui signifie moins de tokens par phrase, un meilleur usage de la fenetre de contexte et un cout inferieur par requete.

Processus BPE : fusion iterative des paires Etape 0 — Vocabulaire initial (caracteres/octets) d | e | v | e | l | o | p | p | e | m | e | n | t Etape 1 — Fusion paire (e,m) → em (frequente) d | e | v | e | l | o | p | p | em | e | n | t Etape 2 — Fusion (em,e) → eme d | e | v | e | l | o | p | p | eme | n | t Etape N — Apres de nombreuses fusions develop | pe | ment Le nombre de fusions determine la taille du vocabulaire final

WordPiece : le tokenizer de BERT

WordPiece est l'algorithme de tokenization developpe par Google et utilise par BERT, DistilBERT, Electra et d'autres modeles de la famille BERT. Il est similaire a BPE mais differe dans son critere de fusion.

Difference avec BPE

Alors que BPE fusionne la paire de tokens la plus frequente, WordPiece fusionne la paire qui maximise la vraisemblance du corpus d'entrainement. Concretement, pour une paire (A, B), WordPiece calcule le score : score(A,B) = freq(AB) / (freq(A) * freq(B)). Ce score favorise les paires dont la co-occurrence est disproportionnellement elevee par rapport a la frequence individuelle de chaque token. Cela tend a produire des fusions plus "significatives" linguistiquement.

WordPiece utilise egalement un prefixe special "##" pour les tokens qui continuent un mot. Par exemple, "embedding" pourrait etre tokenise en ["em", "##bed", "##ding"]. Le prefixe "##" indique que le token ne commence pas un nouveau mot. Cela permet de distinguer "bed" (un mot complet) de "##bed" (une partie de mot).

Vocabulaire et performance de WordPiece

Le vocabulaire standard de BERT est de 30 522 tokens, ce qui est significativement plus petit que les vocabulaires des LLM modernes. Ce petit vocabulaire a des consequences directes : les textes sont decoupes en plus de tokens, ce qui signifie que la fenetre de contexte de 512 tokens de BERT couvre moins de texte. Pour les textes en francais, BERT multilingual (mBERT) tokenise generalement 20 a 40 % de tokens de plus que pour un texte anglais equivalent.

CamemBERT, le modele BERT francophone, utilise un tokenizer SentencePiece entraine specifiquement sur le francais, ce qui ameliore considerablement l'efficacite de la tokenization pour les textes francais. De meme, FlauBERT utilise un tokenizer adapte au francais.

SentencePiece et Unigram : approches alternatives

SentencePiece

SentencePiece, developpe par Google, est un framework de tokenization qui se distingue par son traitement du texte brut sans pre-tokenization. Contrairement a BPE et WordPiece qui necessitent generalement une etape prealable de decoupage par mots (en utilisant les espaces comme delimiteurs), SentencePiece traite le texte comme une sequence brute de caracteres Unicode, incluant les espaces comme des caracteres normaux (representes par le caractere special "▁" en debut de mot).

Cette approche "language-agnostic" est particulierement importante pour les langues qui n'utilisent pas d'espaces entre les mots (chinois, japonais, thai) et pour les langues avec des systemes d'ecriture complexes (arabe, devanagari). SentencePiece peut utiliser soit BPE soit Unigram comme algorithme de sous-tokenization sous-jacent.

L'algorithme Unigram

L'algorithme Unigram (ou Unigram Language Model) prend une approche inverse de BPE. Au lieu de partir des caracteres individuels et de fusionner vers le haut, Unigram commence avec un tres grand vocabulaire initial (typiquement des centaines de milliers de sous-mots possibles) et l'elague progressivement en retirant les tokens qui contribuent le moins a la vraisemblance du corpus.

A chaque iteration, Unigram calcule la perte (negative log-likelihood) que provoquerait le retrait de chaque token du vocabulaire. Les tokens dont le retrait augmente le moins la perte sont elimines. Le processus continue jusqu'a atteindre la taille de vocabulaire souhaitee. Unigram est utilise par T5, ALBERT, XLNet et de nombreux modeles multilingues.

L'avantage d'Unigram est qu'il peut fournir des probabilites pour chaque sous-mot, ce qui permet une tokenization probabiliste : un meme texte peut etre tokenise de plusieurs facons, avec des probabilites differentes. Cette propriete est utile pour la regularisation pendant l'entrainement (subword regularization), ou l'on echantillonne aleatiquement parmi les tokenizations possibles pour rendre le modele plus robuste.

CaracteristiqueBPEWordPieceUnigramSentencePiece
DirectionBottom-up (fusions)Bottom-up (fusions)Top-down (elagage)Framework (BPE ou Unigram)
Critere de fusion/elagageFrequence des pairesVraisemblancePerte minimaleDepend du sous-algorithme
Tokenization deterministeOuiOuiOui (mode greedy) ou Non (mode probabiliste)Depend
Pre-tokenization requiseGeneralement ouiOuiNonNon
Utilise parGPT-*, Llama, ClaudeBERT, DistilBERTT5, ALBERT, XLNetLlama 2, Mistral, mT5
Gestion multilingueByte-level pour universaliteLimiteeBonneExcellente
A retenir : BPE est l'algorithme dominant pour les LLM generatifs (GPT, Llama, Claude). WordPiece est associe a l'ecosysteme BERT. SentencePiece avec Unigram est privilegie pour les modeles multilingues. Le choix de l'algorithme affecte la couverture linguistique, l'efficacite du vocabulaire et la qualite des representations apprises.

Qu'est-ce qu'un embedding et comment fonctionne-t-il ?

Un embedding est une representation vectorielle d'une unite de texte (token, mot, phrase, paragraphe, document) dans un espace multidimensionnel continu. La propriete fondamentale des embeddings est que la proximite geometrique dans cet espace reflete la similarite semantique : deux textes dont le sens est proche auront des vecteurs d'embedding proches.

Des embeddings de tokens aux embeddings de phrases

Dans un modele Transformer, la premiere couche apres la tokenization est la couche d'embedding de tokens. C'est une matrice de taille (V x d), ou V est la taille du vocabulaire et d est la dimension de l'embedding (par exemple, 768 pour BERT-base, 4096 pour GPT-3). Chaque token du vocabulaire correspond a une ligne de cette matrice, qui est son vecteur d'embedding. Ces vecteurs sont appris pendant l'entrainement du modele.

L'embedding de token est le point de depart, mais pour la plupart des applications, on a besoin d'embeddings de phrases ou de documents. Les modeles d'embedding modernes produisent un seul vecteur pour un texte entier, en agissant comme si l'on "resumait" le sens de tout le texte dans un vecteur de dimension fixe. Pour en savoir plus sur ce sujet, consultez notre article qu'est-ce qu'un embedding en IA.

Plusieurs strategies existent pour passer des embeddings de tokens a un embedding de phrase. Le mean pooling calcule la moyenne de tous les embeddings de tokens de la phrase, ponderee ou non par l'attention. Le CLS pooling utilise l'embedding du token special [CLS] (ou equivalent) qui, dans les modeles entraines a cet effet, capture une representation globale de la phrase. Le last token pooling utilise l'embedding du dernier token, parfois utilise pour les modeles auto-regressifs. Les modeles d'embedding modernes sont explicitement entraines pour produire des embeddings de phrases de haute qualite avec l'une de ces strategies.

L'espace vectoriel semantique

L'espace dans lequel vivent les embeddings a des proprietes remarquables. La distance entre deux vecteurs (mesuree par la similarite cosinus, la distance euclidienne ou le produit scalaire) correspond a la similarite semantique. Des relations analogiques peuvent emerger : le celebre exemple "roi - homme + femme ≈ reine" illustre comment les relations semantiques se traduisent en operations vectorielles.

La dimension de l'espace d'embedding est un compromis entre capacite de representation et cout de stockage/calcul. Les embeddings modernes ont typiquement entre 384 et 3072 dimensions. Plus la dimension est elevee, plus l'espace peut capturer de nuances semantiques, mais le stockage et la recherche de similarite deviennent plus couteux. Des techniques comme la Matryoshka Representation Learning (MRL) permettent d'utiliser des sous-ensembles des dimensions pour des recherches rapides, avec un raffinement progressif.

Pipeline : Texte → Tokens → Embeddings → Espace vectoriel Texte brut "Le chat dort" Tokenizer Token IDs [1234, 567, 890] Lookup Token Embeddings 3 vecteurs x 768d Pooling Sentence Embedding 1 vecteur x 768d Espace vectoriel semantique (2D projection) "Le chat dort" "Le felin sommeille" "La voiture roule" "L'auto avance" Proche Proche

Les modeles d'embedding modernes

Le paysage des modeles d'embedding a connu une evolution rapide depuis les premiers word embeddings (Word2Vec, GloVe) jusqu'aux modeles de phrases a base de Transformers. Aujourd'hui, plusieurs modeles se distinguent par leur qualite et leur polyvalence.

OpenAI Embeddings (text-embedding-3-small, text-embedding-3-large)

Les modeles d'embedding d'OpenAI sont parmi les plus utilises en production grace a leur integration facile avec l'API OpenAI. Le modele text-embedding-3-large produit des vecteurs de 3072 dimensions et offre d'excellentes performances sur le benchmark MTEB (Massive Text Embedding Benchmark). Le modele text-embedding-3-small (1536 dimensions) offre un bon compromis entre qualite et cout.

Une innovation majeure de la serie text-embedding-3 est le support des embeddings Matryoshka : les premieres N dimensions du vecteur contiennent deja une representation utile. On peut donc stocker les 256 premieres dimensions pour une recherche rapide, puis utiliser les 3072 dimensions completes pour le re-ranking. Le cout est de $0.13 par million de tokens pour text-embedding-3-large et $0.02 pour text-embedding-3-small.

from openai import OpenAI
import numpy as np

client = OpenAI()

def get_embedding(text, model="text-embedding-3-large", dimensions=None):
    """Obtient l'embedding d'un texte via l'API OpenAI."""
    params = {"input": text, "model": model}
    if dimensions:
        params["dimensions"] = dimensions  # Matryoshka: tronquer a N dimensions
    response = client.embeddings.create(**params)
    return np.array(response.data[0].embedding)

# Embeddings complets (3072 dimensions)
emb1 = get_embedding("Le machine learning transforme l'industrie")
emb2 = get_embedding("L'apprentissage automatique revolutionne le secteur industriel")
emb3 = get_embedding("La recette du gateau au chocolat est simple")

# Similarite cosinus
def cosine_similarity(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

print(f"Similarite (ML/industrie) : {cosine_similarity(emb1, emb2):.4f}")  # ~0.85+
print(f"Similarite (ML/gateau)    : {cosine_similarity(emb1, emb3):.4f}")  # ~0.15

# Embeddings Matryoshka reduits (256 dimensions)
emb1_small = get_embedding("Le machine learning transforme l'industrie",
                            dimensions=256)
print(f"Dimension complete : {len(emb1)}, reduite : {len(emb1_small)}")

Cohere Embed v3

Cohere propose le modele embed-multilingual-v3.0 qui se distingue par son excellent support multilingue (plus de 100 langues) et ses options d'input_type qui permettent de specifier si le texte est une requete de recherche (search_query), un document a indexer (search_document), ou un texte a classifier (classification). Cette distinction permet au modele de produire des embeddings optimises pour chaque cas d'usage.

Le modele Cohere embed-v3 produit des vecteurs de 1024 dimensions et supporte le type de compression (int8, binary) pour reduire les couts de stockage. Les embeddings binaires (1 bit par dimension) offrent une reduction de 32x de l'espace de stockage avec une perte de qualite limitee, utilisable comme premiere etape de filtrage dans un systeme de recherche a deux etapes.

BGE (BAAI General Embedding)

Les modeles BGE, developpes par le Beijing Academy of Artificial Intelligence, sont parmi les meilleurs modeles d'embedding open source. BGE-large-en-v1.5 et bge-m3 (multilingual) offrent des performances comparables aux modeles commerciaux. bge-m3 est particulierement interessant car il supporte trois types de recuperation : dense (embeddings classiques), sparse (similaire a BM25) et colbert (interaction fine entre tokens de la requete et du document).

from sentence_transformers import SentenceTransformer
import numpy as np

# Charger BGE-M3 (multilingual, multi-granularity)
model = SentenceTransformer("BAAI/bge-m3")

# Embeddings francais
phrases = [
    "L'intelligence artificielle revolutionne la medecine",
    "Le deep learning ameliore le diagnostic medical",
    "La cuisine japonaise est appreciee dans le monde entier"
]

embeddings = model.encode(phrases, normalize_embeddings=True)

# Matrice de similarite
for i in range(len(phrases)):
    for j in range(i+1, len(phrases)):
        sim = np.dot(embeddings[i], embeddings[j])
        print(f"Sim({i},{j}) = {sim:.4f} : '{phrases[i][:40]}...' <-> '{phrases[j][:40]}...'")

E5 (EmbEddings from bidirEctional Encoder rEpresentations)

Les modeles E5, developpes par Microsoft Research, sont entraines avec l'approche "text pair" qui distingue explicitement les requetes et les passages. L'innovation d'E5 est l'utilisation de prefixes d'instruction ("query: " pour les requetes, "passage: " pour les documents) qui orientent le modele vers la production d'embeddings optimises pour la recherche asymetrique. Les versions recentes (multilingual-e5-large-instruct) acceptent des instructions personnalisees qui permettent d'adapter le comportement du modele a des domaines specifiques.

Comparaison des modeles d'embedding

ModeleDimensionsMax tokensMultilingueOpen sourceMTEB score (avg)Cout
text-embedding-3-large3 0728 191BonNon64.6$0.13/1M tokens
text-embedding-3-small1 5368 191BonNon62.3$0.02/1M tokens
Cohere embed-v31 024512ExcellentNon64.5$0.10/1M tokens
BGE-M31 0248 192ExcellentOui63.5Gratuit (self-hosted)
E5-large-instruct1 024512BonOui63.8Gratuit (self-hosted)
Nomic Embed v1.57688 192MoyenOui62.3Gratuit (self-hosted)
Voyage AI voyage-31 02432 000BonNon67.1$0.06/1M tokens
A retenir : Pour la production, text-embedding-3-small d'OpenAI offre le meilleur rapport qualite/prix en mode API. Pour le self-hosting, BGE-M3 est le choix de reference, avec un excellent support multilingue et la possibilite d'embeddings denses, sparse et colbert. Pour le francais specifiquement, les modeles multilingual (Cohere, BGE-M3, E5-multilingual) offrent de meilleurs resultats que les modeles anglophones.

La relation entre tokenization et embedding : pourquoi la tokenization affecte la qualite des embeddings

La tokenization et l'embedding ne sont pas des processus independants. Le tokenizer determine les unites fondamentales que le modele apprend a representer. Un mauvais tokenizer produit des decoupages qui obscurcissent le sens, ce qui se propage dans la qualite des embeddings. Cet aspect est souvent sous-estime dans la conception des systemes d'IA. Pour une exploration plus approfondie de cette relation, consultez notre article sur les differences entre embeddings et tokens.

L'impact de la granularite de tokenization

Considerons le mot "developpeurs". Avec un tokenizer ayant un grand vocabulaire bien entraine sur le francais, ce mot sera un seul token, avec un embedding riche en information semantique. Avec un tokenizer a petit vocabulaire ou mal entraine sur le francais, il sera decompose en "de", "vel", "opp", "eurs" — quatre tokens dont aucun ne porte individuellement le sens du mot. Le modele doit alors "recomposer" le sens a travers les couches d'attention, ce qui est moins efficace et peut introduire des artefacts.

Ce phenomene a des consequences directes sur la qualite des embeddings de phrases. Pour deux phrases semantiquement similaires, si l'une est tokenisee de maniere plus fragmentee que l'autre, la similarite cosinus de leurs embeddings peut etre inferieure a ce qu'elle devrait etre. Les modeles modernes compensent en partie ce probleme grace a l'entrainement sur de grands corpus, mais le biais introduit par la tokenization persiste.

L'impact sur la fenetre de contexte

La fenetre de contexte des modeles est mesuree en tokens, pas en mots ou en caracteres. Un tokenizer inefficace pour une langue donnee consomme plus de tokens par mot, ce qui reduit la quantite de texte qui tient dans la fenetre. Pour un modele avec une fenetre de 128 000 tokens, un texte francais consommant 1.3x plus de tokens qu'un texte anglais equivalent representera effectivement une fenetre de ~98 000 "mots equivalents anglais".

Cela a des implications directes pour les systemes RAG. Le nombre de documents recuperes qui peuvent etre inclus dans le contexte depend de la taille en tokens de chaque document. Pour des documents en francais, cette capacite sera inferieure a celle des documents en anglais, toutes choses egales par ailleurs. Optimiser la tokenization est donc un levier direct pour ameliorer les performances du RAG.

Le comptage de tokens avec tiktoken

tiktoken est la bibliotheque de comptage de tokens d'OpenAI. Elle implemente les tokenizers utilises par les modeles GPT et permet de compter precisement le nombre de tokens d'un texte avant de l'envoyer a l'API. C'est un outil essentiel pour la gestion des couts et de la fenetre de contexte.

import tiktoken

# Comparaison des tokenizers
encodings = {
    "GPT-4o (o200k_base)": tiktoken.get_encoding("o200k_base"),
    "GPT-4 (cl100k_base)": tiktoken.get_encoding("cl100k_base"),
    "GPT-3 (p50k_base)": tiktoken.get_encoding("p50k_base"),
}

textes = [
    "Le developpement de l'intelligence artificielle en France",
    "L'anticonstitutionnellement est le plus long mot francais",
    "Les reseaux de neurones profonds transforment notre monde",
    "Bonjour, comment allez-vous aujourd'hui ?",
]

for texte in textes:
    print(f"\n'{texte}'")
    for nom, enc in encodings.items():
        tokens = enc.encode(texte)
        print(f"  {nom}: {len(tokens)} tokens")
        # Afficher le decoupage
        decoupage = [enc.decode([t]) for t in tokens]
        print(f"    Decoupage: {decoupage}")

# Estimation du cout pour un texte long
enc = tiktoken.get_encoding("cl100k_base")
texte_long = open("mon_document.txt", "r").read()  # exemple
nb_tokens = len(enc.encode(texte_long))
cout_embedding = nb_tokens / 1_000_000 * 0.13  # text-embedding-3-large
cout_gpt4o_input = nb_tokens / 1_000_000 * 2.50  # GPT-4o input
print(f"Tokens: {nb_tokens}, Cout embedding: ${cout_embedding:.4f}, Cout GPT-4o: ${cout_gpt4o_input:.4f}")

Impact de la tokenization sur le RAG

Le Retrieval-Augmented Generation (RAG) est l'architecture dominante pour connecter les LLM a des bases de connaissances proprietaires. La tokenization affecte chaque etape du pipeline RAG : le chunking (decoupage des documents), l'indexation (creation des embeddings), la recuperation (recherche de similarite) et la generation (injection dans le contexte du LLM).

Chunking et tokenization

Le chunking consiste a decouper les documents en fragments de taille appropriee pour l'embedding et la recuperation. La taille des chunks est generalement definie en nombre de tokens (par exemple, 512 tokens avec un chevauchement de 50 tokens). Le choix du tokenizer pour le chunking doit etre coherent avec le modele d'embedding utilise : si le modele d'embedding a un maximum de 512 tokens, les chunks doivent etre mesures avec le meme tokenizer que celui du modele.

Les strategies de chunking incluent le chunking par taille fixe (en tokens), le chunking semantique (qui detecte les changements de sujet), le chunking hierarchique (paragraphes, sections, documents) et le chunking par phrases avec recomposition. Chaque strategie interagit differemment avec la tokenization, et le choix optimal depend du type de documents et du modele d'embedding.

Optimisation du RAG pour le francais

Pour les systemes RAG en francais, plusieurs optimisations specifiques sont recommandees. L'utilisation de modeles d'embedding multilingues (BGE-M3, Cohere multilingual) qui ont ete entraines avec des tokenizers adaptes au francais est essentielle. Le chunking doit tenir compte de l'inflation de tokens en francais (des chunks de 400 tokens peuvent etre necessaires au lieu de 512 pour maintenir des tailles semantiquement coherentes). La recherche hybride (dense + BM25) est particulierement benefique pour le francais car elle combine la comprehension semantique (embeddings) avec la correspondance lexicale exacte (BM25), ce qui compense les limites de la tokenization. Pour approfondir le sujet de la vectorisation, consultez notre article sur la vectorisation des donnees en IA.

from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceBgeEmbeddings
import tiktoken

# Chunking adapte au francais avec tiktoken
encoding = tiktoken.get_encoding("cl100k_base")

def token_length(text):
    """Compte les tokens avec le tokenizer GPT-4."""
    return len(encoding.encode(text))

# Chunking semantique avec taille en tokens
splitter = RecursiveCharacterTextSplitter(
    chunk_size=400,          # tokens, pas caracteres
    chunk_overlap=50,        # chevauchement en tokens
    length_function=token_length,
    separators=["\n\n", "\n", ". ", " ", ""],  # priorite de decoupage
)

# Document francais
document = """L'intelligence artificielle generative a connu une
acceleration sans precedent depuis 2022. Les grands modeles de langage
comme GPT-4, Claude et Gemini ont demontre des capacites remarquables
en comprehension et en generation de texte..."""

chunks = splitter.split_text(document)
for i, chunk in enumerate(chunks):
    print(f"Chunk {i}: {token_length(chunk)} tokens, {len(chunk)} caracteres")

# Embedding avec BGE-M3 (multilingue)
embeddings = HuggingFaceBgeEmbeddings(
    model_name="BAAI/bge-m3",
    model_kwargs={"device": "cuda"},
    encode_kwargs={"normalize_embeddings": True}
)

Impact sur la tarification et le cout des API

La tarification des API de LLM et d'embedding est basee sur le nombre de tokens. Comprendre comment votre texte est tokenise est donc directement lie a la gestion des couts.

Calcul des couts

Pour les API de generation (GPT-4o, Claude, etc.), le cout est calcule separement pour les tokens d'entree (input/prompt) et les tokens de sortie (output/completion). Les tokens de sortie sont generalement 2 a 4 fois plus chers que les tokens d'entree. Pour un texte francais consommant 30 % de tokens de plus qu'un texte anglais equivalent, le surcout est directement proportionnel.

ScenarioTexte anglais (tokens)Texte francais (tokens)Surcout FR (%)Cout GPT-4o (FR)
Email court (100 mots)~130~170+31%$0.00043
Article blog (1000 mots)~1 300~1 700+31%$0.0043
Document technique (10k mots)~13 000~17 000+31%$0.043
Livre complet (100k mots)~130 000~170 000+31%$0.43
1M documents RAG (embedding)~200M~260M+30%$33.80 (3-large)

Strategies d'optimisation des couts

Plusieurs strategies permettent de reduire le nombre de tokens et donc les couts. La compression du prompt (prompt compression) utilise des techniques pour reduire la taille du prompt sans perdre d'information essentielle. Le caching des embeddings evite de recalculer les embeddings de textes deja traites. Le choix du modele d'embedding adapte permet d'utiliser un modele moins cher (text-embedding-3-small) pour le filtrage grossier et un modele plus couteux pour le re-ranking. L'utilisation de modeles open source auto-heberges (BGE-M3, E5) elimine les couts d'API pour les volumes importants.

Tokenization multilingue : defis et solutions

La tokenization multilingue est l'un des defis les plus actifs de la recherche en NLP. Les langues different considerablement dans leur structure orthographique, morphologique et syntaxique, ce qui a un impact direct sur l'efficacite de la tokenization.

Le probleme de la "fertilite" des tokens

La fertilite (fertility) d'un tokenizer pour une langue donnee est le nombre moyen de tokens produits par mot. Un tokenizer parfait aurait une fertilite de 1.0 (un token par mot). En pratique, les tokenizers des LLM ont une fertilite de 1.1 a 1.3 pour l'anglais, 1.3 a 1.8 pour le francais, 1.5 a 2.5 pour l'allemand (mots composes), 2 a 4 pour le chinois (caracteres multi-octets), et 3 a 6 pour le thai, le birman ou l'amharique.

Cette disparite signifie que les locuteurs de langues a haute fertilite paient plus cher par mot, disposent de moins de contexte effectif, et peuvent obtenir des performances inferieures car le modele doit "travailler plus dur" pour recomposer le sens a partir de fragments plus petits.

Solutions pour ameliorer la tokenization multilingue

L'approche la plus directe est d'augmenter la taille du vocabulaire. GPT-4o a double le vocabulaire (200k vs 100k) par rapport a GPT-4, ce qui a significativement ameliore l'efficacite pour les langues non anglophones. Llama 3 est passe de 32k a 128k tokens, avec un entrainement sur un corpus plus equilibre linguistiquement.

L'entrainement specifique a la langue produit les meilleurs resultats. CamemBERT (francais), BLOOM (176 langues), mT5 et mBART utilisent des tokenizers entraines sur des corpus multilingues equilibres. Pour les applications critiques en francais, un modele avec un tokenizer francophone produira des resultats superieurs a un modele generique.

La tokenization adaptative est une approche emergente ou le tokenizer s'adapte dynamiquement a la langue ou au domaine du texte d'entree. Certaines architectures utilisent plusieurs tokenizers specialises et selectionnent automatiquement le plus adapte.

Fertilite des tokens par langue (GPT-4 cl100k) Anglais 1.3 tok/mot Francais 1.7 tok/mot Allemand 2.1 tok/mot Chinois 3.0 tok/car Japonais 3.3 tok/car Thai 4.8 tok/mot Plus la barre est longue, plus la langue est "couteuse" en tokens

Tokenization speciale : code, mathematiques et donnees structurees

Tokenization du code source

Le code source a des proprietes lexicales differentes du langage naturel. Les identificateurs de variables suivent des conventions de nommage (camelCase, snake_case), les mots-cles sont specifiques a chaque langage, l'indentation et les espaces sont parfois significatifs (Python), et les symboles speciaux ({, }, ->, ::) portent un sens precis.

Les LLM modernes utilisent des tokenizers qui ont ete entraines sur un mix de texte naturel et de code, mais la qualite de la tokenization du code varie considerablement. Par exemple, une variable nommee "getUserAccountBalance" pourrait etre tokenisee en ["get", "User", "Account", "Balance"] (bon decoupage semantique) ou en ["getUser", "Account", "Bal", "ance"] (decoupage arbitraire). Les modeles specialises code (Code Llama, StarCoder, DeepSeek Coder) utilisent des tokenizers optimises pour le code.

Tokenization des nombres et des mathematiques

Les nombres posent des defis particuliers pour les tokenizers. Le nombre "123456789" peut etre tokenise en un seul token, en groupes de chiffres (["123", "456", "789"]), ou en chiffres individuels. Le decoupage affecte la capacite du modele a effectuer des operations arithmetiques et a comprendre la magnitude des nombres. Certains modeles (comme Minerva de Google) utilisent des tokenizers specialises pour les expressions mathematiques.

Tokens speciaux

Tous les tokenizers definissent des tokens speciaux qui ne correspondent pas a du texte mais servent de signaux pour le modele. Les tokens speciaux courants incluent [BOS] (Beginning Of Sequence), [EOS] (End Of Sequence), [PAD] (Padding), [SEP] (Separator), [CLS] (Classification), [MASK] (pour le masked language modeling), et des tokens de chat (<|user|>, <|assistant|>, <|system|>). Ces tokens consomment des positions dans la fenetre de contexte et doivent etre pris en compte dans le comptage.

Embeddings avances : au-dela des embeddings de phrases

ColBERT et les embeddings a interactions tardives

ColBERT (Contextualized Late Interaction over BERT) est une architecture qui produit des embeddings au niveau des tokens plutot qu'un seul embedding par phrase. Lors de la recherche, chaque token de la requete interagit avec chaque token du document via un produit scalaire maximal (MaxSim). Cette approche capture des correspondances plus fines que les embeddings de phrases.

ColBERT offre d'excellentes performances de recherche (superieures aux embeddings de phrases sur de nombreux benchmarks) mais au prix d'un stockage plus important (un vecteur par token au lieu d'un vecteur par document). Les modeles recents (ColBERTv2, PLAID) optimisent le stockage via la quantification et la compression.

Embeddings multimodaux

Les embeddings multimodaux representent differents types de contenu (texte, images, audio, video) dans un meme espace vectoriel. CLIP (Contrastive Language-Image Pre-training) d'OpenAI est le modele fondateur, qui place les images et leurs descriptions textuelles proches dans l'espace d'embedding. ImageBind de Meta etend ce concept a six modalites. Ces embeddings multimodaux permettent la recherche cross-modale : trouver des images a partir d'une requete textuelle, ou vice versa.

Embeddings de graphes de connaissances

Les embeddings de graphes (TransE, RotatE, ComplEx) representent les entites et les relations d'un graphe de connaissances dans un espace vectoriel. La combinaison d'embeddings textuels et d'embeddings de graphes est une direction de recherche active qui promet d'ameliorer les systemes RAG en integrant des informations structurees.

Evaluation des embeddings

Le benchmark MTEB

Le Massive Text Embedding Benchmark (MTEB) est le standard de facto pour l'evaluation des modeles d'embedding. Il couvre huit categories de taches : classification, clustering, pair classification, reranking, retrieval, STS (Semantic Textual Similarity), summarization et BitextMining. MTEB inclut des evaluations dans plus de 100 langues, ce qui permet de comparer les performances multilingues des modeles.

Le leaderboard MTEB est accessible sur Hugging Face et est mis a jour regulierement. Les scores MTEB doivent etre interpretes avec precaution : un modele qui domine sur le score moyen peut etre mediocre sur une tache specifique. Pour un cas d'usage precis, il est recommande de consulter les scores par categorie de tache et par langue.

Evaluation personnalisee

Pour les applications en production, l'evaluation sur les benchmarks generiques ne suffit pas. Une evaluation personnalisee sur des donnees representatives du cas d'usage reel est indispensable. Cela implique de creer un jeu de test avec des requetes et des documents pertinents annotes manuellement, et de mesurer des metriques comme le recall@k, le MRR (Mean Reciprocal Rank) et le nDCG (normalized Discounted Cumulative Gain).

Tendances et avenir de la tokenization et des embeddings

Tokenization sans tokenizer ?

Plusieurs travaux de recherche explorent des approches qui eliminent ou reduisent le role du tokenizer. Les modeles a base de bytes (comme MegaByte de Meta) traitent directement les octets sans tokenization prealable. Les modeles a base de caracteres avec des mecanismes de pooling hierarchique apprennent implicitement leur propre segmentation. Ces approches sont encore experimentales mais pourraient resoudre les problemes de tokenization multilingue.

Embeddings avec instructions

La tendance recente des embeddings avec instructions (instruction-tuned embeddings) permet de specifier en langage naturel comment le modele doit encoder le texte. Par exemple, "Represent this text for finding similar legal documents" produit un embedding different de "Represent this text for sentiment analysis" pour le meme texte d'entree. Cette approche, popularisee par E5-instruct et Jina Embeddings v3, rend les modeles d'embedding plus polyvalents.

Quantification et efficacite

La quantification des embeddings (de float32 a int8 ou binaire) est une tendance importante pour reduire les couts de stockage et accelerer la recherche. Les embeddings Matryoshka permettent de tronquer les dimensions. Les embeddings binaires (1 bit par dimension) offrent des recherches extremement rapides via des operations XOR/popcount. La combinaison de ces techniques permet de construire des systemes de recherche a tres grande echelle (milliards de documents) avec des couts raisonnables.

A retenir : L'avenir va vers des tokenizers plus efficaces (vocabulaires plus grands, entrainement multilingue), des embeddings plus flexibles (instructions, Matryoshka, multimodaux) et des architectures qui reduisent la dependance au tokenizer. Pour le present, maitriser tiktoken, choisir le bon modele d'embedding et optimiser le chunking sont les competences les plus immediatement utiles pour les praticiens.

Foire aux questions sur la tokenization et les embeddings

Quelle est la difference fondamentale entre un token et un embedding ?

Un token est une unite discrete — un entier qui identifie un fragment de texte dans le vocabulaire du tokenizer. C'est un identifiant symbolique, comme un numero dans un catalogue. Un embedding est un vecteur continu dans un espace multidimensionnel qui encode le sens semantique de ce token (ou d'une sequence de tokens). La relation est sequentielle : le texte est d'abord tokenise (converti en tokens), puis chaque token est transforme en embedding via une table de correspondance (embedding matrix) qui est apprise pendant l'entrainement. L'embedding capture les relations de sens (synonymie, analogie, contexte) que le token seul ne contient pas. Pour approfondir ce sujet, consultez notre article sur la difference entre embeddings et tokens.

Pourquoi un meme texte produit-il un nombre de tokens different selon le modele ?

Chaque modele utilise un tokenizer specifique avec son propre vocabulaire, entraine sur un corpus particulier. Le tokenizer de GPT-4o (o200k_base, 200k tokens) a un vocabulaire deux fois plus grand que celui de GPT-4 (cl100k_base, 100k tokens), ce qui lui permet de representer plus de mots comme des tokens uniques. Un tokenizer entraine principalement sur l'anglais decoupera les mots francais en plus de fragments qu'un tokenizer multilingue. De plus, les algorithmes de tokenization different (BPE vs WordPiece vs Unigram), ce qui produit des decoupages differents meme pour un vocabulaire de meme taille. C'est pourquoi il est essentiel d'utiliser la bonne bibliotheque (tiktoken pour les modeles OpenAI, le tokenizer du modele pour les modeles Hugging Face) pour compter precisement les tokens.

Comment choisir entre BPE, WordPiece et SentencePiece ?

En pratique, le choix est rarement a faire car il est determine par le modele que vous utilisez. Si vous utilisez GPT-4, Claude ou Llama, le tokenizer est BPE. Si vous utilisez BERT, c'est WordPiece. Si vous utilisez T5 ou Mistral, c'est SentencePiece. Le choix ne se pose que si vous entrainez votre propre modele. Dans ce cas, BPE (via tiktoken ou la bibliotheque tokenizers de Hugging Face) est le choix le plus courant pour les LLM generatifs. SentencePiece avec Unigram est recommande pour les modeles multilingues car il ne necessite pas de pre-tokenization et gere mieux les langues sans espaces. WordPiece est surtout utilise par inertie dans l'ecosysteme BERT et n'est generalement pas recommande pour les nouveaux projets.

Les embeddings sont-ils specifiques a une langue ?

Cela depend du modele. Les modeles d'embedding monolingues (entraines sur une seule langue) produisent des embeddings specifiques a cette langue : un texte francais et sa traduction anglaise ne seront pas proches dans l'espace d'embedding. Les modeles multilingues (BGE-M3, Cohere multilingual, E5-multilingual) sont entraines pour que des textes semantiquement equivalents dans des langues differentes soient proches. Cela permet la recherche cross-lingue : une requete en francais peut trouver des documents pertinents en anglais, et vice versa. La qualite de cette alignement interlangue varie selon les paires de langues et les modeles.

Quelle dimension d'embedding choisir ?

La dimension est un compromis entre precision et cout. Des dimensions plus elevees capturent plus de nuances semantiques mais coutent plus cher en stockage et en calcul. Pour la plupart des applications de recherche et de RAG, 768 a 1024 dimensions offrent un excellent compromis. Pour les cas d'usage tres exigeants (recherche juridique, medicale), 1536 a 3072 dimensions peuvent etre justifiees. Pour les systemes a tres grande echelle (milliards de documents), des dimensions reduites (256-384) avec reranking sont souvent preferees. Les embeddings Matryoshka permettent de stocker les dimensions completes mais de n'utiliser qu'un sous-ensemble pour la recherche rapide, combinant le meilleur des deux mondes.

Comment optimiser le chunking pour le RAG en francais ?

Plusieurs recommandations specifiques au francais s'appliquent. Utilisez un modele d'embedding multilingue (BGE-M3, Cohere embed-multilingual-v3) qui a ete entraine avec un tokenizer efficace pour le francais. Mesurez la taille des chunks en tokens (avec le tokenizer du modele d'embedding) plutot qu'en caracteres, car un meme nombre de caracteres en francais et en anglais produira un nombre different de tokens. Reduisez legerement la taille cible des chunks (par exemple 400 tokens au lieu de 512) pour compenser l'inflation de tokens en francais. Utilisez des separateurs adaptes au francais pour le decoupage (paragraphes, phrases, tirets cadratins). Et combinez la recherche dense (embeddings) avec la recherche lexicale (BM25) pour compenser les cas ou la tokenization fragmentee degrade la similarite semantique.

Quel est l'impact de la tokenization sur le fine-tuning ?

La tokenization a un impact direct sur le fine-tuning a plusieurs niveaux. Le cout du fine-tuning est proportionnel au nombre de tokens dans le dataset d'entrainement. Un dataset en francais coutera environ 30 % de plus qu'un dataset anglais equivalent. La qualite de l'apprentissage peut etre affectee si le tokenizer decoupe mal les termes specifiques du domaine cible. Par exemple, un jargon technique non couvert par le vocabulaire sera fragmente, rendant l'apprentissage des associations semantiques plus difficile. Pour les domaines tres specialises, le "vocabulary extension" (ajout de tokens specifiques au domaine dans le vocabulaire) est une technique avancee qui peut ameliorer les resultats, mais elle necessite de re-entrainer les embeddings des nouveaux tokens.

Comment les embeddings gèrent-ils la negation et le contexte ?

C'est une limitation connue des embeddings de phrases. "Ce restaurant est excellent" et "Ce restaurant n'est pas excellent" auront des embeddings relativement proches car ils partagent la plupart des mots. Les modeles d'embedding recents (text-embedding-3, BGE-M3) gerent mieux la negation que les modeles plus anciens, mais la sensibilite reste imparfaite. Pour les applications critiques ou la negation change le sens (avis clients, diagnostics medicaux), il est recommande d'utiliser un reranker cross-encoder apres la recherche initiale par embeddings. Les cross-encoders comparent directement la requete et le document et sont beaucoup plus sensibles aux nuances comme la negation.

La tokenization et les embeddings sont les pierres angulaires invisibles de l'IA generative et de la recherche semantique. Maitriser ces concepts permet d'optimiser les couts, d'ameliorer la qualite des systemes RAG, de comprendre les limitations des modeles et de faire des choix technologiques eclaires. A mesure que les vocabulaires s'agrandissent, que les embeddings deviennent plus flexibles et que les architectures evoluent, ces fondamentaux resteront essentiels pour tout praticien de l'IA.

Architecture des modeles d'embedding : comment sont-ils entraines ?

Comprendre comment les modeles d'embedding sont entraines permet de mieux apprehender leurs forces et leurs faiblesses, et de choisir le modele le plus adapte a chaque cas d'usage. Les modeles d'embedding modernes sont entraines avec des objectifs d'apprentissage contrastif qui optimisent la proximite des paires semantiquement similaires et l'eloignement des paires dissimilaires.

Apprentissage contrastif et paires positives/negatives

Le paradigme dominant pour l'entrainement des modeles d'embedding est l'apprentissage contrastif. Etant donne une ancre (un texte de reference), un positif (un texte semantiquement similaire) et un ou plusieurs negatifs (des textes dissimilaires), le modele est entraine pour rapprocher l'ancre et le positif tout en eloignant l'ancre et les negatifs dans l'espace d'embedding.

La fonction de perte la plus courante est InfoNCE (Noise Contrastive Estimation), qui traite le probleme comme une classification multi-classes : parmi N candidats (1 positif + N-1 negatifs), le modele doit identifier le positif. Plus le nombre de negatifs est eleve (typiquement des centaines a des milliers par batch), plus l'entrainement est discriminant et les embeddings sont de qualite.

Les sources de paires positives incluent les paires titre-paragraphe, les paires question-reponse, les paraphrases, les traductions (pour le multilingue), et les paires requete-document cliquee (provenant des moteurs de recherche). La difficulte des negatifs est cruciale : des negatifs trop faciles (textes completement hors sujet) n'informent pas le modele, tandis que des negatifs difficiles (textes proches mais pas pertinents) forcent le modele a apprendre des distinctions fines.

Entrainement en deux etapes : pre-training + fine-tuning

Les modeles d'embedding modernes sont typiquement entraines en deux etapes. La premiere etape (pre-training contrastif) utilise de grandes quantites de paires faiblement supervisees (titres-passages de Wikipedia, paires de requetes-documents de moteurs de recherche, paraphrases automatiques). Cette etape apprend des representations semantiques generales. La deuxieme etape (fine-tuning supervise) utilise des datasets plus petits mais de haute qualite, specifiquement annotes pour des taches de retrieval, de classification ou de STS. Cette etape specialise le modele pour des taches specifiques.

Certains modeles ajoutent une troisieme etape d'instruction-tuning, ou le modele est entraine a suivre des instructions qui definissent comment encoder le texte. C'est le cas de E5-instruct et de Jina Embeddings v3, qui acceptent des instructions personnalisees comme "Represent this text for finding similar legal documents" et produisent des embeddings adaptes a la tache decrite.

Architecture des encodeurs

Les modeles d'embedding utilisent generalement des architectures Transformer bidirectionnelles (type BERT) plutot que des architectures auto-regressives (type GPT). La raison est que l'encodage bidirectionnel permet a chaque position de prendre en compte l'ensemble du contexte (avant et apres), ce qui produit des representations plus riches pour l'encodage de phrases completes. L'architecture typique est un BERT-like de 12 a 24 couches, avec 768 a 1024 dimensions, et entre 110M et 560M de parametres.

Des travaux recents montrent que les modeles auto-regressifs (GPT-like) peuvent egalement produire de bons embeddings avec des techniques specifiques comme l'embedding du dernier token ou l'ajout d'un token [EOS] entraine pour l'encodage. Cependant, les modeles bidirectionnels restent dominants pour les taches d'embedding en raison de leur meilleur rapport performance/efficacite.

Bases de donnees vectorielles : stocker et rechercher des embeddings a l'echelle

Les embeddings n'ont de valeur que si on peut les stocker efficacement et rechercher les plus proches rapidement. Les bases de donnees vectorielles sont les systemes specialises dans le stockage, l'indexation et la recherche de vecteurs a grande echelle.

Algorithmes de recherche de plus proches voisins

La recherche exacte de plus proches voisins (KNN exacte) a une complexite lineaire en O(n) : il faut comparer le vecteur de requete avec tous les vecteurs de la base. Pour des collections de millions ou milliards de vecteurs, c'est impraticable. Les algorithmes de recherche approximative de plus proches voisins (ANN, Approximate Nearest Neighbors) offrent des temps de recherche sub-lineaires au prix d'une precision legerement reduite.

Les principaux algorithmes ANN sont HNSW (Hierarchical Navigable Small World), qui construit un graphe multicouche navigable et offre d'excellentes performances avec un compromis configurable entre vitesse et precision. IVF (Inverted File Index) partitionne l'espace en clusters et ne recherche que dans les clusters les plus proches de la requete. Product Quantization (PQ) compresse les vecteurs en sous-vecteurs quantifies, reduisant la memoire et accelerant le calcul de distance. La combinaison IVF+PQ est courante pour les tres grandes collections.

Choix d'une base de donnees vectorielle

SolutionTypeScalabiliteIndexMultitenancyFiltrage
PineconeSaaSMilliardsProprietaireNatifMetadata
WeaviateOpen sourceMilliardsHNSWNatifGraphQL
QdrantOpen sourceMilliardsHNSWNatifPayload
MilvusOpen sourceMilliardsHNSW, IVF, PQPossibleExpressions
ChromaOpen sourceMillionsHNSWCollectionsMetadata
pgvectorExtension PGMillionsHNSW, IVFSchemasSQL natif

Pour les projets de petite a moyenne echelle (moins de 10 millions de vecteurs), pgvector (extension PostgreSQL) offre l'avantage d'utiliser l'infrastructure existante sans ajouter un nouveau systeme a operer. Pour les projets a grande echelle ou avec des exigences de latence strictes, les solutions specialisees (Qdrant, Weaviate, Milvus) sont recommandees.

Optimisation du stockage et de la recherche

Plusieurs techniques permettent d'optimiser le cout et la performance de la recherche vectorielle. La quantification scalaire (int8) reduit la taille de chaque vecteur de 4x (float32 vers int8) avec une perte de precision minimale (typiquement < 2 % de degradation du recall). La quantification binaire (1 bit par dimension) offre une reduction de 32x et des recherches extremement rapides via des operations XOR+popcount, mais avec une perte de precision plus significative (5-10 %). L'approche recommandee est un pipeline a deux etapes : recherche rapide sur des embeddings quantifies (oversampling 2-5x), puis reranking sur les embeddings complets.

# Exemple avec Qdrant : recherche a deux etapes
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, QuantizationConfig, ScalarQuantization

# Configuration avec quantification scalaire
client = QdrantClient(url="http://localhost:6333")
client.create_collection(
    collection_name="documents",
    vectors_config=VectorParams(
        size=1024,  # BGE-M3 dimension
        distance=Distance.COSINE,
    ),
    quantization_config=QuantizationConfig(
        scalar=ScalarQuantization(
            type="int8",
            quantile=0.99,
            always_ram=True,  # Vecteurs quantifies en RAM pour la vitesse
        )
    ),
)

# Recherche avec oversampling et rescore
results = client.search(
    collection_name="documents",
    query_vector=query_embedding,
    limit=10,
    search_params={
        "quantization": {
            "rescore": True,     # Rescore avec les vecteurs originaux
            "oversampling": 3.0, # Recupere 3x plus de candidats pour le rescore
        }
    }
)

Chunking avance : strategies pour maximiser la qualite du RAG

Le chunking (decoupage des documents en fragments) est l'etape la plus impactante sur la qualite d'un systeme RAG, et il est intimement lie a la tokenization. Un mauvais chunking peut detruire la coherence semantique des passages et degrader significativement la pertinence de la recherche.

Chunking semantique

Le chunking semantique depasse le simple decoupage par taille en utilisant la semantique pour identifier les points de decoupage naturels. L'idee est de regrouper les phrases qui traitent du meme sujet et de couper entre les changements de sujet. Les approches incluent l'embedding de phrases avec detection de rupture (on calcule l'embedding de chaque phrase et on detecte les baisses de similarite entre phrases consecutives), le chunking par paragraphes avec regroupement (chaque paragraphe est un candidat, les paragraphes adjacents traitant du meme sujet sont regroupes), et le chunking hierarchique (documents, sections, paragraphes) qui preservent la structure du document.

from langchain_experimental.text_splitter import SemanticChunker
from langchain_community.embeddings import HuggingFaceBgeEmbeddings

# Chunking semantique avec detection de rupture
embeddings = HuggingFaceBgeEmbeddings(model_name="BAAI/bge-m3")
semantic_chunker = SemanticChunker(
    embeddings,
    breakpoint_threshold_type="percentile",
    breakpoint_threshold_amount=80,  # Coupe quand la similarite chute sous le 80e percentile
)

# Application
chunks = semantic_chunker.split_text(long_document)
for i, chunk in enumerate(chunks):
    print(f"Chunk {i}: {len(chunk)} caracteres")
    print(f"  Debut: {chunk[:80]}...")

Late chunking

Le "late chunking" est une technique recente qui encode d'abord le document complet avec le modele d'embedding (en utilisant toute la fenetre de contexte disponible), puis decoupe les embeddings de tokens en chunks. Chaque token a ainsi beneficie du contexte complet du document lors de l'encodage, meme si les chunks sont stockes separement. Cela produit des embeddings de chunks plus riches semantiquement que le chunking suivi de l'encodage independant de chaque chunk.

Parent-child retrieval

La strategie parent-child (ou small-to-big) indexe de petits chunks (quelques phrases) pour la recherche de precision, mais retourne les chunks parents (sections ou paragraphes complets) comme contexte pour le LLM. Cela combine la precision de la recherche (petits chunks specifiques) avec la richesse du contexte (grands passages coherents). Cette strategie est particulierement efficace pour les documents structures (documentation technique, articles longs).

Embeddings specialises : cas d'usage verticaux

Embeddings pour le code source

Les modeles d'embedding generalistes fonctionnent mal sur le code source car ils ne comprennent pas les relations syntaxiques et semantiques specifiques au code. Des modeles specialises comme CodeBERT, UniXcoder, StarEncoder et Voyage Code produisent des embeddings qui capturent la semantique du code : fonctions semantiquement equivalentes mais syntaxiquement differentes seront proches dans l'espace d'embedding. Ces modeles sont essentiels pour les systemes de recherche de code, la detection de duplicats et l'aide au developpement.

Embeddings pour les images (CLIP et successeurs)

CLIP (Contrastive Language-Image Pre-training) d'OpenAI a ouvert la voie aux embeddings multimodaux en entrainant conjointement un encodeur de texte et un encodeur d'images pour que les representations textuelles et visuelles soient alignees dans le meme espace. Les successeurs comme SigLIP, EVA-CLIP et OpenCLIP offrent des performances ameliorees. Ces embeddings permettent la recherche d'images par texte, la classification zero-shot, et le RAG multimodal integrant images et texte.

Embeddings pour les documents longs

La plupart des modeles d'embedding ont une fenetre de contexte limitee (512 a 8192 tokens). Pour les documents longs depassant cette limite, plusieurs strategies existent. La segmentation et moyenne pondérée decoupe le document en segments, encode chaque segment, et calcule une moyenne ponderee (les premiers et derniers segments ayant souvent plus de poids). Le modele Jina Embeddings v3 supporte des contextes de 8192 tokens, suffisants pour la plupart des documents. Les modeles long-context comme Nomic Embed (8192 tokens) et Voyage AI voyage-3 (32000 tokens) permettent d'encoder des documents entiers sans segmentation.

Metriques avancees de similarite : au-dela du cosinus

La similarite cosinus est la metrique la plus utilisee pour comparer des embeddings, mais elle n'est pas toujours optimale. D'autres metriques peuvent etre plus adaptees selon le cas d'usage.

Similarite cosinus vs produit scalaire vs distance euclidienne

La similarite cosinus mesure l'angle entre deux vecteurs, independamment de leur norme. Elle est ideale pour les embeddings normalises (de norme 1). Le produit scalaire (dot product) combine la direction et la magnitude des vecteurs. Pour les embeddings normalises, il est equivalent a la similarite cosinus. Pour les embeddings non normalises, il peut capturer l'importance (magnitude) en plus de la direction. La distance euclidienne mesure la distance geometrique entre deux points. Elle est sensible a la magnitude et peut etre preferable pour certaines taches de clustering.

En pratique, la plupart des modeles d'embedding modernes produisent des vecteurs normalises, rendant les trois metriques equivalentes. Verifiez la documentation du modele pour savoir quelle metrique est recommandee. Si le modele est entraine avec une perte de cosinus, utilisez la similarite cosinus. Si la normalisation n'est pas garantie, le produit scalaire peut etre preferable.

Maximum Marginal Relevance (MMR)

MMR est un algorithme de re-ranking qui equilibre la pertinence et la diversite des resultats. Au lieu de retourner les k documents les plus similaires a la requete (qui peuvent etre redondants entre eux), MMR selectionne iterativement des documents qui sont pertinents par rapport a la requete ET diversifies par rapport aux documents deja selectionnes. Cela produit un ensemble de resultats plus informatif et moins redondant, particulierement utile pour le RAG ou la diversite des passages injectes dans le contexte ameliore la qualite de la generation.

import numpy as np

def mmr_selection(query_embedding, document_embeddings, k=5, lambda_param=0.7):
    """Selection MMR : equilibre pertinence et diversite.

    Args:
        query_embedding: vecteur de la requete
        document_embeddings: matrice (n_docs, dim)
        k: nombre de documents a selectionner
        lambda_param: equilibre pertinence (1.0) vs diversite (0.0)
    """
    # Similarite avec la requete
    query_similarities = np.dot(document_embeddings, query_embedding)

    selected = []
    remaining = list(range(len(document_embeddings)))

    for _ in range(k):
        if not remaining:
            break

        mmr_scores = []
        for idx in remaining:
            relevance = query_similarities[idx]
            # Diversite : max similarite avec les docs deja selectionnes
            if selected:
                selected_embeddings = document_embeddings[selected]
                diversity = np.max(np.dot(selected_embeddings, document_embeddings[idx]))
            else:
                diversity = 0
            mmr_score = lambda_param * relevance - (1 - lambda_param) * diversity
            mmr_scores.append((idx, mmr_score))

        best_idx = max(mmr_scores, key=lambda x: x[1])[0]
        selected.append(best_idx)
        remaining.remove(best_idx)

    return selected

Reranking : ameliorer la precision apres la recherche vectorielle

Le reranking est une etape de post-traitement qui reordonne les resultats de la recherche vectorielle pour ameliorer la precision. Les modeles de reranking sont des cross-encoders qui prennent en entree la paire (requete, document) et produisent un score de pertinence direct, sans passer par des embeddings intermediaires.

Cross-encoders vs bi-encoders

Les bi-encoders (modeles d'embedding) encodent la requete et le document independamment, ce qui permet un pre-calcul des embeddings de documents. La recherche se fait par similarite vectorielle rapide. Les cross-encoders prennent la concatenation (requete, document) en entree et produisent un score. Ils sont beaucoup plus precis (car ils peuvent capturer les interactions fines entre la requete et le document) mais beaucoup plus lents (pas de pre-calcul possible, chaque paire doit etre evaluee).

L'approche standard est un pipeline a deux etapes : (1) retrieval rapide avec un bi-encoder (top-100 candidats), (2) reranking precis avec un cross-encoder (selection du top-10 final). Les modeles de reranking populaires incluent Cohere Rerank, ms-marco-MiniLM-L-12-v2, bge-reranker-v2-m3, et Jina Reranker v2.

# Pipeline complet : retrieval + reranking
from sentence_transformers import CrossEncoder
import numpy as np

# Etape 1 : Recherche vectorielle (bi-encoder)
query_embedding = bi_encoder.encode(query)
candidates = vector_db.search(query_embedding, limit=100)  # Top 100

# Etape 2 : Reranking (cross-encoder)
reranker = CrossEncoder("BAAI/bge-reranker-v2-m3")
pairs = [[query, doc.text] for doc in candidates]
rerank_scores = reranker.predict(pairs)

# Tri par score de reranking
ranked_indices = np.argsort(rerank_scores)[::-1]
final_results = [candidates[i] for i in ranked_indices[:10]]  # Top 10 final

Optimisation des embeddings pour les applications de production

Caching strategique

Le calcul d'embeddings est couteux (appels API ou inference GPU). Un caching strategique peut reduire les couts de 50 a 80 % selon le pattern d'utilisation. Les strategies de cache incluent le cache de requetes exactes (si la meme requete est soumise deux fois, retourner l'embedding mis en cache), le cache semantique (si une requete tres similaire a deja ete calculee, retourner l'embedding le plus proche), et le pre-calcul des embeddings pour les contenus statiques (documents, FAQ, produits). Le choix du backend de cache (Redis, Memcached, disque) depend de la taille et de la frequence d'acces.

Batch processing et parallelisme

Pour l'indexation de grands corpus, le traitement par batch est essentiel. Les modeles d'embedding sont optimises pour les batches (utilisation efficace du GPU). Un batch de 32 a 128 textes est typiquement 10-30x plus rapide par texte qu'un traitement sequentiel. Pour les API cloud (OpenAI, Cohere), le respect des rate limits et l'utilisation de l'endpoint batch (avec remise sur le prix) sont recommandes.

Monitoring des embeddings en production

Les embeddings en production doivent etre surveilles pour detecter les drifts (changement dans la distribution des embeddings au fil du temps, indiquant un changement dans les donnees ou les requetes), les degradations de performance (baisse du recall ou du nDCG sur des requetes de reference), et les anomalies (requetes avec des embeddings tres eloignes de la distribution normale, pouvant indiquer des attaques ou des erreurs). Les outils comme Arize Phoenix et WhyLabs offrent des capacites de monitoring specifiques aux embeddings.

A retenir : En production, combinez retrieval vectoriel (bi-encoder) avec reranking (cross-encoder) pour le meilleur rapport qualite/latence. Implementez un cache strategique pour reduire les couts. Surveillez les metriques de qualite (recall@k, nDCG) en continu. Utilisez la quantification et le pipeline a deux etapes pour les grandes collections. Le choix de la strategie de chunking a souvent plus d'impact que le choix du modele d'embedding.

Construction d'un pipeline RAG complet en francais : de la tokenization au deploiement

Pour illustrer concretement l'integration de la tokenization et des embeddings dans un systeme de production, construisons pas a pas un pipeline RAG complet optimise pour le francais. Ce pipeline couvre l'ingestion de documents, le chunking adaptatif, l'embedding, l'indexation, la recherche hybride et la generation augmentee.

Etape 1 : Ingestion et pre-traitement

L'ingestion de documents est la premiere etape du pipeline. Elle comprend la lecture de differents formats (PDF, Word, HTML, Markdown), l'extraction du texte brut, le nettoyage (suppression des headers/footers, des numeros de page, des artefacts de mise en page), et la detection de la langue. Pour les documents en francais, le pre-traitement doit preserver les accents, les ligatures et les caracteres speciaux (guillemets francais, tirets cadratins) qui peuvent etre endommages par certains parseurs.

from pathlib import Path
import tiktoken
from langchain_community.document_loaders import PyPDFLoader, UnstructuredHTMLLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

class FrenchRAGPipeline:
    """Pipeline RAG optimise pour le francais."""

    def __init__(self, embedding_model="BAAI/bge-m3", chunk_size=400, chunk_overlap=50):
        self.tokenizer = tiktoken.get_encoding("cl100k_base")
        self.chunk_size = chunk_size
        self.chunk_overlap = chunk_overlap

        # Separateurs adaptes au francais
        self.separators = [
            "\n\n",           # Double saut de ligne (paragrapes)
            "\n",             # Saut de ligne
            ". ",             # Fin de phrase
            " ; ",            # Point-virgule (courant en francais)
            " : ",            # Deux-points (courant en francais)
            ", ",             # Virgule
            " ",              # Espace
            "",               # Caractere
        ]

        self.splitter = RecursiveCharacterTextSplitter(
            chunk_size=chunk_size,
            chunk_overlap=chunk_overlap,
            length_function=lambda t: len(self.tokenizer.encode(t)),
            separators=self.separators,
        )

    def ingest_document(self, file_path: str) -> list:
        """Ingere un document et retourne des chunks tokenises."""
        path = Path(file_path)
        if path.suffix == ".pdf":
            loader = PyPDFLoader(str(path))
        elif path.suffix in [".html", ".htm"]:
            loader = UnstructuredHTMLLoader(str(path))
        else:
            raise ValueError(f"Format non supporte: {path.suffix}")

        documents = loader.load()
        all_chunks = []
        for doc in documents:
            text = self._clean_french_text(doc.page_content)
            chunks = self.splitter.split_text(text)
            for i, chunk in enumerate(chunks):
                token_count = len(self.tokenizer.encode(chunk))
                all_chunks.append({
                    "text": chunk,
                    "source": str(path),
                    "chunk_index": i,
                    "token_count": token_count,
                    "metadata": doc.metadata,
                })
        return all_chunks

    def _clean_french_text(self, text: str) -> str:
        """Nettoyage specifique au francais."""
        import re
        text = re.sub(r'\n{3,}', '\n\n', text)  # Max 2 sauts de ligne
        text = re.sub(r' {2,}', ' ', text)       # Max 1 espace
        text = text.replace('\xad', '')            # Soft hyphen
        text = text.replace('\u200b', '')          # Zero-width space
        return text.strip()

    def compute_statistics(self, chunks: list) -> dict:
        """Statistiques sur les chunks pour validation."""
        token_counts = [c["token_count"] for c in chunks]
        return {
            "total_chunks": len(chunks),
            "total_tokens": sum(token_counts),
            "avg_tokens_per_chunk": sum(token_counts) / len(token_counts),
            "min_tokens": min(token_counts),
            "max_tokens": max(token_counts),
            "chunks_over_limit": sum(1 for t in token_counts if t > self.chunk_size),
        }

Etape 2 : Embedding et indexation

Apres le chunking, chaque chunk est encode en un vecteur d'embedding et stocke dans une base de donnees vectorielle. Le choix du modele d'embedding et de la base de donnees impacte directement la qualite de la recherche et les performances du systeme.

from sentence_transformers import SentenceTransformer
import chromadb
from chromadb.config import Settings

class EmbeddingIndexer:
    """Indexeur d'embeddings pour le pipeline RAG."""

    def __init__(self, model_name="BAAI/bge-m3", collection_name="documents_fr"):
        self.model = SentenceTransformer(model_name)
        self.client = chromadb.PersistentClient(path="./chroma_db")
        self.collection = self.client.get_or_create_collection(
            name=collection_name,
            metadata={"hnsw:space": "cosine"}
        )

    def index_chunks(self, chunks: list, batch_size: int = 64):
        """Indexe les chunks dans la base vectorielle."""
        texts = [c["text"] for c in chunks]
        ids = [f"{c['source']}_{c['chunk_index']}" for c in chunks]
        metadatas = [{"source": c["source"], "tokens": c["token_count"]} for c in chunks]

        # Embedding par batch
        for i in range(0, len(texts), batch_size):
            batch_texts = texts[i:i+batch_size]
            batch_ids = ids[i:i+batch_size]
            batch_meta = metadatas[i:i+batch_size]

            embeddings = self.model.encode(
                batch_texts,
                normalize_embeddings=True,
                show_progress_bar=True,
                batch_size=batch_size,
            ).tolist()

            self.collection.add(
                documents=batch_texts,
                embeddings=embeddings,
                ids=batch_ids,
                metadatas=batch_meta,
            )

        print(f"Indexe {len(texts)} chunks, total collection: "
              f"{self.collection.count()}")

    def search(self, query: str, n_results: int = 10,
               source_filter: str = None) -> list:
        """Recherche les chunks les plus pertinents."""
        query_embedding = self.model.encode(
            query, normalize_embeddings=True
        ).tolist()

        where_filter = {"source": source_filter} if source_filter else None
        results = self.collection.query(
            query_embeddings=[query_embedding],
            n_results=n_results,
            where=where_filter,
            include=["documents", "distances", "metadatas"],
        )
        return results

Etape 3 : Recherche hybride

La recherche hybride combine la recherche vectorielle (semantique) avec la recherche lexicale (BM25). Cette combinaison est particulierement benefique pour le francais, ou la recherche lexicale capture les termes techniques exacts que la recherche semantique peut manquer (en raison de la fragmentation du tokenizer), et la recherche semantique capture les reformulations et les synonymes que la recherche lexicale ne trouve pas.

L'implementation utilise la fusion de scores reciproques (Reciprocal Rank Fusion, RRF) pour combiner les deux listes de resultats. RRF est simple, efficace et ne necessite pas de normalisation des scores entre les deux systemes de recherche.

from rank_bm25 import BM25Okapi
import re

class HybridSearch:
    """Recherche hybride dense + sparse pour le francais."""

    def __init__(self, vector_indexer, documents):
        self.vector_indexer = vector_indexer
        self.documents = documents

        # Index BM25 pour la recherche lexicale
        tokenized_docs = [self._tokenize_french(doc["text"]) for doc in documents]
        self.bm25 = BM25Okapi(tokenized_docs)

    def _tokenize_french(self, text: str) -> list:
        """Tokenization simple pour BM25 (mots, lowercase, sans stopwords)."""
        stopwords_fr = {"le", "la", "les", "de", "du", "des", "un", "une",
                       "et", "en", "est", "que", "qui", "dans", "pour",
                       "au", "aux", "ce", "il", "ne", "pas", "sur", "se"}
        words = re.findall(r'\b\w+\b', text.lower())
        return [w for w in words if w not in stopwords_fr and len(w) > 2]

    def search(self, query: str, n_results: int = 10,
               alpha: float = 0.7) -> list:
        """Recherche hybride avec RRF.

        Args:
            alpha: poids de la recherche vectorielle (1.0 = tout dense)
        """
        # Recherche dense (vectorielle)
        dense_results = self.vector_indexer.search(query, n_results=n_results * 2)
        dense_ids = dense_results["ids"][0]

        # Recherche sparse (BM25)
        query_tokens = self._tokenize_french(query)
        bm25_scores = self.bm25.get_scores(query_tokens)
        sparse_top = sorted(range(len(bm25_scores)),
                          key=lambda i: bm25_scores[i], reverse=True)[:n_results * 2]
        sparse_ids = [self.documents[i]["id"] for i in sparse_top]

        # Reciprocal Rank Fusion
        rrf_scores = {}
        k = 60  # Constante RRF

        for rank, doc_id in enumerate(dense_ids):
            rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + alpha / (k + rank + 1)

        for rank, doc_id in enumerate(sparse_ids):
            rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + (1 - alpha) / (k + rank + 1)

        # Tri par score RRF
        sorted_results = sorted(rrf_scores.items(), key=lambda x: -x[1])
        return sorted_results[:n_results]

Benchmarking personnel : comment evaluer votre pipeline

Les benchmarks publics (MTEB, BEIR) sont utiles pour comparer les modeles, mais ils ne refletent pas forcement les performances sur vos donnees specifiques. Un benchmark personnel, cree a partir de vos donnees reelles, est indispensable pour valider vos choix techniques.

Creation d'un benchmark de recherche

Un benchmark de recherche minimal comprend 50 a 200 requetes representatives de votre cas d'usage, avec pour chaque requete les documents pertinents annotes manuellement (ground truth). Les metriques a calculer incluent le Recall@k (proportion de documents pertinents recuperes dans les k premiers resultats), le MRR (Mean Reciprocal Rank, rang moyen du premier document pertinent), le nDCG@k (normalized Discounted Cumulative Gain, mesure plus fine qui prend en compte la position des documents pertinents), et le Hit Rate (proportion de requetes pour lesquelles au moins un document pertinent est dans les k premiers resultats).

Ce benchmark doit etre execute a chaque modification du pipeline (changement de modele d'embedding, modification du chunking, ajustement des parametres de recherche) pour verifier que les modifications ameliorent effectivement les performances et ne provoquent pas de regressions. L'automatisation de ce benchmark dans un pipeline CI/CD est fortement recommandee.

Maitriser la tokenization et les embeddings est un investissement qui porte ses fruits dans chaque aspect de l'utilisation des LLM et de la recherche semantique. De la comprehension fine de pourquoi un texte francais coute plus cher qu'un texte anglais, a l'optimisation d'un pipeline RAG complet avec recherche hybride et reranking, ces fondements techniques sont la base sur laquelle se construisent les applications d'IA les plus performantes et les plus economiques.

Questions frequentes supplementaires

Comment evaluer la qualite d'un tokenizer pour le francais ?

La metrique principale est la fertilite (nombre moyen de tokens par mot francais). Un bon tokenizer pour le francais devrait avoir une fertilite inferieure a 1.5 pour un texte courant et inferieure a 2.0 pour un texte technique. Vous pouvez calculer cette metrique en tokenisant un corpus representatif de vos documents et en comparant le nombre de tokens au nombre de mots. Un tokenizer avec une fertilite de 1.3 pour le francais (contre 1.1 pour l'anglais) signifie que chaque mot francais coute en moyenne 18 % de tokens de plus qu'un mot anglais. Comparez plusieurs tokenizers sur vos donnees specifiques : le tokenizer de GPT-4o (o200k_base) est generalement meilleur que celui de GPT-4 (cl100k_base) pour le francais grace a son vocabulaire plus large. Pour les modeles open source, les tokenizers de Llama 3 (128k tokens) et Qwen 2.5 sont parmi les plus efficaces pour le francais.

Quelle est la difference entre un embedding dense et un embedding sparse ?

Un embedding dense est un vecteur ou toutes les dimensions sont actives (non nulles), typiquement de 384 a 3072 dimensions. C'est la representation standard produite par les modeles d'embedding comme text-embedding-3, BGE ou E5. Un embedding sparse est un vecteur de tres haute dimension (potentiellement des millions) ou la grande majorite des dimensions sont a zero, avec seulement quelques dimensions actives. BM25 produit un embedding sparse implicite, et des modeles comme SPLADE produisent des embeddings sparse appris. Les embeddings denses capturent la semantique (synonymes, paraphrases), tandis que les embeddings sparse capturent la correspondance lexicale exacte (mots specifiques). La combinaison des deux (recherche hybride) offre generalement les meilleurs resultats, car elle capture a la fois la semantique et les termes specifiques qui peuvent etre critiques pour la pertinence.

Peut-on fine-tuner un modele d'embedding pour un domaine specifique ?

Oui, et c'est souvent recommande pour les domaines specialises. Le fine-tuning d'un modele d'embedding consiste a l'entrainer sur des paires (requete, document pertinent) specifiques a votre domaine. La bibliotheque sentence-transformers de Hugging Face facilite ce processus. Les sources de donnees de fine-tuning incluent les logs de recherche (requetes + documents cliques), les paires question-reponse de votre FAQ, les tickets de support avec leurs resolutions, et les paires generees synthetiquement par un LLM. Un fine-tuning sur 5000 a 50000 paires peut ameliorer le recall@10 de 10 a 30 % par rapport a un modele generique. Le fine-tuning est particulierement benefique pour les domaines avec un vocabulaire tres specifique (medical, juridique, scientifique) que les modeles generiques ne maitrisent pas bien.

Comment gerer les mises a jour d'un modele d'embedding en production ?

Le changement de modele d'embedding en production necessite la re-indexation de tous les documents, car les embeddings de differents modeles ne sont pas comparables. Cela peut etre couteux en temps et en calcul pour de grandes collections. Les strategies de migration incluent la migration par blue-green (creer un nouvel index avec le nouveau modele en parallele, basculer le trafic, puis supprimer l'ancien index), la migration progressive (re-indexer par batches pendant les heures creuses), et le double indexing temporaire (maintenir les deux index pendant la transition et comparer les resultats). Planifiez le changement de modele comme une migration de base de donnees, avec des scripts de rollback en cas de probleme. Evaluez le nouveau modele sur votre benchmark personnalise avant la migration pour verifier que le changement est benefique.

Cas pratiques : resolution de problemes courants avec la tokenization et les embeddings

Probleme 1 : Le modele ne comprend pas les acronymes de mon domaine

Les acronymes techniques (RSSI, RGPD, ANSSI, MLOps, SRE) sont souvent decomposes en lettres individuelles par les tokenizers, ce qui perd leur signification. La solution consiste a verifier comment le tokenizer decompose vos acronymes cles avec tiktoken ou le tokenizer du modele, a utiliser des modeles avec un vocabulaire large (GPT-4o avec 200k tokens inclut davantage d'acronymes courants), a enrichir vos prompts avec des definitions explicites des acronymes, et pour les systemes RAG, a inclure un glossaire dans les chunks indexees. Pour un fine-tuning, incluez de nombreux exemples utilisant ces acronymes dans leur contexte habituel.

Probleme 2 : Les embeddings de phrases negatives sont trop proches des phrases positives

La negation est un defi connu des embeddings. "Ce produit est excellent" et "Ce produit n'est pas excellent" auront des embeddings relativement proches car ils partagent la plupart des tokens. Les solutions pratiques incluent l'utilisation d'un reranker cross-encoder en deuxieme etape, qui est beaucoup plus sensible a la negation, la reformulation des requetes pour eviter les negations (chercher "produit defaillant" plutot que "produit pas bon"), l'utilisation de modeles d'embedding recents (text-embedding-3, bge-m3) qui gerent mieux la negation grace a un entrainement sur des paires contrastives incluant des negations, et le fine-tuning du modele d'embedding avec des paires explicites incluant des negations si le probleme persiste.

Probleme 3 : Le chunking coupe au milieu d'une phrase ou d'un tableau

Le chunking par taille fixe peut couper au milieu d'une phrase, d'un tableau, d'un exemple de code ou d'un raisonnement. Les solutions incluent l'utilisation de separateurs hierarchiques (paragraphes d'abord, puis phrases, puis mots) avec RecursiveCharacterTextSplitter, la detection des structures de document (tableaux, blocs de code, listes) et leur traitement comme des unites atomiques indivisibles, l'augmentation du chevauchement (overlap) entre chunks pour que les informations a la frontiere soient presentes dans les deux chunks adjacents, et le post-traitement des chunks pour verifier qu'ils commencent et finissent a des limites syntaxiques valides. Pour les documents HTML, le chunking par structure (sections, articles, divs) est generalement preferable au chunking par taille.

Probleme 4 : Les requetes courtes donnent de mauvais resultats de recherche

Les requetes tres courtes (1 a 3 mots) produisent souvent des embeddings peu discriminants. Les solutions incluent l'expansion de requete (utiliser un LLM pour reformuler la requete courte en une phrase complete avant l'embedding), l'utilisation de modeles avec des input types distincts (comme Cohere embed avec search_query vs search_document), l'utilisation de prefixes (comme E5 avec "query: " pour les requetes), et la recherche hybride (BM25 est souvent meilleur que les embeddings pour les requetes de mots-cles courts). L'expansion de requete est particulierement efficace : transformer "LoRA fine-tuning" en "Comment fonctionne la technique LoRA pour le fine-tuning des modeles de langage ?" produit un embedding beaucoup plus riche et discriminant.

Glossaire des termes cles

Pour faciliter la comprehension de cet article et servir de reference, voici les definitions des termes techniques les plus importants utilises. Un token est l'unite fondamentale de traitement des modeles de langage, un fragment de texte identifie par un entier dans le vocabulaire du tokenizer. Un embedding est un vecteur numerique dans un espace multidimensionnel representant le sens semantique d'un texte. La similarite cosinus est une mesure de proximite angulaire entre deux vecteurs, variant de -1 (opposes) a +1 (identiques). BPE signifie Byte Pair Encoding, l'algorithme de tokenization le plus utilise, qui fusionne iterativement les paires de tokens les plus frequentes. Un vocabulaire est le dictionnaire fixe associant chaque token a un identifiant entier, dont la taille determine l'equilibre entre couverture et efficacite. Le pooling est la technique de reduction d'une sequence d'embeddings de tokens en un seul embedding de phrase, par moyenne (mean pooling) ou par utilisation du token CLS. La fertilite est le nombre moyen de tokens produits par mot dans une langue donnee, mesurant l'efficacite du tokenizer pour cette langue. MTEB est le Massive Text Embedding Benchmark, le standard de reference pour l'evaluation et la comparaison des modeles d'embedding. Un reranker (ou cross-encoder) est un modele qui re-classe les resultats de recherche en evaluant directement la pertinence de la paire requete-document. La quantification des embeddings est la reduction de la precision numerique des vecteurs (float32 vers int8 ou binaire) pour economiser le stockage et accelerer la recherche.