Clearwave48 commited on
Commit
bfcdaa3
Β·
verified Β·
1 Parent(s): 6ec3b40

Update translator.py

Browse files
Files changed (1) hide show
  1. translator.py +73 -30
translator.py CHANGED
@@ -5,16 +5,20 @@ Fallback chain:
5
  1. Helsinki-NLP β€” dedicated per-language model (best for te/hi/ta/kn)
6
  2. NLLB-1.3B β€” covers all other languages
7
  3. Google Translate β€” last resort fallback
 
8
  LANGUAGE ACCURACY (after upgrade):
9
  Telugu (enβ†’te): 85% (was 82% with NLLB)
10
  Hindi (enβ†’hi): 87% (was 84% with NLLB)
11
  Tamil (enβ†’ta): 84% (was 81% with NLLB)
12
  Kannada (enβ†’kn): 83% (was 80% with NLLB)
13
  Others : NLLB handles (unchanged)
 
14
  FIXES IN THIS VERSION:
 
 
 
 
15
  - Pre-loads Telugu + Hindi models at startup in background thread
16
- so first user request is fast instead of waiting 2-3 minutes
17
- - Summarize kept for API compatibility
18
  - Telugu/Indic sentence ending (ΰ₯€) in sentence splitter
19
  - Reduced chunk size for Indic languages (subword tokenization)
20
  """
@@ -46,6 +50,15 @@ HELSINKI_MODELS = {
46
  ("en", "ru"): "Helsinki-NLP/opus-mt-en-ru", # English β†’ Russian
47
  }
48
 
 
 
 
 
 
 
 
 
 
49
  # NLLB codes (fallback for languages not in Helsinki map)
50
  NLLB_CODES = {
51
  "en": "eng_Latn", "te": "tel_Telu", "hi": "hin_Deva",
@@ -55,6 +68,9 @@ NLLB_CODES = {
55
  "ru": "rus_Cyrl",
56
  }
57
 
 
 
 
58
  INDIC_LANGS = {"te", "hi", "ta", "kn", "ar"}
59
  CHUNK_WORDS = 80
60
  CHUNK_WORDS_INDIC = 50
@@ -64,7 +80,8 @@ MAX_TOKENS = 512
64
 
65
  class Translator:
66
  def __init__(self):
67
- self._helsinki_models = {} # cache: model_id β†’ pipeline
 
68
  self._pipeline = None
69
  self._tokenizer = None
70
  self._model = None
@@ -91,7 +108,7 @@ class Translator:
91
  model_id = HELSINKI_MODELS.get((src, tgt))
92
  if model_id:
93
  print(f"[Translator] Pre-loading {src}β†’{tgt} ({model_id})...")
94
- self._get_helsinki_pipeline(model_id)
95
  print(f"[Translator] βœ… {src}β†’{tgt} pre-loaded and ready!")
96
  except Exception as e:
97
  print(f"[Translator] Pre-load {src}β†’{tgt} failed: {e}")
@@ -110,16 +127,16 @@ class Translator:
110
  print(f"[Translator] {len(chunks)} chunks ({max_words}w), "
111
  f"{len(text)} chars, {src_lang}β†’{tgt_lang}")
112
 
113
- # ── Special: Indicβ†’English uses Google first (accurate meaning) ──
114
  # Helsinki opus-mt-mul-en transliterates Telugu instead of translating
115
- INDIC_TO_EN = {"te", "kn", "ml", "bn", "gu", "mr", "pa", "ur"}
116
  if src_lang in INDIC_TO_EN and tgt_lang == "en":
117
  try:
118
  result = self._google_chunks(chunks, src_lang, tgt_lang)
119
  if "[Translation failed" not in result[0]:
 
120
  return result
121
  except Exception as e:
122
- logger.warning(f"Google te→en failed ({e}), trying Helsinki")
123
 
124
  # ── Priority 1: Helsinki-NLP ───────────────────────────────────
125
  if (src_lang, tgt_lang) in HELSINKI_MODELS:
@@ -171,19 +188,52 @@ class Translator:
171
 
172
  # ══════════════════════════════════════════════════════════════════
173
  # HELSINKI-NLP β€” PRIMARY
 
 
174
  # ══════════════════════════════════════════════════════════════════
 
 
 
 
 
 
 
 
 
 
 
 
175
  def _helsinki_chunks(self, chunks, src_lang, tgt_lang):
 
176
  t0 = time.time()
177
  model_id = HELSINKI_MODELS[(src_lang, tgt_lang)]
178
- pipe = self._get_helsinki_pipeline(model_id)
179
- results = []
 
 
180
 
 
181
  for i, chunk in enumerate(chunks):
182
  if not chunk.strip():
183
  continue
184
  try:
185
- out = pipe(chunk, max_length=MAX_TOKENS)
186
- results.append(out[0]["translation_text"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  except Exception as e:
188
  logger.warning(f"Helsinki chunk {i+1} failed: {e}")
189
  results.append(chunk)
@@ -193,20 +243,6 @@ class Translator:
193
  short_name = model_id.split("/")[-1]
194
  return translated, f"Helsinki-NLP ({short_name}, {len(chunks)} chunks)"
195
 
196
- def _get_helsinki_pipeline(self, model_id: str):
197
- """Load and cache Helsinki-NLP pipeline β€” one per language pair."""
198
- if model_id not in self._helsinki_models:
199
- from transformers import pipeline as hf_pipeline
200
- print(f"[Translator] Loading {model_id}...")
201
- self._helsinki_models[model_id] = hf_pipeline(
202
- "translation",
203
- model=model_id,
204
- device_map="auto",
205
- max_length=MAX_TOKENS,
206
- )
207
- print(f"[Translator] βœ… {model_id} ready")
208
- return self._helsinki_models[model_id]
209
-
210
  # ══════════════════════════════════════════════════════════════════
211
  # CHUNKING
212
  # ══════════════════════════════════════════════════════════════════
@@ -222,7 +258,7 @@ class Translator:
222
  count += w
223
  if cur:
224
  chunks.append(" ".join(cur))
225
- return chunks
226
 
227
  # ══════════════════════════════════════════════════════════════════
228
  # NLLB β€” FALLBACK
@@ -303,10 +339,17 @@ class Translator:
303
  def _init_nllb(self):
304
  try:
305
  from transformers import pipeline as hf_pipeline
306
- self._pipeline = hf_pipeline(
307
- "translation", model=NLLB_MODEL_ID,
308
- device_map="auto", max_length=MAX_TOKENS,
309
- )
 
 
 
 
 
 
 
310
  print("[Translator] βœ… NLLB pipeline ready")
311
  except Exception as e:
312
  logger.warning(f"NLLB pipeline init failed ({e}), trying manual")
 
5
  1. Helsinki-NLP β€” dedicated per-language model (best for te/hi/ta/kn)
6
  2. NLLB-1.3B β€” covers all other languages
7
  3. Google Translate β€” last resort fallback
8
+
9
  LANGUAGE ACCURACY (after upgrade):
10
  Telugu (enβ†’te): 85% (was 82% with NLLB)
11
  Hindi (enβ†’hi): 87% (was 84% with NLLB)
12
  Tamil (enβ†’ta): 84% (was 81% with NLLB)
13
  Kannada (enβ†’kn): 83% (was 80% with NLLB)
14
  Others : NLLB handles (unchanged)
15
+
16
  FIXES IN THIS VERSION:
17
+ - FIX: Removed "translation" / "text2text-generation" pipeline guessing.
18
+ Newer transformers dropped both for Helsinki models.
19
+ Now uses AutoTokenizer + MarianMTModel directly (always works).
20
+ - FIX: Removed duplicate INDIC_TO_EN block in translate()
21
  - Pre-loads Telugu + Hindi models at startup in background thread
 
 
22
  - Telugu/Indic sentence ending (ΰ₯€) in sentence splitter
23
  - Reduced chunk size for Indic languages (subword tokenization)
24
  """
 
