Construyendo principalmentelucid-nmt: Un Servicio de Traducción de Producción-Listo (compatible con EasyNMT) (Español (Spanish))

Construyendo principalmentelucid-nmt: Un Servicio de Traducción de Producción-Listo (compatible con EasyNMT)

Saturday, 08 November 2025

//

50 minute read

Introducción

Una implementación FastAPI que copia exactamente la API de EasyNMT (https://github.com/UKPLab/EasyNMT) un excelente pero abandonado proyecto de traducción de máquinas neuronales.

Pero he añadido MUCHAS características agradables para aumentar la fiabilidad y hacer que esté listo para su uso en un sistema de producción.Piensa en una traducción rápida.).

Desde el comienzo de este blog, una gran pasión ha sido la traducción automática de artículos de blog.

SÍ Sé que 'google hace esto' en los navegadores etc...etc...pero ese no es el punto.mostlylucid-nmt¡Quería saber CÓMO hacerlo!

Además, es agradable dar la bienvenida a las personas que no leen inglés (incluso si leen inglés como segundo idioma, es FAR más difícil de analizar).Así que descubrí cómo hacerlo; así como compartir cómo construir este tipo de sistema.Oh y me dio ideas sobre cómo usarlo en ASP.NET para la localización automática de texto (incluyendo texto dinámico) usando SignalR y un sistema de actualización en tiempo real. (

¡Manténganse atentosOh y he hecho una demo disponible aquí; https://nmtdemo.mostlylucid.net/demo/ sólo se ejecuta en un viejo portátil sin GPU, pero le da la idea (y me permite probar la lengevity).Esencialmente; los humanos escriben texto basura que es SUPER ruidoso para que las máquinas puedan manejar eficientemente.

Así que mucho fue averiguar cómo trabajar en torno a los problemas con EasyNMT (fue realmente un proyecto de investigación).

Ahora

está diseñado para ser una batalla probada (bien traducir las decenas de miles de palabras aquí!) sistema útil para cualquier traducción. Una especie de API de BabelFish. También tiene todos los aprendizajes que tengo de tres décadas de construcción de servidores y sistemas de producción. Variando de 429 códigos para decirle al cliente que se retire, devolviendo metadatos sobre traducciones para ayudar a los clientes, puntos finales adicionales para obtener más datos y OF COURSE una página de demostración

lo que me permite tanto a mí mientras se desarrolla, así como a usted una manera de tener una obra de teatro.

Iescribió todo un sistemahttp://<server>:<port>/demopara hacer que eso suceda con un proyecto increíble llamado EasyNMT.

Demo

[TOC]

Sin embargo, si usted acaba de comprobar que repo usted sabe que hay un problema ... que no ha sido tocado durante AÑOS.

Es una manera sencilla y rápida de obtener una API de traducción sin la necesidad de pagar algún servicio o ejecutar un LLM de tamaño completo para obtener la traducción (lento).

En nuestros posts anteriores, discutimos cómo integrar EasyNMT con las aplicaciones ASP.NET para la traducción de fondo.

**Pero a medida que pasaba el tiempo, las grietas empezaban a mostrarse.**Era hora de algo mejor.

**Como siempre está todo en GitHub y todo gratis para su uso, etc...**Tirador de muelles

  • ✓ 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
  • **Demo ... ver más tarde para la página de demostración!**Una página completa de demostración interactiva (en su mayoría)
  • (eno sólo la raíz)
  • **¿Qué hay de nuevo?**Antes de sumergirnos en el inicio rápido, esto es lo que hace que esta versión cambie de juego:

Actualizaciones importantes (v3.1) - Inteligencia y visibilidadNuevo en v3.1:

  • **¡La última versión aporta mejoras masivas a la fiabilidad, visibilidad de rendimiento y selección inteligente de modelos!**1.
  • Caché de modelo inteligente con visibilidad- Ver exactamente lo que está pasando:
  • Registro de HIT de cachéRegistro del MISS de caché
  • Seguimiento del estado de caché: Muestra el porcentaje de utilización y los modelos cargados
  • Advertencias de desalojo: Borrar alertas cuando la caché está llena y los modelos son desalojados
  • Aumento de la capacidad:
    ====================================================================================================
      🚀 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
    ====================================================================================================
    

**: Tamaño de caché predeterminado alcanzado a 10 modelos (a partir de 6)**Registro del dispositivo por modelo

  • : Vea exactamente qué GPU/CPU utiliza cada modelo2.
  • Progreso de descarga mejorado- No más preguntas si está atascado:
  • Tamaño antes de descargar:
    [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)
    
  • **: Muestra el tamaño total de la descarga (por ejemplo, "Tamaño total: 2,46 GB")**Conteo de archivos
  • : Muestra el número de archivos a descargarVisualización del dispositivo

: Muestra el dispositivo de destino (GPU/CPU) en el bannerBarras de progreso

  • **: Hermoso progreso tqdm para cada archivo (cuando en TTY)**banner de terminación
  • : Borrar el mensaje de éxito cuando el modelo está listoEjemplo de salida
  • 3.:
    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)
    

Selección inteligente de pivotes impulsada por datos- No más intentos ciegos:

  • Lógica de intersección inteligenteLoading mbart50 model on GPU (cuda:0)
  • : Encuentra lenguajes donde existen ambas patas pivotantesModel loaded on device: cuda:0
  • Evita intentos fallidosSuccessfully loaded... on GPU (cuda:0)

: No intentará en→es→bn si es→bn no existeEjemplo para en→bn

  • Prioridad de retrocesoen->hi, hi->bn
  • : inglés → español → francés → alemán → chino → ruso
  • Registro transparente[Pivot] Both legs loaded and cached. Ready to translate.

: Vea exactamente por qué cada pivote fue elegido o omitido4.

  • Retroceso automático corregido
    • No más dobles:model_familySiempre trata de retrocesos
  • : Incluso si la familia preferida "debería" apoyar al par

