bhashantar
English to 25 Indic languages with AI4Bharat's IndicTrans2 (en-indic-dist-200M), on CPU, through CTranslate2 int8. No transformers, no torch, no fairseq and no IndicTransToolkit at runtime.
from bhashantar import translate
translate(["The weather in Mumbai is pleasant today."], tgt="hin_Deva")
# ['मुंबई में आज मौसम सुखद है।']
$ bhashantar translate --tgt hin_Deva "Send the report to ops@example.com by 15-03-2025."
रिपोर्ट को 15-03-2025 द्वारा ops@example.com पर भेजें।
Why this exists
The official checkpoint loads through trust_remote_code files written against transformers 4.3x. On a fresh Python 3.12 venv with transformers 5.22.2 and IndicTransToolkit 1.1.1 (reproduced 2026-11-09):
from IndicTransToolkit.processor import IndicProcessor
ImportError: cannot import name 'PreTrainedTokenizerBase' from 'transformers.tokenization_utils'
AutoTokenizer.from_pretrained(..., trust_remote_code=True)
ModuleNotFoundError: No module named 'transformers.onnx'
AutoModelForSeq2SeqLM.from_pretrained(..., trust_remote_code=True)
ModuleNotFoundError: No module named 'transformers.onnx'
This is IndicTransToolkit#29. A fix for the remote code is open as discussion #15 on the model repo, unmerged at the time of writing. Pinning transformers 4.50.3 makes the stack load again; that pinned stack is the reference this package is measured against.
Data path
raw text
-> punctuation and digit normalisation, URLs/emails/numbers swapped for <IDn> (textprep.prepare)
-> Moses tokenisation, "eng_Latn hin_Deva" tags, SentencePiece pieces (engine)
-> CTranslate2 int8 beam search (beam 5), Devanagari-unified pieces out
-> detokenise pieces, restore <IDn>, Devanagari -> target script, reattach punctuation (textprep.finish)
The model writes every Brahmic target in Devanagari; Tamil, Bengali and the rest come from a fixed Unicode offset afterwards. That step and the placeholder restore are where a port silently goes wrong, so both are vendored from IndicTransToolkit and indic_nlp_library (MIT, see NOTICE) with their regexes and quirks intact. Two deliberate changes: placeholder maps travel with each sentence instead of a shared queue, and placeholder numbering is sorted instead of following set order, which varied between processes.
Two ways this fails, and the guard for each
Silent quality loss from conversion or quantisation. CTranslate2 has no converter for this checkpoint's custom class, so bhashantar.convert maps the Hugging Face tensors onto the same TransformerSpec the fairseq converter fills. A wrong position offset or a missed layer norm still produces fluent-looking text. The guard is bench/parity.py: chrF against human references for both this package and the upstream model, with the pass bar fixed before the first run.
Breakage on library upgrades. Runtime dependencies are only what inference touches: ctranslate2 (capped below 5, since model.bin is tied to the major), sentencepiece, sacremoses, regex, huggingface_hub. .github/workflows/smoke.yml installs the newest versions in range every week and translates.
The opinion: parity is published as a number, not claimed. If the delta ever exceeds the bar, the table says so.
Parity
FLORES-200 devtest, first 100 sentences, English source. The FLORES files come from the public dl.fbaipublicfiles.com/nllb/flores200_dataset.tar.gz; AI4Bharat's IN22 and the Hugging Face FLORES+ copy are both gated. Reference = upstream model, transformers 4.50.3 + IndicTransToolkit 1.1.1, fp32, beam 5. Pass bar, set before running: |delta| <= 1.0 chrF.
| pair | chrF reference | chrF bhashantar | delta | identical outputs | pass |
|---|---|---|---|---|---|
| eng_Latn-hin_Deva | 65.69 | 65.17 | -0.52 | 61/100 | yes |
| eng_Latn-ben_Beng | 54.52 | 54.62 | +0.10 | 54/100 | yes |
| eng_Latn-tam_Taml | 61.33 | 61.59 | +0.27 | 56/100 | yes |
Reproduce: python bench/parity.py score <flores devtest dir> bench/results (outputs and parity.json are committed in bench/results).
"Identical outputs" counts sentences where both systems produce the same string; the rest differ in word choice (for example कनाडाई मधुमेह संघ against कैनेडियन डायबिटीज एसोसिएशन), the expected effect of int8 weights on beam search. Conversion error and quantisation error are not separated: an fp32 CTranslate2 conversion did not fit on the build machine's disk.
Reference weights: the official repo is gated, so the reference used Raghavan/indictrans2-en-indic-dist-200M, whose pytorch_model.bin has the same Git LFS pointer as the official file (sha256 52671a26...0fef0, verified locally); tokenizer files match the official Git blob ids.
Speed and memory
| bhashantar (CT2 int8) | upstream (transformers fp32) | |
|---|---|---|
| model load | 0.47 s | not timed |
| ms per sentence, eng to tam_Taml, beam 5 | 323 (all 100 in one call) | 2,786 (batch 2, 80 sentences timed) |
| peak RSS | 707 MB | 1,472 MB |
The two latency figures are not like for like: the reference had to run at batch 2 because the build machine ran out of commit memory at batch 8, and it was resumed once mid-run, so only the last 80 sentences were timed. bhashantar on Hindi and Bengali: 328 and 308 ms per sentence.
Measured on a laptop (Windows 11, 16 GB RAM, 4 threads, other jobs running), so treat them as relative, not absolute.
Limits
- One direction: English to Indic. The
indic-enandindic-indiccheckpoints are not converted. - Caps, refused with the cap named in the error: 1,000 characters per sentence, 512 sentences and 50,000 characters per call, 256 subword tokens per sentence after preprocessing. Long input is refused, not truncated.
- Santali (
sat_Olck) output mixes in Oriya and Arabic words for some sentences. The upstream model does the same; the test marks it as a known model limit. - Sentence-level: split paragraphs yourself.
Model files
translate() looks for a model in this order: the model_dir argument, $BHASHANTAR_MODEL, a local conversion at $HF_HOME/bhashantar/en-indic-dist-200M-ct2-int8, then the Hugging Face repo zaidwhy/indictrans2-en-indic-dist-200M-ct2-int8 (cache first, so it works offline after the first download).
That repo is not published yet. Until it is, convert once yourself (needs a Hugging Face login with the model's terms accepted):
pip install "bhashantar[convert]"
hf auth login
bhashantar convert
Development
pip install -e ".[dev]"
pytest # offline tests, no model
pytest -m model # end to end, needs the converted model
Maintainer: publishing (not done yet)
# 1. weights, from a machine with the converted model
hf auth login
hf repo create zaidwhy/indictrans2-en-indic-dist-200M-ct2-int8 --repo-type model
hf upload zaidwhy/indictrans2-en-indic-dist-200M-ct2-int8 ~/.cache/huggingface/bhashantar/en-indic-dist-200M-ct2-int8 .
# 2. check that a clean install fetches it
python -m venv /tmp/bt && /tmp/bt/bin/pip install . && /tmp/bt/bin/bhashantar translate --tgt tam_Taml "Good morning."
# 3. package
python -m pip install build twine
python -m build
twine check dist/*
twine upload dist/*
The model directory carries AI4Bharat's MIT LICENSE and a provenance.json with the source weights' sha256; add a model card stating the parity numbers above before uploading.
License
MIT. Vendored code and model weights keep their own MIT notices; see NOTICE.
Metadata
Release files for bhashantar 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| bhashantar-0.1.0.tar.gz | 75.0 kB | Without provenance | Details for bhashantar-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| bhashantar-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for bhashantar-0.1.0-py3-none-any.whl |
Total release size: 94.6 kB
Release files / bhashantar-0.1.0.tar.gz
| Download URL | bhashantar-0.1.0.tar.gz |
|---|---|
| Size | 75.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
861f99155b86320da73c999b5d233b5e3af8b5ea5cf5e7b36c60c6e20cb6b11f
|
|
BLAKE2b-256 checksum How to use checksums |
012cac9b72172c33f396915d96ab135aa3b3e8c05219324c18073292be8b1317
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / bhashantar-0.1.0-py3-none-any.whl
| Download URL | bhashantar-0.1.0-py3-none-any.whl |
|---|---|
| Size | 19.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9b7dcae312e66dcae3f8178bd1ffed369eb285a3aee716c48ed797338526d6b8
|
|
BLAKE2b-256 checksum How to use checksums |
f2d8d7faf3711a6c7921d634da00a4e771a968b8fee2ad2a851ee75ed4fcd4b9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for bhashantar 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.