50
  ("en", "ru"): "Helsinki-NLP/opus-mt-en-ru", # English β†’ Russian
51
  }
52
 
53
+ # Language prefix tokens needed by opus-mt-en-mul for target language selection
54
+ # opus-mt-en-mul is a multilingual model β€” you must prepend >>lang<< token
55
+ OPUS_MUL_TARGET_TOKENS = {
56
+ "te": ">>tel<<", # Telugu
57
+ "ta": ">>tam<<", # Tamil
58
+ "kn": ">>kan<<", # Kannada
59
+ "fr": ">>fra<<", # French (if routed here)
60
+ }
61
+
62
  # NLLB codes (fallback for languages not in Helsinki map)
63
  NLLB_CODES = {
64
  "en": "eng_Latn", "te": "tel_Telu", "hi": "hin_Deva",
 
68
  "ru": "rus_Cyrl",
69
  }
70
 
71
+ # Languages that should use Google first for X→English (Helsinki transliterates)
72
+ INDIC_TO_EN = {"te", "kn", "ml", "bn", "gu", "mr", "pa", "ur"}
73
+
74
  INDIC_LANGS = {"te", "hi", "ta", "kn", "ar"}
75
  CHUNK_WORDS = 80
76
  CHUNK_WORDS_INDIC = 50
 
80
 
81
  class Translator:
82
  def __init__(self):
83
+ # Cache: model_id β†’ (tokenizer, model)
84
+ self._helsinki_cache = {}
85
  self._pipeline = None
86
  self._tokenizer = None
87
  self._model = None
 
108
  model_id = HELSINKI_MODELS.get((src, tgt))
109
  if model_id:
110
  print(f"[Translator] Pre-loading {src}β†’{tgt} ({model_id})...")
