Files
Omika/tests/fixtures/sentencepiece/LISEZMOI.md
T
2026-10-23 18:46:50 +02:00

10 KiB

Fixtures SentencePiece — découpage de référence

Ce dossier contient les fixtures de référence du tokeniseur SentencePiece maison (src/Omika.App.Local/Services/SentencePieceTokenizer.cs).

À quoi ça sert

Le tokeniseur est réécrit à la main : lecture protobuf minimale, Viterbi UNIGRAM, BPE, repli octet, normalisation. Son risque n'est pas de lever une exception — c'est de découper légèrement autrement que le modèle, et donc de dégrader les traductions sans aucun signal : pas de plantage, pas de message, juste des phrases un peu plus mauvaises.

Une fixture transforme ce risque en mesure : pour une série de textes, elle fige ce que produit la bibliothèque sentencepiece de référence.

{
  "modele": "vocab.spm",
  "sentencepieceVersion": "0.2.0",
  "vocabSize": 32000,
  "addDummyPrefix": true,
  "removeExtraWhitespaces": true,
  "escapeWhitespaces": true,
  "normalizerName": "nmt_nfkc",
  "cas": [
    { "texte": "Bonjour le monde.", "ids": [1, 2, 3], "decode": "Bonjour le monde." }
  ]
}
  • modele : nom du fichier .spm décrit (modèle cherché dans ce dossier) ;
  • sentencepieceVersion : version de la référence — elle date la comparaison, pour qu'une divergence constatée dans six mois ne se confonde pas avec un changement de version de la bibliothèque ;
  • vocabSize, addDummyPrefix, removeExtraWhitespaces, escapeWhitespaces, normalizerName : les réglages du modèle qui décident du découpage, lus dans son normalizer_spec — c'est la première chose à comparer quand un découpage diverge ;
  • cas : ids vient de EncodeAsIds(texte), decode de DecodeIds(ids) appliqué à ces identifiants. La fixture est donc auto-cohérente : le test compare le tokeniseur C# à la référence, jamais à une transcription manuelle qui figerait une erreur de recopie ;
  • un fichier par modèle, nommé <nomDuModeleSansExtension>.fixtures.json, UTF-8 sans BOM, indenté.

Les textes couverts sont construits par le script et comprennent, au minimum : français accentué, espagnol accentué avec ¿¡, espaces de tête, de queue et doubles, chaîne vide, chaîne d'espaces seuls, chiffres et ponctuation, phrase très longue, casse mêlée, et une écriture non latine (arabe et cyrillique).

Générer les fixtures

Prérequis : Python 3 et la bibliothèque de référence.

pip install sentencepiece
.\scripts\Generer-Fixtures-SentencePiece.ps1 -DossierModeles C:\Chemin\Vers\Les\Modeles

Le générateur cherche récursivement tous les *.spm sous -DossierModeles et écrit un fixture par modèle dans tests\fixtures\sentencepiece (ou dans le dossier passé à -Sortie). Relancer le script est sans danger : il réécrit ses propres fixtures et ne supprime rien d'autre.

Options utiles :

# Copie chaque .spm à côté de son fixture : le test cherche le modèle ici.
.\scripts\Generer-Fixtures-SentencePiece.ps1 -DossierModeles C:\Modeles -CopierModeles

# Autre dossier de sortie (utile pour un pack non versionné).
.\scripts\Generer-Fixtures-SentencePiece.ps1 -DossierModeles C:\Modeles -Sortie C:\Temp\fixtures

Où trouver un .spm

Le vocabulaire est livré dans chaque pack de langue, un fichier par sens de traduction :

langues/{code}/{sens}/vocab.spm        # {code} = es, pt, ar…  {sens} = versFrancais, depuisFrancais