Único intento por familia

: No más reintentar el mismo modelo dos vecesFlujo de ejemplo

  • Claridad de la GPU
    • Siempre sepa dónde están sus modelos:
  • Cada carga de modelo muestra:
  • Después de la carga confirma:

**El mensaje de éxito incluye:**6.

  • Caché modelo Pivot- Reutilización eficiente del pivote:
  • **Ambas patas de pivote caché por separado:**La próxima vez que en→hi o hi→bn necesita, el caché instantáneo golpeó!
  • **Limpiar el registro:**7.
  • Selección de modelos por solicitud

**- Ya trabaja en demo:**Demo desplegable permite seleccionar opus-mt, mbart50, o m2m100

  • Respetos del motor
  • parámetro por solicitud
  • Modelos en caché separados por familia para conmutación instantánea
  • Principales actualizaciones (v3.0)
  • 1.requirements-prod.txtPágina de demostración mejorada

**- Interfaz interactiva preparada para la producción:**Diseño completo de la vista (100vw/100vh) para la experiencia de traducción inmersiva

  • **Selección de menús desplegables temáticos adecuados (no más entrada/lista de datos torpes)**Conmutación de la familia del modelo en vivo (Opus-MT, mBART50, M2M100)
  • Carga de lenguaje dinámico basada en el modelo seleccionadoÁreas de salida desplazables para grandes traducciones
  • **2.**Predeterminados optimizados para el rendimiento
    • "Lo más rápido posible" fuera de la caja:
  • GPU

**: FP16 activado, BATCH_SIZE=64, MAX_INFLIGHT=1 (óptimo para una sola GPU)**CPU

  • : WEB_CONCURRENCY=4, MAX_INFLIGHT=4, BATCH_SIZE=16 (utiliza todos los núcleos)
  • Cierre rápido
  • : 5 segundos de tiempo fuera elegante (no más 20 segundos cuelga)
  • Los contenedores se detienen limpiamente sin miedo a los mensajes SIGKILL
  • Optimización de la construcción de la producción

**- Imágenes más pequeñas y rápidas:**Dependencias de prueba eliminadas (pytest, pytest-cov) de las construcciones de producción

  • Guarda ~200MB por imagenImágenes de CPU: ~8-10GB (completo), ~3-4GB (min)
  • **Imágenes GPU: ~12-15GB (completo), ~6-8GB (min)**Todos los usos
  • para una huella mínima4.

Pruebas completas y pruebas de carga- Validar todo:

  • Suite de pruebas API en vivo
  • (30+ pruebas) para la salud, la traducción, la detección, el descubrimiento
  • Ensayo de carga k6

con pautas de tráfico realistasScripts de validación multiplataforma

  • /discover/opus-mt(PowerShell + Bash)
  • /discover/mbart50Pruebas para descargas de modelos y copia de seguridad de la traducción de pivotes
  • /discover/m2m100Pruebas automatizadas de humo para una validación rápida

**5.**Documentación de despliegue

    • Preparados para la producción desde el primer día:
  • 4 escenarios de afinación: Máximo rendimiento, baja latencia, alta concurrencia, conformado por memoria
  • Docker Componga ejemplos con configuraciones de GPU/CPU

Manifiestos de Kubernetes con PVC, límites de recursos, chequeos médicosEjemplos de casos de contenedores de Azure

  • scottgal/mostlylucid-nmt:cpuOrientación sobre las pruebas de carga y recomendaciones de supervisión:latestExplicación de las compensaciones entre divisas y rendimientos
  • scottgal/mostlylucid-nmt:cpu-min6.
  • scottgal/mostlylucid-nmt:gpuTres familias modelo
  • scottgal/mostlylucid-nmt:gpu-min- Elija lo mejor para sus necesidades:

Opus-MT: 1200+ pares, la mejor calidad (modelos separados)

  • mBART50latest, min, gpu, gpu-min: 50 idiomas, modelo único 2.4GB, 2.450 pares
  • M2M10020250108.143022: 100 idiomas, modelo único 2.2GB, 9.900 pares

Auto-caída- Selecciona inteligentemente el mejor modelo disponible:

  • **Establecer la familia primaria (por ejemplo, Opus-MT para la calidad)**Intenta automáticamente mBART50/M2M100 si el par no está disponible
  • Cobertura máxima sin sacrificar la calidad8.
  • Descubrimiento del modelo- Consulta dinámicamente los modelos disponibles:
    • Todos los más de 1200 pares de Hugging Face

- Todos los pares mBART50- Todos los pares M2M100

  • Imágenes mínimas

- Despliegues más pequeños y flexibles:

No hay modelos precargados (descargar bajo demanda)

Caché persistente con mapas de volumen

Cambiar familias modelo sin reconstrucción**10.**Repositorio de un solo Docker

  • Todas las variantes en un solo lugar: |-----|-----------------|------|-------------|----------| | cpu(olatest) | scottgal/mostlylucid-nmt:cpu) - CPU | cpu-min | scottgal/mostlylucid-nmt:cpu-min- CPU mínima | gpu | scottgal/mostlylucid-nmt:gpu- GPU con CUDA 12,6 | gpu-min | scottgal/mostlylucid-nmt:gpu-min- GPU mínima

**11.**Versión adecuada

    • Todas las imágenes incluyen versión datetime:
  • Etiquetas con nombre (
  • ) siempre señalar a la construcción más reciente
  • Etiquetas de versión inmutables (por ejemplo,

) para fijar construcciones específicas

Etiquetas completas de OCI para el seguimiento de versiones, fechas de compilación y git commits

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

12.

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

Últimas imágenes de base

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

- Mejora de la seguridad y el rendimiento:

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

para imágenes de CPU (direcciones vulnerabilidades de Python 3.11)