111
+ self._load_helsinki(model_id)
112
  print(f"[Translator] βœ… {src}β†’{tgt} pre-loaded and ready!")
113
  except Exception as e:
114
  print(f"[Translator] Pre-load {src}β†’{tgt} failed: {e}")
 
127
  print(f"[Translator] {len(chunks)} chunks ({max_words}w), "
128
  f"{len(text)} chars, {src_lang}β†’{tgt_lang}")
129
 
130
+ # ── Special: Indicβ†’English uses Google first ──────────────────
131
  # Helsinki opus-mt-mul-en transliterates Telugu instead of translating
 
132
  if src_lang in INDIC_TO_EN and tgt_lang == "en":
133
  try:
134
  result = self._google_chunks(chunks, src_lang, tgt_lang)
135
  if "[Translation failed" not in result[0]:
136
+ print(f"[Translator] βœ… Google used for {src_lang}β†’en")
137
  return result
138
  except Exception as e:
139
+ logger.warning(f"Google {src_lang}β†’en failed ({e}), trying Helsinki")
140
 
141
  # ── Priority 1: Helsinki-NLP ───────────────────────────────────
142
  if (src_lang, tgt_lang) in HELSINKI_MODELS:
 
188
 
189
  # ══════════════════════════════════════════════════════════════════
190
  # HELSINKI-NLP β€” PRIMARY
191
+ # FIX: Use MarianMTModel directly instead of pipeline() β€” avoids the
192
+ # "Unknown task text2text-generation" error in newer transformers
193
  # ══════════════════════════════════════════════════════════════════
194
+ def _load_helsinki(self, model_id: str):
195
+ """Load and cache Helsinki-NLP (Marian) tokenizer + model."""
196
+ if model_id not in self._helsinki_cache:
197
+ from transformers import MarianMTModel, MarianTokenizer
198
+ print(f"[Translator] Loading {model_id}...")
199
+ tokenizer = MarianTokenizer.from_pretrained(model_id)
200
+ model = MarianMTModel.from_pretrained(model_id)
201
+ model.eval()
202
+ self._helsinki_cache[model_id] = (tokenizer, model)
203
+ print(f"[Translator] βœ… {model_id} ready")
204
+ return self._helsinki_cache[model_id]
205
+
206
  def _helsinki_chunks(self, chunks, src_lang, tgt_lang):
207
+ import torch
208
  t0 = time.time()
209
  model_id = HELSINKI_MODELS[(src_lang, tgt_lang)]
210
+ tokenizer, model = self._load_helsinki(model_id)
211
+
212
+ # opus-mt-en-mul requires a target language prefix token
213
+ prefix = OPUS_MUL_TARGET_TOKENS.get(tgt_lang, "")
214
 
215
+ results = []
216
  for i, chunk in enumerate(chunks):
217
  if not chunk.strip():
218
  continue
219
  try:
220
+ text_in = f"{prefix} {chunk}".strip() if prefix else chunk
221
+ inputs = tokenizer(
222
+ [text_in],
223
+ return_tensors="pt",
224
+ padding=True,
225
+ truncation=True,
226
+ max_length=MAX_TOKENS,
227
+ )
228
+ with torch.no_grad():
229
+ translated_ids = model.generate(
230
+ **inputs,
231
+ num_beams=4,
232
+ max_length=MAX_TOKENS,
233
+ early_stopping=True,
234
+ )
235
+ out = tokenizer.decode(translated_ids[0], skip_special_tokens=True)
236
+ results.append(out)
237
  except Exception as e:
238
  logger.warning(f"Helsinki chunk {i+1} failed: {e}")
239
  results.append(chunk)
 
243
  short_name = model_id.split("/")[-1]
244
  return translated, f"Helsinki-NLP ({short_name}, {len(chunks)} chunks)"
245
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  # ══════════════════════════════════════════════════════════════════
247
  # CHUNKING
248
  # ══════════════════════════════════════════════════════════════════
 
258
  count += w
259
  if cur:
260
  chunks.append(" ".join(cur))
261
+ return chunks or [text] # FIX: never return empty list
262
 
263
  # ══════════════════════════════════════════════════════════════════
264
  # NLLB β€” FALLBACK
 
339
  def _init_nllb(self):
340
  try:
341
  from transformers import pipeline as hf_pipeline
342
+ # NLLB uses "translation" task which is still supported for NLLB models
343
+ try:
344
+ self._pipeline = hf_pipeline(
345
+ "translation", model=NLLB_MODEL_ID,
346
+ device_map="auto", max_length=MAX_TOKENS,
347
+ )
348
+ except Exception:
349
+ self._pipeline = hf_pipeline(
350
+ "text2text-generation", model=NLLB_MODEL_ID,
351
+ device_map="auto", max_length=MAX_TOKENS,
352
+ )
353
  print("[Translator] βœ… NLLB pipeline ready")
354
  except Exception as e:
355
  logger.warning(f"NLLB pipeline init failed ({e}), trying manual")