Ce chemin est celui du stockage local de l'application (voir LanguagePaths dans src/Omika.App.Local/Infrastructure). Deux façons de se procurer le fichier :

  1. le récupérer depuis le poste où le pack a été téléchargé (dossier de données de l'application, sous langues/) ;
  2. le demander à l'API des langues, qui le sert avec le reste du pack.

Un même dossier langues/{code}/{sens} peut donc servir de -DossierModeles.

Les .spm ne sont pas versionnés

Les fichiers .spm (comme les encoder.onnx) restent hors du dépôt :

  • ils pèsent plusieurs centaines de kilo-octets à plusieurs méga-octets chacun, multipliés par le nombre de langues et de sens ;
  • ce sont des artefacts de modèle, produits par un entraînement et soumis à leurs propres conditions de licence : leur place est dans un pack de langue distribué, pas dans l'historique Git.

Le générateur ne versionne que les fixtures (quelques kilo-octets de texte), qui décrivent le comportement attendu sans embarquer le modèle.

Comment le test trouve tout ça

tests/Omika.Tests/Mobile/SentencePieceFixturesTests.cs :

  1. dossier des fixtures : la variable d'environnement OMIKA_SPM_FIXTURES si elle est définie et existe, sinon tests/fixtures/sentencepiece résolu depuis la racine du dépôt (racine trouvée en remontant depuis l'assembly de test jusqu'au dossier contenant Omika.slnx — aucun chemin absolu codé en dur, aucun recours au dossier courant) ;
  2. pour chaque *.fixtures.json, le .spm annoncé par modele est cherché dans ce même dossier (nom simple, ou sous-dossier annoncé par la fixture) ;
  3. si le .spm est absent, la fixture est ignorée — jamais un échec ;
  4. si le dossier est absent ou vide, le test passe en expliquant pourquoi sur la sortie standard.

C'est délibéré : une suite rouge sur une machine fraîchement clonée, pour de simples fichiers absents, pousserait à désactiver le test — exactement l'inverse du but. Ces tests ne font échouer que sur une divergence réellement mesurée.

Pour vérifier un pack sans rien copier dans le dépôt :

$env:OMIKA_SPM_FIXTURES = 'C:\Chemin\Vers\fixtures-et-spm'
dotnet test tests\Omika.Tests\Omika.Tests.csproj --filter FullyQualifiedName~SentencePieceFixturesTests

Deux autres tests de ce fichier s'exécutent même sans fixture : l'idempotence de l'aller-retour (Decode(Encode(x)) rejoué donne le même texte — jamais Decode(Encode(x)) == x, qui dépend du vocabulaire), et le fait qu'un fichier inexploitable lève une SentencePieceException en français plutôt qu'une erreur système.

Lire un échec

Le message d'échec est conçu pour être exploitable seul :

Découpage divergent pour le cas « Bonjour le monde. » (fixture vocab.fixtures.json).
Premier écart à l'index 1 : référence 2537 « le », tokeniseur 12 « ▁le ».
Identifiants de référence (4) : 1, 2537, 232, 4
Identifiants obtenus (4)       : 1, 12, 232, 4
Texte reconstruit par la référence : « Bonjour le monde. ».

Ce qu'il faut y lire :

  • l'index du premier écart : les identifiants qui précèdent sont corrects, la divergence commence là. Un écart à l'index 0 est un problème de préfixe factice ou de normalisation ; un écart en milieu de phrase est un problème de segmentation (Viterbi, scores, longueur maximale de pièce) ;
  • les deux identifiants et leur pièce : 12 contre 2537 dit quelle pièce le tokeniseur a choisie à la place de celle du modèle. Une pièce très courte (▁, un caractère) à la place d'une pièce longue signale une table de pièces incomplète ou un score mal lu ; l'inverse signale un score trop favorable ;
  • les deux listes : des tailles différentes signalent une couverture incomplète (caractère devenu <unk> au lieu d'une pièce) ;
  • decode : « Décodage divergent » n'a rien à voir avec le découpage — le décodage est testé sur les identifiants de la référence, donc l'erreur est dans la reconstruction (marqueur ▁, rognage des bords, reconstitution des octets) ;
  • les réglages en tête de fixture (addDummyPrefix, normalizerName…) sont à comparer avec ProfilNormalisation et Avertissements du tokeniseur chargé.

Vérifier aussi que la fixture et le modèle vont bien ensemble : le test échoue explicitement si la taille de vocabulaire de la fixture ne correspond pas à celle du .spm (mauvais appariement de fichiers, pas divergence de segmentation).

Que faire d'une divergence

La signaler. Ne jamais l'absorber.

  • Ne pas desserrer la valeur attendue, ne pas régénérer la fixture « jusqu'à ce que ça passe », ne pas supprimer le cas gênant : une fixture réécrite pour coller au tokeniseur ne mesure plus rien, et la divergence repartira en production sous forme de traductions dégradées.
  • Ouvrir une anomalie avec : le nom de la fixture, le texte du cas, l'index du premier écart, les deux identifiants et les deux pièces, les deux listes, la version sentencepieceVersion, et la taille du vocabulaire.
  • Regarder d'abord les limites assumées documentées dans l'en-tête de SentencePieceTokenizer.cs : la table de normalisation compilée (precompiled_charsmap) n'est pas appliquée, et les pièces USER_DEFINED sont traitées comme des pièces ordinaires. Une divergence sur un texte en pleine largeur (123), une ligature (fi), un exposant ou une forme de compatibilité vient très souvent de là — la propriété Avertissements du tokeniseur le dit déjà quand c'est le cas. En revanche add_dummy_prefix, escape_whitespaces, remove_extra_whitespaces et le repli octet sont lus dans le modèle et honorés : une divergence sur ces points n'est plus une limite connue, c'est un défaut à corriger.
  • Vérifier en priorité les cas « espaces seuls » et espaces de bord : le Decode du tokeniseur ne retire que le préfixe factice qu'il a lui-même ajouté, alors que la référence applique sa propre règle de rognage. C'est le premier écart susceptible d'apparaître sur un run réel.
  • Corriger le tokeniseur (avec un test qui reproduit le cas), jamais la fixture. Si la référence elle-même change de comportement d'une version à l'autre, mettre à jour sentencepieceVersion et justifier l'écart dans le message de commit.

Fichiers de ce dossier

Fichier Contenu
LISEZMOI.md ce document
<modele>.fixtures.json fixtures de référence, régénérables, versionnées
*.spm non versionnés — modèles, à copier sur le poste de test