Back to "Bâtiment principalementlucide-nmt: A Production-Ready (compatible EasyNMT) Service de traduction"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

API CUDA Docker EasyNMT FastAPI Helsinki-NLP mostlylucid-nmt Neural Machine Translation Python PyTorch Transformers

Bâtiment principalementlucide-nmt: A Production-Ready (compatible EasyNMT) Service de traduction

Saturday, 08 November 2025

Présentation

Une implémentation FastAPI Copier EXACTEMENT l'API d'EasyNMT (https://github.com/UKPLab/EasyNMT) un excellent projet de traduction neurale-machine abandonné.

Mais j'ai ajouté SO MANY belles fonctionnalités pour augmenter la fiabilité et le rendre prêt pour une utilisation dans un système de production.Pensez traduction rapide soi-même hébergé...).

Depuis le début de ce blog, une grande passion a été la traduction automatique des articles de blog.

OUI Je sais que 'google fait ça' dans les navigateurs etc...etc... mais ce n'est pas le but.mostlylucid-nmtJe voulais savoir comment le faire !

De plus, c'est agréable d'accueillir des gens qui ne lisent pas l'anglais (même s'ils lisent l'anglais comme langue seconde, c'est FAR plus difficile à analyser).J'ai donc travaillé sur la façon de le faire, ainsi que sur le partage de la façon de construire ce genre de système.Oh et il m'a donné des idées sur la façon de l'utiliser dans ASP.NET pour la localisation automatique du texte (y compris le texte dynamique) en utilisant SignalR & un système de mise à jour en temps réel slick. (

Restez à l'écoute Oh et j'ai fait une démo disponible ici; https://nmtdemo.mostlylucid.net/demo/ ne fonctionne que dans un ancien ordinateur portable sans GPU, mais vous donne l'idée (et me laisse tester la lengevity).Essentiellement, les humains écrivent du texte de merde qui est SUPER bruyant pour les machines à manipuler efficacement.

Donc beaucoup de choses étaient en train de trouver comment travailler sur des questions avec EasyNMT (ce fut vraiment un projet de recherche).

Tout de suite.

est conçu pour être une bataille testée (bien traduire les dizaines de milliers de mots sur ici!) système utile pour toute traduction. Une sorte d'API BabelFish. Il a également tous les enseignements que j'ai de trois décennies de construction de serveurs et de systèmes de production. Rangant à partir de 429 codes pour dire au client de reculer, retour de métadonnées sur les traductions pour aider les clients, des paramètres supplémentaires pour obtenir plus de données et DE COURSE une page de démonstration

ce qui me permet à la fois tout en développant ainsi que vous un moyen d'avoir une pièce de théâtre.

Annexe Ia écrit tout un systèmehttp://<server>:<port>/demopour que cela se produise avec un projet incroyable appelé EasyNMT.

Demo

[TOC]

Chaque fois, si tu as vérifié la repo, tu sais qu'il y a un problème... ça n'a pas été touché pendant des années.

C'est un moyen simple et rapide d'obtenir une API de traduction sans avoir besoin de payer pour un service ou d'exécuter un LLM complet pour obtenir la traduction (slowly).

Dans nos précédents articles, nous avons discuté de la façon d'intégrer EasyNMT aux applications ASP.NET pour la traduction de fond.

**Mais avec le temps, les fissures ont commencé à se manifester.**Il était temps pour quelque chose de mieux.

