Files
Omika/docs/LIVRAISON_MODELES_LANGUES.md
T
2026-10-23 18:46:50 +02:00

35 KiB
Raw Blame History

Livrer un pack de langue : obtenir les modèles, les déposer, mettre à jour le manifeste

Objet : procédure complète de livraison d'une langue (modèles ONNX + vocabulaire) sur SRV-OMIKA, jusqu'à la mise à jour du manifeste servi par OmikaLanguesApi.

Prérequis : le service OmikaLanguesApi est installé, Langues__RootPath est posée en variable MACHINE, et le vhost Apache expose le service en HTTPS (DEPLOIEMENT_API_LANGUES.md §7).

Ce document ne traite pas de la qualité de traduction (choix du modèle) ni du poids du livrable mobile : voir VALIDATION_TRADUCTION_ONNX.md §4.


0. Vue d'ensemble

# Étape Où Qui
1 Exporter les modèles en ONNX poste avec accès Internet dev
2 Construire l'arborescence du pack poste dev
3 Valider le pack sur le poste poste dev
4 Déposer les fichiers sur le serveur SRV-OMIKA admin
5 Générer le manifeste sur le serveur SRV-OMIKA admin
6 Contrôler (disque, HTTP, empreinte) SRV-OMIKA admin
7 Rafraîchir et télécharger sur le téléphone appareil utilisateur

Trois règles, et tout le reste en découle :

  1. Le manifeste n'est jamais recopié d'une autre machine. Il contient la taille et l'empreinte SHA-256 de vos fichiers : il se génère sur le serveur, à partir de ce qui est réellement sur son disque (§5).
  2. Les fichiers d'abord, le manifeste ensuite. Un manifeste qui décrit des fichiers absents (ou pas encore remplacés) fait refuser le pack.
  3. Aucun redémarrage de service n'est nécessaire — mais le mobile garde son index mémorisé : il faut un rafraîchissement explicite (§7).

1. Étape 1 — Obtenir les modèles (poste de développement)

Les modèles ne sont pas livrés avec Omika (poids + licences) et ne sont pas versionnés dans le dépôt. Le plus simple est un modèle OPUS-MT de Helsinki-NLP, exporté en ONNX :

pip install "optimum[onnxruntime]" transformers sentencepiece

# fr -> es  : sert le sens « depuisFrancais » du pack « es »
optimum-cli export onnx --model Helsinki-NLP/opus-mt-fr-es .\export\fr-es

# es -> fr  : sert le sens « versFrancais » du pack « es »
optimum-cli export onnx --model Helsinki-NLP/opus-mt-es-fr .\export\es-fr

⚠️ optimum-cli peut rester introuvable juste après une installation réussie. Avec le Python du Microsoft Store, pip range les programmes dans un dossier utilisateur qui n'est pas dans le PATH — pip install se termine sans erreur, puis chaque fenêtre répond optimum-cli : commande introuvable. Pour savoir où ils sont allés :

python -c "import sysconfig; print(sysconfig.get_path('scripts','nt_user'))"

Le script scripts\Exporter-Packs-Langues.ps1 sait aller le chercher là tout seul (§1.1). Pour les commandes manuelles ci-dessus, utilisez le chemin complet, ou pour la session en cours :

$env:Path += ';' + (python -c "import sysconfig; print(sysconfig.get_path('scripts','nt_user'))")

⚠️ ERROR: Could not install packages due to an OSError: [WinError 206] Nom de fichier ou extension trop long — l'installation s'arrête, et optimum-cli n'est jamais créé : le script d'export continuera de dire « introuvable ». Ce n'est ni un bug de pip ni un bug du script : Windows plafonne les chemins à 260 caractères, et le Python du Microsoft Store installe ses paquets dans un dossier déjà très long — …\LocalCache\local-packages\Python312\site-packages\ = 140 caractères à lui seul. Or torch embarque des fichiers de licence imbriqués profondément : le chemin du dossier atteint 249 caractères, et les fichiers qu'il contient dépassent la limite (mesuré sur ce poste, LongPathsEnabled = 0).

Solution : installer dans un environnement virtuel au chemin court. Cela retire ~110 caractères au préfixe, sans droit administrateur :

python -m venv C:\OmikaPy
C:\OmikaPy\Scripts\python.exe -m pip install "optimum[onnxruntime]" transformers sentencepiece
C:\OmikaPy\Scripts\Activate.ps1     # à réactiver dans chaque nouvelle fenêtre

