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

683 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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-<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) :
```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 `<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 :
```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/<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).
```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\<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 :
>
> ```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 `<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**.
```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://<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é.
```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 = `<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) :
```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.