CUDA 12,6

con Ubuntu 24.04 para imágenes GPU (última pila 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 con 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 con el tiempo de ejecución de CUDA 12,6)

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

Todas las dependencias actualizadas a las últimas versiones seguras

13.

curl http://localhost:8000/healthz

Advertencias de depredación fijas

- A prueba de futuro:

Transformers_CACHE eliminados obsoletos (ahora utilizando HF_HOME)Compatible con Transformers v5Inicio rápido (5 minutos)

http://localhost:8000/demo/

Demo

### ¿Quieres que te traduzca?

Esta es la forma más simple de ejecutar mayoritariamente lucid-nmt:

Imágenes Docker disponibles

  • Todas las variantes están disponibles desde
  • un repositorio
  • con diferentes etiquetas:

Tag Nombre completo de la imagen Tamaño Descripción Caso de uso

  • (o
  • ~2.5GB CPU con código fuente Implementaciones de CPU de producción
  • ~1.5GB CPU mínimo, sin modelos precargados Caché con mapas de volumen, flexible
  • ~5GB GPU con CUDA 12,6 + fuente Producción GPU despliegues
  • ~4GB GPU mínimo, no hay modelos precargados GPU con caché con mapas de volumen

Imágenes mínimas

  • se recomiendan para:
  • Implementaciones de producción con caché con mapas de volumen
  • Utilizando mBART50 o M2M100 (modelos grandes individuales)

Mantener el tamaño del envase pequeño

  • Flexibilidad para cambiar de modelo de familias sin reconstrucciónInicio más sencillo ( CPU Opus-MT)
    • Tira y corre:
  • **2.**Traducir un poco de texto:
    • Respuesta:
    • GPU Acelerada (10 veces más rápida)

Requiere tiempo de ejecución NVIDIA Docker:

  • Con caché modelo persistenteDescargar modelos una vez y mantenerlos a través de contenedores reinicia:
  • **Linux/Mac:**Ventanas (PowerShell):
  • Ventanas (CMD):¡Los modelos descargan automáticamente en el primer uso y persisten en su directorio local!

Chequeo médico:

  • ¡Ese es el comienzo rápido de 5 minutos!
    • **Para el despliegue de producción, configuración y funciones avanzadas, siga leyendo.**Página de demostración interactiva
    • El servicio incluye un servicio completopágina de demostración interactiva
    • **que hace que sea fácil probar traducciones sin escribir ningún código.**Acceda a ella en:
  • Características de la demostración

La página de demostración proporciona un entorno completo de pruebas de traducción con:

// 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

Selección de idiomas

Auto-poblado lenguaje desplegables desde el servicio en vivo

  • Intercambio de idiomas fuente/objetivo con un solo clic
  • Soporta todos los más de 100 idiomas configurados en el servicio
  • Recorte de texto inteligente

Maneja automáticamente entradas de texto grandes de cualquier tamaño

  • Se divide inteligentemente por párrafos, preservando la estructura del documento
  • Vuelve a la división de oraciones para párrafos muy largos
  • Muestra el progreso de las traducciones multi-chunk ("Traducción del trozo 2/5...")
  • Reensambla pedazos sin costuras con el espaciamiento adecuado

3.

  • Detección de idiomas
  • Detectar el idioma de origen con un clic
  • Automáticamente pobla el menú desplegable del idioma de origen
  • Funciona con texto de hasta 5000 caracteres

4.

  1. Opciones avanzadas:

    • Tamaño del haz
    • : Control de calidad de la traducción (1-10)
    • Valores más altos = mejor calidad pero más lentos
    • Valores inferiores = rendimiento más rápido
  2. Dividir la sentencia:

    • : Conmutar la división automática de frases
    • Activado (por defecto): Divide textos largos en oraciones para una mejor calidad
    • Desactivado: Traduce texto entero como un bloque (más rápido para textos cortos)
  3. Estadísticas en tiempo real:

    • Tiempo de traducción
    • : Muestra la duración real de la traducción del lado del servidor
    • Recuento de caracteres
    • : Cuenta en vivo a medida que escribes

Indicador de situación

: Inactivo → Traducción → Hecho/Error

  • **6.**Descubrimiento de la familia modelo
  • **Explore los pares de traducción disponibles para cada familia de modelos:**Opus-MT
  • : 1200+ pares de idiomasmBART50
  • : 50 idiomas, 2.450 paresM2M100

: 100 idiomas, 9.900 pares/demo/Vea exactamente qué pares de idiomas están disponibles antes de traducir

Cómo funciona el troceo de texto

La demo implementa texto inteligente troceado en el lado del cliente:¿ Por qué usar la demostración?Pruebas rápidasProbar traducciones sin escribir códigoValidar la disponibilidad del par de idiomas

Comparar calidad de traducción con diferentes tamaños de haz

  1. **Cajas de borde de prueba (emoji, símbolos, caracteres especiales)**Ayuda al desarrollo
  2. Consulte el formato exacto de solicitud/respuesta de APIVerificar la salud de los servicios antes de integrar
  3. Rendimiento de prueba con diferentes tamaños de textoDescubra las familias modelo disponibles
  4. Referencia del clienteMuestra los patrones de uso de API adecuados
  5. **Demostra el manejo de errores (429, detección de lenguaje)**Ejemplo de aplicación de trozos
  6. Lógica de reintentar en el mundo realEjemplo de usoTraducción sencilla.
  7. **Pegar texto: "Hola, ¿cómo estás hoy?"**Seleccionar objetivo: Alemán
  8. **Haga clic en "Traducir"**Resultado: "¿Hallo, wie geht es Ihnen heute?"

Traducción de documentos largos

Pegar la entrada completa del blog (5000+ palabras)Demo automáticamente lo divide en piezas manejablesMuestra el progreso mientras cada trozo se traduce

Devuelve el documento completamente traducido

Detección de idiomasPegar texto en un idioma desconocidoHaga clic en "Detectar idioma"

Demo identifica el menú desplegable de idioma y actualizaciones

  • Listo para traducir inmediatamenteDetalles técnicos
  • **La página de demostración es:**Self-contained
  • : Un solo archivo HTML con JavaScript incrustadoCero dependencias
  • : No se necesitan bibliotecas externasMóvil amigable
  • : El diseño responsivo funciona en todos los dispositivosProducción lista
  • : La misma lógica de troceado se puede utilizar en tus aplicacionesAcceda a la demo en vivo en

¡En tu caso de carrera!

  • Los problemas con EasyNMTAhora bien, esto no es dúmping en
  • EasyNMTNo hizo nada más podría y he construido
  • a MUCHOS proyectos que lo utilizan
  • **Se está haciendo largo en el diente, así que... ¿qué problemas tenemos?**Oh, Dios, hay muchos.
  • **EasyNMT fue construido hace casi una década.**La tecnología ha avanzado... y nunca se pretendía que fuera un sistema de nivel de producción.
  • **Estos son algunos de los temas:**Se estrella... mucho.

No está diseñado para recuperarse de problemas tan a menudo simplemente se cae.

  • **Es SUPER PICKY sobre su entrada.**Emoticonos, símbolos, incluso números pueden confundirlo.
  • **No está diseñado para ninguna carga.**Véase supra.
  • **Nunca fue diseñado para ser.**No está diseñado para actualizar sus modelos
  • **o ser (fácilmente) construido con modelos incorporados.**Su material de GPU CUDA es antiguo
  • **tan lento de lo que tiene que ser.**No puedes arreglar nada.
  • El código Python está en la repo, pero de nuevoNo muy bien.

**No hay contrapresión ni cola.**Envía demasiadas peticiones y se acaba.MODEL_FAMILYNo hay observabilidad.

# 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

Cuando las cosas salen mal, estás volando a ciegas.

La solución: Mayormente Lucid-NMTAsí que... decidí construir una nueva y mejorada EasyNMT, ahoraprincipalmente lucid-nmt


  1. Esto no es sólo un trabajo de parche; es una reescritura completa con el uso de la producción en mente.MODEL_FAMILYEsto es lo que lo hace mejor:opus-mtApoyo a la familia multimodelo
  2. La mayoría de Lucid-NMT ahora soporta
  3. tres familias modelo de traducción
  4. , dándole flexibilidad en función de sus necesidades:

Opus-MT (Helsinki-NLP) - Predeterminado

# 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

Cobertura:

  • 1200+ pares de traducción para más de 150 idiomasArquitectura:
  • Modelo separado por dirección de traducciónCalidad:
  • Mejor calidad general de la traducciónCaso de uso:
  • Traducciones de producción donde la calidad importaTamaño del modelo:

300-500MB por dirección

# 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

Ejemplo

Español→Alemán es un modelo diferente al alemán→Inglés

  1. **mBART50 (Facebook)**Cobertura:
  2. 50 idiomas, traducción completaArquitectura:
  3. Modelo multilingüe únicoCalidad:
  4. Buena calidad, especialmente para los principales idiomasCaso de uso:
  5. Despliegues con limitaciones de espacio o muchos pares de idiomasTamaño del modelo:
  6. **~2.4GB (modelo único para los 50 idiomas)**Ventaja:
  7. Un modelo maneja 2.450 pares de traducciónM2M100 (Facebook)
  8. **Cobertura:**100 idiomas, traducción completa
  9. **Arquitectura:**Modelo multilingüe único
  10. **Calidad:**Buena calidad con una cobertura lingüística más amplia
  11. **Caso de uso:**Cobertura lingüística máxima-minTamaño del modelo:

~2.2GB (modelo único para los 100 idiomas)

Ventaja:

Un modelo maneja 9.900 pares de traducción

Cambiar es fácil.

    • sólo establecer el
  • variable de entorno:
  • Modelo Automático Familia Retroceso - ¡NUEVO!

Una de las nuevas características más poderosas es

  • respaldo automático entre familias modelo
  • Esto asegura la máxima cobertura del par de idiomas mientras prioriza la calidad de la traducción.

**Cómo funciona:**Usted establece un primario

  • **(por ejemplo,**para la mejor calidad)
  • Cuando se solicita un par de traducción no disponible en la familia primariaEl sistema intenta automáticamente la siguiente familia en el orden de reserva
  • Esto continúa hasta que se encuentre un modelo adecuadoEscenario de ejemplo:

Prestaciones:

Cobertura máxima:

Soporta más de 100 idiomas sin administrar múltiples implementaciones

  • Prioridad de calidad:
  • Siempre utiliza el mejor modelo disponible para cada par
  • Configuración cero:
  • Funciona automáticamente, sin necesidad de intervención manual

Registro transparente:

  • Ver qué familia modelo se utilizó para cada traducción
  • Configuración:
  • Esta característica es perfecta para entornos de producción donde desea la máxima cobertura sin sacrificar la calidad!
  • Mejoras clave

Apoyo a la familia multimodelo

# 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/

- Elija entre Opus-MT (1200+ pares), mBART50 (50 idiomas), o M2M100 (100 idiomas).

Parámetros de descubrimiento de modelos

    • Consulta dinámicamente los modelos disponibles de Hugging Face para cada familia.
  • Manejo de entradas robusto
    • ¿Emoji?
  • ¿Números?

¿Símbolos?

  • Vamos.
  • El servicio ahora incluye desinfección completa de entradas y enmascaramiento de símbolos.
  • Solicitar cola y contrapresión
    • Cola integrada basada en semáforos con estimaciones inteligentes de reintento después.

Caché modelo LRU

  • Administra automáticamente VRAM desalojando modelos antiguos cuando la caché está llena. |--------|------|------| Soporte CUDA moderno
  • Utiliza PyTorch con CUDA 12.6, soporta FP16/BF16 para mejoras de velocidad 2x. Observabilidad lista para la producción
  • Controles de salud, sondas de preparación, estado de caché, registro estructurado. Agraciado cierre

- No más solicitudes de huérfanos o estado corrupto.

API compatible con EasyNMT

    • Reemplazo de las integraciones existentes.
  • Cambio de traducción de Pivot
    • Si un par de idiomas directos no está disponible, se encamina automáticamente a través del inglés (o el pivote elegido).
  • Imágenes Docker mínimas
    • Nuevo

variantes con caché con mapas de volumen para implementaciones más pequeñas.

  • ⚠️ 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

¿Por qué NMT sobre LLMs para la traducción?

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)