Le venv est isolé : le torch à moitié installé dans le Python du Store (resté après l'échec) ne le perturbe pas. Le script d'export trouve alors optimum-cli seul (§1.1) ; sans activation, indiquez l'interpréteur : -Python C:\OmikaPy\Scripts\python.exe.

Autre voie, sans environnement virtuel : autoriser les chemins longs dans Windows (PowerShell en administrateur), puis relancer la même installation. C'est un réglage machine, utile à tous les outils.

Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name LongPathsEnabled -Value 1 -Type DWord

L'export produit notamment :

encoder_model.onnx
decoder_model.onnx
decoder_with_past_model.onnx     <-- NE PAS LIVRER (voir ci-dessous)
source.spm
target.spm

⚠️ decoder_with_past_model.onnx ne doit jamais être livré. C'est un décodeur à cache de clés/valeurs destiné à la génération incrémentale : l'application l'exclut du chargement, mais s'il est présent dans le dossier il est déclaré dans le manifeste et donc téléchargé par chaque téléphone — ~120 Mo de données mobiles pour rien. Le générateur de manifeste le signale (§5).

⚠️ Un modèle Marian a DEUX vocabulaires (source.spm et target.spm) : les livrer tous les deux est obligatoire, sinon la traduction produira du charabia (les identifiants de sortie seraient décodés dans le mauvais référentiel).

1.1 Plusieurs langues d'un coup (Europe, Est, Caucase…)

Pour livrer un ensemble de langues, le script scripts\Exporter-Packs-Langues.ps1 enchaîne tout : vérification de l'existence de chaque paire sur HuggingFace, export ONNX, rangement dans l'arborescence du pack, rapport.

# 0. UNE SEULE FOIS : l'outil d'export. Sans lui le script s'arrête aussitôt,
#    AVANT toute écriture : « rien ne se passe » = c'est ce prérequis qui manque.
pip install "optimum[onnxruntime]" transformers sentencepiece

# 1. VOIR LE PLAN avant de payer ~30 Gio (aucun réseau, rien n'est écrit)
.\scripts\Exporter-Packs-Langues.ps1 -Simulation

# 1bis. QUELLES LANGUES SONT LIVRABLES ? Internet suffit, ni Python ni optimum :
#       ~100 appels d'API, une à deux minutes, RIEN n'est téléchargé.
.\scripts\Exporter-Packs-Langues.ps1 -SansExporter

# 2. Tout exporter (51 langues ≈ 102 modèles)
.\scripts\Exporter-Packs-Langues.ps1

# Sélections partielles
.\scripts\Exporter-Packs-Langues.ps1 -Zone Est,Caucase
.\scripts\Exporter-Packs-Langues.ps1 -Codes es,pt,uk,ru
.\scripts\Exporter-Packs-Langues.ps1 -Codes ar          # l'arabe est en zone « Divers »

Si l'étape 0 échoue avec [WinError 206] Nom de fichier ou extension trop long, l'outil n'a pas pu être installé (limite de 260 caractères de Windows) : voir la solution « environnement virtuel au chemin court » au §1, puis lancer le script avec -Python C:\OmikaPy\Scripts\python.exe.

  • Pour savoir à l'avance quelles langues sont livrables : .\scripts\Exporter-Packs-Langues.ps1 -SansExporter interroge HuggingFace pour toute la table et écrit le rapport sans rien télécharger (~100 appels d'API, une à deux minutes). Les langues sans paire finissent dans « Paires indisponibles », les autres dans « Paires disponibles (vérifiées, non exportées) ». Cette commande ne demande ni Python ni optimum-cli : elle répond donc avant même l'installation des outils.
  • Deux familles de modèles sont essayées, dans l'ordre : opus-mt-<langue>-fr (petit, ~300 Mo par sens) puis opus-mt-tc-big-<langue>-fr (~1 Go par sens), publiée par Helsinki-NLP pour des paires que la première ne couvre pas. Le nom réellement retenu est inscrit au rapport. Une langue déclarée absente l'est donc après quatre vérifications (2 familles × 2 sens).
  • Repli familial : pour it, es, pt, ca, gl et ro, le modèle multilingue opus-mt-tc-big-itc-itc couvre les deux sens en un seul export. Il n'est employé que si une paire bilingue manque (pt et gl n'en ont aucune ; it n'a que it-fr), et ses 1,15 Go sont écrits une seule fois dans <code>\commun\ : les deux sens le partagent (§2.1.1).
  • Les paires qui n'existent pas sont signalées, jamais inventées. Helsinki-NLP ne couvre pas toutes les combinaisons avec le français : le script teste chaque modèle sur HuggingFace avant de télécharger, et écrit les absentes dans RAPPORT-LANGUES.md. Prévoir que plusieurs langues de la liste (Caucase, Asie centrale) seront indisponibles.
  • Si HuggingFace est injoignable, le script s'arrête au lieu de déclarer les paires absentes : « je ne sais pas » n'est pas « il n'y en a pas ».
  • Un pack n'est écrit que si les deux directions existent (-InclureDirectionsPartielles pour l'accepter malgré tout).
  • decoder_with_past_model.onnx et les fichiers de tokenizer ne sont jamais conservés : le dossier de travail de l'export est supprimé après chaque sens.
  • Le script est reprenable : une langue déjà exportée est ignorée (-Forcer pour la refaire). Un arrêt en cours de route ne perd rien.
  • Volume : ~300 Mo par sens, soit ~30 Gio pour 51 langues, et plusieurs heures de téléchargement puis de conversion.

