# 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 : ```powershell 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 : > > ```powershell > 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 : > > ```powershell > $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 : > > ```powershell > 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.* > > ```powershell > 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. ```powershell # 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--fr` (petit, ~300 Mo par sens) puis **`opus-mt-tc-big--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 `\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) | `\\\` (et `\commun\` pour un modèle familial) | conservé — c'est ce qu'on transfère | | **Cache HuggingFace** | `\.hf-cache\hub\` | supprimé après chaque export réussi | | **Travail temporaire** | `\.tmp--\` | supprimé après chaque sens | `` 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) : ```powershell .\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 `\.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) | `` / `` 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 : ```json { "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//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). ```powershell # 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. ```powershell '{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 : ```powershell 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\\` | | 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 : > > ```powershell > [Environment]::GetEnvironmentVariable('Langues__RootPath', 'Machine') > ``` ### 4.2 Copie (nouveau pack) ```powershell $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 : ```powershell (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 : ```powershell # 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) ```powershell 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 `\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**. ```powershell # À 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) ```powershell .\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) ```powershell $url = 'https://' $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é. ```powershell $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 = ``, `▁`, 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) : ```powershell 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. ```powershell 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.