Usted podría preguntarse: "¿Por qué utilizar un servicio NMT dedicado cuando LLMs como GPT-4, Claude, o Llama puede traducir?" Gran pregunta.

Aquí está la comprobación de la realidad basada en el uso de la producción:

  • Velocidad: 10-100x Más rápido
  • NMT (principalmente lúcido-nmt):
  • CPU: ~0.3-1.0 segundos por frase
  • GPU (FP16): ~0,05-0,2 segundos por frase
  • Procesamiento por lotes: más de 50 oraciones/segundo en GPU
  • LLMs:

GPT-4: 3-10 segundos por solicitud (latencia API + generación)

  • Llama 3 70B: 5-15 segundos por oración (local)
  • Claude 3: 2-8 segundos por solicitud (latencia API)
  • Ejemplo real:
  • Traduciendo un post de blog de 1000 palabras:
  • En su mayoría lucid-nmt (GPU)

: 5-10 segundos

API GPT-4**: 30-60 segundos**Llama local 70B

  • : 2-5 minutos
  • Cuando estás auto-traduciendo cientos de posts de blog a más de 12 idiomas, esa diferencia de velocidad es MASA.
  • Tamaño del modelo: 500MB vs 140GB
  • Modelos NMT:

Opus-MT (por dirección): 300-500MB

