Реалізація FastAPI EXACT копіює чудовий, але покинутий проект роботи з нейронним апаратом. https: // gitub.com/UKPLAB/EasyNMT).
Але я додав БАГАТЬОХ гарних рис, щоб збільшити надійність і підготувати її до використання у виробничій системі.Думайте про те, що швидкий переклад сам підтримується...).
З самого початку цього блогу, великим захопленням стали статті у автотрансляційних блогах.
ТАК Я знаю, що "Google робить це в браузерах і т.д., але справа не в цьому.mostlylucid-nmtЯ хотіла знати, як це зробити!
Крім того, приємно вітати людей, які не читають англійською (навіть якщо вони читають англійську як другу мову, це важче опрацювати).Тож я вирішив, як це зробити, і поділився тим, як побудувати таку систему.О, і вона дала мені ідеї щодо того, як використовувати текст у ASP.NET для автоматичного локалізації тексту (включаючи динамічний текст) за допомогою SNR & хитрої системи оновлення реального часу. (
Залишайтеся на зв'язкуПо суті, люди пишуть нісенітниці текст, який є ДУЖЕ шумним для машин, щоб ефективно поводитися.
Отже, багато з них з'ясовували, як працювати з FasyNMT (це був справді дослідний проект).
Iзаписано цілу системуhttp://<server>:<port>/demoщоб це сталося з чудовим проектом під назвою EasyNMT.
Це простий, швидкий спосіб отримати API перекладу без потреби платити за якусь послугу або запустити повний розмір LLM для отримання перекладу (повільно).
**Але з часом почали з'являтися тріщини.**Прийшов час на щось краще.
**Як завжди на GitHub і все безкоштовно для використання і т.д.**Docker pulls
Reusing loaded model for en->de (3/10 models in cache)Need to load model for en->fr (3/10 models in cache)Основні оновлення (версія 3. 1) - Розвідка і видимістьСтворити у v3. 1:
====================================================================================================
🚀 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
====================================================================================================
**: Типовий розмір кешу зв' язано з 10 моделями (з 6)**Журналювання за моделюванням пристрою
[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)
: Показує пристрій призначення (GPU/CPU) у банціСмужка поступу
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)
Data- Driven Intesent Pivot Вибір- Більше ніяких сліпих спроб:
Loading mbart50 model on GPU (cuda:0)Model loaded on device: cuda:0Successfully loaded... on GPU (cuda:0): Не буде намагатися en →bn, якщо es→bn не існуєПриклад для en→bn
en->hi, hi->bn[Pivot] Both legs loaded and cached. Ready to translate.: з'ясувати, чому було обрано або пропущено кожен з поворотів4.
model_familyЗавжди пробувати зворотні результати**: Більше ніяких спроб однієї моделі двічі.**Приклад потоку
**Повідомлення про успіх містить:**6.
**- Вже працює в демонстраційному режимі:**За допомогою спадного списку Демонстрація можна обрати opus- mt, mbart50 або m2m100
requirements-prod.txtПокращена демонстрація сторінка**- Готовий до створення інтерактивний інтерфейс:**Повне компонування viewport (100vw/ 100vh) для незахоплюючих перекладів
**: увімкнено FP16, BATCH_ SIZE=64, MAX_ INFWYL=1 (додатковий для окремого GPU)**Процесор
**- Менші, швидші зображення:**Вилучено залежності перевірки (pytest, pytest- cov) з побудови
Точне тестування і завантаження- Перевірте все:
з реалістичними типами трафікуКросплатформові скрипти перевірки
/discover/opus-mt(Повершель + Баш)/discover/mbart50Повернення тестів на створення моделей звантажень і перекладу turn/discover/m2m100Автоматизовані тести на швидку перевірку диму**5.**Документація з впровадженням
Kubernetes показується з PVC, обмеження ресурсів, перевірки стану здоров'яПриклади контейнерів Azure
scottgal/mostlylucid-nmt:cpuЗавантажити вказівки для перевірки та моніторингу:latestОбъясняние распространяемых торговых событийscottgal/mostlylucid-nmt:cpu-min6.scottgal/mostlylucid-nmt:gpuТри зразкові сім'їscottgal/mostlylucid-nmt:gpu-min- Виберіть найкраще для ваших потреб:Opus-MT: 1200+пари, найкраща якість (вимірювання моделей)
latest, min, gpu, gpu-min: 50 мов, єдина модель 2.4GB, 2.450 пар20250108.143022: 100 мов, єдина модель 2.2GB, 9 900 парАвтоматичний відступ- Розумно вибирає найкращу модель:
- Всі пари mBART50- Всі пари M2M100
Немає попередньо завантажених моделей (завантажувати на- деменд)
Перемкнути зразкові сім'ї без перебудовування**Десять.**Сховище окремих об' єктів
cpu(абоlatest) | scottgal/mostlylucid-nmt:cpu) - Процесор
| cpu-min | scottgal/mostlylucid-nmt:cpu-min- Мінімум ЦП
| gpu | scottgal/mostlylucid-nmt:gpu- GPU з CUDA 12.6
| gpu-min | scottgal/mostlylucid-nmt:gpu-min- Мінімум GPU**11.**Правильна версія
Повноцінні мітки OCI для стеження за версіями, датами збирання і внесками git
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"
}'
Останні базові зображення
{
"translated": ["Hallo, wie geht es Ihnen?"],
"target_lang": "de",
"source_lang": "en",
"translation_time": 0.34
}
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
CUDA 12. 6
з Ubuntu 24. 04 для зображень GPU (latest 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 with 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
(сумісна з СУДА 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
Всі залежності оновлено до останніх безпечних версій
13.
curl http://localhost:8000/healthz
Попереджальні попередження про фіксоване амортизацію
Вилучено застаріле REFORMERS_ CCHACE (тепер з використанням HF_ HOME)Сумісний з Transformers v5Швидкий запуск (5 хвилин)
http://localhost:8000/demo/
Ось абсолютний найпростіший спосіб запуску biglucid-nmt:
Доступні зображення панелі
Передня частина зображення має бути розміщена так, щоб вона була розміщена у центрі уваги.
Мінімальні зображення
Зберігати розміри контейнера малими
Потрібний час запуску NVIDIA:
Перевірка здоров'я:
// 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
Автозаселення мов зі спадного меню служби
Автоматично виконувати великі текстові входи будь- якого розміру
3.
Додаткові параметри:
Розділення речень:
Статистика реального часу:
: Бездіяльний → Трансляція → Виконано/ Помилка
: 100 мовами, 9700 пар/demo/Визначити, які пари мов доступні перед перекладом
Демонстрація реалізує інтелектуальний фрагмент тексту на стороні клієнта:Чому слід використовувати демонізм?Швидке тестуванняПеревіряти переклади без коду записуПеревірити доступність пари мов
Порівняти якість перекладу з різними розмірами променя
Вставити весь допис блогу (5000 слів)Демонстрація автоматично зрізає її на частини, які можна налаштуватиПоказує поступ під час перекладу кожного шматка
Виявлення мовиВставити текст невідомою мовоюНатисніть " Вилучити мову "
**Ніяких відштовхувань чи чергування.**Надсилайте забагато запитів, і він просто з'являється.MODEL_FAMILYНіякої упертості.
# 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
Вирішення: здебільшогоLucid-NMTТому... я вирішив побудувати новий та покращений THENMT, заразumcid-nmt
MODEL_FAMILYОсь що робить це кращим:opus-mtБагатомодерна підтримка сім'їOpus- MT (Helsinki- NLP) - типовий
# 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
Обкладинка:
300- 500 Мб на напрямок
# 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
Приклад:
-minРозмір моделі:Переваги:
Перемикання просте
Однією з найсильніших нових рис є
**Як це працює:**Ви встановили основний
Користі:
Підтримка 100+ мов без керування декількома розповсюдженнями
Прозоре ведення журналу:
Багатомодельована підтримка сім' ї
# 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/
Кінцеві точки відкриття моделі
Символи?
Кечування моделей LRU
Сумісний з EasyNMT API
Варіанти з кешем на основі об' єму для менших значень.
Чому НМТ над LLMs для перекладу?
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)
Ось реальна перевірка на основі використання виробництва:
GPT- 4: 3- 10 секунд на запит (покоління латок + створення)
GPT-4 API**: 30- 60 секунд**Локальна Llama 70B
Opus- MT (у напрямку): 300- 500MB
mBART50 (всі 50 мов): 2. 4GBM2M100 (всі 100 мов): 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:
Retry-AfterВартість: 10- 20/ місячний VPS**Міць NMT:**Навчені спеціально для перекладуRetry-AfterВідповідна якість (те саме значення, що і вхід = той самий вивід)
Не потрібно негайної інженерії
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
Скрипт прикладу:
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" Коли використовувати кожен (👋) (!) (:) ($99.99)
**Post-Processing:**
After translation, we remove "symbol loops" - repeated symbols that weren't in the source:
Використовувати NMT (здебільшого вимірюваного- nmt), якщо: Вам потрібен послідовний, швидкий переклад у масштабі Бюджет має значення (збереження або високий об' єм)
### 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]
Ви перекладаєте технічний вміст, код, структуровані дані
Вам потрібна творча адаптація, а не буквальний переклад
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 --> [*]
Контекстні та культурні нюанси важливіші, ніж швидкість
(мій випадок використання), NMT є ясним переможцем:
# 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
Перекладає дописи 100+ блогів на 12 мов у ~30 хвилин (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
Послідовна якість для всіх дописів
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
Загальне налаштування: один контейнер панелі
Лише різниця швидкості робить NMT єдиним практичним вибором для створення транспортних трубопроводів.
NMT побудовано з метою перекладу, працює на звичайному апаратному забезпеченні, він є на 10- 100x швидшим за LLM. Якщо вам потрібно швидкий, послідовний, ефективних результатів перекладу на шкалі, NMT перехоплює руки.
# 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}")
Огляд архітектури
OrderedDictПотік запиту є простим:→ Запит негайно відправляється до перекладацької служби
# 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)
Ні
(LRU) постачає моделі перекладу:
# 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)
)
Удар кешу → Швидка відповідь
max_inflight)@asynccontextmanagerreturn to clientВхідна лінія трубопроводу
# 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
Служба використовує складний багатосмуговий трубопровод для обробки безладного тексту реального світу:
# 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 🚀"
ДЗ: "ось світ має значення MSK0}МСК1 +МСК2 +МСК3"
👋Моделі не задушуються великими входами.__SYMBOL_0__Ми можемо пакетизувати ефективноЧому це є важливим:
# 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
Пам' ять GPU є цінною
.!?…Ми тримаємо 6 найсвіжіших моделей гарячими.Pivot через англійську:
# 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
Це подвоює спізнення, але забезпечує покриття всіх пар мов, які підтримуються.
httpxДавайте дослідимо деякі з найцікавіших частин бази коду!enОднією з найкрутіших можливостей є кеш з кмітливою моделлю, який знає як обробляти пам' ять GPU:deЩо тут відбувається?Helsinki-NLP/opus-mt-en-deОчищення 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()
: Виселяючи модель, ми явно переносимо її до процесорної пам' яті і наказуємо GPU випускати свої ресурси
max_inflight=1Ця кмітлива можливість автоматично перевіряє декілька постачальників комп' ютерної моделі, якщо перша пара не має потрібної пари мов:max_inflight=4Що тут відбувається?DEVICE=cuda:1: Успішні моделі захаращуються ключем до мови
# 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
Запит у чергу з Burpressure (HTTP 429)
Retry-AfterЕкспоненціальний рух середньостатистичний: Згладжує шипи у час запиту
тимчасово
: Моделі перекладу деколи руйнують або усувають емоджі - це зберігає їх у досконалому стані!
# 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
# 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
Розбиває довгі тексти на шматки, які відповідають моделяційним обмеженням під час збереження меж речень:
**Що тут відбувається?**Розбиття речень
opus-mt: Застосовує regex для поділу наmbart50під час збереження пунктуаціїm2m100Жадібний кусень: пакує якомога більше речень до кожного шматка без перевищення обмеженняПоділ на межі слова
1: Якщо одне речення задовге, розбиття на пробіли замість скорочення середнього слова0Чому це важливо**: Моделі перекладу мають вхідні обмеження (зазвичай, 512- 1024 маркерів).**Це гарантує, що ми ніколи не перевищимо їх, зберігаючи контекст непошкодженим.
"opus-mt,mbart50,m2m100"Асинхронне відкриття моделі з кешуванням"m2m100,mbart50,opus-mt"Що тут відбувається?Асинхронний клієнт HTTP): Робить неблоковані запити HTTP до обрізування лисиці
/models: зберігає результати протягом однієї години, щоб уникнути обмеження швидкості-v ./model-cache:/modelsі
fp16відbf16Чому це важливоfp32: У Hacking Face є 1200+ Моделі Operus-MT.# 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
# 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
# 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
# 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=" "
# 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.
# 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"}, ...]
# Enable two-hop translation via pivot
PIVOT_FALLBACK=1
# Pivot language (usually English)
PIVOT_LANG=en
# 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
# Periodically clear CUDA cache (seconds, 0=disabled)
CUDA_CACHE_CLEAR_INTERVAL_SEC=0
# 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
# GET request
curl "http://localhost:8000/translate?target_lang=de&text=Hello%20world&source_lang=en"
# Response
{
"translations": ["Hallo Welt"]
}
# 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
}
# 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
}
# 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"]}
# 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
# 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
Плавна повторна оцінка, яка пристосовується до поточної тривалості запиту:
Приклад:
.\build-all.ps1
Що тут відбувається?
chmod +x build-all.sh
./build-all.sh
: Подібно до середньої ваги, яка надає більшої ваги недавнім цінностям.Коефіцієнт згладжування (⇩):
latest, min, gpu, gpu-minЧому це не є тільки середньостатистичний?20250108.143022: Надає клієнтам реалістичну інформаціючас, який слід застосувати до поточного навантаження на систему
# 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
Керування виконавцями
: Кмітливий кеш, кусочок і паралельна обробка
docker inspect scottgal/mostlylucid-nmt:cpu | jq '.[0].Config.Labels'
Спостереження: Детальне стеження за лісозаготівлею і вимірами.
# 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
# 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"
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:
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
MODEL_ FAMILY
EASYNMT_MODEL_ARGS='{"torch_dtype":"fp16"}'
: хороша якість, 100 мов, єдина модель 2.2 ГБ
# Start high, reduce if you get OOM
EASYNMT_BATCH_SIZE=64 # Try 128 on large GPUs
AUTO_ MODEL_ FALLBACK
PRELOAD_MODELS="en->de,de->en,en->fr,fr->en,en->es,es->en"
: Автоматично випробовувати інші сім' ї, якщо пара недоступна
WEB_CONCURRENCY=1
MAX_INFLIGHT_TRANSLATIONS=1
(типовий): увімкнено - максимальна обкладинка
MAX_CACHED_MODELS=10 # Keep more models in VRAM
: Непрацездатний - строгий режим однієї сім'ї
# beam_size=1 is 3-5x faster than beam_size=5
# Quality difference is often minimal
curl -X POST ... -d '{"beam_size": 1, ...}'
: Порядок пріоритету для повернення назад
EASYNMT_BATCH_SIZE=8
Типове значення:
MAX_WORKERS_BACKEND=4
MAX_INFLIGHT_TRANSLATIONS=4
WEB_CONCURRENCY=2
(перший якість)
PERFORM_SENTENCE_SPLITTING_DEFAULT=0
(перший захист)
// Bad: 100 separate requests
for (const text of texts) {
await translate(text);
}
// Good: 1 batch request
await translate(texts);
MODEL_ CCHE_ 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;
}
}
: Постійне взірцеве сховище на основі томів докерів.
// Reuse HTTP connections
const agent = new https.Agent({ keepAlive: true });
Встановити до
// 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");
Приклади використання
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
# 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
}
Пакетний переклад (рекомендовано)
Лише виявлення мови |---------|---------|-----------------| | Точка завершення спостереженняЯк справлятися з придушенням | Будівництво і перекладТепер у всіх зображеннях Docker міститься відповідна версія і метадані для стеження. | Швидке збиранняЗібрати всі 4 варіанти з автоматичною версією дати: | **Вікна:**Linux/Mac: | Стратегія перекладуСтворення кожної збірки | два міткиМітка з назвою | ) - завжди вказує на останніМітка версії | (e.g.,) - незмінний знімок | **Приклади:**Мітки OCI | **Кожне зображення містить метадані:**Версія | **: Часовий штамп збирання (РРРРМРРД. ГГМСС)**Дата збирання | : ISO 8601 Часовий штампGit- внески
: cpu- full, cpu- min, gpu- full або gpu- minПеревірка міток:
Докладніші настанови щодо збирання і інтеграції CI/CD можна знайти у розділі
MAX_QUEUE_SIZEMAX_INFLIGHT_TRANSLATIONSВпровадження GPUОптимізація швидкодіїСписок перевірок Оптимізації GPU
Використовувати точність FP16
ENABLE_QUEUE=1Пакетний розмірЗаздалегідь завантажувати гарячі моделі
Один працівник на GPU
EASYNMT_BATCH_SIZEMAX_CACHED_MODELSEASYNMT_MODEL_ARGS='{"torch_dtype":"fp16"}'WEB_CONCURRENCY=1Збільшує паралелізмMAX_INFLIGHT_TRANSLATIONS=1Найкращі вправи клієнтаПакетні запити
Повага до повторних спроб
PRELOAD_MODELS="en->de,de->en"
Група за парою мов Helsinki-NLP/opus-mt-{src}-{tgt}Спостереження за і спостереженням
Метрика клавіш для стеження
PIVOT_FALLBACK=1(запити/ сек)curl http://localhost:8000/lang_pairsГлибина черги(поточний рахунок очікування)
Частота влучання у кеш
MASK_EMOJI=0Частота помилокMASK_PUNCT=0SYMBOL_MASKING=0(якщо можна застосувати)
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; }
}
Використання пам' яті
services.AddHttpClient<MostlyLucidNmtClient>(client =>
{
client.BaseAddress = new Uri("http://translator:8000");
client.Timeout = TimeSpan.FromMinutes(3);
});
Приклад Метриці Prometheus**Якщо ви інтегруєте Prometheus (не вбудовано, але просто додати):**Приклад структурованого журналу
Порівняння: EasyNMT vs Mis Lucid- NMT
Обробка вхідних даних
Спостереження
, без cashine LRU cache with auto- evicce}
Налаштування** 40 + env vars для тонкої тиші**Сумісність API
# 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"}'
OOM (Не пам' ять) на GPU
Причина:
Пакетний розмір занадто високий або забагато моделей збережено.
Повільне перше прохання[Причина:
Translation NMT Neural Machine Translation Python FastAPI Docker CUDA PyTorch Transformers Helsinki-NLP Production Microservices API
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.