**Comme d'habitude, tout est sur GitHub et tout est gratuit pour l'utilisation etc...**Docker tire

  • cpu: Reusing loaded model for en->de (3/10 models in cache)
  • cpu-min: Need to load model for en->fr (3/10 models in cache)
  • gpugpu-min
  • **Démo... voir plus tard pour la page de démonstration !**Une page de démonstration interactive complète (principalement)
  • **(par rapport à l'année précédente)**ou juste la racine)
  • **Quoi de neuf ?**Avant de plonger dans le démarrage rapide, voici ce qui fait cette version de changement de jeu:

Principales mises à jour (v3.1) - Intelligence et visibilitéNouveau dans v3.1:

  • **La dernière version apporte des améliorations massives à la fiabilité, la visibilité des performances et la sélection de modèles intelligents !**1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.
  • Modèle intelligent Caching avec visibilité- Voir exactement ce qui se passe :
  • Cache HIT logageCache MISS journalisation
  • Suivi de l'état de la cache: affiche le pourcentage d'utilisation et les modèles chargés
  • Avertissements d'éviction: Effacer les alertes lorsque le cache est plein et que les modèles sont expulsés
  • Capacité accrue:
    ====================================================================================================
      🚀 DOWNLOADING MODEL
      Model: facebook/mbart-large-50-many-to-many-mmt
      Family: mbart50
      Direction: en → bn
      Device: GPU (cuda:0)
      Total Size: 2.46 GB
      Files: 6 main files
    ====================================================================================================
    [Progress bars for each file...]
    ====================================================================================================
      ✅ MODEL READY
      Model: facebook/mbart-large-50-many-to-many-mmt
      Translation: en → bn is now available
    ====================================================================================================
    

**: La taille du cache par défaut est passée à 10 modèles (à partir de 6)**Enregistrement de périphérique par modèle

  • : Voir exactement quel GPU/CPU chaque modèle utilise2. Le Président. — L'ordre du jour appelle le rapport (doc.
  • Amélioration des progrès de téléchargement- Plus besoin de se demander si c'est coincé.
  • Taille avant téléchargement:
    [Pivot] Languages reachable from en: 85 languages
    [Pivot] Languages that can reach bn: 42 languages
    [Pivot] Found 38 possible pivot languages
    [Pivot] Selected pivot: en → hi → bn (both legs verified)
    
  • **: affiche la taille totale du téléchargement (par exemple, "Taille totale: 2,46 GB")**Nombre de fichiers
  • : Affiche le nombre de fichiers à téléchargerAffichage du périphérique

: Affiche le périphérique cible (GPU/CPU) dans la bannièreBarres de progression

  • **: Belle progression tqdm pour chaque fichier (quand sur TTY)**Bannière d'achèvement
  • : Message de succès clair lorsque le modèle est prêtExemple de sortie
  • 3. Les droits de l'homme sont garantis par le Pacte international relatif aux droits économiques, sociaux et culturels.:
    Request: en→bn with opus-mt
    Trying families: ['opus-mt', 'mbart50', 'm2m100']  ✓ All three!
    opus-mt: Failed (model doesn't exist)
    mbart50: Success! (auto-fallback worked)
    

Sélection de pivots intelligents à transmission de données- Plus de tentatives aveugles :

  • Logique intelligente d'intersectionLoading mbart50 model on GPU (cuda:0)
  • : trouve les langues où il existe les deux jambes pivotantesModel loaded on device: cuda:0
  • Évite les tentatives infructueusesSuccessfully loaded... on GPU (cuda:0)

: N'essayera pas fr→es→bn si es→bn n'existe pasExemple pour en→bn

  • Priorité de replien->hi, hi->bn
  • : Anglais → Espagnol → Français → Allemand → Chinois → Russe
  • Enregistrement transparent[Pivot] Both legs loaded and cached. Ready to translate.

: Voir exactement pourquoi chaque pivot a été choisi ou sauté4. Le Président. — L'ordre du jour appelle le rapport (doc.

  • Fixé Fallback automatique
    • Plus de double-trys :model_familyToujours essayer les replis
  • : Même si la famille préférée "devrait" soutenir la paire

Tentative unique par famille

: Plus de réessayer le même modèle deux foisExemple de débit

  • Clarté du GPU
    • Savoir toujours où sont vos modèles :
  • Chaque charge de modèle indique:
  • Après le chargement confirme:

**Le message de réussite comprend :**6.

  • Pivot Modèle de cache- Réutilisation efficace du pivot :
  • **Les deux pattes du pivot sont mises en cache séparément:**La prochaine fois en→hi ou hi→bn nécessaire, cache instantané frappé!
  • **Effacer l'enregistrement & #160;:**7.
  • Sélection du modèle de demande

**- Ça marche déjà en démo :**La démo déroulante permet de sélectionner opus-mt, mbart50 ou m2m100

  • Respects de l'arrière-plan
  • paramètre par requête
  • Modèles mis en cache séparément par famille pour la commutation instantanée
  • Principales mises à jour (3.0)
    1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.requirements-prod.txtPage de démonstration améliorée

**- Interface interactive prête à la production :**Full viewport layout (100vw/100vh) pour une expérience de traduction immersive

  • **Sélection des menus déroulants (plus d'entrée/liste de données maladroite)**Commutation de la famille de modèles vivants (Opus-MT, mBART50, M2M100)
  • Chargement en langage dynamique basé sur le modèle sélectionnéZones de sortie défilantes pour les grandes traductions
  • **2. Le Président. — L'ordre du jour appelle le rapport (doc.**Défauts optimisés en matière de performance
    • "Fast as possible" hors de la boîte:
  • GPU

**: FP16 activé, BATCH_SIZE=64, MAX_INFLIGHT=1 (optimal pour un seul GPU)**CPU

  • : WEB_CONCURRENCE=4, MAX_INFLIGHT=4, BATCH_SIZE=16 (utiliser tous les cœurs)
  • Arrêt rapide
  • : 5 secondes de temps libre gracieuse (pas plus de 20 secondes)
  • Les conteneurs s'arrêtent sans messages SIGKILL effrayants
    1. Les droits de l'homme sont garantis par le Pacte international relatif aux droits économiques, sociaux et culturels.
  • Optimisation de la construction de production

**- Images plus petites et plus rapides:**Dépendances d'essai supprimées (pytest, pytest-cov) des constructions de production

  • Enregistre ~200 Mo par imageImages du processeur: ~8-10 Go (plein), ~3-4 Go (min)
  • **Images GPU: ~12-15 Go (plein), ~6-8 Go (min)**Toutes les utilisations
  • pour une empreinte minimale4. Le Président. — L'ordre du jour appelle le rapport (doc.

Essais complets et essais de charge- Valider tout :

  • Suite de test d'API en direct
  • (30+ tests) pour la santé, la traduction, la détection, la découverte
  • Essai de charge de k6

avec des schémas de trafic réalistesScénarios de validation transplateforme

  • /discover/opus-mt(PowerShell + Bash)
  • /discover/mbart50Tests pour les téléchargements de modèles et le repli de la traduction de pivot
  • /discover/m2m100Essais automatisés de fumée pour une validation rapide

**5.**Documentation sur le déploiement

    • Prête à la production dès le premier jour :
  • 4 scénarios de calibrage : débit maximal, faible latence, haute écueil, manque de mémoire
  • Docker Composez des exemples avec des configurations GPU/CPU

Kubernetes manifeste avec PVC, limites de ressources, contrôles de santéExemples d'instances de conteneurs Azure

  • scottgal/mostlylucid-nmt:cpuDirectives pour les essais de charge et recommandations en matière de surveillance:latestCompensation de la contre-valeur et du débit expliquée
  • scottgal/mostlylucid-nmt:cpu-min6.
  • scottgal/mostlylucid-nmt:gpuTrois familles modèles
  • scottgal/mostlylucid-nmt:gpu-min- Choisissez le meilleur pour vos besoins :

Opus-MT: 1200+ paires, meilleure qualité (modèles séparés)

  • mBART50latest, min, gpu, gpu-min: 50 langues, modèle simple de 2,4 Go, 2,450 paires
  • M2M10020250108.143022: 100 langues, modèle unique de 2,2 Go, 9 900 paires

Auto-Fallback- Sélectionne intelligemment le meilleur modèle disponible:

  • **Définir la famille primaire (p. ex. Opus-MT pour la qualité)**Essaie automatiquement mBART50/M2M100 si la paire n'est pas disponible
  • Couverture maximale sans sacrifier la qualité8.
  • Découverte du modèle- Requête dynamique des modèles disponibles :
    • Toutes les paires de 1200+ de Hugging Face

- Toutes les paires de mBART50- Toutes les paires M2M100

  • Images minimales

- Déploiements plus petits et flexibles:

Pas de modèles préchargés (téléchargement à la demande)

Cache persistante en format volumétrique

Switch familles de modèles sans reconstruction**10. Le Conseil de l'Europe s'est engagé à promouvoir l'égalité des chances entre les femmes et les hommes dans le domaine de l'éducation et de la formation tout au long de la vie, en particulier dans le domaine de l'éducation et de la formation tout au long de la vie.**Dépôt unique Docker

  • Toutes les variantes en un seul endroit: |-----|-----------------|------|-------------|----------| | cpu(oulatest) | scottgal/mostlylucid-nmt:cpu) - CPU | cpu-min | scottgal/mostlylucid-nmt:cpu-min- Minimum du processeur | gpu | scottgal/mostlylucid-nmt:gpu- GPU avec CUDA 12,6 | gpu-min | scottgal/mostlylucid-nmt:gpu-min- Minimum de GPU

**11. Le Conseil de l'Europe s'est prononcé en faveur de l'élargissement de l'Union européenne et de l'élargissement de l'Union européenne à l'Union européenne.**Version appropriée

    • Toutes les images incluent la version date :
  • Balises nommées (
  • ) toujours pointer vers la construction la plus récente
  • Balises de version immuables (p. ex.,

) pour épingler des constructions spécifiques

Étiquettes complètes de l'OCI pour le suivi des versions, les dates de construction et les commits git

docker run -d \
  --name mostlylucid-nmt \
  -p 8000:8000 \
  scottgal/mostlylucid-nmt

12. Le Conseil de sécurité des Nations Unies a adopté le projet de résolution adopté par le Conseil de sécurité à sa dix-neuvième session.

curl -X POST "http://localhost:8000/translate" \
  -H "Content-Type: application/json" \
  -d '{
    "text": ["Hello, how are you?"],
    "target_lang": "de"
  }'

Dernières images de base

{
  "translated": ["Hallo, wie geht es Ihnen?"],
  "target_lang": "de",
  "source_lang": "en",
  "translation_time": 0.34
}

- Amélioration de la sécurité et des performances :

Python 3.12-slim

docker run -d \
  --name mostlylucid-nmt \
  --gpus all \
  -p 8000:8000 \
  -e EASYNMT_MODEL_ARGS='{"torch_dtype":"fp16"}' \
  scottgal/mostlylucid-nmt:gpu

pour les images CPU (adresses des vulnérabilités Python 3.11)

CUDA 12,6

avec Ubuntu 24.04 pour les images GPU (dernière pile NVIDIA)

docker run -d \
  --name mostlylucid-nmt \
  -p 8000:8000 \
  -v $HOME/model-cache:/models \
  -e MODEL_CACHE_DIR=/models \
  scottgal/mostlylucid-nmt:cpu-min

PyTorch avec CUDA 12,4

docker run -d `
  --name mostlylucid-nmt `
  -p 8000:8000 `
  -v ${HOME}/model-cache:/models `
  -e MODEL_CACHE_DIR=/models `
  scottgal/mostlylucid-nmt:cpu-min

(compatible avec CUDA 12,6 temps d'exécution)

docker run -d ^
  --name mostlylucid-nmt ^
  -p 8000:8000 ^
  -v %USERPROFILE%/model-cache:/models ^
  -e MODEL_CACHE_DIR=/models ^
  scottgal/mostlylucid-nmt:cpu-min

Toutes les dépendances sont mises à jour pour les dernières versions sécurisées

13.

curl http://localhost:8000/healthz

Avertissements de déprécation fixes

- À l'épreuve de l'avenir :

Supprimé TRANSFORMERS_CACHE déprécié (maintenant en utilisant HF_HOME)Compatible avec Transformers v5Démarrage rapide (5 minutes)

http://localhost:8000/demo/

Demo

### Tu veux juste te faire traduire ?

Voici la façon la plus simple de courir principalementlucide-nmt:

Images Docker disponibles

  • Toutes les variantes sont disponibles à partir de
  • un dépôt
  • avec différentes étiquettes:

Marque Nom de l'image complète Taille Description du boîtier d'utilisation

  • (ou
  • CPU avec code source de production Déploiements de CPU de production de CPU
  • CPU minimal, pas de modèles préchargés
  • ~5GB=GPU avec CUDA 12,6 + source=Déploiements de GPU de production=
  • GPU minimal, pas de modèles préchargés GPU avec cache en volume

Images minimales

  • sont recommandés pour:
  • Déploiements de production avec cache en volume
  • Utilisation de mBART50 ou M2M100 (modèles simples de grande taille)

Garder la taille du récipient petit

  • Flexibilité pour changer de famille de modèles sans reconstructionDémarrage le plus simple (CPU Opus-MT)
      1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.
    • Tirer et courir:
  • **2. Le Président. — L'ordre du jour appelle le rapport (doc.**Traduire un texte :
    • Réponse :
    • GPU accéléré (10x plus rapide)

Nécessite NVIDIA Docker runtime:

  • Avec Cache modèle persistantTéléchargez les modèles une fois et gardez-les à travers les redémarrages de conteneurs:
  • **Linux/Mac :**Fenêtres (PowerShell) :
  • **Windows (CMD) :**Les modèles sont téléchargés automatiquement lors de la première utilisation et persistent dans votre répertoire local !

Contrôle de santé:

  • C'est le départ rapide de 5 minutes !
    • **Pour le déploiement de la production, la configuration et les fonctionnalités avancées, continuez à lire.**Page de démonstration interactive
    • Le service comprend une fonction complètepage de démonstration interactive
    • **qui rend facile de tester les traductions sans écrire de code.**Accédez à ce site à l'adresse suivante :
  • Caractéristiques de la démo

La page de démonstration fournit un environnement de test de traduction complet avec:

  1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.
// Example: Translating a 5000-word article
Input: Long article with multiple paragraphs

Step 1: Split by paragraphs (preserves structure)
  → Paragraph 1 (800 chars)
  → Paragraph 2 (1200 chars)
  → Paragraph 3 (600 chars)
  ...

Step 2: Group into ~1000 character chunks
  → Chunk 1: Paragraphs 1-2
  → Chunk 2: Paragraph 3-4
  → Chunk 3: Paragraphs 5-6

Step 3: Translate each chunk sequentially
  → Shows progress: "Translating chunk 1/3..."
  → Shows progress: "Translating chunk 2/3..."
  → Shows progress: "Translating chunk 3/3..."

Step 4: Reassemble with paragraph breaks
  → Final output: Complete translated article with preserved formatting

Sélection de la langue

Baisses de langues auto-populées du service en direct

  • Échanger les langues source/cible en un seul clic
  • Prise en charge des plus de 100 langues configurées dans le service
    1. Le Président. — L'ordre du jour appelle le rapport (doc.
  • Numérisation de texte intelligent

Gestion automatique des grandes entrées de texte de n'importe quelle taille

  • Séparer intelligemment par paragraphes, en préservant la structure du document
  • Retour à la phrase fractionnement pour de très longs paragraphes
  • Affiche les progrès pour les traductions multi-pouces ("Traduire le morceau 2/5...")
  • Réassemble sans couture des morceaux avec un espacement approprié

3. Les droits de l'homme sont garantis par le Pacte international relatif aux droits économiques, sociaux et culturels.

  • Détection de langue
  • Détecter la langue source en un seul clic
  • Déploie automatiquement la liste déroulante du langage source
  • Fonctionne avec un texte jusqu'à 5000 caractères

4. Le Président. — L'ordre du jour appelle le rapport (doc.

  1. Options avancées:

    • Taille du faisceau
    • : Qualité de la traduction de contrôle (1-10)
    • Valeurs plus élevées = meilleure qualité mais plus lente
    • Valeurs inférieures = débit plus rapide
  2. Séparation de la peine:

    • : Couper automatiquement les phrases
    • Active (par défaut) : divise les textes longs en phrases pour une meilleure qualité
    • Handicapés: Traduit le texte entier comme un bloc (plus rapide pour les textes courts)
  3. Statistiques en temps réel:

    • Heure de traduction
    • : Affiche la durée réelle de la traduction côté serveur
    • Nombre de caractères
    • : La vie compte comme vous tapez

Indicateur de situation

: Idle → Traduire → Fait/Erreur

  • **6.**Modèle de découverte familiale
  • **Explorez les paires de traductions disponibles pour chaque famille de modèles :**Opus-MT
  • : 1200+ paires de languesmBART50
  • : 50 langues, 2 450 pairesM2M100

: 100 langues, 9 900 paires/demo/Voir exactement les paires de langues disponibles avant de traduire

Comment fonctionne le découpage de texte

La démo implémente le chunking intelligent de texte du côté client :Pourquoi utiliser la démo ?Essai rapideTester les traductions sans code d'écritureValider la disponibilité de la paire de langues

Comparer la qualité de la traduction avec différentes tailles de faisceau

  1. **Cas de bord d'essai (emoji, symboles, caractères spéciaux)**Aide au développement
  2. Voir le format exact de requête/réponse de l'APIVérifier la santé des services avant d'intégrer
  3. Performance d'essai avec différentes tailles de texteDécouvrez les familles de modèles disponibles
  4. Référence clientAffiche les modèles d'utilisation de l'API appropriés
  5. **Démontre le traitement des erreurs (429, détection de langue)**Exemple de mise en œuvre de chunking
  6. Logique de réessayer du monde réelExemple d'utilisationTraduction simple.
  7. **Coller le texte : "Bonjour, comment allez-vous aujourd'hui ?"**Sélectionnez la cible : Allemand
  8. **Cliquez sur "Traduit par la Rédaction"**Résultat: "Hallo, wie geht es Ihnen heute?"

Traduction des documents longs

Coller l'article entier du blog (5000+ mots)Demo le découpe automatiquement en morceaux gérablesAffiche les progrès au fur et à mesure que chaque morceau se traduit

Retourne le document entièrement traduit

Détection de langueColler le texte en langue inconnueCliquez sur "Detect language"

Démo identifie la langue et les mises à jour déroulantes

  • Prêt à traduire immédiatementDétails techniques
  • **La page de démonstration est :**Indépendants
  • : Un seul fichier HTML avec JavaScript intégréDépendances nulles
  • : Aucune bibliothèque externe n'est requiseMobile friendly
  • : La conception réactive fonctionne sur tous les appareilsLa production est prête
  • : La même logique de chunking peut être utilisée dans vos applicationsAccédez à la démo live à

sur votre instance en cours d'exécution!

  • Les problèmes avec EasyNMTMaintenant, ce n'est pas du dumping sur
  • FacileNMTIl n'a rien pu faire d'autre et j'ai construit
  • un LOT de projets l'utilisant
  • **C'est de plus en plus long dans la dent, alors... Quels problèmes avons-nous ?**Oh mon, il y en a beaucoup.
  • **EasyNMT a été construit il y a près de dix ans.**La technologie a évolué... et elle n'a jamais été conçue pour être un système de production.
  • **Voici quelques-uns des enjeux :**Il s'écrase... un peu.

Il n'est pas conçu pour se remettre des problèmes si souvent juste tombe sur.

  • **C'est SUPER PICKY à propos de son entrée.**Les émoticônes, les symboles, même les nombres peuvent le confondre.
  • **Il n'est conçu pour aucune charge.**Voir ci-dessus.
  • **Il n'a jamais été conçu pour l'être.**Il n'est pas conçu pour mettre à jour ses modèles
  • **ou être (facilement) construit avec des modèles intégrés.**Son truc GPU CUDA est ancien
  • **tellement plus lent qu'il n'en a besoin.**Tu ne peux rien arranger.
  • Le code Python est sur la repo mais à nouveaupas grand

**Pas de contrepression ou de file d'attente.**Envoie trop de demandes et ça se calme.MODEL_FAMILYPas d'observabilité.

# Opus-MT (default, best quality)
MODEL_FAMILY=opus-mt

# mBART50 (50 languages, single model)
MODEL_FAMILY=mbart50

# M2M100 (100 languages, broadest coverage)
MODEL_FAMILY=m2m100

Quand les choses tournent mal, tu voles aveugle.

La solution : surtout Lucid-NMTDonc... j'ai décidé de construire un EasyNMT nouveau et amélioré, maintenantprincipalement lucide-nmt


  1. Ce n'est pas seulement un travail de patch ; c'est une réécriture complète avec l'utilisation de la production à l'esprit.MODEL_FAMILYVoici ce qui le rend meilleur :opus-mtSoutien familial multi-modèles
  2. La plupart du temps Lucid-NMT soutient maintenant
  3. trois familles de modèles de traduction
  4. , vous donnant de la flexibilité en fonction de vos besoins:

Opus-MT (Helsinki-NLP) - Par défaut

# Set primary to Opus-MT (best quality)
MODEL_FAMILY=opus-mt
AUTO_MODEL_FALLBACK=1
MODEL_FALLBACK_ORDER=opus-mt,mbart50,m2m100

# Request Ukrainian → French
# 1. Try Opus-MT first (not available)
# 2. Automatically fall back to mBART50 (available!)
# 3. Translation succeeds with mBART50

Couverture

  • 1200+ paires de traductions pour 150+ languesArchitecture :
  • Modèle séparé par direction de traductionQualité:
  • Meilleure qualité générale de la traductionCas d'utilisation :
  • Traductions de production où la qualité est importanteTaille du modèle :

300-500 Mo par direction

# Enable auto-fallback (default: enabled)
AUTO_MODEL_FALLBACK=1

# Set fallback priority (default: opus-mt → mbart50 → m2m100)
MODEL_FALLBACK_ORDER="opus-mt,mbart50,m2m100"

# Disable for strict single-family mode
AUTO_MODEL_FALLBACK=0

Exemple :

Anglais→L'allemand est un modèle différent de l'allemand→L'anglais

  1. **mBART50 (Facebook)**Couverture
  2. 50 langues, traduction intégraleArchitecture :
  3. Modèle multilingue uniqueQualité:
  4. Bonne qualité, en particulier pour les langues principalesCas d'utilisation :
  5. Déploiements encombrés dans l'espace ou de nombreuses paires de languesTaille du modèle :
  6. **~2.4 Go (modèle unique pour les 50 langues)**Avantage:
  7. Un modèle gère 2 450 paires de traductionsM2M100 (Facebook)
  8. Couverture100 langues, traduction intégrale
  9. **Architecture :**Modèle multilingue unique
  10. **Qualité:**Bonne qualité avec une couverture linguistique plus large
  11. **Cas d'utilisation :**Couverture linguistique maximale-minTaille du modèle :

~2.2 Go (modèle unique pour les 100 langues)

Avantage:

Un modèle gère 9 900 paires de traductions

Le changement est facile

    • juste mettre le
  • variable d'environnement:
  • Famille de modèles automatique Fallback - NOUVEAU!

L'une des nouvelles fonctionnalités les plus puissantes est

  • repli automatique entre les familles de modèles
  • Cela garantit une couverture linguistique maximale tout en accordant la priorité à la qualité de la traduction.

**Comment ça marche :**Tu as établi un primaire.

  • **(par exemple,**pour la meilleure qualité)
  • Lorsque vous demandez une paire de traduction non disponible dans la famille primaireLe système essaie automatiquement la prochaine famille dans l'ordre de repli
  • Cela se poursuit jusqu'à ce qu'un modèle approprié soit trouvéExemple de scénario :

Avantages:

Couverture maximale:

Prise en charge de plus de 100 langues sans gestion de déploiements multiples

  • Priorité en matière de qualité:
  • Toujours utiliser le meilleur modèle disponible pour chaque paire
  • Configuration zéro & #160;:
  • Fonctionne automatiquement, pas d'intervention manuelle nécessaire

Enregistrement transparent :

  • Voir quelle famille de modèles a été utilisée pour chaque traduction
  • Configuration & #160;:
  • Cette fonctionnalité est parfaite pour les environnements de production où vous voulez une couverture maximale sans sacrifier la qualité!
  • Principales améliorations

Soutien familial multi-modèles

# NMT: Fits on a USB stick
du -sh model-cache/
2.5G    model-cache/

# LLM: Needs serious storage
du -sh llama-models/
140G    llama-models/

- Choisissez parmi Opus-MT (1200+ paires), mBART50 (50 langues), ou M2M100 (100 langues).

Paramètres de découverte du modèle

    • Requête dynamique des modèles disponibles à partir de Hugging Face pour chaque famille.
  • Manipulation d'entrées robustes
    • Emoji ?
  • Des chiffres ?

Des symboles ?

  • Amène-le.
  • Le service comprend désormais une désinfection complète des entrées et un masque de symboles.
  • Demander une file d'attente et une contre-pression
    • Une file d'attente sémaphore intégrée avec des estimations intelligentes.

Modèle LRU en cache

  • Gère automatiquement VRAM en expulsant les anciens modèles lorsque le cache est plein. |--------|------|------| Soutien CUDA moderne
  • Utilise PyTorch avec CUDA 12.6, prend en charge FP16/BF16 pour des améliorations de vitesse 2x. Observabilité prête à la production
  • Contrôles de santé, sondes de préparation, état de cache, enregistrement structuré. Fermeture gracieuse

- Plus de demandes orphelines ou d'état corrompu.

API compatible EasyNMT

    • Remplacement à l'abandon pour les intégrations existantes.
  • Retour en arrière de la traduction des pivots
    • Si une paire de langues directes n'est pas disponible, il passe automatiquement par l'anglais (ou le pivot choisi).
  • Minimale des images Docker
    • Nouveau

variantes avec cache en volume pour des déploiements plus petits.

  • ⚠️ Sometimes adds interpretations not in original
  • ⚠️ Quality varies with prompt phrasing
  • ⚠️ Can be "creative" with technical terms
  • ⚠️ Needs careful prompt engineering
  • ⚠️ Unpredictable with edge cases

Pourquoi NMT Over LLMs pour la traduction?

Input: "The API returns a 429 status code when rate limited."

NMT (Opus-MT): "Die API gibt einen 429-Statuscode zurück, wenn sie ratenbegrenzt ist."
(Accurate, preserves technical terms)

LLM (might do): "Die API sendet den Fehlercode 429, wenn zu viele Anfragen gestellt werden."
(Interprets rather than translates, adds context not in original)

Vous vous demandez peut-être : « Pourquoi utiliser un service NMT dédié quand les LLM comme GPT-4, Claude ou Llama peuvent traduire ? » Grande question.

Voici la vérification de la réalité basée sur l'utilisation de la production:

  • Vitesse: 10-100x Plus rapide
  • NMT (principalement lylucide-nmt):
  • CPU: ~0.3-1.0 secondes par phrase
  • GPU (FP16): ~0.05-0.2 secondes par phrase
  • Traitement par lots: 50+ phrases/seconde sur GPU
  • LLMs:

GPT-4: 3-10 secondes par demande (latence API + génération)

  • Lama 3 70B: 5-15 secondes par phrase (locale)
  • Claude 3: 2-8 secondes par demande (latence API)
  • Exemple réel:
  • Traduire un billet de blog de 1000 mots:
  • principalement lucide-nmt (GPU)

: 5-10 secondes

API GPT-4**: 30-60 secondes**Local Llama 70B

  • : 2-5 minutes
  • Lorsque vous traduisez automatiquement des centaines de billets de blog dans plus de 12 langues, cette différence de vitesse est MASSIVE.
  • Taille du modèle: 500 Mo vs 140 Go
  • Modèles NMT:

Opus-MT (par direction): 300-500 Mo

mBART50 (toutes les 50 langues): 2,4 GoM2M100 (toutes les 100 langues): 2.2GB

Total pour plus de 100 langues: ~2.2 Go

flowchart LR
    A[HTTP Client] --> B[API Gateway]
    B --> C[Translation Endpoint]
    C --> D{Has Capacity?}
    D -->|Yes| E[Translation Service]
    D -->|No| F[Queue with 429]
    F --> E
    E --> G[Process Pipeline]
    G --> H[Get Model from Cache]
    H --> I[Translate]
    I --> J[Return Response]
    J --> A

LLMs:

  1. Lama 3 8B: ~16 GoLama 3 70B: ~140 Go
  2. Mélangeur 8x7B: ~90 GoGPT-4: Non disponible pour l'auto-hébergement
  3. **Impact sur le stockage:**Besoins en ressources: Ordinateur portable vs Serveur Farm
    • **Déploiement du processeur NMT :**Fonctionne bien sur: 2 cœurs de processeur, 4 Go de RAM
    • Image Docker: 1,5-2.5 GoInférence: CPU seulement, pas de GPU nécessaireRetry-AfterCoût : 10-20 $/mois VPS
  4. **Exigences en matière de LLM:**Lama 3 70B: besoins 80GB+ VRAM (A100 GPU)
    • Modèles 7B-13B plus petits: Toujours 16-32 Go de RAM minimum
    • Coûts de l'API : 0,03-0,30 $ par 1000 jetons (additionne vite !)
    • Auto-hébergement: 1000 $+/mois pour GPU sérieux
    • Comparaison des coûts réels pour 10.000 traductions de post blog:
  5. Méthode Coût TempsPrincipalementlucide-nmt (CPU) $20/mois VPS $2-3 heures
    • La plupart du temps, c'est-à-dire 50 $ par mois, c'est-à-dire 15-30 minutes.
    • API GPT-4 : 150-300 $ 8-15 heures
    • Claude API de 200 \(à 400\) 6 à 12 heures
  6. **Local Llama 70B=$1000+/mois matériel=\(20-40 heures=\)**Qualité : Objectif-construire vs général-bénéfice

**Concentrations NMT:**Formé spécifiquement pour la traductionRetry-AfterQualité cohérente (même entrée = même sortie)

Pas d'hallucinations - traduction pure

Poignées contenu technique, code, bien formater

Pas besoin d'ingénierie rapide

sequenceDiagram
    participant Client
    participant API
    participant Queue
    participant Translator
    participant Cache
    participant Model

    Client->>API: POST /translate
    API->>Queue: Acquire slot

    alt Queue has space
        Queue-->>API: Slot acquired
        API->>Translator: Process translation
        Translator->>Translator: Sanitize input
        Translator->>Translator: Split sentences
        Translator->>Translator: Chunk text
        Translator->>Translator: Mask symbols
        Translator->>Cache: Get model (en→de)

        alt Cache hit
            Cache-->>Translator: Return cached model
        else Cache miss
            Cache->>Model: Load from Hugging Face
            Model-->>Cache: Pipeline loaded
            Cache->>Cache: Evict old if at capacity
            Cache-->>Translator: Return model
        end

        Translator->>Model: Translate batches
        Model-->>Translator: Translations
        Translator->>Translator: Unmask symbols
        Translator->>Translator: Post-process
        Translator-->>API: Translations
        API->>Queue: Release slot
        API-->>Client: 200 OK + translations
    else Queue full
        Queue-->>API: Overflow error
        API-->>Client: 429 Too Many Requests\nRetry-After: X seconds
    end

Défis liés à la gestion durable des terres :

Exemple de scénario :

graph LR
    A[Raw Input] --> B{Sanitize?}
    B -->|Yes| C[Check Noise]
    B -->|No| D[Split Sentences]
    C -->|Is Noise| Z[Return Placeholder]
    C -->|Valid| D

    D --> E[Enforce Max Length]
    E --> F[Chunk for Batching]
    F --> G{Symbol Masking?}

    G -->|Yes| H[Mask Digits/Punct/Emoji]
    G -->|No| I[Translate]
    H --> I

    I --> J{Direct Model?}
    J -->|Available| K[Direct Translation]
    J -->|Not Available| L{Pivot Fallback?}

    L -->|Yes| M[src→en→tgt]
    L -->|No| Z
    K --> N[Unmask Syis robust input handling. Here's what happens:

**Noise Detection:**
- Strips control characters (except \t, \n, \r)
- Checks minimum character count (default: 1)
- Calculates alphanumeric ratio (default: must be ≥20%)
- Rejects pure emoji, pure punctuation, or pure whitespace

**Symbol Masking:**
Why mask symbols? Translation models are trained on text, not emoji or special symbols. These can confuse them or get mangled. So we:

1. Extract all digits, punctuation, and emoji as contiguous runs
2. Replace them with sentinel tokens: `⟪MSK0⟫`, `⟪MSK1⟫`, etc.
3. Translate the masked text
4. Restore the original symbols in their positions

Example:

Input: "Hello 👋 world! Price: $99.99" Quand utiliser Chacun (👋) (!) (:) ($99.99)


**Post-Processing:**
After translation, we remove "symbol loops" - repeated symbols that weren't in the source:

Utiliser NMT (pratiquement lylucide-nmt) lorsque: Vous avez besoin d'une traduction cohérente et rapide à l'échelle Questions budgétaires (auto-accueil ou volume élevé)


### Sentence Splitting & Chunking

Long texts get split intelligently:

```mermaid
graph TD
    A[Long Text] --> B[Split on . ! ? …]
    B --> C{Sentence > 500 chars?}
    C -->|Yes| D[Split on word boundaries]
    C -->|No| E[Keep sentence]
    D --> E

    E --> F[Group into chunks ≤900 chars]
    F --> G[Translate each chunk]
    G --> H[Join with space]

Vous traduisez du contenu technique, du code, des données structurées

  • Vous avez besoin d'une sortie déterministe (même entrée = même sortie)
  • Vous voulez exécuter sur CPU ou du matériel modeste
  • Vous construisez des pipelines de traduction automatisés

Utiliser des LLM lorsque :

Vous avez besoin d'adaptation créative, pas de traduction littérale

stateDiagram-v2
    [*] --> CheckCache
    CheckCache --> CacheHit: Model exists
    CheckCache --> CacheMiss: Model not loaded

    CacheHit --> MoveToEnd: Update LRU order
    MoveToEnd --> ReturnModel

    CacheMiss --> CheckCapacity
    CheckCapacity --> LoadModel: Space available
    CheckCapacity --> EvictOldest: Cache full

    EvictOldest --> MoveToCPU: Free VRAM
    MoveToCPU --> ClearCUDA: torch.cuda.empty_cache()
    ClearCUDA --> LoadModel

    LoadModel --> AddToCache
    AddToCache --> ReturnModel
    ReturnModel --> [*]

Le contexte et la nuance culturelle comptent plus que la vitesse

  • Vous faites des traductions uniques et à faible volume.
  • Vous devez traduire + résumer + réécrire en une seule étape
  • Vous êtes d'accord avec les coûts variables et le traitement plus lent
  • La ligne de fond
  • Pour

traduction automatique de blog

(mon cas d'utilisation), NMT est le gagnant net:

# Semaphore limits concurrent translations
MAX_INFLIGHT = 1  # On GPU, 1 at a time for efficiency
MAX_QUEUE_SIZE = 1000  # Up to 1000 waiting

# When full:
# - Returns 429 Too Many Requests
# - Includes Retry-After header
# - Estimates wait time based on average duration

Traduit plus de 100 billets de blog en 12 langues en ~30 minutes (GPU)

avg_duration = 2.5 seconds (tracked with EMA)
waiters = 100
slots = 1
estimated_wait = (100 / 1) * 2.5 = 250 seconds
clamped = min(250, 120) = 120 seconds
Retry-After: 120

Exécute sur un VPS de 50 $/mois

Qualité constante de tous les postes

graph LR
    A[Ukrainian Text] --> B{Direct uk→fr?}
    B -->|Exists| C[Translate Directly]
    B -->|Missing| D[Pivot via English]

    D --> E[uk→en]
    E --> F[en→fr]
    F --> G[French Result]
    C --> G

Configuration totale : Un conteneur Docker

L'essayer avec des LLM coûterait des centaines de dollars par mois en frais d'API ou nécessiterait un serveur GPU de 2000 $+ pour s'auto-héberger.

La différence de vitesse seule fait de NMT le seul choix pratique pour les pipelines de traduction de production.

TL;DR:

NMT est conçu pour la traduction, fonctionne sur du matériel modeste, et est 10-100x plus rapide que les LLMs. Si vous avez besoin de traduction rapide, cohérente, rentable à l'échelle, NMT gagne mains en bas.

# src/core/cache.py
from collections import OrderedDict
import torch

class LRUPipelineCache:
    """LRU cache that automatically cleans up GPU memory when evicting models."""

    def __init__(self, capacity: int):
        self.cache = OrderedDict()  # Maintains insertion order
        self.capacity = capacity

    def get(self, key: str):
        """Get model from cache, moves it to end (most recently used)."""
        if key not in self.cache:
            return None
        self.cache.move_to_end(key)  # Mark as recently used
        return self.cache[key]

    def put(self, key: str, value):
        """Add model to cache, evicting oldest if at capacity."""
        if key in self.cache:
            self.cache.move_to_end(key)
        else:
            self.cache[key] = value

        # If cache is full, evict the oldest model
        if len(self.cache) > self.capacity:
            oldest_key, oldest_pipeline = self.cache.popitem(last=False)

            # MAGIC: Move evicted model to CPU to free GPU memory
            try:
                oldest_pipeline.model.to("cpu")
                if torch.cuda.is_available():
                    torch.cuda.empty_cache()  # Tell GPU to release memory
                logger.info(f"Evicted {oldest_key}, freed GPU memory")
            except Exception as e:
                logger.warning(f"Failed to clean GPU memory: {e}")

Vue d'ensemble de l'architecture

  • OrderedDictLe flux de demande est simple:
  • Clientenvoie une demande de traduction à API Gateway (Gunicorne + travailleurs Uvicorn)
  • Passerelle de l'APIitinéraires vers le point d'arrivée de la traduction
  • Vérification de la capacité: Contrôle du système s'il a la capacité de traiter la demande

Oui

→ La demande va au service de traduction immédiatement

# src/services/model_manager.py
def get_pipeline(self, src: str, tgt: str):
    """Try to get translation model, with automatic fallback to other providers."""

    # Determine which model families support this language pair
    families_to_try = []

    if config.AUTO_MODEL_FALLBACK:
        # Try families in priority order: opus-mt → mbart50 → m2m100
        for family in config.MODEL_FALLBACK_ORDER.split(","):
            if self._is_pair_supported(src, tgt, family.strip()):
                families_to_try.append(family.strip())

    # Try each family until one succeeds
    last_error = None
    for family in families_to_try:
        try:
            model_name, src_lang, tgt_lang, _ = self._get_model_name_and_langs(src, tgt, family)

            if family != config.MODEL_FAMILY:
                logger.info(f"Using fallback '{family}' for {src}->{tgt}")

            # Load the model from HuggingFace
            pipeline = transformers.pipeline(
                "translation",
                model=model_name,
                device=device_manager.device_index,
                src_lang=src_lang,
                tgt_lang=tgt_lang
            )

            self.cache.put(f"{src}->{tgt}", pipeline)
            return pipeline

        except Exception as e:
            last_error = e
            logger.warning(f"Family '{family}' failed for {src}->{tgt}: {e}")
            continue  # Try next family

    # All families failed
    raise ModelLoadError(f"{src}->{tgt}", last_error)

Numéro

  • → Requête en attente, le client reçoit HTTP 429 avecen-tête
  • Service de traductiontraite la demande par l'intermédiaire du pipeline:
  • Assainissement des entrées et fractionnement des phrasesMasquage des symboles (emojis, caractères spéciaux)
  • Traduction à l'aide de modèles mis en cacheDémantèlement et post-traitement des symboles

Modèle Cache

(LRU) fournit des modèles de traduction:

# src/services/queue_manager.py
import asyncio
from contextlib import asynccontextmanager

class QueueManager:
    """Manages request queuing and backpressure."""

    def __init__(self, max_inflight: int, max_queue: int):
        self.semaphore = asyncio.Semaphore(max_inflight)  # Limit concurrent translations
        self.max_queue_size = max_queue
        self.waiting_count = 0
        self.inflight_count = 0
        self.avg_duration_sec = 5.0  # Exponential moving average

    @asynccontextmanager
    async def acquire_slot(self):
        """Try to get a translation slot, track metrics, handle queueing."""

        # Check if queue is too full
        if self.waiting_count >= self.max_queue_size:
            # Calculate how long client should wait before retrying
            retry_after = self._estimate_retry_after()
            raise QueueOverflowError(self.waiting_count, retry_after)

        self.waiting_count += 1
        try:
            # Wait for available slot (this is the queue!)
            await self.semaphore.acquire()
            self.waiting_count -= 1
            self.inflight_count += 1

            start_time = time.time()
            yield  # Let the translation happen

            # Update average duration for retry-after estimates
            duration = time.time() - start_time
            alpha = config.RETRY_AFTER_ALPHA  # Smoothing factor (0.2)
            self.avg_duration_sec = alpha * duration + (1 - alpha) * self.avg_duration_sec

        finally:
            self.inflight_count -= 1
            self.semaphore.release()

    def _estimate_retry_after(self) -> int:
        """Smart calculation: how many waiting / how many slots * avg time per request."""
        if self.inflight_count == 0:
            return config.RETRY_AFTER_MIN_SEC

        # If 10 people waiting and 2 slots available, and each takes 5 seconds:
        # retry_after = (10 / 2) * 5 = 25 seconds
        retry_sec = (self.waiting_count / self.semaphore._value) * self.avg_duration_sec

        # Clamp between min and max
        return max(
            config.RETRY_AFTER_MIN_SEC,
            min(int(retry_sec), config.RETRY_AFTER_MAX_SEC)
        )

Cache frappé → Réponse rapide

  • Cache miss → Charger depuis HuggingFace HubCache complet → Anciens modèles d'auto-expulsion, mémoire claire CUDAmax_inflight)
  • Réponse (@asynccontextmanagerretour au client
  • **Conception clé :**Le mécanisme de contre-pression (rue + HTTP 429) empêche les collisions sous charge.
  • Lorsque débordé, le service file d'attentes demande au lieu de mourir, donnant aux clients intelligemment réessayer le timing viaEn-têtes.
  • Comment ça marche : plongée profondeFlux de demande

Quand une demande de traduction arrive, voici ce qui se passe :

Pipeline de traitement d'entrées

# src/utils/symbol_masking.py
import re

def mask_symbols(text: str) -> tuple[str, dict[str, str]]:
    """Replace special symbols with placeholders before translation."""

    originals = {}
    masked_text = text
    placeholder_counter = 0

    # Pattern: Match emojis, symbols, special punctuation
    # \U0001F300-\U0001F9FF = emoji range
    # [\u2600-\u26FF\u2700-\u27BF] = misc symbols
    symbol_pattern = re.compile(
        r'[\U0001F300-\U0001F9FF\u2600-\u26FF\u2700-\u27BF'
        r'\u00A9\u00AE\u2122\u2139\u3030\u303D\u3297\u3299]+'
    )

    for match in symbol_pattern.finditer(text):
        symbol = match.group()
        placeholder = f"__SYMBOL_{placeholder_counter}__"
        originals[placeholder] = symbol
        masked_text = masked_text.replace(symbol, placeholder, 1)
        placeholder_counter += 1

    return masked_text, originals

def unmask_symbols(text: str, originals: dict[str, str]) -> str:
    """Restore original symbols after translation."""
    for placeholder, original in originals.items():
        text = text.replace(placeholder, original)
    return text

Le service utilise un pipeline multi-étapes sophistiqué pour gérer le texte désordonné du monde réel:

# Before translation:
text = "Hello! 👋 Check out this cool feature 🚀"

# Mask symbols:
masked, originals = mask_symbols(text)
# masked = "Hello! __SYMBOL_0__ Check out this cool feature __SYMBOL_1__"
# originals = {"__SYMBOL_0__": "👋", "__SYMBOL_1__": "🚀"}

# Translate the masked text:
translated = translate(masked, "de")  # → "Hallo! __SYMBOL_0__ Schau dir diese coole Funktion an __SYMBOL_1__"

# Unmask symbols:
final = unmask_symbols(translated, originals)
# final = "Hallo! 👋 Schau dir diese coole Funktion an 🚀"

Masqué: "Bonjour, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde, le monde.

  • **Source: "Monde de salut"**Mauvaise traduction: "Hola mundo!!!!!!!"
  • **Nettoyé : "Hola mundo" # Enlève la boucle !!!!**Cela garantit:👋Les modèles ne s'étouffent pas sur d'énormes entrées__SYMBOL_0__Nous pouvons loter efficacement
  • Le contexte est préservé à l'intérieur de limites raisonnablesModèle Caching & Gestion de la mémoire

Le cache LRU est intelligent sur la mémoire GPU:

Pourquoi cela importe-t-il :

# src/utils/text_processing.py
def chunk_sentences(sentences: list[str], max_chars: int = 900) -> list[list[str]]:
    """Group sentences into chunks that fit within model's max input length."""

    chunks = []
    current_chunk = []
    current_length = 0

    for sentence in sentences:
        sentence_len = len(sentence)

        # If this sentence alone is too long, it goes in its own chunk
        if sentence_len > max_chars:
            if current_chunk:
                chunks.append(current_chunk)
                current_chunk = []
                current_length = 0
            chunks.append([sentence])
            continue

        # If adding this sentence exceeds limit, start new chunk
        if current_length + sentence_len + 1 > max_chars:
            chunks.append(current_chunk)
            current_chunk = [sentence]
            current_length = sentence_len
        else:
            current_chunk.append(sentence)
            current_length += sentence_len + 1  # +1 for space

    # Don't forget the last chunk!
    if current_chunk:
        chunks.append(current_chunk)

    return chunks

def split_sentences(text: str, max_sentence_chars: int = 500) -> list[str]:
    """Split text into sentences, enforcing max length."""

    # Split on common sentence terminators
    sentences = re.split(r'([.!?…]+\s+)', text)

    result = []
    for sentence in sentences:
        if not sentence or sentence.isspace():
            continue

        # If sentence is too long, split on word boundaries
        if len(sentence) > max_sentence_chars:
            words = sentence.split()
            current = []
            current_len = 0

            for word in words:
                if current_len + len(word) + 1 > max_sentence_chars:
                    result.append(' '.join(current))
                    current = [word]
                    current_len = len(word)
                else:
                    current.append(word)
                    current_len += len(word) + 1

            if current:
                result.append(' '.join(current))
        else:
            result.append(sentence.strip())

    return result

La mémoire GPU est précieuse

  • Les modèles de traduction sont de 300-500 Mo chacunLes modèles de chargement sont lents (1-3 secondes).!?…Nous gardons les 6 modèles les plus récents chaud
  • Les anciens modèles sont automatiquement expulsésEn file d'attente et contre-pression
  • **Au lieu de s'écraser sous la charge, les files d'attente de service demandent:**L'estimation de réessayer est intelligente:
  • Pivot Traduction FallbackToutes les paires de langues n'ont pas de modèles directs sur Hugging Face.

Une solution ?

Pivot à travers l'anglais:

# src/services/model_discovery.py
import httpx
from datetime import datetime, timedelta

class ModelDiscoveryService:
    """Discovers available translation models with 1-hour cache."""

    def __init__(self):
        self._cache = {}  # Cache results to avoid hammering HuggingFace API
        self._cache_ttl = timedelta(hours=1)
        self._hf_api_base = "https://huggingface.co/api/models"

    async def discover_opus_mt_pairs(self, force_refresh: bool = False):
        """Query HuggingFace for all Helsinki-NLP Opus-MT models."""

        cache_key = "opus-mt"

        # Check cache first
        if not force_refresh and cache_key in self._cache:
            cached_data, cached_time = self._cache[cache_key]
            if datetime.now() - cached_time < self._cache_ttl:
                return cached_data  # Cache hit!

        # Cache miss - query HuggingFace API
        async with httpx.AsyncClient() as client:
            response = await client.get(
                self._hf_api_base,
                params={
                    "author": "Helsinki-NLP",
                    "search": "opus-mt",
                    "limit": 1000
                },
                timeout=30.0
            )
            models = response.json()

        # Extract language pairs from model names
        # Example: "Helsinki-NLP/opus-mt-en-de" → ("en", "de")
        pairs = []
        for model in models:
            model_id = model.get("modelId", "")
            if model_id.startswith("Helsinki-NLP/opus-mt-"):
                # Extract the language codes after "opus-mt-"
                lang_part = model_id.replace("Helsinki-NLP/opus-mt-", "")
                if "-" in lang_part:
                    src, tgt = lang_part.split("-", 1)
                    pairs.append({"source": src, "target": tgt})

        # Cache the results
        self._cache[cache_key] = (pairs, datetime.now())

        return pairs

Cela double latence mais assure une couverture pour toutes les paires de langues supportées.

  • Code Plongée profonde: Caractéristiques fraîches expliquées (httpxExplorons quelques-unes des parties les plus intéressantes de la base de codes !
  • **Ce sont des modes de production réels qui rendent le service robuste et efficace.**Chaque extrait comprend des explications adaptées aux développeurs non-Python.
  • **1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.**Smart LRU Cache avec GPU Memory ManagementenL'une des caractéristiques les plus cool est le cache de modèle intelligent qui sait gérer la mémoire GPU:deQu'est-ce qui se passe ici ?Helsinki-NLP/opus-mt-en-de
  • : Comme un dictionnaire régulier, mais se souvient que les articles de commande ont été ajoutésLRU (Least Recently Used)

: Lorsque le cache est plein, lancez le modèle qui n'a pas été utilisé dans le plus long laps de temps

Nettoyage du GPU

# src/core/device.py
import torch

class DeviceManager:
    """Smart device selection with GPU auto-detection."""

    def __init__(self):
        self.use_gpu = self._should_use_gpu()
        self.device_index = self._resolve_device()
        self.device_str = "cpu" if self.device_index < 0 else f"cuda:{self.device_index}"

        # Auto-configure parallel translation slots based on device
        if self.device_index >= 0:
            # GPU: Run translations serially to avoid VRAM fragmentation
            self.max_inflight = 1
        else:
            # CPU: Can handle multiple translations in parallel
            self.max_inflight = config.MAX_WORKERS_BACKEND

        self._log_device_info()

    def _should_use_gpu(self) -> bool:
        """Check if GPU should be used."""
        if config.USE_GPU.lower() == "false":
            return False
        if config.USE_GPU.lower() == "true":
            return torch.cuda.is_available()
        # "auto" mode: use GPU if available
        return torch.cuda.is_available()

    def _resolve_device(self) -> int:
        """Returns device index: -1 for CPU, 0+ for CUDA."""
        if not self.use_gpu:
            return -1

        # Check if specific CUDA device requested
        if config.DEVICE and config.DEVICE.startswith("cuda:"):
            device_num = int(config.DEVICE.split(":")[1])
            return device_num

        return 0  # Use first GPU

    def _log_device_info(self):
        """Log device information at startup."""
        if self.device_index >= 0:
            gpu_name = torch.cuda.get_device_name(self.device_index)
            vram_gb = torch.cuda.get_device_properties(self.device_index).total_memory / 1e9
            logger.info(f"Using GPU: {gpu_name} ({vram_gb:.1f}GB VRAM)")
            logger.info(f"Max inflight translations: {self.max_inflight} (GPU mode)")
        else:
            cpu_count = os.cpu_count()
            logger.info(f"Using CPU ({cpu_count} cores)")
            logger.info(f"Max inflight translations: {self.max_inflight} (CPU mode)")

# Global singleton instance
device_manager = DeviceManager()

: Lors de l'expulsion d'un modèle, nous le déplaçons explicitement dans la mémoire CPU et disons au GPU de libérer ses ressources

  • Pourquoi c'est important: Sans cela, la mémoire GPU se remplirait et s'écraserait après le chargement de 2-3 modèles!
  • **2. Le Président. — L'ordre du jour appelle le rapport (doc.**Famille de modèles automatique Fallbackmax_inflight=1Cette fonctionnalité intelligente essaie plusieurs fournisseurs de modèles d'IA automatiquement si le premier n'a pas la paire de langues dont vous avez besoin:max_inflight=4Qu'est-ce qui se passe ici ?
  • Chaîne de repli: Si Opus-MT n'a pas d'ukrainien→Français, essayez automatiquement mBART50, puis M2M100DEVICE=cuda:1
  • Pas d'intervention manuelle: Les utilisateurs ne demandent qu'une traduction et obtiennent le meilleur modèle disponible
  • Gestion des erreurs: Si toutes les familles échouent, nous lançons une erreur claire avec la dernière raison d'échec

Cache intelligente

: Les modèles réussis sont mis en cache avec la clé de la paire de langues

# Snippet from QueueManager showing EMA calculation
def update_avg_duration(self, new_duration: float):
    """Update average duration using exponential moving average."""

    # EMA formula: new_avg = α × new_value + (1 - α) × old_avg
    # α = smoothing factor (0.0 to 1.0)
    #   - Higher α = more weight to recent values (faster adaptation)
    #   - Lower α = more weight to historical values (more stable)

    alpha = 0.2  # 20% weight to new value, 80% to historical

    self.avg_duration_sec = (
        alpha * new_duration +
        (1 - alpha) * self.avg_duration_sec
    )

3. Les droits de l'homme sont garantis par le Pacte international relatif aux droits économiques, sociaux et culturels.

# Initial average: 5.0 seconds
# New request takes: 10.0 seconds

# EMA calculation:
new_avg = 0.2 * 10.0 + 0.8 * 5.0
        = 2.0 + 4.0
        = 6.0 seconds

# Next request takes: 3.0 seconds
new_avg = 0.2 * 3.0 + 0.8 * 6.0
        = 0.6 + 4.8
        = 5.4 seconds

Demander une réponse avec contrepression (HTTP 429)

  • **Les files d'attente de qualité de production qui empêchent le serveur de s'écraser sous une charge lourde:**Qu'est-ce qui se passe ici ?
  • Sémaphore: Comme un videur dans un club - permet seulement N personnes à la fois (N =
  • Gestionnaire de contexte): Suivi automatique des métriques et nettoyage
  • Réessayez-vous après: Indique aux clients "revenir dans 25 secondes" en fonction de la profondeur de la file d'attente et du temps moyen de demandeRetry-AfterMoyenne mobile exponentielle

: Lisse les pics dans la durée de la demande

  • Pourquoi c'est important: Sous charge lourde, retourne HTTP 429 au lieu de planter ou de file d'attente infiniment
  • **4. Le Président. — L'ordre du jour appelle le rapport (doc.**Magie de masque de symbole
  • **Préserve des caractères spéciaux (emojis, symboles) que les modèles de traduction pourraient gâcher:**Exemple d'utilisation :
  • **Qu'est-ce qui se passe ici ?**Modèle Regex
  • : Correspond aux gammes d'emoji et de symbole spécial UnicodeSystème de localisation
  • : Swapsavec

temporairement

Pourquoi c'est important

: Modèles de traduction parfois corrompus ou supprimer les emojis - cela les préserve parfaitement!

5.

# Prefer GPU if available (default)
USE_GPU=auto

# Force GPU
USE_GPU=true

# Force CPU
USE_GPU=false

# Explicit device override
DEVICE=cuda:0
DEVICE=cpu

Découpe intelligente de texte

# Model family selection (NEW in v2.0!)
MODEL_FAMILY=opus-mt   # Best quality (default)
MODEL_FAMILY=mbart50   # 50 languages, single model
MODEL_FAMILY=m2m100    # 100 languages, maximum coverage

# Auto-fallback between model families (NEW in v2.0!)
AUTO_MODEL_FALLBACK=1  # Enabled by default
MODEL_FALLBACK_ORDER="opus-mt,mbart50,m2m100"  # Priority order

# Volume-mapped model cache (NEW in v2.0!)
MODEL_CACHE_DIR=/models  # Persistent cache directory

# Model arguments passed to transformers.pipeline
EASYNMT_MODEL_ARGS='{"torch_dtype":"fp16"}'
EASYNMT_MODEL_ARGS='{"torch_dtype":"bf16","cache_dir":"/models"}'

# Preload models at startup (reduces first-request latency)
PRELOAD_MODELS="en->de,de->en,fr->en"

# LRU cache capacity
MAX_CACHED_MODELS=6

Découpe les longs textes en morceaux qui correspondent aux limites du modèle tout en préservant les limites des phrases :

  • **Qu'est-ce qui se passe ici ?**Séparation des peines

    • opus-mt: Utilise le régex pour se fractionner
    • mbart50tout en préservant la ponctuation
    • m2m100Bouchées d'avidité
  • : Emballe autant de phrases que possible dans chaque tranche sans dépasser la limiteSéparation des limites des mots

    • 1: Si une seule phrase est trop longue, se divise sur les espaces au lieu de couper à mi-mot
    • 0Pourquoi c'est important
  • **: Les modèles de traduction ont des limites d'entrée (généralement 512-1024 jetons).**Cela garantit que nous ne les surpassons jamais tout en gardant le contexte intact.

    • 6."opus-mt,mbart50,m2m100"Découverte du modèle Async avec cache
    • Découvre dynamiquement les modèles de traduction disponibles de HuggingFace:"m2m100,mbart50,opus-mt"Qu'est-ce qui se passe ici ?
  • Client HTTP Async) : Fait des requêtes HTTP non-bloquantes à HuggingFace

    • Cuisson dans le temps/models: Conserve les résultats pendant 1 heure pour éviter la limitation de vitesse-v ./model-cache:/models
    • Analyse du nom de modèle
    • : Extraits

et

  • fp16à partir de
  • bf16Pourquoi c'est important
  • fp32: HuggingFace a 1200+ modèles Opus-MT.

Les interroger prend ~10 secondes.

# Batch size for translation (higher = faster but more VRAM)
EASYNMT_BATCH_SIZE=16  # CPU: 8-16, GPU: 32-64

# Maximum text length per item
EASYNMT_MAX_TEXT_LEN=1000

# Maximum beam size (higher = better quality but slower)
EASYNMT_MAX_BEAM_SIZE=5

# Worker thread pools
MAX_WORKERS_BACKEND=1    # Translation workers
MAX_WORKERS_FRONTEND=2   # Language detection workers

La mise en cache le rend instantané !

# Enable request queueing (highly recommended)
ENABLE_QUEUE=1

# Max concurrent translations
# Auto: 1 on GPU, MAX_WORKERS_BACKEND on CPU
MAX_INFLIGHT_TRANSLATIONS=1

# Max queued requests before 429
MAX_QUEUE_SIZE=1000

# Per-request timeout (0 = disabled)
TRANSLATE_TIMEOUT_SEC=180

# Retry-After estimation
RETRY_AFTER_MIN_SEC=1      # Floor
RETRY_AFTER_MAX_SEC=120    # Ceiling
RETRY_AFTER_ALPHA=0.2      # EMA smoothing factor

7.

# Enable input filtering
INPUT_SANITIZE=1

# Minimum alphanumeric ratio (0.2 = 20%)
INPUT_MIN_ALNUM_RATIO=0.2

# Minimum character count
INPUT_MIN_CHARS=1

# Language code for undetermined/noise
UNDETERMINED_LANG_CODE=und

Détection automatique du périphérique

# Default sentence splitting behavior
PERFORM_SENTENCE_SPLITTING_DEFAULT=1

# Max chars per sentence before word-boundary split
MAX_SENTENCE_CHARS=500

# Max chars per chunk for batching
MAX_CHUNK_CHARS=900

# Sentence joiner
JOIN_SENTENCES_WITH=" "

Détecte et utilise automatiquement GPU si disponible:

# Enable symbol masking
SYMBOL_MASKING=1

# What to mask
MASK_DIGITS=1    # Mask 0-9
MASK_PUNCT=1     # Mask .,!? etc.
MASK_EMOJI=1     # Mask 😀🎉 etc.

Qu'est-ce qui se passe ici ?

# Align response array length to input
ALIGN_RESPONSES=1

# Placeholder for failed items (when aligned)
SANITIZE_PLACEHOLDER=""

# Response format
EASYNMT_RESPONSE_MODE=strings    # ["translation1", "translation2"]
EASYNMT_RESPONSE_MODE=objects    # [{"text":"translation1"}, ...]

Détection GPU

# Enable two-hop translation via pivot
PIVOT_FALLBACK=1

# Pivot language (usually English)
PIVOT_LANG=en

: Utilise PyTorch pour vérifier si CUDA est disponible

# Log level
LOG_LEVEL=INFO

# Per-request logging (verbose)
REQUEST_LOG=1

# Format
LOG_FORMAT=plain    # Human-readable
LOG_FORMAT=json     # Structured JSON

# File logging with rotation
LOG_TO_FILE=1
LOG_FILE_PATH=/var/log/marian-translator/app.log
LOG_FILE_MAX_BYTES=10485760    # 10MB
LOG_FILE_BACKUP_COUNT=5

# Include raw text in logs (privacy risk!)
LOG_INCLUDE_TEXT=0

Configuration automatique

# Periodically clear CUDA cache (seconds, 0=disabled)
CUDA_CACHE_CLEAR_INTERVAL_SEC=0

: Ensembles

# Worker count (use 1 for single GPU)
WEB_CONCURRENCY=1

# Request timeout
TIMEOUT=60

# Graceful shutdown timeout
GRACEFUL_TIMEOUT=20

# Keep-alive timeout
KEEP_ALIVE=5

sur GPU (éviter la fragmentation VRAM) vs

sur le CPU (parallélisme maximal)

# GET request
curl "http://localhost:8000/translate?target_lang=de&text=Hello%20world&source_lang=en"

# Response
{
  "translations": ["Hallo Welt"]
}

Sélection du périphérique

# POST request
curl -X POST http://localhost:8000/translate \
  -H 'Content-Type: application/json' \
  -d '{
    "text": [
      "Hello world",
      "This is a test",
      "Machine translation is amazing"
    ],
    "target_lang": "de",
    "source_lang": "en",
    "beam_size": 1,
    "perform_sentence_splitting": true
  }'

# Response
{
  "target_lang": "de",
  "source_lang": "en",
  "translated": [
    "Hallo Welt",
    "Das ist ein Test",
    "Maschinenübersetzung ist erstaunlich"
  ],
  "translation_time": 0.342
}

: Peut cibler GPU spécifique avec

# Omit source_lang for auto-detection
curl -X POST http://localhost:8000/translate \
  -H 'Content-Type: application/json' \
  -d '{
    "text": ["Bonjour le monde"],
    "target_lang": "en"
  }'

# Response
{
  "target_lang": "en",
  "source_lang": "fr",  # Detected
  "translated": ["Hello world"],
  "translation_time": 0.156
}

Exploitation forestière

# GET
curl "http://localhost:8000/language_detection?text=Hola%20mundo"
# {"language": "es"}

# POST with batch
curl -X POST http://localhost:8000/language_detection \
  -H 'Content-Type: application/json' \
  -d '{"text": ["Hello", "Bonjour", "Hola"]}'
# {"languages": ["en", "fr", "es"]}

: Affiche le nom GPU et VRAM au démarrage pour le débogage

# Health check
curl http://localhost:8000/healthz
# {"status": "ok"}

# Readiness
curl http://localhost:8000/readyz
# {
#   "status": "ready",
#   "device": "cuda:0",
#   "queue_enabled": true,
#   "max_inflight": 1
# }

# Cache status
curl http://localhost:8000/cache
# {
#   "capacity": 6,
#   "size": 3,
#   "keys": ["en->de", "de->en", "fr->en"],
#   "device": "cuda:0",
#   "inflight": 1,
#   "queue_enabled": true
# }

# Model info
curl http://localhost:8000/model_name | jq

Modèle de monotone

# When queue is full, you get 429
curl -X POST http://localhost:8000/translate \
  -H 'Content-Type: application/json' \
  -d '{"text": ["test"], "target_lang": "de"}'

# Response: 429 Too Many Requests
# Headers: Retry-After: 45
# Body:
{
  "message": "Too many requests; queue full",
  "retry_after_sec": 45
}

# Proper client behavior:
# 1. Read Retry-After header
# 2. Wait that long + jitter
# 3. Retry request

: Une instance partagée sur l'ensemble de l'application

Moyenne exponentielle de déplacement pour réessayer-après

Estimation de retour à l'essai qui s'adapte aux durées réelles de la demande :

Exemple :

.\build-all.ps1

Qu'est-ce qui se passe ici ?

chmod +x build-all.sh
./build-all.sh

EMA (moyenne mobile exponentielle)

: Comme une moyenne pondérée qui donne plus d'importance aux valeurs récentesFacteur de lissage (α):

  1. : Contrôle la rapidité avec laquelle nous nous adaptons aux changements (latest, min, gpu, gpu-minPourquoi pas une simple moyenne ?
  2. : EMA s'adapte plus rapidement aux changements tout en filtrant les picsPourquoi c'est important20250108.143022: donne des clients réalistes

temps qui s'adaptent à la charge actuelle du système

# Always get the latest version
docker pull scottgal/mostlylucid-nmt:cpu
# Or use the :latest alias
docker pull scottgal/mostlylucid-nmt:latest

# Pin to a specific version for reproducibility
docker pull scottgal/mostlylucid-nmt:cpu-20250108.143022
docker pull scottgal/mostlylucid-nmt:cpu-min-20250108.143022

Ces modèles de code démontrent les pratiques de production de Python :

Gestion des ressources

  • : Nettoyage explicite de la mémoire GPUDégradation gracieuse
  • : Retour automatique entre les fournisseurs de modèlesManipulation de la contre-pression
  • : Queue + HTTP 429 au lieu de crashesIntégrité des données
  • : Le masquage des symboles préserve les caractères spéciauxOptimisation des performances

: Cacherie intelligente, chunking, et traitement parallèle

docker inspect scottgal/mostlylucid-nmt:cpu | jq '.[0].Config.Labels'

Observabilité: Suivi détaillé de l'enregistrement et des mesures.

Chacune de ces fonctionnalités résout un vrai problème de production qui causerait des accidents, des erreurs ou une mauvaise expérience utilisateur sans eux!

Guide de configuration

# Using pre-built image from Docker Hub (recommended)
docker run -d \
  --name translator \
  -p 8000:8000 \
  -e ENABLE_QUEUE=1 \
  -e MAX_QUEUE_SIZE=500 \
  -e EASYNMT_BATCH_SIZE=16 \
  -e TIMEOUT=180 \
  -e LOG_LEVEL=INFO \
  -e REQUEST_LOG=0 \
  scottgal/mostlylucid-nmt

# Or build locally
docker build -t mostlylucid-nmt .
docker run -d --name translator -p 8000:8000 mostlylucid-nmt

# Check logs
docker logs -f translator

Le service est hautement configurable via des variables d'environnement.

# Using pre-built GPU image from Docker Hub (recommended)
docker run -d \
  --name translator-gpu \
  --gpus all \
  -p 8000:8000 \
  -e USE_GPU=true \
  -e DEVICE=cuda:0 \
  -e PRELOAD_MODELS="en->de,de->en,en->fr,fr->en,en->es,es->en" \
  -e EASYNMT_MODEL_ARGS='{"torch_dtype":"fp16"}' \
  -e EASYNMT_BATCH_SIZE=64 \
  -e MAX_CACHED_MODELS=8 \
  -e ENABLE_QUEUE=1 \
  -e MAX_QUEUE_SIZE=2000 \
  -e WEB_CONCURRENCY=1 \
  -e TIMEOUT=180 \
  -e GRACEFUL_TIMEOUT=30 \
  -e LOG_FORMAT=json \
  -e LOG_TO_FILE=1 \
  -v /var/log/translator:/var/log/marian-translator \
  scottgal/mostlylucid-nmt:gpu

# Or build locally
docker build -f Dockerfile.gpu -t mostlylucid-nmt:gpu .
docker run -d --name translator-gpu --gpus all -p 8000:8000 mostlylucid-nmt:gpu

# Monitor cache and performance
watch -n 5 "curl -s http://localhost:8000/cache | jq"

Voici le guide complet:

version: '3.8'

services:
  translator:
    image: scottgal/mostlylucid-nmt:gpu  # Use pre-built image
    container_name: translator
    restart: unless-stopped

    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

    ports:
      - "8000:8000"

    environment:
      USE_GPU: "true"
      DEVICE: "cuda:0"
      PRELOAD_MODELS: "en->de,de->en,en->fr,fr->en"
      EASYNMT_MODEL_ARGS: '{"torch_dtype":"fp16"}'
      EASYNMT_BATCH_SIZE: "64"
      MAX_CACHED_MODELS: "8"
      ENABLE_QUEUE: "1"
      MAX_QUEUE_SIZE: "2000"
      WEB_CONCURRENCY: "1"
      TIMEOUT: "180"
      LOG_FORMAT: "json"
      LOG_TO_FILE: "1"

    volumes:
      - translator-logs:/var/log/marian-translator
      - translator-cache:/root/.cache/huggingface

    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/healthz"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

volumes:
  translator-logs:
  translator-cache:

Sélection du périphérique

apiVersion: apps/v1
kind: Deployment
metadata:
  name: translator
spec:
  replicas: 2  # Scale horizontally for CPU, use 1 per GPU
  selector:
    matchLabels:
      app: translator
  template:
    metadata:
      labels:
        app: translator
    spec:
      containers:
      - name: translator
        image: scottgal/mostlylucid-nmt:gpu
        ports:
        - containerPort: 8000
        env:
        - name: USE_GPU
          value: "true"
        - name: EASYNMT_MODEL_ARGS
          value: '{"torch_dtype":"fp16"}'
        - name: PRELOAD_MODELS
          value: "en->de,de->en"
        - name: ENABLE_QUEUE
          value: "1"
        - name: MAX_QUEUE_SIZE
          value: "2000"

        resources:
          requests:
            memory: "4Gi"
            cpu: "2"
            nvidia.com/gpu: 1
          limits:
            memory: "8Gi"
            cpu: "4"
            nvidia.com/gpu: 1

        livenessProbe:
          httpGet:
            path: /healthz
            port: 8000
          initialDelaySeconds: 30
          periodSeconds: 10

        readinessProbe:
          httpGet:
            path: /readyz
            port: 8000
          initialDelaySeconds: 20
          periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
  name: translator
spec:
  selector:
    app: translator
  ports:
  - port: 80
    targetPort: 8000
  type: LoadBalancer

Configuration du modèle

Nouvelle configuration expliquée :

  1. MODÈLE_FAMILIAL

    EASYNMT_MODEL_ARGS='{"torch_dtype":"fp16"}'
    
    • : Choisissez la famille de modèles à utiliser comme primaire
    • : Meilleure qualité, 1200+ paires, modèles séparés
    • : Bonne qualité, 50 langues, modèle simple de 2,4 Go
  2. : Bonne qualité, 100 langues, modèle unique 2.2GB

    # Start high, reduce if you get OOM
    EASYNMT_BATCH_SIZE=64  # Try 128 on large GPUs
    
  3. AUTO_MODEL_FALLBACK

    PRELOAD_MODELS="en->de,de->en,en->fr,fr->en,en->es,es->en"
    
  4. : Essayez automatiquement d'autres familles si la paire n'est pas disponible

    WEB_CONCURRENCY=1
    MAX_INFLIGHT_TRANSLATIONS=1
    
  5. (par défaut) : Activé - couverture maximale

    MAX_CACHED_MODELS=10  # Keep more models in VRAM
    
  6. : Handicapé - mode unifamiliale strict

    # beam_size=1 is 3-5x faster than beam_size=5
    # Quality difference is often minimal
    curl -X POST ... -d '{"beam_size": 1, ...}'
    

MODÈLE D'ORDRE_D'ORDRE_DE FABRICATION

  1. : Ordre de priorité pour le retour en arrière

    EASYNMT_BATCH_SIZE=8
    
  2. Par défaut & #160;:

    MAX_WORKERS_BACKEND=4
    MAX_INFLIGHT_TRANSLATIONS=4
    WEB_CONCURRENCY=2
    
  3. (qualité d'abord)

    PERFORM_SENTENCE_SPLITTING_DEFAULT=0
    

Autre solution :

  1. (couverture d'abord)

    // Bad: 100 separate requests
    for (const text of texts) {
      await translate(text);
    }
    
    // Good: 1 batch request
    await translate(texts);
    
  2. MODÈLE_CACHE_DIR

    async function translateWithRetry(texts) {
      try {
        return await translate(texts);
      } catch (err) {
        if (err.status === 429) {
          const retryAfter = err.headers['retry-after'];
          const jitter = Math.random() * 5;
          await sleep((retryAfter + jitter) * 1000);
          return translateWithRetry(texts);
        }
        throw err;
      }
    }
    
  3. : Stockage de modèle persistant via les volumes Docker

    // Reuse HTTP connections
    const agent = new https.Agent({ keepAlive: true });
    
  4. Réglé sur

    // Bad: mixed language pairs in one request
    translate([
      { text: "Hello", sourceLang: "en", targetLang: "de" },
      { text: "Bonjour", sourceLang: "fr", targetLang: "de" }
    ]);
    
    // Good: group by language pair
    translateBatch(enToDe, "en", "de");
    translateBatch(frToDe, "fr", "de");
    

et volume de la carte:

Les modèles persistent à travers les redémarrages des conteneurs

  1. cache partagé entre plusieurs conteneursoptions de type torch_dtype & #160;:
  2. (float16): 2x plus rapide sur GPU, la moitié de la mémoire, perte de qualité négligeable(bfloat16) : Une meilleure stabilité numérique que fp16 nécessite des GPU modernes
  3. (float32): Précision totale, plus lente mais plus préciseParamètres de traduction
  4. Mise en file d'attente & PerformanceHygiène des entrées
  5. Traitement des peinesMasquage des symboles
  6. Comportement de réponseRetour en arrière du pivot
  7. Exploitation forestièreEntretien

Gunicorne (Docker)

Exemples d'utilisation

translation_requests_total{lang_pair="en->de",status="success"} 1523
translation_requests_total{lang_pair="en->de",status="error"} 7
translation_duration_seconds{lang_pair="en->de",quantile="0.5"} 0.342
translation_duration_seconds{lang_pair="en->de",quantile="0.95"} 1.234
translation_queue_depth 23
translation_cache_size 6
translation_cache_hits_total 8234
translation_cache_misses_total 142

Traduction de base

# Enable JSON logging
LOG_FORMAT=json REQUEST_LOG=1

# Output example
{
  "ts": "2025-01-08T15:30:45+0000",
  "level": "INFO",
  "name": "app",
  "message": "translate_post done items=5 dt=0.342s",
  "req_id": "a3d2f5b1-c4e6-4f7a-9d8c-1e2f3a4b5c6d",
  "endpoint": "/translate",
  "src": "en",
  "tgt": "de",
  "items": 5,
  "duration_ms": 342
}

Traduction par lots (Recommandé)

Détection automatique du langage

Détection de langue seulement |---------|---------|-----------------| | Points finals de l'observabilitéManipulation de la contre-pression | Construction et mise en formeToutes les images Docker incluent maintenant une version appropriée et des métadonnées pour le suivi. | Construction rapideConstruisez les 4 variantes avec la version automatique datetime : | **Fenêtres & #160;:**Linux/Mac : | Stratégie de mise en formeChaque construction crée | deux étiquettesÉtiquette nommée | ) - indique toujours le plus récentÉtiquette de version | (par exemple,) - instantané immuable | **Exemples:**Étiquettes du BEC | **Chaque image comprend des métadonnées :**Version | **: Construisez l'horodatage (AAAAMMJ.HHMMSS)**Date de construction | : Horodatage ISO 8601Git commit

: SHA courte

Variante

: cpu-plein, cpu-min, gpu-plein, ou gpu-minInspecter les étiquettes:

Pour les instructions détaillées de construction et l'intégration CI/CD, voir

  • BÂTIMENT.mdMAX_QUEUE_SIZE
  • Déploiement
  • Déploiement du processeurMAX_INFLIGHT_TRANSLATIONSDéploiement du GPU
  • Composez Docker

Déploiement de Kubernetes

Optimisation des performancesListe de contrôle pour l'optimisation du GPU

Utiliser la précision FP16

  • 2x inférence plus rapideENABLE_QUEUE=1
  • La moitié de l'utilisation VRAM

Perte de qualité négligeable pour la traduction

Taille du lotPrécharger les modèles chauds

Travailleur unique par GPU

  • Augmenter la taille du cacheEASYNMT_BATCH_SIZE
  • Taille du faisceau inférieur pour le débitMAX_CACHED_MODELS
  • Liste de contrôle pour l'optimisation du processeurEASYNMT_MODEL_ARGS='{"torch_dtype":"fp16"}'
  • Taille inférieure du lotWEB_CONCURRENCY=1Augmenter le parallélismeMAX_INFLIGHT_TRANSLATIONS=1

Désactiver le fractionnement des phrases pour les textes courts

Meilleures pratiques du clientDemandes de lots

Respectez la réessayer-après

PRELOAD_MODELS="en->de,de->en"

Utiliser la mise en commun des connexions

Groupe par paire de langues Helsinki-NLP/opus-mt-{src}-{tgt}Surveillance et observation

Chiffres clés à suivre

  • Débit de traductionPIVOT_FALLBACK=1(demandes/sec)
  • Latence moyennecurl http://localhost:8000/lang_pairs

(p50, p95, p99)

Profondeur de la file d'attente(compte d'attente en cours)

Taux de succès de la cache

  • (% des requêtes touchant le cache)MASK_EMOJI=0Taux d'erreurMASK_PUNCT=0
  • (5xx réponses)SYMBOL_MASKING=0

Utilisation du GPU

(le cas échéant)

public class MostlyLucidNmtClient
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl;

    public MostlyLucidNmtClient(HttpClient httpClient, string baseUrl)
    {
        _httpClient = httpClient;
        _baseUrl = baseUrl;
    }

    public async Task<TranslationResponse> TranslateAsync(
        List<string> texts,
        string targetLang,
        string sourceLang = "",
        int beamSize = 1,
        bool performSentenceSplitting = true,
        CancellationToken cancellationToken = default)
    {
        var request = new TranslationRequest
        {
            Text = texts,
            TargetLang = targetLang,
            SourceLang = sourceLang,
            BeamSize = beamSize,
            PerformSentenceSplitting = performSentenceSplitting
        };

        var response = await _httpClient.PostAsJsonAsync(
            $"{_baseUrl}/translate",
            request,
            cancellationToken);

        if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
        {
            // Read Retry-After header
            var retryAfter = response.Headers.RetryAfter?.Delta?.TotalSeconds ?? 30;
            var jitter = Random.Shared.Next(0, 5);
            await Task.Delay(TimeSpan.FromSeconds(retryAfter + jitter), cancellationToken);

            // Retry
            return await TranslateAsync(texts, targetLang, sourceLang, beamSize,
                performSentenceSplitting, cancellationToken);
        }

        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<TranslationResponse>(cancellationToken);
    }
}

public class TranslationRequest
{
    [JsonPropertyName("text")]
    public List<string> Text { get; set; }

    [JsonPropertyName("target_lang")]
    public string TargetLang { get; set; }

    [JsonPropertyName("source_lang")]
    public string SourceLang { get; set; }

    [JsonPropertyName("beam_size")]
    public int BeamSize { get; set; }

    [JsonPropertyName("perform_sentence_splitting")]
    public bool PerformSentenceSplitting { get; set; }
}

public class TranslationResponse
{
    [JsonPropertyName("target_lang")]
    public string TargetLang { get; set; }

    [JsonPropertyName("source_lang")]
    public string SourceLang { get; set; }

    [JsonPropertyName("translated")]
    public List<string> Translated { get; set; }

    [JsonPropertyName("translation_time")]
    public double TranslationTime { get; set; }
}

Utilisation de la mémoire

services.AddHttpClient<MostlyLucidNmtClient>(client =>
{
    client.BaseAddress = new Uri("http://translator:8000");
    client.Timeout = TimeSpan.FromMinutes(3);
});

(VRAM pour GPU, RAM pour CPU)

Exemple Prométhée Métrique**Si vous intégrez Prométhée (pas intégré, mais facile à ajouter) :**Exemple d'exploitation forestière structurée

Vous pouvez le pipe à Elasticsearch, CloudWatch, ou n'importe quel agrégateur de log.

Comparaison: EasyNMT vs MostlyLucid-NMT

  • Caractéristiques FacileNMT , surtout Lucid-NMT ,
  • Stabilité
  • Crashes fréquemment Assemblage d'erreurs gracieuse et prête à la production

Traitement des entrées

  • Effacement sur emoji/symboles
  • Contre-pression
  • Aucun, OOMs sous charge.Semaphore + file d'attente avec réessayer-après.

Observabilité

  • Minimale (Health/ready/cache endpoints), logs structurés (Health/ready/cache endpoints) (Health/ready/cache endpoints) (Logs structurés) (Health/ready/cache endpoints) (Health/cache end/cache endpoints) (Health/cache end/cache endpoints) (Health/cache end/cache endpoints) (Health/cache end/cache) (Health/cache end/cache) (Health/cache end/cache) (Health/cache end/cache) (Health/cache end/cache) (Health/cache end/cache) (Health/cache) (Health/cache)
  • Prise en charge du GPU
  • CUDA 10.x (ancien) CUDA 12.6, FP16/BF16 soutien
  • Gestion des modèles

Manuel, pas de cache LRU avec auto-expulsion

  • Traitement des peines
  • Regroupement de base Regroupement intelligent + batchage
  • Pivot Traduction
  • Référence Retour automatique via l'anglais
  • Fermeture gracieuse

Oui, avec timeout

Configuration40+ env vars pour le réglage finCompatibilité de l'API

Endpoints EasyNMT 100% compatibles + extensions

  • Qualité du codeSans entretien, monolithique, modulaire, dactylographié, testé
  • Dépannage429 Trop de demandes
  • **Cause:**La file d'attente est pleine.
  • **Solution:**Augmentation
  • **Ajouter d'autres répliques (échelle horizontale)**Augmentation
  • **(si vous avez la salle de tête)**Réduire la taille des lots des clients
  • 503 Service non disponibleCause:
  • **En file d'attente désactivée et toutes les fentes occupées.**Solution:

Activer la file d'attente & #160;:

# Maximum coverage with auto-fallback (recommended!)
docker run -d -p 8000:8000 \
  -v ./model-cache:/models \
  -e MODEL_CACHE_DIR=/models \
  -e AUTO_MODEL_FALLBACK=1 \
  -e MODEL_FALLBACK_ORDER="opus-mt,mbart50,m2m100" \
  scottgal/mostlylucid-nmt:cpu-min

# GPU with best quality
docker run -d --gpus all -p 8000:8000 \
  -e USE_GPU=true \
  -e MODEL_FAMILY=opus-mt \
  -e EASYNMT_MODEL_ARGS='{"torch_dtype":"fp16"}' \
  scottgal/mostlylucid-nmt:gpu

# Test it
curl -X POST http://localhost:8000/translate \
  -H 'Content-Type: application/json' \
  -d '{"text": ["Hello world"], "target_lang": "de"}'

Augmentation de la limite de vol si vous avez des ressources

OOM (Out of Memory) sur GPU

Cause:

Taille du lot trop haut ou trop de modèles mis en cache.


Solution:

et

Première demande lente[Cause:

Modèle non préchargé.

Translation NMT Neural Machine Translation Python FastAPI Docker CUDA PyTorch Transformers Helsinki-NLP Production Microservices API

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.