mBART50 (los 50 idiomas): 2,4GBM2M100 (los 100 idiomas): 2,2GB

Total para más de 100 idiomas: ~2,2GB

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. Llama 3 8B: ~16GBLlama 3 70B: ~140GB
  2. Mixtral 8x7B: ~90GBGPT-4: No disponible para auto-anfitriones
  3. **Impacto en el almacenamiento:**Requisitos de recursos: Laptop vs.
    • **Despliegue de CPU NMT:**Funciona bien en: 2 núcleos de CPU, 4 GB de RAM
    • Imagen Docker: 1,5-2.5GBInferencia: solamente CPU, no se necesita GPURetry-AfterCosto: $10-20 por mes VPS
  4. **Requisitos de LLM:**Llama 3 70B: Necesita 80GB + VRAM (A100 GPU)
    • Modelos 7B-13B más pequeños: Todavía 16-32GB mínimo de RAM
    • Costos API: $0.03-0.30 por 1000 tokens (suma rápido!)
    • Auto-anfitrión: $1000+/mes para GPU grave
    • Comparación de costos reales para 10,000 traducciones de posts de blog:
  5. **# Método # # Costo # # Tiempo #**Principalmente lucid-nmt (CPU) $20/mes VPS 2-3 horas
    • $50/mes GPU VPS 15-30 minutos
    • GPT-4 API $150-300 $8-15 horas
    • Claude API $200-400 $6-12 horas
  6. Local Llama 70B $1000+/mes hardware 20-40 horasCalidad: Propósito-Construido vs. Propósito General

**Puntos fuertes de la NMT:**Formado específicamente para la traducciónRetry-AfterCalidad consistente (misma entrada = misma salida)

No hay "alucinaciones" - traducción pura

Maneja bien el contenido técnico, el código, el formato

No se necesita ingeniería rápida

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

Desafíos relacionados con la ordenación sostenible de las tierras:

Escenario de ejemplo:

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" Cuándo usar cada uno (👋) (!) (:) ($99.99)


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

Usar NMT (principalmente lúcido-nmt) cuando: Usted necesita una traducción consistente y rápida a escala Cuestiones presupuestarias (autoasentamiento o gran volumen)


### 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]

Está traduciendo contenido técnico, código, datos estructurados

  • Necesita salida determinista (misma entrada = misma salida)
  • Quiere ejecutarse en CPU o hardware modesto
  • Estás construyendo tuberías de traducción automatizadas.

Use LLMs cuando:

Necesitas adaptación creativa, no traducción literal.

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 --> [*]

El contexto y los matices culturales importan más que la velocidad

  • Estás haciendo traducciones puntuales de bajo volumen.
  • Usted necesita traducir + resumir + reescribir en un paso
  • Usted está bien con los costos variables y el procesamiento más lento
  • La línea de fondo
  • Por

traducción automatizada del blog

(mi caso de uso), NMT es el ganador claro:

# 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

