683 lines
35 KiB
Markdown
683 lines
35 KiB
Markdown
# 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.
|