Transync / transync_inference.py
RohanMuralidharan's picture
Update transync_inference.py
385a1f9 verified
Raw
History Blame Contribute Delete
16.2 kB
""
Transync - Indic Multilingual Translation Inference
Supports 50+ languages including all major Indian languages
"""
import sys
import io
import torch
from transformers import MBartForConditionalGeneration, MBart50Tokenizer
# Fix Windows console encoding
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
# Language code mapping (short code to MBart format)
LANG_CODES = {
'eng': 'en_XX',
'hin': 'hi_IN',
'tel': 'te_IN',
'tam': 'ta_IN',
'mal': 'ml_IN',
'kan': 'kn_IN',
'ben': 'bn_IN',
'guj': 'gu_IN',
'mar': 'mr_IN',
'pan': 'pa_IN',
'urd': 'ur_PK',
'asm': 'as_IN',
'npi': 'ne_NP',
'ory': 'or_IN',
'san': 'sa_IN',
'mai': 'mai_IN',
'brx': 'brx_IN',
'doi': 'doi_IN',
'gom': 'gom_IN',
'mni': 'mni_IN',
'sat': 'sat_IN',
'kas': 'ks_IN',
'snd': 'sd_IN',
# Additional ML50 languages
'ara': 'ar_AR',
'ces': 'cs_CZ',
'deu': 'de_DE',
'spa': 'es_XX',
'est': 'et_EE',
'fin': 'fi_FI',
'fra': 'fr_XX',
'heb': 'he_IL',
'hrv': 'hr_HR',
'ind': 'id_ID',
'ita': 'it_IT',
'jpn': 'ja_XX',
'kat': 'ka_GE',
'kaz': 'kk_KZ',
'khm': 'km_KH',
'kor': 'ko_KR',
'lit': 'lt_LT',
'lav': 'lv_LV',
'mkd': 'mk_MK',
'mon': 'mn_MN',
'mya': 'my_MM',
'nld': 'nl_XX',
'pol': 'pl_PL',
'pus': 'ps_AF',
'por': 'pt_XX',
'ron': 'ro_RO',
'rus': 'ru_RU',
'sin': 'si_LK',
'slk': 'sl_SI',
'swe': 'sv_SE',
'swa': 'sw_KE',
'tha': 'th_TH',
'tgl': 'tl_XX',
'tur': 'tr_TR',
'ukr': 'uk_UA',
'vie': 'vi_VN',
'xho': 'xh_ZA',
'zho': 'zh_CN',
'aze': 'az_AZ',
'fas': 'fa_IR',
'glg': 'gl_ES',
'afr': 'af_ZA',
}
# Reverse mapping for display
CODE_TO_LANG = {
'eng': 'English', 'hin': 'Hindi', 'tel': 'Telugu', 'tam': 'Tamil',
'mal': 'Malayalam', 'kan': 'Kannada', 'ben': 'Bengali', 'guj': 'Gujarati',
'mar': 'Marathi', 'pan': 'Punjabi', 'urd': 'Urdu', 'asm': 'Assamese',
'npi': 'Nepali', 'ory': 'Odia', 'san': 'Sanskrit', 'mai': 'Maithili',
'brx': 'Bodo', 'doi': 'Dogri', 'gom': 'Konkani', 'mni': 'Manipuri',
'sat': 'Santali', 'kas': 'Kashmiri', 'snd': 'Sindhi',
'ara': 'Arabic', 'ces': 'Czech', 'deu': 'German', 'spa': 'Spanish',
'est': 'Estonian', 'fin': 'Finnish', 'fra': 'French', 'heb': 'Hebrew',
'hrv': 'Croatian', 'ind': 'Indonesian', 'ita': 'Italian', 'jpn': 'Japanese',
'kat': 'Georgian', 'kaz': 'Kazakh', 'khm': 'Khmer', 'kor': 'Korean',
'lit': 'Lithuanian', 'lav': 'Latvian', 'mkd': 'Macedonian', 'mon': 'Mongolian',
'mya': 'Burmese', 'nld': 'Dutch', 'pol': 'Polish', 'pus': 'Pashto',
'por': 'Portuguese', 'ron': 'Romanian', 'rus': 'Russian', 'sin': 'Sinhala',
'slk': 'Slovak', 'swe': 'Swedish', 'swa': 'Swahili', 'tha': 'Thai',
'tgl': 'Tagalog', 'tur': 'Turkish', 'ukr': 'Ukrainian', 'vie': 'Vietnamese',
'xho': 'Xhosa', 'zho': 'Chinese', 'aze': 'Azerbaijani', 'fas': 'Persian',
'glg': 'Galician', 'afr': 'Afrikaans',
}
# Load model and tokenizer (cached after first load)
_model = None
_tokenizer = None
_device = None
def _get_device() -> str:
"""Detect and return the best available device (CUDA/CPU)."""
global _device
if _device is None:
if torch.cuda.is_available():
_device = "cuda"
print(f"โœ“ Using GPU: {torch.cuda.get_device_name(0)}")
else:
_device = "cpu"
print("โ„น Using CPU (CUDA not available)")
return _device
def _load_model():
"""Lazy load model and tokenizer with device optimization."""
global _model, _tokenizer
if _model is None:
device = _get_device()
print("Loading Transync model...")
_model = MBartForConditionalGeneration.from_pretrained('.').to(device)
_tokenizer = MBart50Tokenizer.from_pretrained('.')
if device == "cuda":
_model = _model.half() # Use FP16 for faster inference on GPU
print("โœ“ Model ready")
return _model, _tokenizer
def translate_onemt(
text: str,
source_lang: str,
target_lang: str,
max_length: int = 256,
num_beams: int = 5,
temperature: float = 1.0,
repetition_penalty: float = 1.3,
no_repeat_ngram_size: int = 3,
) -> str:
"""
Translate text from source language to target language.
Args:
text: Input text to translate.
source_lang: Source language code (e.g., 'eng', 'hin', 'tel').
target_lang: Target language code (e.g., 'eng', 'hin', 'tel').
max_length: Maximum length of generated translation.
num_beams: Number of beams for beam search.
temperature: Sampling temperature (higher = more diverse).
repetition_penalty: Penalty for repeating tokens.
no_repeat_ngram_size: Size of n-grams to avoid repeating.
Returns:
Translated text.
Raises:
ValueError: If an unsupported language code is provided.
Example:
>>> translate_onemt("Hello, how are you?", "eng", "hin")
'เคจเคฎเคธเฅเคคเฅ‡, เค†เคช เค•เฅˆเคธเฅ‡ เคนเฅˆเค‚?'
"""
if not text or not text.strip():
return ""
model, tokenizer = _load_model()
# Get MBart language codes
src_code = LANG_CODES.get(source_lang, source_lang)
tgt_code = LANG_CODES.get(target_lang, target_lang)
# Validate source language
if src_code not in tokenizer.lang_code_to_id:
valid_codes = sorted(LANG_CODES.keys())
raise ValueError(
f"Unsupported source language: '{source_lang}'. "
f"Supported codes: {', '.join(valid_codes)}"
)
# Validate target language
tgt_token_id = tokenizer.lang_code_to_id.get(tgt_code)
if tgt_token_id is None:
valid_codes = sorted(LANG_CODES.keys())
raise ValueError(
f"Unsupported target language: '{target_lang}'. "
f"Supported codes: {', '.join(valid_codes)}"
)
# Set source language and tokenize
tokenizer.src_lang = src_code
inputs = tokenizer(
text,
return_tensors="pt",
truncation=True,
max_length=max_length,
padding=True,
).to(_device)
# Generate translation
with torch.no_grad():
outputs = model.generate(
**inputs,
forced_bos_token_id=tgt_token_id,
max_length=max_length,
num_beams=num_beams,
no_repeat_ngram_size=no_repeat_ngram_size,
repetition_penalty=repetition_penalty,
temperature=temperature,
early_stopping=True,
)
# Decode
translated = tokenizer.decode(outputs[0], skip_special_tokens=True)
return translated
def translate_batch(
texts: list,
source_lang: str,
target_lang: str,
batch_size: int = 32,
max_length: int = 256,
num_beams: int = 5,
show_progress: bool = True,
) -> list:
"""
Translate a batch of texts efficiently using optimized batching.
Args:
texts: List of input texts to translate.
source_lang: Source language code.
target_lang: Target language code.
batch_size: Number of texts to process at once (default: 32).
max_length: Maximum length of generated translation.
num_beams: Number of beams for beam search.
show_progress: Whether to show a progress bar.
Returns:
List of translated texts.
Example:
>>> translate_batch(["Hello", "How are you?"], "eng", "hin")
['เคจเคฎเคธเฅเคคเฅ‡', 'เค†เคช เค•เฅˆเคธเฅ‡ เคนเฅˆเค‚?']
"""
if not texts:
return []
model, tokenizer = _load_model()
tgt_code = LANG_CODES.get(target_lang, target_lang)
tgt_token_id = tokenizer.lang_code_to_id.get(tgt_code)
if tgt_token_id is None:
raise ValueError(f"Unsupported target language: {target_lang}")
results = []
total_batches = (len(texts) + batch_size - 1) // batch_size
if show_progress:
try:
from tqdm import tqdm
iterator = tqdm(
range(0, len(texts), batch_size),
desc="Translating",
unit="batch",
total=total_batches,
)
except ImportError:
iterator = range(0, len(texts), batch_size)
print(f"Translating {len(texts)} texts in {total_batches} batches...")
else:
iterator = range(0, len(texts), batch_size)
tokenizer.src_lang = LANG_CODES.get(source_lang, source_lang)
for i in iterator:
batch_texts = texts[i:i + batch_size]
# Tokenize batch
inputs = tokenizer(
batch_texts,
return_tensors="pt",
truncation=True,
max_length=max_length,
padding=True,
).to(_device)
# Generate batch
with torch.no_grad():
outputs = model.generate(
**inputs,
forced_bos_token_id=tgt_token_id,
max_length=max_length,
num_beams=num_beams,
no_repeat_ngram_size=3,
repetition_penalty=1.3,
early_stopping=True,
)
# Decode batch results
batch_results = tokenizer.batch_decode(outputs, skip_special_tokens=True)
results.extend(batch_results)
return results
def list_languages(category: str = "all") -> None:
"""
Print available languages and their codes.
Args:
category: Filter by category ('all', 'indic', 'other').
"""
indic_langs = {
'asm': 'Assamese', 'ben': 'Bengali', 'brx': 'Bodo', 'doi': 'Dogri',
'gom': 'Konkani', 'guj': 'Gujarati', 'hin': 'Hindi', 'kan': 'Kannada',
'kas': 'Kashmiri', 'mai': 'Maithili', 'mal': 'Malayalam', 'mar': 'Marathi',
'mni': 'Manipuri', 'npi': 'Nepali', 'ory': 'Odia', 'pan': 'Punjabi',
'san': 'Sanskrit', 'sat': 'Santali', 'snd': 'Sindhi', 'tam': 'Tamil',
'tel': 'Telugu', 'urd': 'Urdu',
}
print("\nโ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—")
print("โ•‘ Transync - Supported Languages โ•‘")
print("โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•")
if category in ("all", "indic"):
print(f"\n๐Ÿ“š Indian Languages ({len(indic_langs)}):")
print("โ”€" * 45)
for code in sorted(indic_langs):
print(f" {code:6s} โ†’ {indic_langs[code]}")
if category in ("all", "other"):
other = {k: v for k, v in sorted(CODE_TO_LANG.items()) if k not in indic_langs}
print(f"\n๐ŸŒ Other Languages ({len(other)}):")
print("โ”€" * 45)
for code, name in other.items():
print(f" {code:6s} โ†’ {name}")
print()
# CLI interface
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Transync - Indic Multilingual Translation Tool",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python transync_inference.py eng hin "Hello, how are you?"
python transync_inference.py hin tel "เคจเคฎเคธเฅเคคเฅ‡, เค†เคช เค•เฅˆเคธเฅ‡ เคนเฅˆเค‚?" --beams 3
python transync_inference.py --batch eng hin -f input.txt -o output.txt
python transync_inference.py --list-langs
""",
)
parser.add_argument(
"source_lang",
nargs="?",
help="Source language code (e.g., 'eng', 'hin', 'tel')",
)
parser.add_argument(
"target_lang",
nargs="?",
help="Target language code (e.g., 'eng', 'hin', 'tel')",
)
parser.add_argument(
"text",
nargs="*",
help="Text to translate",
)
parser.add_argument(
"--beams",
type=int,
default=5,
help="Number of beams for beam search (default: 5)",
)
parser.add_argument(
"--max-length",
type=int,
default=256,
help="Maximum translation length (default: 256)",
)
parser.add_argument(
"--temperature",
type=float,
default=1.0,
help="Sampling temperature (default: 1.0)",
)
parser.add_argument(
"--list-langs",
action="store_true",
help="List all supported languages and exit",
)
parser.add_argument(
"--batch",
action="store_true",
help="Batch translation mode (requires --file)",
)
parser.add_argument(
"-f", "--file",
type=str,
help="Input file path for batch translation",
)
parser.add_argument(
"-o", "--output",
type=str,
help="Output file path for batch translation",
)
parser.add_argument(
"--batch-size",
type=int,
default=32,
help="Batch size for batch translation (default: 32)",
)
parser.add_argument(
"--no-progress",
action="store_true",
help="Hide progress bar during batch translation",
)
args = parser.parse_args()
# List languages mode
if args.list_langs:
list_languages()
sys.exit(0)
# Validate required arguments
if not args.source_lang or not args.target_lang:
parser.print_help()
print("\nโŒ Error: source_lang and target_lang are required.")
print(" Use --list-langs to see all supported language codes.")
sys.exit(1)
# Batch translation from file
if args.batch or args.file:
if not args.file:
print("โŒ Error: --file is required for batch translation mode.")
sys.exit(1)
try:
with open(args.file, "r", encoding="utf-8") as f:
texts = [line.strip() for line in f if line.strip()]
except FileNotFoundError:
print(f"โŒ Error: File not found: {args.file}")
sys.exit(1)
if not texts:
print("โŒ Error: Input file is empty.")
sys.exit(1)
print(f"๐Ÿ“– Loaded {len(texts)} texts from {args.file}")
results = translate_batch(
texts,
args.source_lang,
args.target_lang,
batch_size=args.batch_size,
max_length=args.max_length,
num_beams=args.beams,
show_progress=not args.no_progress,
)
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
for result in results:
f.write(result + "\n")
print(f"โœ“ Results written to {args.output}")
else:
for i, (orig, trans) in enumerate(zip(texts, results), 1):
print(f"\n[{i}]")
print(f" Input: {orig}")
print(f" Output: {trans}")
# Single translation
elif args.text:
text = " ".join(args.text)
src_name = CODE_TO_LANG.get(args.source_lang, args.source_lang)
tgt_name = CODE_TO_LANG.get(args.target_lang, args.target_lang)
print(f"\n๐Ÿ”ค {src_name} โ†’ {tgt_name}")
print(f" Input: {text}")
result = translate_onemt(
text,
args.source_lang,
args.target_lang,
max_length=args.max_length,
num_beams=args.beams,
temperature=args.temperature,
)
print(f" Output: {result}")
else:
print("โŒ Error: No text provided for translation.")
print(" Usage: python transync_inference.py <source> <target> <text>")
sys.exit(1)