Traduce más de 100 entradas de blog a 12 idiomas en ~30 minutos (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

Se ejecuta con un VPS de $50 al mes

Calidad constante en todos los puestos

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

Configuración total: Un contenedor Docker

Probar esto con LLMs costaría cientos de dólares al mes en tarifas de API o requeriría un servidor GPU de $2000+ para auto-host.

La diferencia de velocidad por sí sola hace de NMT la única opción práctica para las tuberías de traducción de producción.

TL;DR:

NMT está diseñado específicamente para la traducción, se ejecuta en hardware modesto, y es 10-100x más rápido que LLMs. Si necesita una traducción rápida, consistente y rentable a escala, NMT gana mano a mano.

# 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}")

Descripción general de la arquitectura

  • OrderedDictEl flujo de petición es sencillo:
  • Clienteenvía una solicitud de traducción a API Gateway (Gunicornio + Uvicorn trabajadores)
  • API Gatewayrutas a Traduction Endpoint
  • Comprobación de la capacidad: Sistema comprueba si tiene capacidad para manejar la solicitud

Sí

→ Solicitud va al Servicio de Traducción inmediatamente

# 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)

No

  • → Solicitud en cola, el cliente recibe HTTP 429 conencabezado
  • Servicio de Traducciónprocesa la solicitud a través de la tubería:
  • Sanitización de entradas y división de oracionesEnmascaramiento de símbolos (emojis, caracteres especiales)
  • Traducción usando modelos en cachéDesenmascaramiento de símbolos y procesamiento posterior

Caché modelo

(LRU) ofrece modelos de traducción:

# 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)
        )

Caché → Respuesta rápida

  • Caché falta → Carga desde HuggingFace HubCaché completo → Modelos antiguos de auto-évito, memoria CUDA claramax_inflight)
  • Respuesta (@asynccontextmanagerdevuelve al cliente
  • **Diseño de claves:**El mecanismo de contrapresión (queue + HTTP 429) evita caídas bajo carga.
  • Cuando está abrumado, las colas de servicio solicita en lugar de morir, dando a los clientes tiempo de reintentar inteligente a través decabeceras.
  • Cómo funciona: Buceo profundoRequest Flow

Cuando llega una solicitud de traducción, esto es lo que sucede:

Tubería de procesamiento de entrada

# 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

El servicio utiliza una sofisticada tubería multietapa para manejar texto desordenado del mundo real:

# 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 🚀"

Enmascarado: "Hello MSK0 worldMSK1 PriceMSK2 MSK3"

  • **Fuente: "Hola mundo"**Mala traducción: "Hola mundo!!!!!!!"
  • **Limpiado: "Hola mundo" # Elimina el bucle !!!!**Esto garantiza:👋Los modelos no se ahogan con entradas enormes__SYMBOL_0__Podemos lote eficientemente
  • El contexto se preserva dentro de límites razonablesCaché de modelos y gestión de memoria

La memoria caché LRU es inteligente sobre la memoria GPU:

Por qué esto importa:

# 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 memoria de la GPU es preciosa

  • Los modelos de traducción son de 300-500MB cada unoLos modelos de carga son lentos (1-3 segundos).!?…Mantenemos los 6 modelos más recientes en caliente
  • Los modelos antiguos son automáticamente desalojadosEncolado y contrapresión
  • **En lugar de estrellarse bajo carga, las colas de servicio solicitan:**La estimación de reintento es inteligente:
  • Traducción de PivotNo todos los pares de idiomas tienen modelos directos en Hugging Face.

¿Solución?

Pivot a través del inglés:

# 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

Esto duplica la latencia, pero asegura la cobertura para todos los pares de idiomas soportados.

  • Código Buceo Profundo: Características Cool Explicado (httpxVamos a explorar algunas de las partes más interesantes de la base de código!
  • **Estos son patrones de producción reales que hacen que el servicio sea robusto y eficiente.**Cada fragmento incluye explicaciones adecuadas para desarrolladores no pitónicos.
  • **1.**Caché LRU inteligente con gestión de memoria GPUenUna de las características más geniales es la caché de modelo inteligente que sabe manejar la memoria GPU:de¿Qué está pasando aquí?Helsinki-NLP/opus-mt-en-de
  • : Como un diccionario regular, pero recuerda los artículos de orden fueron añadidosLRU (menos utilizado recientemente)

: Cuando la caché está llena, echa el modelo que no se ha utilizado en el tiempo más largo

Limpieza de la 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()

: Al desalojar un modelo, lo movemos explícitamente a la memoria de la CPU y le decimos a la GPU que libere sus recursos

  • ¿Por qué importa?: Sin esto, la memoria GPU se llenaría y se estrellaría después de cargar 2-3 modelos!
  • **2.**Caída automática de la familia del modelomax_inflight=1Esta característica inteligente intenta múltiples proveedores de modelos de IA automáticamente si el primero no tiene el par de idiomas que necesita:max_inflight=4¿Qué está pasando aquí?
  • Cadena de retroceso: Si el Opus-MT no tiene ucraniano→Francés, pruebe automáticamente mBART50, luego M2M100DEVICE=cuda:1
  • Sin intervención manual: Los usuarios sólo solicitar una traducción y obtener el mejor modelo disponible
  • Manipulación de errores: Si todas las familias fracasan, lanzamos un error claro con la última razón de fracaso

Caché inteligente

: Los modelos exitosos se guardan en caché con la clave del par de idiomas

# 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.

# 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

Solicitud de cola con contrapresión (HTTP 429)

  • Cola de producción-grado que evita que el servidor se desplome bajo carga pesada:¿Qué está pasando aquí?
  • Semaforo: Como un portero en un club - sólo deja entrar a N personas a la vez (N =
  • Gestor de contextos): Rastrea automáticamente las métricas y limpia
  • Reintentar inteligente después: Le dice a los clientes "volver en 25 segundos" basado en la profundidad de la cola y el tiempo promedio de solicitudRetry-AfterPromedio móvil exponencial

: Suaviza los picos en la duración de la solicitud

  • ¿Por qué importa?: Bajo carga pesada, devuelve HTTP 429 en lugar de estrellarse o colar infinitamente
  • **4.**Magia de enmascaramiento de símbolos
  • **Preserva caracteres especiales (emojis, símbolos) que los modelos de traducción podrían arruinar:**Uso de ejemplo:
  • **¿Qué está pasando aquí?**Patrón Regex
  • : Coincide emoji y símbolos especiales Unicode rangosSistema de marcadores de posición
  • : Swapscon

temporalmente

¿Por qué importa?

: Modelos de traducción a veces corruptos o eliminar emojis - esto los preserva perfectamente!

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

Recorte de texto inteligente

# 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

Rompe textos largos en trozos que se ajustan a los límites del modelo mientras preserva los límites de la oración:

  • **¿Qué está pasando aquí?**Dividición de la sentencia

    • opus-mt: Utiliza regex para dividir en
    • mbart50mientras se conserva la puntuación
    • m2m100Picaduras codiciosas
  • : Envasa tantas frases como sea posible en cada trozo sin exceder el límiteDividir el límite de la palabra

    • 1: Si una sola frase es demasiado larga, se divide en espacios en lugar de cortar la palabra media
    • 0¿Por qué importa?
  • **: Los modelos de traducción tienen límites de entrada (generalmente 512-1024 tokens).**Esto asegura que nunca los superemos mientras mantenemos el contexto intacto.

    • 6."opus-mt,mbart50,m2m100"Descubrimiento del modelo de Async con Caching
    • Dinámicamente descubre los modelos de traducción disponibles de HuggingFace:"m2m100,mbart50,opus-mt"¿Qué está pasando aquí?
  • Cliente HTTP de Async): No bloquea las solicitudes HTTP a HuggingFace

    • Caché en caché basado en el tiempo/models: Almacena los resultados durante 1 hora para evitar la limitación de tarifas-v ./model-cache:/models
    • Análisis del nombre del modelo
    • : Extractos

y

  • fp16desde
  • bf16¿Por qué importa?
  • fp32: HuggingFace tiene más de 1200 modelos Opus-MT.

Consultarlos a todos tardan ~10 segundos.

# 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

¡El almacenamiento en caché lo hace instantáneo!

# 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

Detección automática del dispositivo

# 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=" "

Detecta y utiliza automáticamente la GPU si está 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á pasando aquí?

# 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"}, ...]

Detección de GPU

# Enable two-hop translation via pivot
PIVOT_FALLBACK=1

# Pivot language (usually English)
PIVOT_LANG=en

: Utiliza PyTorch para comprobar 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

Autoconfiguración

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

: Sets

# 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

sobre la GPU (evitar la fragmentación de la VRAM) vs

en CPU (maximizar el paralelismo)

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

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

Selección del dispositivo

# 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
}

