Premiere commit - Omika

This commit is contained in:
2026-10-23 18:46:50 +02:00
commit 8ea76c1b24
2155 changed files with 984301 additions and 0 deletions
+682
View File
@@ -0,0 +1,682 @@
# 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.