1.2 Où vont les fichiers

Chemin Après l'opération
Packs ONNX (le livrable) <RacinePacks>\<code>\<sens>\ (et <code>\commun\ pour un modèle familial) conservé — c'est ce qu'on transfère
Cache HuggingFace <RacinePacks>\.hf-cache\hub\ supprimé après chaque export réussi
Travail temporaire <RacinePacks>\.tmp-<code>-<sens>\ supprimé après chaque sens

<RacinePacks> vaut packs par défaut, résolu depuis la racine du dépôt — donc D:\Avalonia\Omika\packs sur ce poste. Pour l'écrire ailleurs (autre disque, dossier de livraison) :

.\scripts\Exporter-Packs-Langues.ps1 -RacinePacks D:\PacksLangues

Deux points importants :

  • Sans intervention, le cache HuggingFace va dans %USERPROFILE%\.cache\huggingface (donc sur C:), et chaque modèle y occupe une seconde copie de sa taille : ~29 Gio de plus. Le script force donc HF_HOME vers <RacinePacks>\.hf-cache pour que tout reste sur le même disque, et supprime la copie du modèle après chaque export réussi (elle n'a plus d'usage). Le pic de disque réel est donc « les packs + ~300 Mo », pas le double. -GarderCacheHuggingFace conserve le cache (utile pour enchaîner des -Forcer sans retélécharger). Un HF_HOME déjà défini dans l'environnement est respecté.
  • Par défaut la sortie est dans l'arborescence des sources (D:\Avalonia\Omika\packs). Ce n'est pas un dépôt git ici, donc rien ne sera committé, mais 29 Gio de binaires au milieu du code n'est pas souhaitable à long terme : préférer -RacinePacks D:\PacksLangues.
  • Les dossiers .hf-cache et .tmp-* sont ignorés par le générateur de manifeste (New-OmikaLanguesManifeste.ps1) : ils ne sont jamais pris pour des langues ni signalés comme fichiers non déclarés.

Pourquoi un modèle par langue, et pas un seul modèle multilingue ? Le pipeline amorce le décodeur sur le seul jeton de début du vocabulaire (OnnxTranslationService.GenererAsync) : il n'existe aucun mécanisme de langue cible forcée (forced_bos_token_id). Un modèle multilingue (NLLB, mBART, M2M-100) produirait donc la langue par défaut du modèle, pas celle demandée. Passer à NLLB — un seul modèle pour toutes les langues, bien plus léger au total — demanderait d'ajouter ce jeton au contrat : une option de pipeline, un champ dans le manifeste et sa résolution. Ce n'est pas un réglage de déploiement.


2. Étape 2 — Construire l'arborescence du pack

Si vous utilisez le script du §1.1, cette arborescence est produite automatiquement : cette étape sert alors de référence (et de mode opératoire manuel pour une langue isolée).

Un pack = un code de langue, contenant deux sens. La convention est imposée par l'API et par l'application ; elle n'est pas configurable.

es/
├── versFrancais/            (espagnol -> français : dictée, avant insertion)
│   ├── encoder_model.onnx   <- depuis .\export\es-fr\
│   ├── decoder_model.onnx   <- depuis .\export\es-fr\
│   ├── source.spm           <- depuis .\export\es-fr\source.spm   (ESPAGNOL)
│   ├── target.spm           <- depuis .\export\es-fr\target.spm   (FRANÇAIS)
│   ├── vocab.json           <- depuis .\export\es-fr\              (IDENTIFIANTS)
│   └── config.json          <- depuis .\export\es-fr\              (DÉCODEUR)
└── depuisFrancais/          (français -> espagnol : lecture, affichage seul)
    ├── encoder_model.onnx   <- depuis .\export\fr-es\
    ├── decoder_model.onnx   <- depuis .\export\fr-es\
    ├── source.spm           <- depuis .\export\fr-es\source.spm   (FRANÇAIS)
    ├── target.spm           <- depuis .\export\fr-es\target.spm   (ESPAGNOL)
    ├── vocab.json           <- depuis .\export\fr-es\
    └── config.json          <- depuis .\export\fr-es\