: Puede apuntar GPU específica con

# 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
}

Registro

# 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"]}

: Muestra el nombre de GPU y VRAM al inicio para depuración

# 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

Patrón monoton

# 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

: Una instancia compartida en toda la aplicación

Promedio de movimiento exponencial para reintentar-después

Procesamiento suave después de la estimación que se adapta a la duración real de la solicitud:

Ejemplo

.\build-all.ps1

¿Qué está pasando aquí?

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

EMA (media exponencial móvil)

: Como una media ponderada que da más importancia a los valores recientesFactor de suavización (α):

  1. : Controla la rapidez con la que nos adaptamos a los cambios (latest, min, gpu, gpu-min¿Por qué no simplemente promedio?
  2. : EMA se adapta más rápido a los cambios mientras filtra los picos¿Por qué importa?20250108.143022: Da a los clientes realistas

tiempos que se adaptan a la carga actual del sistema

# 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

Estos patrones de código demuestran prácticas de Python de grado de producción:

Gestión de los recursos

  • : Limpieza explícita de la memoria GPUDegradación agraciada
  • : Retorno automático entre proveedores de modelosManejo de la contrapresión
  • : Cola + HTTP 429 en lugar de choquesIntegridad de los datos
  • : El enmascaramiento de símbolos preserva caracteres especialesOptimización del rendimiento

: Caché inteligente, troceado y procesamiento paralelo

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

Observabilidad: Registro detallado y seguimiento de métricas.

¡Cada una de estas características resuelve un verdadero problema de producción que causaría fallos, errores o mala experiencia de usuario sin ellos!

Guía de configuración

# 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

El servicio es altamente configurable a través de variables de entorno.

# 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"

Aquí está la guía completa:

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:

Selección de dispositivos

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

Configuración del modelo

Nueva configuración explicada:

  1. MODELO_FAMILIA

    EASYNMT_MODEL_ARGS='{"torch_dtype":"fp16"}'
    
    • : Elija qué familia modelo utilizar como primario
    • : Mejor calidad, 1200+ pares, modelos separados
    • : Buena calidad, 50 idiomas, modelo único de 2,4 GB
  2. : Buena calidad, 100 idiomas, modelo simple 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. : Pruebe automáticamente otras familias si el par no está disponible

    WEB_CONCURRENCY=1
    MAX_INFLIGHT_TRANSLATIONS=1
    
  5. (predeterminado): Habilitado - cobertura máxima

    MAX_CACHED_MODELS=10  # Keep more models in VRAM
    
  6. : Discapacitado - modo unifamiliar estricto

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

MODELO_FALLBACK_ORDER

  1. : Orden de prioridad para reserva

    EASYNMT_BATCH_SIZE=8
    
  2. Predeterminado:

    MAX_WORKERS_BACKEND=4
    MAX_INFLIGHT_TRANSLATIONS=4
    WEB_CONCURRENCY=2
    
  3. (la calidad primero)

    PERFORM_SENTENCE_SPLITTING_DEFAULT=0
    

Alternativa:

  1. (Primero cobertura)

    // Bad: 100 separate requests
    for (const text of texts) {
      await translate(text);
    }
    
    // Good: 1 batch request
    await translate(texts);
    
  2. MODELO_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. : Almacenamiento de modelos persistentes a través de volúmenes Docker

    // Reuse HTTP connections
    const agent = new https.Agent({ keepAlive: true });
    
  4. Establecer a

    // 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");
    

y volumen del mapa:

Los modelos persisten en los reinicios del contenedor

  1. Caché compartida entre múltiples contenedoresopciones de tipo torch_d:
  2. (float16): 2 veces más rápido en GPU, la mitad de la memoria, pérdida de calidad insignificante(bfloat16): Mejor estabilidad numérica que fp16, requiere GPUs modernas
  3. (float32): Plena precisión, más lento pero más precisoConfiguración de la traducción
  4. Colaje y rendimientoSanación de entradas
  5. Procesamiento de sentenciasEnmascaramiento de símbolos
  6. Comportamiento de la respuestaRetroceso de pivote
  7. RegistroMantenimiento

Guinicornio (Docker)

Ejemplos de uso

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

Traducción básica

# 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
}

