IndicMT
Compact multilingual machine translation for English and the 22 Scheduled Indian languages.
Usage · Languages · Results · Limitations · Citation
Compact multilingual machine translation between English and the 22 Scheduled Indian languages (44 directions). This repository holds the OpenNMT-py training checkpoint and an int8 CTranslate2 conversion of it for fast CPU inference.
- Model: Transformer encoder-decoder, 12 encoder and 12 decoder layers,
d_model512, feed-forward 2048, 16 heads, 153,807,872 parameters, one shared 128k SentencePiece (unigram) vocabulary with tied embeddings. - Training: from scratch with OpenNMT-py 3.5.1 on AI4Bharat's BPCC. Pre-trained on an 8:2 mix of mined and human-translated data (step 13,600), then fine-tuned on human data only (step 1,800).
- Code, data pipeline, evaluation and serving: github.com/ujjwalsharmaIITB/indicMT. Technical report: Technical report. Blog: Medium blog. Trailer: watch on Google Drive.
Files
| Path | Contents | Size |
|---|---|---|
indicMT/ |
CTranslate2 model, int8 weights (recommended for CPU inference) | 170.6 MB |
indicMT.pt |
OpenNMT-py 3.5.1 checkpoint, float32, as saved at the end of training | 2.4 GB |
indicMT.spm.model |
SentencePiece model (128k unigram), shared by both | 3.5 MB |
indicmt_config.json |
Variants, language tags and the source format, machine-readable |
indicMT/ holds model.bin, config.json and shared_vocabulary.json. The checkpoint is the unmodified training file:
model weights, vocabulary, training options and the Adam optimizer state, so fine-tuning can continue from it.
Usage
CTranslate2 (int8, CPU)
pip install ctranslate2==4.8.2 sentencepiece==0.2.2 huggingface_hub
import ctranslate2
import sentencepiece as spm
from huggingface_hub import snapshot_download
path = snapshot_download("ujjwalsharmaIITB/indicMT", allow_patterns=["indicMT/*", "indicMT.spm.model"])
sp = spm.SentencePieceProcessor(model_file=f"{path}/indicMT.spm.model")
translator = ctranslate2.Translator(f"{path}/indicMT", device="cpu")
def translate(texts, tgt, beam_size=5):
"""texts: raw sentences in one source language; tgt: target tag without brackets, e.g. "hi" or "en"."""
tag = f"<{tgt}>"
sources = sp.encode([f"{tag} {t}" for t in texts], out_type=str) # tag and sentence as one string
results = translator.translate_batch(sources, beam_size=beam_size)
return [sp.decode(r.hypotheses[0]) for r in results]
print(translate(["Hello, how are you?"], "hi")) # English -> Hindi
print(translate(["नमस्ते, आप कैसे हैं?"], "en")) # Hindi -> English
print(translate(translate(["नमस्ते, आप कैसे हैं?"], "en"), "ta")) # Hindi -> Tamil, pivoted through English
Encoding the input. Use
sp.encode(f"<xx> {text}")on the whole string. Do not use['<xx>'] + sp.encode(text).
The tag names the target language. The output carries no tag; detokenize it with sp.decode. Translation between
two Indian languages goes through English (two calls), as in the last example.
OpenNMT-py checkpoint (GPU or CPU)
pip install torch==2.2.0 OpenNMT-py==3.5.1 sentencepiece==0.2.2 huggingface_hub
import subprocess
import sentencepiece as spm
from huggingface_hub import snapshot_download
path = snapshot_download("ujjwalsharmaIITB/indicMT", allow_patterns=["indicMT.pt", "indicMT.spm.model"])
sp = spm.SentencePieceProcessor(model_file=f"{path}/indicMT.spm.model")
texts, tag = ["Hello, how are you?"], "<hi>"
with open("src.spm", "w", encoding="utf-8") as f:
for pieces in sp.encode([f"{tag} {t}" for t in texts], out_type=str): # same source format as above
f.write(" ".join(pieces) + "\n")
subprocess.run(["onmt_translate", "-model", f"{path}/indicMT.pt", "-src", "src.spm",
"-output", "hyp.spm", "-beam_size", "5", "-gpu", "0"], check=True) # drop "-gpu 0" on CPU
with open("hyp.spm", encoding="utf-8") as f:
print([sp.decode(line.split()) for line in f])
The checkpoint stores its training options as a Python object, so it loads with torch.load(..., weights_only=False).
That is the default in torch 2.2.0, which OpenNMT-py 3.5.1 pins. From torch 2.6 the default changed and loading fails.
Load only files you trust this way.
Batch inference and serving
The GitHub repository has the code for larger jobs:
- Batch translation and scoring of test sets with the int8 model:
deployment/ct2_eval.py, built on the shared inference moduledeployment/ct2_infer.py. - Batch translation with the OpenNMT-py checkpoint:
evaluation/run_eval.py. - REST API and web interface (FastAPI, CPU, Docker):
deployment/api/main.py; setup indeployment/README.md.
Language tags
| Language | Tag | Code | Script |
|---|---|---|---|
| English | <en> |
eng_Latn |
Latin |
| Assamese | <as> |
asm_Beng |
Bengali |
| Bengali | <bn> |
ben_Beng |
Bengali |
| Bodo | <br> |
brx_Deva |
Devanagari |
| Dogri | <doi> |
doi_Deva |
Devanagari |
| Gujarati | <gu> |
guj_Gujr |
Gujarati |
| Hindi | <hi> |
hin_Deva |
Devanagari |
| Kannada | <kn> |
kan_Knda |
Kannada |
| Kashmiri | <ks> |
kas_Arab |
Perso-Arabic |
| Konkani | <gom> |
gom_Deva |
Devanagari |
| Maithili | <mai> |
mai_Deva |
Devanagari |
| Malayalam | <ml> |
mal_Mlym |
Malayalam |
| Manipuri | <mni> |
mni_Mtei |
Meitei (Meetei Mayek) |
| Marathi | <mr> |
mar_Deva |
Devanagari |
| Nepali | <ne> |
npi_Deva |
Devanagari |
| Odia | <or> |
ory_Orya |
Odia |
| Punjabi | <pa> |
pan_Guru |
Gurmukhi |
| Sanskrit | <sa> |
san_Deva |
Devanagari |
| Santali | <sat> |
sat_Olck |
Ol Chiki |
| Sindhi | <sd> |
snd_Deva |
Devanagari |
| Tamil | <ta> |
tam_Taml |
Tamil |
| Telugu | <te> |
tel_Telu |
Telugu |
| Urdu | <ur> |
urd_Arab |
Perso-Arabic |
The Bodo tag is <br> (ISO 639-3 brx). It is not Breton.
Results
Macro chrF++ over the 22 languages, beam 5, corpus-level sacreBLEU (word_order=2). All reported results are from
the CTranslate2 float32 model, the checkpoint in this repository converted to CTranslate2 without quantization.
| Test set | en→xx | xx→en |
|---|---|---|
| IN22-Gen | 47.15 | 53.87 |
| IN22-Conv | 43.15 | 50.59 |
Against IndicTrans2 1B run with the same protocol (paired bootstrap, 22 directions pooled), IndicMT is slightly but significantly ahead on en→xx on IN22-Gen (47.31 against 46.53, p = 0.001; 11 directions better and 8 worse at p < 0.05) and on IN22-Conv (43.23 against 42.60, p = 0.001; 8 better, 13 worse). IndicTrans2 is clearly ahead on xx→en (62.48 against 53.87 on IN22-Gen, 55.63 against 50.59 on IN22-Conv) and has 7.2 times more parameters.
An LLM judge (Gemma 4 31B-it) rated every IN22-Gen en→xx translation (22,528 sentences) for fluency, adequacy and overall quality on a 1–5 scale. Shown the human reference, it rates 63.8% of IndicMT translations perfect (5/5/5; mean overall 4.33), against 70.3% (4.48) for IndicTrans2 1B. Without the reference: 66.9% (4.38) against 73.8% (4.52).
Quantization and the files in this repository
This repository ships the OpenNMT-py checkpoint (indicMT.pt) and the int8 CTranslate2 model (indicMT/); the reported
results are from the CTranslate2 float32 conversion of the same checkpoint. chrF++, beam 5; latency is the median for one sentence on CPU (8 threads), IN22-Gen.
| Model | IN22-Gen en→xx | IN22-Gen xx→en | IN22-Conv en→xx | IN22-Conv xx→en | p50 latency | Size |
|---|---|---|---|---|---|---|
OpenNMT-py checkpoint (indicMT.pt) |
46.95 | 53.69 | 42.97 | 50.38 | 2.4 GB | |
| CTranslate2 float32 (reported) | 47.15 | 53.87 | 43.15 | 50.59 | 399.2 ms | 630.4 MB |
| CTranslate2 int16 | 47.15 | 53.86 | 43.15 | 50.59 | 367.1 ms | 323.2 MB |
CTranslate2 int8 (indicMT/) |
47.16 | 53.85 | 43.14 | 50.59 | 374.2 ms | 170.6 MB |
int16 and int8 are not significantly different from float32 on IN22-Gen, and int8 is 3.7 times smaller.
Compared with other systems
IndicMT (CTranslate2 float32), IndicTrans2, Indic Translate and Sarvam Translate are from our own runs (corpus-level chrF++; beam 5 for
IndicMT and IndicTrans2, greedy decoding for the two LLMs). All other baselines are taken
from the
bodhan-ai/indic-translate model page (read on 2026-09-19),
which reports sentence-level chrF++ with greedy decoding on IN22-Gen only, so those rows are indicative. A star marks a
system that covers only 16 of the 22 languages.
| Model | Parameters | IN22-Gen en→xx | IN22-Gen xx→en | IN22-Conv en→xx | IN22-Conv xx→en | Source |
|---|---|---|---|---|---|---|
| IndicMT | 0.154B | 47.15 | 53.87 | 43.15 | 50.59 | our run |
| IndicTrans2 1B | 1.1B | 46.39 | 62.48 | 42.61 | 55.63 | our run |
| Indic Translate | 7.94B | 47.73 | 60.33 | 45.04 | 55.10 | our run |
| Sarvam Translate | 4B | 48.74 | 51.82 | 45.71 | 49.77 | our run |
| gemini-3.5-flash | undisclosed | 40.30 | 66.60 | Indic Translate page | ||
| gemma-4-31B-it | 30.7B | 39.20 | 59.60 | Indic Translate page | ||
| translategemma-27b-it * | 27B | 39.60 | 58.50 | Indic Translate page | ||
| gemma-4-26B-a4b-it | 25.2B | 36.20 | 56.60 | Indic Translate page | ||
| translategemma-12b-it * | 12B | 37.10 | 55.00 | Indic Translate page | ||
| gemma-3-27b-it | 27.43B | 31.90 | 55.50 | Indic Translate page | ||
| gemma-3-12b-it | 12.19B | 28.80 | 53.20 | Indic Translate page | ||
| translategemma-4b-it * | 4B | 31.20 | 49.20 | Indic Translate page | ||
| gemma-3-4b-it | 4.3B | 22.90 | 47.10 | Indic Translate page |
Under the same scoring, IndicMT is 0.58 chrF++ below Indic Translate and 1.59 below Sarvam Translate on IN22-Gen en→xx (1.89 and 2.56 below on IN22-Conv), and ahead of Sarvam Translate on xx→en on both test sets. BLEU from our runs (IN22-Gen en→xx / xx→en, IN22-Conv en→xx / xx→en): IndicMT 19.19 / 29.19, 15.73 / 28.36; IndicTrans2 17.88 / 39.12, 15.22 / 34.02; Indic Translate 19.37 / 36.43, 17.30 / 33.37; Sarvam Translate 20.67 / 28.32, 17.95 / 27.51.
Per-language chrF++ (CTranslate2 float32, beam 5)
| Language | IN22-Gen en→xx | IN22-Gen xx→en | IN22-Conv en→xx | IN22-Conv xx→en |
|---|---|---|---|---|
| Assamese | 44.74 | 56.02 | 44.58 | 55.39 |
| Bengali | 47.24 | 54.39 | 45.01 | 53.75 |
| Bodo | 49.30 | 52.34 | 46.39 | 49.18 |
| Dogri | 55.86 | 61.04 | 50.64 | 58.29 |
| Gujarati | 50.88 | 57.92 | 50.21 | 56.42 |
| Hindi | 53.19 | 56.99 | 47.14 | 55.34 |
| Kannada | 49.06 | 55.31 | 31.70 | 43.40 |
| Kashmiri | 32.99 | 50.47 | 27.55 | 47.18 |
| Konkani | 43.51 | 49.67 | 41.41 | 45.31 |
| Maithili | 49.69 | 56.72 | 44.08 | 51.92 |
| Malayalam | 44.83 | 53.35 | 38.19 | 48.49 |
| Manipuri | 46.26 | 50.29 | 41.64 | 46.54 |
| Marathi | 48.63 | 54.94 | 46.08 | 52.94 |
| Nepali | 49.68 | 59.00 | 49.60 | 57.08 |
| Odia | 46.23 | 57.14 | 40.87 | 54.57 |
| Punjabi | 47.12 | 53.73 | 52.55 | 56.25 |
| Sanskrit | 37.26 | 45.86 | 34.35 | 41.76 |
| Santali | 41.11 | 43.72 | 42.99 | 45.11 |
| Sindhi | 39.73 | 50.54 | 37.11 | 46.16 |
| Tamil | 47.63 | 50.60 | 38.19 | 41.90 |
| Telugu | 49.58 | 54.03 | 42.56 | 47.61 |
| Urdu | 62.73 | 61.04 | 56.44 | 58.34 |
Compute
GPU-hours are GPUs × wall-clock hours, from the cluster's job accounting. Training ran on 4 × A100 80 GB; evaluation and the LLM judge ran on A40 and L40 GPUs.
Release model only. What it takes to reproduce the released model:
| Stage | GPUs | GPU-hours |
|---|---|---|
| Pre-training, combined data, to step 13,600 | 4 × A100 | 100 |
| Fine-tuning on human data, to step 1,800 | 4 × A100 | 11 |
| IN22-Gen and IN22-Conv evaluation of one checkpoint, 88 directions, OpenNMT-py | 2 × A40 | 0.5 |
| chrF++ trajectory: the same evaluation for 63 checkpoints (54 pre-training up to step 13,600, 9 fine-tuning up to step 1,800) | 2 × A40 | 32 |
| IN22-Gen and IN22-Conv evaluation, CTranslate2 float32 (reported scores) | CPU, 16 cores | 0 (about 1 hour) |
| LLM judge, English→Indic | 2 × A40 | 7.7 |
| Total (the trajectory includes the single checkpoint) | about 150 |
Whole project. Everything run for IndicMT, including models that were not released:
| Work | GPU-hours |
|---|---|
| Pre-training (human, mined and combined data) | 1,539 |
| Deeper architectures (12+18 and 18+18 layers) | 715 |
| IN22 evaluation of checkpoints | 473 |
| Fine-tuning (with and without replay, deeper models) | 248 |
| LLM judge | 135 |
| Baseline runs (IndicTrans2, Indic Translate, Sarvam Translate) | 15 |
| Setup and tests | 4 |
| Total | 3,129 |
By GPU type: 2,507 A100, 479 A40, 143 L40.
Limitations
- Sentence-level. Trained on sentences of at most 200 SentencePiece pieces. Split paragraphs and documents into sentences first.
- Indic to Indic pivots through English: two passes, so errors compound and latency doubles.
- One script per language (table above). For example, Sindhi is Devanagari only, Kashmiri Perso-Arabic only,
Santali Ol Chiki only and Manipuri Meetei Mayek only. Text in other scripts is out of domain. Characters outside the
vocabulary become
<unk>(no byte fallback). - Uneven quality. The weakest directions are English to Kashmiri (32.99 IN22-Gen) and English to Sanskrit (37.26). Conversational English-to-Kannada (31.70 IN22-Conv) and English-to-Tamil (38.19) are much lower than on IN22-Gen. Indic-to-English trails IndicTrans2 1B by 8.61 chrF++ on average (IN22-Gen).
- Automatic metrics only. Scores are chrF++ and BLEU; there is no human evaluation.
- Data. BPCC includes mined web data, and the model can reproduce its errors and biases. This is a research model: outputs may contain errors, so review them before any high-stakes use.
Training data and evaluation
- Training: AI4Bharat BPCC, human-translated (
bpcc-seed-latest,ilci) and mined (comparable,samanantar-filtered,nllb-filtered) configs, with script-consistency filtering and temperature-sampled mixing. - Evaluation: IN22-Gen (1,024 sentences per direction) and IN22-Conv (1,503).
Citation
@techreport{sharma2026indicmt,
author = {Sharma, Ujjwal},
title = {{IndicMT}: Compact Multilingual Machine Translation for 22 Indian Languages},
year = {2026},
note = {Technical report},
url = {https://www.cse.iitb.ac.in/~ujjwalsharma/blogs/indicMT/}
}
License
Apache License 2.0. Copyright 2026 Ujjwal Sharma. The training data (BPCC) and test sets (IN22) are AI4Bharat's and have their own terms.
Dataset used to train ujjwalsharmaIITB/indicMT
Evaluation results
- chrF++ en-xx (macro over 22 languages, CTranslate2 float32) on IN22-Gentest set self-reported47.150
- chrF++ xx-en (macro over 22 languages, CTranslate2 float32) on IN22-Gentest set self-reported53.870
- chrF++ en-xx (macro over 22 languages, CTranslate2 float32) on IN22-Convtest set self-reported43.150
- chrF++ xx-en (macro over 22 languages, CTranslate2 float32) on IN22-Convtest set self-reported50.590