2.1 Noms de fichiers reconnus

L'application résout les rôles par le nom, avec des replis automatiques. Les noms ci-dessous sont ceux qu'elle cherche en premier : les respecter évite tout ambiguïté.

Fichier Rôle Si absent
encoder.onnx / encoder_model.onnx modèle principal du sens premier .onnx du dossier qui n'est pas un décodeur
decoder.onnx / decoder_model.onnx décodeur séparé (décodage glouton) mode « modèle unique »
*_with_past*.onnx jamais utilisé —
vocab.spm vocabulaire commun (NLLB, mBART, M2M-100) premier .spm
source.spm vocabulaire d'entrée (Marian/OPUS-MT) vocab.spm, puis premier .spm
target.spm vocabulaire de sortie (Marian/OPUS-MT) vocab.spm, puis premier .spm
vocab.json identifiants du modèle (obligatoire) numérotation du .spm — traduction fausse
config.json amorce et fin du décodeur (obligatoire) <s> / </s> du .spm — génération fausse
modele.json métadonnées d'un modèle multilingue (>>fra<<) modèle dédié à une paire

2.1.1 Dossier partagé commun/

Un modèle multilingue (famille de langues, type opus-mt-tc-big-itc-itc) sert plusieurs langues avec les mêmes poids. Les déclarer dans chaque sens ferait télécharger deux fois 1,15 Go sur chaque téléphone : ces fichiers vont donc une seule fois dans un dossier commun/.

pt/
├── commun/                  (poids PARTAGÉS par les deux sens)
│   ├── encoder_model.onnx
│   ├── decoder_model.onnx
│   ├── source.spm
│   ├── target.spm
│   ├── vocab.json
│   └── config.json
├── versFrancais/
│   └── modele.json          { "jetonCible": ">>fra<<" }
└── depuisFrancais/
    └── modele.json          { "jetonCible": ">>por<<" }