Traducción por lotes (recomendado)

Detección automática del lenguaje

Solo detección de idiomas |---------|---------|-----------------| | Puntos finales de la ObservabilidadManejo de la contrapresión | Construcción y versionesTodas las imágenes Docker ahora incluyen versiones y metadatos adecuados para el seguimiento. | Construcción rápidaConstruya todas las 4 variantes con versión automática de la fecha y hora: | **Ventanas:**Linux/Mac: | Estrategia de versionesCada construcción crea | dos etiquetasEtiqueta con nombre | ) - siempre apunta a la más recienteEtiqueta de la versión | (por ejemplo,) - instantánea inmutable | **Ejemplos:**Etiquetas OCI | **Cada imagen incluye metadatos:**Versión | **: Marca de tiempo de construcción (AAAAMMDD.HHMMSS)**Fecha de construcción | : ISO 8601 timestampGit commit

: Short SHA

Variante

: cpu-full, cpu-min, gpu-full, o gpu-minInspeccione las etiquetas:

Para instrucciones detalladas de construcción e integración CI/CD, ver

  • BUILD.mdMAX_QUEUE_SIZE
  • Despliegue
  • Despliegue de CPUMAX_INFLIGHT_TRANSLATIONSDespliegue de la GPU
  • Docker Composite

Despliegue de Kubernetes

Optimización del rendimientoLista de verificación de la optimización de la GPU

Usar precisión FP16

  • 2 inferencias más rápidasENABLE_QUEUE=1
  • La mitad del uso de VRAM

Pérdida de calidad insignificante para la traducción

Tamaño del lote de sintoníaModelos de precarga en caliente

Un solo trabajador por GPU

  • Aumentar el tamaño de la cachéEASYNMT_BATCH_SIZE
  • Tamaño del haz inferior para el rendimientoMAX_CACHED_MODELS
  • Lista de verificación de optimización de CPUEASYNMT_MODEL_ARGS='{"torch_dtype":"fp16"}'
  • Tamaño del lote inferiorWEB_CONCURRENCY=1Aumentar el paralelismoMAX_INFLIGHT_TRANSLATIONS=1

Desactivar la división de oraciones para textos cortos

Mejores prácticas del clienteSolicitudes por lotes

Respetar después de volver a intentarlo

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

Usar la conexión de conexión

Agrupar por pares de idiomas Helsinki-NLP/opus-mt-{src}-{tgt}Monitorización y Observabilidad

Métricas clave para rastrear

  • Rendimiento de la traducciónPIVOT_FALLBACK=1(solicitudes/seg)
  • Latencia mediacurl http://localhost:8000/lang_pairs

(p50, p95, p99)

Profundidad de la cola(Cuento de espera actual)

Tasa de éxito de caché

  • (% de solicitudes golpeando caché)MASK_EMOJI=0Tasa de errorMASK_PUNCT=0
  • (5xx respuestas)SYMBOL_MASKING=0

Utilización de la GPU

(si procede)

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; }
}

Uso de memoria

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

(VRAM para GPU, RAM para CPU)

Ejemplo de Métricas Prometeo**Si integra Prometheus (no integrado, pero fácil de añadir):**Ejemplo de registro estructurado

Usted puede enchufar esto a Elasticsearch, CloudWatch, o cualquier agregador de registro.

Comparación: EasyNMT vs. principalmenteLucid-NMT

  • Característica # # EasyNMT # # Mayormente Lucid-NMT

  • Estabilidad
  • Crashes con frecuencia Producción-listo, manejo de error elegante

Manejo de entradas

  • Falla en los emojis/símbolos Robusta sanitización + enmascaramiento de símbolos
  • Retraso en la presión
  • Ninguno, OOMs bajo carga Semaphore + cola con reintento-después

Observabilidad

  • Mínimo Salud/listo/caché endpoints, registros estructurados
  • Apoyo a la GPU
  • CUDA 10.x (anterior) CUDA 12.6, FP16/BF16 apoyo
  • Gestión de modelos

Manual, no caché caché LRU con auto-evitación

  • Tratamiento de las sentencias
  • Desciframiento básico Descifrado inteligente + loteado
  • Traducción de Pivot
  • No Retroceso automático vía Inglés
  • Apagado agraciado

# No # Sí, con tiempo muerto

ConfiguraciónLimitado 40+ env vars para el ajuste finoCompatibilidad con API

Endpoints EasyNMT 100% compatibles + extensiones

  • Calidad del códigoInmantenido, monolítico Modular, tecleado, probado
  • Solución de problemas429 Demasiadas peticiones
  • **Causa:**La cola está llena.
  • **Solución:**Aumento
  • **Añadir más réplicas (escalado horizontal)**Aumento
  • **(si tienes espacio para la cabeza)**Reducir el tamaño de los lotes de los clientes
  • 503 Servicio no disponibleCausa:
  • **La cola está desactivada y todas las ranuras están ocupadas.**Solución:

Habilitar la cola:

# 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"}'

Aumente el límite de vuelo si tiene recursos

OOM (fuera de memoria) en la GPU

Causa:

Tamaño del lote demasiado alto o demasiados modelos en caché.


Solución:

y

Primera solicitud lenta[Causa:

Modelo no precargado.

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

Finding related posts...
logo

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