L'application cherche d'abord dans le dossier du sens, puis dans commun/ : un pack qui fournit son propre source.spm garde la priorité, et un pack qui n'a que des fichiers partagés fonctionne dans les deux sens. Le modele.json reste par sens — c'est la seule chose qui diffère (l'étiquette de langue : >>fra<< pour traduire vers le français, >>por<< depuis le français).

Les paires bilingues classiques (opus-mt-es-fr et opus-mt-fr-es) n'ont pas de dossier commun : leurs poids diffèrent dans chaque sens.

Un téléphone ne télécharge que la langue secondaire choisie par l'utilisateur, jamais les autres : le partage ci-dessus divise par deux ce qu'il reçoit, il ne lui impose rien de plus.

Langues servies par un modèle de famille

Un seul dépôt couvre plusieurs langues, dans les deux sens, quand aucune paire bilingue n'existe. L'exporteur le fait pour vous (-Codes pt,gl,it suffit) : il exporte le modèle une fois, écrit les poids dans commun/ et les deux modele.json.

Langue Jeton aller Jeton retour Paire bilingue ?
it >>fra<< >>ita<< it-fr seule (pas fr-it)
es >>fra<< >>spa<< les deux (familial inutile)
pt >>fra<< >>por<< aucune
ca >>fra<< >>cat<< les deux (familial inutile)
gl >>fra<< >>glg<< aucune
ro >>fra<< >>ron<< les deux (familial inutile)

Le modèle est Helsinki-NLP/opus-mt-tc-big-itc-itc (1,15 Go : encoder 451 Mo + décodeur 697 Mo). Il n'est employé que si une des deux directions manque, et il couvre alors les deux : inutile de payer deux paires bilingues.

Le modele.json complet, tel que l'exporteur l'écrit :

{
  "version": 1,
  "modeleSource": "Helsinki-NLP/opus-mt-tc-big-itc-itc",
  "famille": "itc",
  "jetonCible": ">>por<<"
}

Sans ce jeton, le modèle écrirait sa langue par défaut (l'espagnol) : du texte parfaitement lisible, dans la mauvaise langue. C'est la seule chose qui diffère entre les deux sens d'un pack familial.

Le manifeste déclare ces fichiers avec "sens": "commun" : le générateur les accepte, et le mobile les range dans langues/<code>/commun/.

vocab.json n'est pas un doublon du .spm. Le .spm découpe le texte ; vocab.json donne les identifiants que le modèle attend. Les deux numérotations diffèrent : mesuré sur opus-mt-es-fr, la pièce ▁Ho vaut 5293 pour le .spm mais 584 pour le modèle, et , vaut 4 contre 3. Sans vocab.json, le modèle reçoit un autre texte que celui dicté et produit du charabia sans lever la moindre erreur.

config.json apporte deux identifiants que le .spm ne contient pas : l'amorce du décodeur (74821 sur opus-mt-es-fr, 35282 sur opus-mt-tc-big-itc-itc) et sa fin (0 et 29158). Les deviner donne un décodeur qui démarre au hasard et ne s'arrête jamais.

Ces deux fichiers pèsent quelques centaines de kilo-octets : ils voyagent avec le pack comme les .spm. Le script d'export les copie ; pour les packs exportés avant leur introduction, voir le §2.2.

Un .spm portant un autre nom (vocab-espagnol.spm) n'est pas reconnu : il serait pris comme repli « premier .spm », ce qui peut être le mauvais vocabulaire — donc une traduction sans aucun sens, sans aucune erreur. Le générateur le signale (§5).

2.2 Compléter un pack exporté avant vocab.json

Les packs déjà sur le disque ne contiennent que les quatre fichiers d'origine. Les compléter ne demande aucun réexport : les poids sont bons, seuls les deux référentiels manquent (quelques Mo par sens).

# 1. Voir où on en est, sans rien écrire
.\scripts\Completer-Packs-Langues.ps1 -Verifier

# 2. Compléter (idempotent : relancer ne refait rien)
.\scripts\Completer-Packs-Langues.ps1

# 3. Une ou deux langues seulement
.\scripts\Completer-Packs-Langues.ps1 -Codes "es,pt"

Un pack familial (§2.1.1) est ignoré par cette complétion — et c'est volontaire : il n'a pas de source.spm par sens, donc rien à aligner. Ses vocab.json et config.json sont déjà dans commun/, écrits par l'export.

Le script déduit le dépôt d'origine du code et du sens (es + versFrancais → opus-mt-es-fr), puis vérifie que le source.spm téléchargé est identique, octet pour octet, à celui du pack. Sans cette comparaison, il attacherait le vocabulaire d'un autre modèle — exactement le défaut qu'il répare, en pire. Un pack dont l'empreinte diffère est signalé [ÉCART] et laissé intact : il vient d'une autre famille de modèles et doit être réexporté par le §1.1.

2.3 Nommer le dossier de langue

  • minuscules, chiffres et tiret uniquement (es, pt-br, zh) ;
  • 2 à 10 caractères, ni tiret au début ni à la fin.

Un code hors de ces règles fait ignorer le pack silencieusement par le mobile. Le générateur refuse de l'écrire (§5).

2.4 Vérifier le poids avant de transférer

Un modèle Marian pèse ~300 Mo pour les deux sens. C'est la contrainte la plus lourde de toute la fonctionnalité : elle se mesure ici, pas après le déploiement.

'{0:N0} Mo' -f ((Get-ChildItem .\es -Recurse -File | Measure-Object Length -Sum).Sum / 1MB)

3. Étape 3 — Valider le pack sur le poste (avant tout transfert)

On ne débogue pas un modèle de 120 Mo par déploiements successifs. Trois commandes suffisent, et elles se font toutes sur le poste :

dotnet run --project tools/Omika.LanguesProbe -- inspecter .\es\versFrancais\source.spm
dotnet run --project tools/Omika.LanguesProbe -- encoder .\es\versFrancais\source.spm "Buenos días, ¿cómo está?"
dotnet run --project tools/Omika.LanguesProbe -- traduire `
    --dossier .\es\versFrancais --code es --sens versFrancais --texte "Sustitución de la pieza"

Ce qu'il faut regarder, et l'arbre de décision en cas d'anomalie, sont au §2.5 et au §3.4 de VALIDATION_TRADUCTION_ONNX.md. En résumé : la sortie doit être une phrase française plausible ; une suite de mots espagnols ou du texte répété signifie un mauvais vocabulaire de sortie, pas un problème de déploiement.


4. Étape 4 — Déposer les fichiers sur le serveur

4.1 Destination

Chemin
Racine des modèles C:\Omika\Data\Langues (= Langues__RootPath)
Cible d'un pack C:\Omika\Data\Langues\<code>\
Manifeste C:\Omika\Data\Langues\manifest.json

Ne jamais déposer les modèles dans C:\Omika\Apps\LanguesApi : ce dossier est écrasé à chaque redéploiement. Vérifier la variable avant de copier :

[Environment]::GetEnvironmentVariable('Langues__RootPath', 'Machine')

4.2 Copie (nouveau pack)

$root = 'C:\Omika\Data\Langues'
New-Item -ItemType Directory -Force -Path $root | Out-Null

# Depuis le dossier de livraison (ex. C:\Omika\Deploy\Langues\es)
robocopy 'C:\Omika\Deploy\Langues\es' "$root\es" /E /R:2 /W:2
  • Ne copier que es\ (le dossier de la langue), jamais un manifest.json préparé ailleurs : il est généré à l'étape suivante, sur ce serveur.
  • Les droits NTFS sont posés par héritage (svc_omika_langues a la lecture seule sur la racine, en (OI)(CI)R) : rien à faire pour un fichier ajouté. Contrôle rapide :
    (Get-Acl 'C:\Omika\Data\Langues').Access |
      Where-Object { $_.IdentityReference -like "*svc_omika_langues" } |
      Select-Object IdentityReference, FileSystemRights, InheritanceFlags
    

4.3 Remplacer un modèle existant (mise à jour)

Écrire le nouveau fichier à côté puis le renommer, plutôt que d'écraser directement :

# 1) Déposer la nouvelle version sous un nom temporaire, dans le MÊME dossier
Copy-Item 'C:\Omika\Deploy\Langues\es\versFrancais\encoder_model.onnx' `
          'C:\Omika\Data\Langues\es\versFrancais\encoder_model.onnx.new' -Force

# 2) Renommer sur le nom définitif (instantané, même volume)
Move-Item 'C:\Omika\Data\Langues\es\versFrancais\encoder_model.onnx.new' `
          'C:\Omika\Data\Langues\es\versFrancais\encoder_model.onnx' -Force

Si un téléphone est en train de télécharger ce fichier, l'étape 2 est refusée (« le processus ne peut pas accéder au fichier car il est en cours d'utilisation par un autre processus »). C'est protecteur, pas gênant : Windows empêche de produire un fichier mélangé. Réessayez dans quelques secondes — la fin du téléchargement libère le fichier.

Si le remplacement échoue malgré tout, ne pas supprimer le fichier pour « débloquer » : cela provoquerait un 404 pour les téléphones. Attendre, puis réessayer.


5. Étape 5 — Générer le manifeste (sur le serveur)

cd C:\Omika\Deploy\Deploy
.\New-OmikaLanguesManifeste.ps1

Le script lit Langues__RootPath (MACHINE), parcourt l'arborescence, calcule taille et empreinte de chaque fichier et écrit <RootPath>\manifest.json. Il affiche le récapitulatif des packs retenus :

    es     Espagnol           4 fichier(s)       283,0 Mio
    pt     Portugais          4 fichier(s)       275,0 Mio

Il refuse d'écrire (code 1) si aucun pack n'est exploitable, et signale sans échouer : un dossier de sens inconnu, un code de langue invalide, un fichier *_with_past*, un .spm au nom non reconnu, un sous-dossier inatteignable.

Trois dossiers de sens sont reconnus : versFrancais, depuisFrancais et commun (poids partagés d'un modèle familial, §2.1.1). Un pack familial apparaît donc comme ceci — les 1,15 Go une seule fois, plus un modele.json par sens :

    pt     Portugais          8 fichier(s)      1,15 Gio

Le manifeste précédent est conservé automatiquement en manifest.json.bak — c'est votre point de retour (§10).

5.1 Numéroter la version

-Version (défaut 1.0.0) est écrit dans chaque pack et commandé par l'application : c'est ce champ qui fait apparaître, sur le téléphone, la mention « Installée (v1.0.0) — mise à jour disponible » et le bouton Mettre à jour.

# À CHAQUE remplacement de fichier : incrémenter la version
.\New-OmikaLanguesManifeste.ps1 -Version 1.0.1

Si la version ne change pas, le bouton affiche « Réinstaller » et l'utilisateur n'a aucun signal qu'un nouveau modèle existe : il gardera l'ancien indéfiniment. Le téléchargement, lui, ne récupère de toute façon que les fichiers dont l'empreinte a changé.


6. Étape 6 — Contrôler

6.1 Sur le disque (aucun droit administrateur requis)

.\New-OmikaLanguesManifeste.ps1 -Verifier

Compare le manifeste aux fichiers réels : fichier absent, taille ou empreinte divergente, sens à la mauvaise casse, fichier déclaré inutile, fichier présent mais non déclaré. Code retour 1 au moindre écart — utilisable dans un script de livraison.

6.2 Par HTTP (bout en bout : API + Apache)

$url = 'https://<SRV_LANGUES>'
$manifeste = Invoke-RestMethod "$url/api/langues"
$manifeste.packs | Select-Object code, libelle, version,
    @{ n = 'NbFichiers'; e = { $_.fichiers.Count } }

6.3 Télécharger un fichier et vérifier son empreinte

C'est le contrôle décisif : il prouve que le proxy sert bien les octets attendus, et pas une page d'erreur HTML ni un fichier tronqué.

$fichier = (Invoke-RestMethod "$url/api/langues").packs[0].fichiers[0]
$local = Join-Path $env:TEMP 'omika-controle.bin'
Invoke-WebRequest "$url$($fichier.url)" -OutFile $local

(Get-Item $local).Length -eq $fichier.tailleOctets
(Get-FileHash $local -Algorithm SHA256).Hash.ToLowerInvariant() -eq $fichier.sha256

Les deux lignes doivent renvoyer True. Sinon : problème de proxy (page d'erreur), de troncature, ou de cache.


7. Étape 7 — Côté mobile

Aucune mise à jour de l'application n'est nécessaire.

  1. Paramètres → Langue secondaire → « Actualiser la liste » (le bouton recharge le manifeste depuis le serveur ; sans lui, l'application réutilise son index mémorisé et ne verra pas le nouveau pack).
  2. Appuyer sur Télécharger (ou Mettre à jour) sur la langue concernée : la ligne affiche le poids avant le lancement, puis la progression (« 42 Mo / 122 Mo »).
  3. Le téléchargement installe et sélectionne la langue en une seule action.
  4. Dicter dans la langue secondaire → le champ reçoit du français ; sur une note existante, « ⇄ Traduire » affiche la langue secondaire. Rien de traduit ne doit apparaître dans l'Outbox (page Synchronisation).

8. Pièges (symptôme → cause)

Symptôme Cause la plus probable
GET /api/langues répond 404 Manifeste absent ou illisible — lire le log du service : il nomme le chemin cherché. Le manifeste est-il bien dans Langues__RootPath ?
Le pack apparaît mais le téléphone le refuse à la fin Empreinte différente : fichier modifié après la génération du manifeste → regénérer puis -Verifier
Un fichier répond 404 au téléchargement Le manifeste déclare un fichier absent du disque (les deux ont été désynchronisés)
La langue n'apparaît pas du tout dans la liste Code de langue invalide → pack ignoré silencieusement par le mobile (§2.2)
Traduction = suite de mots de la langue source, ou texte répété Mauvais vocabulaire de sortie : target.spm manquant ou faux (§2.1)
Traduction = <unk>, ▁, caractères isolés source.spm/target.spm ne viennent pas du même modèle que les .onnx
Le téléphone ne propose aucune mise à jour version non incrémentée dans le manifeste (§5.1) : le bouton reste « Réinstaller »
120 Mo téléchargés pour rien decoder_with_past_model.onnx livré dans le dossier (§1)
Les modèles disparaissent à chaque déploiement Ils sont dans C:\Omika\Apps\LanguesApi au lieu de Langues__RootPath (§4.1)
Le script d'export ne fait rien (« rien ne se passe ») optimum-cli absent : il s'arrête avant toute écriture (§1.1). Lancé par double-clic, la fenêtre se ferme avant qu'on lise le message — lancer depuis une fenêtre PowerShell déjà ouverte
optimum-cli introuvable alors que pip install a réussi Python du Microsoft Store : pip écrit les programmes dans un dossier absent du PATH (§1). Le script d'export sait l'y trouver seul ; pour un usage manuel, ajouter ce dossier au PATH
pip s'arrête sur [WinError 206] Nom de fichier ou extension trop long Windows plafonne les chemins à 260 caractères ; le Python du Store installe dans un dossier de 140 caractères et torch dépasse la limite. Installer dans un venv au chemin court (§1) — le paquet n'est alors jamais installé, donc optimum-cli reste introuvable
Toutes les paires manquantes sont en HTTP 401 et aucune en 404 Un dépôt absent répond 401 avec {"error":"Repository not found"}, jamais 404 — et la page publique s'est mise à répondre 401 elle aussi, alors qu'un dépôt existant répond 200 au même instant. Plus aucun code d'erreur ne tranche. Le script ne demande donc plus « ce dépôt existe-t-il ? » : il lit une fois la liste publique des dépôts Helsinki-NLP (/api/models?author=Helsinki-NLP&limit=1000, toujours 200, paginée par l'entête Link) et l'existence devient une égalité de chaîne. « Absent » est alors une preuve (nom hors liste), citée avec la taille du catalogue consulté. La sonde par modèle et le verdict de page restent en repli si le catalogue est illisible
Le rapport annonce un poids total très inférieur au dossier Corrigé : le rapport affiche désormais deux lignes — le poids des packs traités par l'exécution, et le poids réellement présent sous la racine. Auparavant -Codes it affichait 520 Mio alors que le dossier en contenait 21 Gio
Une langue n'a qu'un seul sens (versFrancais trouvé, depuisFrancais absent) Helsinki-NLP ne publie pas toujours les deux directions. Le script ignore la langue ; -InclureDirectionsPartielles la construit quand même. Conséquence : la dictée fonctionne, la lecture échoue avec un message explicite (OnnxTranslationService, « Le modèle … est absent »). Rien ne bloque l'installation : LangueDisponible.EstComplet existe mais n'est utilisé nulle part
Tous les exports échouent d'un coup en quelques secondes, Poids de cette execution : 0 octets, et le détail affiché est un simple UserWarning: … cache-system uses symlinks L'appel a été lancé avec le flux d'erreur redirigé sur tout le script (2>&1 | Tee-Object, *>&1 > log) : PowerShell 5.1 transforme chaque ligne de stderr d'un programme natif en erreur fatale ($ErrorActionPreference = 'Stop'). optimum-cli émet cet avertissement de huggingface_hub dès le début : l'export était tué juste après config.json, avant le moindre octet de poids. Corrigé : l'export n'est plus jugé que par son code de sortie, et stderr est neutralisé le temps de l'appel (test §9). Les rapports déjà écrits par une version antérieure sont à jeter
Le script affiche catalogue : 2 depots publics puis déclare tout absent La liste des dépôts est revenue tronquée (limite ignorée, page unique). Mesure du 16/10 : 2 entrées → 66 paires « absentes », dont 11 dont le modèle est sur le disque depuis des semaines — le script se contredisait lui-même. Corrigé : en dessous de 100 dépôts le catalogue est rejeté (catalogue inutilisable) et le verdict repasse par modèle. Vérifier la réponse brute avec (Invoke-WebRequest 'https://huggingface.co/api/models?author=Helsinki-NLP&limit=1000' -UseBasicParsing).Content.Length
Le rapport annonce des dizaines de « paires absentes » d'un coup (HTTP 401 … page impossible, HTTP 429) Deux causes réelles, mesurées : (1) un 429 de quota — ce n'est pas une absence ; (2) un 401 qui ne discrimine rien. Corrigé : une seule lecture du catalogue remplace 102 interrogations (donc plus de quota), et un verdict indécis interrompt le run au lieu d'écrire un rapport faux qui ferait supprimer 22 langues du produit
Une langue à moitié faite re-télécharge le sens qui existait déjà (500 Mo) Le test de reprise portait sur la langue entière : avec -InclureDirectionsPartielles, le sens déjà présent était réexporté. Corrigé : reprise par sens (encoder+.spm présents ⇒ ignoré, test §4bis)
optimum-cli écrit des avertissements alors que l'export se déroule bien Bénin (huggingface_hub signale un cache sans liens symboliques sous D:\…\packs\.hf-cache). Le cache fonctionne en mode dégradé — donc plus gros, jamais faux. HF_HUB_DISABLE_SYMLINKS_WARNING=1 pour le taire

9. Retirer une langue / rollback

Retirer une langue du catalogue (les téléphones qui l'ont déjà installée la gardent : elle est locale, et le retrait du serveur ne les touche pas) :

Remove-Item 'C:\Omika\Data\Langues\pt' -Recurse -Force
.\New-OmikaLanguesManifeste.ps1

Revenir au manifeste précédent : le générateur conserve manifest.json.bak à chaque écriture.

Copy-Item 'C:\Omika\Data\Langues\manifest.json.bak' `
          'C:\Omika\Data\Langues\manifest.json' -Force
.\New-OmikaLanguesManifeste.ps1 -Verifier

Remettre l'ancien manifeste alors que les fichiers ont déjà été remplacés provoquera un refus d'empreinte : un rollback complet se fait sur les deux (fichiers et manifeste).


10. Hors périmètre

  • Qualité de traduction : choix du modèle, pas de la procédure.
  • Poids du livrable mobile : décision produit (80 à 300 Mo par langue).
  • Reprise de téléchargement inter-fichiers : un fichier interrompu repart de zéro ; les fichiers déjà conformes ne sont pas retéléchargés.
  • Transfert des 300 Mo vers le serveur : aucun mécanisme d'upload n'existe. Le transfert se fait par partage réseau, clé USB ou dossier de livraison (C:\Omika\Deploy\Langues), hors de cette procédure.