hari31416 commited on
Commit
3e3a414
·
verified ·
1 Parent(s): 298eaf1

Initial release of 1B models

Browse files
.gitattributes CHANGED
@@ -33,3 +33,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ decoder_model.onnx.data filter=lfs diff=lfs merge=lfs -text
37
+ decoder_with_past_model.onnx.data filter=lfs diff=lfs merge=lfs -text
38
+ encoder_model.onnx.data filter=lfs diff=lfs merge=lfs -text
39
+ model.SRC filter=lfs diff=lfs merge=lfs -text
40
+ model.TGT filter=lfs diff=lfs merge=lfs -text
41
+ tokenizer_src.json filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ language:
4
+ - as
5
+ - bn
6
+ - brx
7
+ - doi
8
+ - gu
9
+ - hi
10
+ - kn
11
+ - ks
12
+ - kok
13
+ - mai
14
+ - ml
15
+ - mni
16
+ - mr
17
+ - ne
18
+ - or
19
+ - pa
20
+ - sa
21
+ - sat
22
+ - sd
23
+ - ta
24
+ - te
25
+ - ur
26
+ - en
27
+ tags:
28
+ - translation
29
+ - onnx
30
+ - indic
31
+ - indictrans2
32
+ - browser
33
+ - fp32
34
+ pipeline_tag: translation
35
+ library_name: onnx
36
+ base_model: ai4bharat/indictrans2-indic-en-1B
37
+ ---
38
+
39
+ # IndicTrans2 1B (indic→en) — ONNX bundle [FP32 (Full Precision)]
40
+
41
+ > [!TIP]
42
+ > This model is part of a suite of optimized/quantized ONNX versions of the base model.
43
+ > Other variants in this direction:
44
+ > - **FP32 (Full Precision / Base)**: [`hari31416/indictrans2-indic-en-1B-ONNX`](https://huggingface.co/hari31416/indictrans2-indic-en-1B-ONNX) *(Current)*
45
+ > - **FP16 (Half Precision)**: [`hari31416/indictrans2-indic-en-1B-ONNX-fp16`](https://huggingface.co/hari31416/indictrans2-indic-en-1B-ONNX-fp16)
46
+ > - **INT8 (Dynamic Quantization)**: [`hari31416/indictrans2-indic-en-1B-ONNX-int8`](https://huggingface.co/hari31416/indictrans2-indic-en-1B-ONNX-int8)
47
+ > - **Q4F16 (4-bit Block Quantization)**: [`hari31416/indictrans2-indic-en-1B-ONNX-q4f16`](https://huggingface.co/hari31416/indictrans2-indic-en-1B-ONNX-q4f16)
48
+
49
+ ONNX-exported and quantized version of [`ai4bharat/indictrans2-indic-en-1B`](https://huggingface.co/ai4bharat/indictrans2-indic-en-1B)
50
+ for in-browser and local edge inference.
51
+
52
+ - **Precision**: FP32 (Full Precision)
53
+ - **Description**: Baseline full-precision ONNX export.
54
+ - **Source Pipeline & Details**: For pipeline details, benchmarks, and usage instructions, see the [indictrans2-onnx-export GitHub repository](https://github.com/Hari31416/indictrans2-onnx-export).
55
+
56
+ Built for use with [Transformers.js](https://github.com/huggingface/transformers.js)
57
+ and [onnxruntime-web](https://onnxruntime.ai/docs/get-started/with-javascript.html)
58
+ in the browser, with fast BPE tokenizer.json files that don't require the
59
+ SentencePiece WASM runtime.
60
+
61
+ ## Performance Visualizations
62
+
63
+ These charts show overall tradeoffs, language-level parity, and category breakdown.
64
+
65
+ ![Overall Tradeoffs](./overall.png)
66
+ ![Language-Level Parity](./languages.png)
67
+ ![Category breakdown](./categories.png)
68
+
69
+ ## Performance Tradeoffs & Size Comparison
70
+
71
+ Compared against the FP32 ONNX oracle on the golden evaluation fixtures.
72
+
73
+ | Format | Model Size | Exact Match (Token) | Exact Match (Text) | SacreBLEU (Raw) | Latency (Mean) | Speedup vs. FP32 |
74
+ | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
75
+ | FP32 | 4.88 GB | 100.00% | 100.00% | 100.00 | 171.8 ms | 1.000x |
76
+ | FP16 | 2.44 GB | 99.91% | 99.91% | 99.98 | 180.5 ms | 0.952x |
77
+ | INT8 | 1.22 GB | 94.18% | 94.18% | 97.94 | 76.4 ms | 2.196x |
78
+ | Q4F16 | 625.0 MB | 87.55% | 87.55% | 95.17 | 85.5 ms | 1.962x |
79
+
80
+
81
+ ## Files
82
+
83
+ - `encoder_model.onnx` (and optional `.onnx.data` weights sidecar)
84
+ - `decoder_model.onnx` (and optional `.onnx.data` weights sidecar)
85
+ - `decoder_with_past_model.onnx` (and optional `.onnx.data` weights sidecar)
86
+ - `translate.py` — self-contained Python inference helper (see Usage below)
87
+ - Fast tokenizer config files (`tokenizer_src.json`, `tokenizer_tgt.json`, `tokenizer_meta.json`)
88
+ - Model configuration configs (`config.json`, `generation_config.json`)
89
+
90
+ ## Usage Example (Python, onnxruntime)
91
+
92
+ ```python
93
+ # translate.py is included in this repo alongside the ONNX bundle.
94
+ # You can also find it (and read the full source) at:
95
+ # https://github.com/Hari31416/indictrans2-onnx-export/blob/main/src/translate.py
96
+
97
+ from translate import IndicTransONNX
98
+
99
+ # Pass a HF repo ID for automatic download, or a local bundle directory path
100
+ model = IndicTransONNX("hari31416/indictrans2-indic-en-1B-ONNX")
101
+ print(model.translate("चुनाव कौन जीतेगा?", src_lang="hin_Deva", tgt_lang="eng_Latn"))
102
+ ```
103
+
104
+ Required packages:
105
+
106
+ ```bash
107
+ pip install onnxruntime tokenizers huggingface-hub
108
+ ```
109
+
110
+ ## License
111
+
112
+ MIT (preserved from upstream AI4Bharat).
__pycache__/tokenization_indictrans.cpython-311.pyc ADDED
Binary file (13.5 kB). View file
 
categories.png ADDED
config.json ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name_or_path": "ai4bharat/indictrans2-indic-en-1B",
3
+ "activation_dropout": 0.0,
4
+ "activation_function": "gelu",
5
+ "architectures": [
6
+ "IndicTransForConditionalGeneration"
7
+ ],
8
+ "auto_map": {
9
+ "AutoConfig": "configuration_indictrans.IndicTransConfig",
10
+ "AutoModelForSeq2SeqLM": "modeling_indictrans.IndicTransForConditionalGeneration"
11
+ },
12
+ "tokenizer_class": "IndicTransTokenizer",
13
+ "attention_dropout": 0.0,
14
+ "bos_token_id": 0,
15
+ "decoder_attention_heads": 16,
16
+ "decoder_embed_dim": 1024,
17
+ "decoder_ffn_dim": 8192,
18
+ "decoder_layerdrop": 0,
19
+ "decoder_layers": 18,
20
+ "decoder_normalize_before": true,
21
+ "decoder_start_token_id": 2,
22
+ "decoder_vocab_size": 32296,
23
+ "vocab_size": 32296,
24
+ "dropout": 0.2,
25
+ "encoder_attention_heads": 16,
26
+ "encoder_embed_dim": 1024,
27
+ "encoder_ffn_dim": 8192,
28
+ "encoder_layerdrop": 0,
29
+ "encoder_layers": 18,
30
+ "encoder_normalize_before": true,
31
+ "encoder_vocab_size": 122706,
32
+ "eos_token_id": 2,
33
+ "init_std": 0.02,
34
+ "is_encoder_decoder": true,
35
+ "layernorm_embedding": false,
36
+ "max_source_positions": 256,
37
+ "max_target_positions": 256,
38
+ "model_type": "IndicTrans",
39
+ "num_hidden_layers": 18,
40
+ "pad_token_id": 1,
41
+ "scale_embedding": true,
42
+ "share_decoder_input_output_embed": false,
43
+ "torch_dtype": "float32",
44
+ "transformers_version": "4.32.1",
45
+ "use_cache": true,
46
+ "attn_implementation": "eager"
47
+ }
decoder_model.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1402f93d1d02cb3624089fc85e3fa1fe93532d3ee4bfa4760b36ffcd6fd66702
3
+ size 1750892
decoder_model.onnx.data ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c626a44a2bd2c6e84d407ff09f2429943c366aef20f13d88ca4acab15ad8eadc
3
+ size 2078212096
decoder_with_past_model.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:99fb1dd0167139301d7a0350b538e3e452f756d1118f03778884d606a46ddbf4
3
+ size 1693322
decoder_with_past_model.onnx.data ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5f622722d3172aad0a79c55a9e096f815b1c9c6855da0fe6f0554373ff4f4c72
3
+ size 1927069696
dict.SRC.json ADDED
The diff for this file is too large to render. See raw diff
 
dict.TGT.json ADDED
The diff for this file is too large to render. See raw diff
 
encoder_model.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4a780d6188a20c3f8292b36cec48aa405c147a72cc8978b603a3d7c4503ab675
3
+ size 1452471
encoder_model.onnx.data ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f106d51bc1c2edc508542e2bdbacbd89ac90f75e5945872366fa4d1ca1409c14
3
+ size 2013814784
generation_config.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "bos_token_id": 0,
4
+ "decoder_start_token_id": 2,
5
+ "eos_token_id": 2,
6
+ "pad_token_id": 1,
7
+ "transformers_version": "4.32.1"
8
+ }
languages.png ADDED
model.SRC ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ac9257c8e76b8b607705b959cc3d075656ea33032f7a974e467b8941df6e98d4
3
+ size 3256903
model.TGT ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3cedc5cbcc740369b76201942a0f096fec7287fee039b55bdb956f301235b914
3
+ size 759425
overall.png ADDED
special_tokens_map.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token": "<s>",
3
+ "eos_token": "</s>",
4
+ "pad_token": "<pad>",
5
+ "unk_token": "<unk>"
6
+ }
tokenization_indictrans.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+
4
+ from transformers.utils import logging
5
+ from typing import Dict, List, Optional, Union, Tuple
6
+
7
+ from sentencepiece import SentencePieceProcessor
8
+ from transformers.tokenization_utils import PreTrainedTokenizer
9
+
10
+
11
+ logger = logging.get_logger(__name__)
12
+
13
+ # Convert LANGUAGE_TAGS to a frozen set for faster lookups
14
+ LANGUAGE_TAGS = frozenset(
15
+ {
16
+ "asm_Beng",
17
+ "awa_Deva",
18
+ "ben_Beng",
19
+ "bho_Deva",
20
+ "brx_Deva",
21
+ "doi_Deva",
22
+ "eng_Latn",
23
+ "gom_Deva",
24
+ "gon_Deva",
25
+ "guj_Gujr",
26
+ "hin_Deva",
27
+ "hne_Deva",
28
+ "kan_Knda",
29
+ "kas_Arab",
30
+ "kas_Deva",
31
+ "kha_Latn",
32
+ "lus_Latn",
33
+ "mag_Deva",
34
+ "mai_Deva",
35
+ "mal_Mlym",
36
+ "mar_Deva",
37
+ "mni_Beng",
38
+ "mni_Mtei",
39
+ "npi_Deva",
40
+ "ory_Orya",
41
+ "pan_Guru",
42
+ "san_Deva",
43
+ "sat_Olck",
44
+ "snd_Arab",
45
+ "snd_Deva",
46
+ "tam_Taml",
47
+ "tel_Telu",
48
+ "urd_Arab",
49
+ "unr_Deva",
50
+ }
51
+ )
52
+
53
+ VOCAB_FILES_NAMES = {
54
+ "src_vocab_fp": "dict.SRC.json",
55
+ "tgt_vocab_fp": "dict.TGT.json",
56
+ "src_spm_fp": "model.SRC",
57
+ "tgt_spm_fp": "model.TGT",
58
+ }
59
+
60
+
61
+ class IndicTransTokenizer(PreTrainedTokenizer):
62
+ _added_tokens_encoder: Dict[str, int] = {}
63
+ _added_tokens_decoder: Dict[str, int] = {}
64
+ vocab_files_names = VOCAB_FILES_NAMES
65
+ model_input_names = ["input_ids", "attention_mask"]
66
+
67
+ def __init__(
68
+ self,
69
+ src_vocab_fp=None,
70
+ tgt_vocab_fp=None,
71
+ src_spm_fp=None,
72
+ tgt_spm_fp=None,
73
+ unk_token="<unk>",
74
+ bos_token="<s>",
75
+ eos_token="</s>",
76
+ pad_token="<pad>",
77
+ do_lower_case=False,
78
+ **kwargs,
79
+ ):
80
+ self.src_vocab_fp = src_vocab_fp
81
+ self.tgt_vocab_fp = tgt_vocab_fp
82
+ self.src_spm_fp = src_spm_fp
83
+ self.tgt_spm_fp = tgt_spm_fp
84
+
85
+ # Store token content directly instead of accessing .content
86
+ self.unk_token = (
87
+ hasattr(unk_token, "content") and unk_token.content or unk_token
88
+ )
89
+ self.pad_token = (
90
+ hasattr(pad_token, "content") and pad_token.content or pad_token
91
+ )
92
+ self.eos_token = (
93
+ hasattr(eos_token, "content") and eos_token.content or eos_token
94
+ )
95
+ self.bos_token = (
96
+ hasattr(bos_token, "content") and bos_token.content or bos_token
97
+ )
98
+
99
+ # Load vocabularies
100
+ self.src_encoder = self._load_json(self.src_vocab_fp)
101
+ self.tgt_encoder = self._load_json(self.tgt_vocab_fp)
102
+
103
+ # Validate tokens
104
+ if self.unk_token not in self.src_encoder:
105
+ raise KeyError("<unk> token must be in vocab")
106
+ if self.pad_token not in self.src_encoder:
107
+ raise KeyError("<pad> token must be in vocab")
108
+
109
+ # Pre-compute reverse mappings
110
+ self.src_decoder = {v: k for k, v in self.src_encoder.items()}
111
+ self.tgt_decoder = {v: k for k, v in self.tgt_encoder.items()}
112
+
113
+ # Load SPM models
114
+ self.src_spm = self._load_spm(self.src_spm_fp)
115
+ self.tgt_spm = self._load_spm(self.tgt_spm_fp)
116
+
117
+ # Initialize current settings
118
+ self._switch_to_input_mode()
119
+
120
+ # Cache token IDs
121
+ self.unk_token_id = self.src_encoder[self.unk_token]
122
+ self.pad_token_id = self.src_encoder[self.pad_token]
123
+ self.eos_token_id = self.src_encoder[self.eos_token]
124
+ self.bos_token_id = self.src_encoder[self.bos_token]
125
+
126
+ super().__init__(
127
+ src_vocab_file=self.src_vocab_fp,
128
+ tgt_vocab_file=self.tgt_vocab_fp,
129
+ do_lower_case=do_lower_case,
130
+ unk_token=unk_token,
131
+ bos_token=bos_token,
132
+ eos_token=eos_token,
133
+ pad_token=pad_token,
134
+ **kwargs,
135
+ )
136
+
137
+ def add_new_language_tags(self, new_tags: List[str]) -> None:
138
+ global LANGUAGE_TAGS
139
+ LANGUAGE_TAGS = frozenset(LANGUAGE_TAGS | set(new_tags))
140
+
141
+ def _switch_to_input_mode(self) -> None:
142
+ self.spm = self.src_spm
143
+ self.padding_side = "left"
144
+ self.encoder = self.src_encoder
145
+ self.decoder = self.src_decoder
146
+ self._tokenize = self._src_tokenize
147
+
148
+ def _switch_to_target_mode(self) -> None:
149
+ self.spm = self.tgt_spm
150
+ self.padding_side = "right"
151
+ self.encoder = self.tgt_encoder
152
+ self.decoder = self.tgt_decoder
153
+ self._tokenize = self._tgt_tokenize
154
+
155
+ @staticmethod
156
+ def _load_spm(path: str) -> SentencePieceProcessor:
157
+ return SentencePieceProcessor(model_file=path)
158
+
159
+ @staticmethod
160
+ def _save_json(data: Union[Dict, List], path: str) -> None:
161
+ with open(path, "w", encoding="utf-8") as f:
162
+ json.dump(data, f, indent=2)
163
+
164
+ @staticmethod
165
+ def _load_json(path: str) -> Union[Dict, List]:
166
+ with open(path, "r", encoding="utf-8") as f:
167
+ return json.load(f)
168
+
169
+ @property
170
+ def src_vocab_size(self) -> int:
171
+ return len(self.src_encoder)
172
+
173
+ @property
174
+ def tgt_vocab_size(self) -> int:
175
+ return len(self.tgt_encoder)
176
+
177
+ def get_src_vocab(self) -> Dict[str, int]:
178
+ return dict(self.src_encoder, **self.added_tokens_encoder)
179
+
180
+ def get_tgt_vocab(self) -> Dict[str, int]:
181
+ return dict(self.tgt_encoder, **self.added_tokens_decoder)
182
+
183
+ def get_vocab(self) -> Dict[str, int]:
184
+ return self.get_src_vocab()
185
+
186
+ @property
187
+ def vocab_size(self) -> int:
188
+ return self.src_vocab_size
189
+
190
+ def _convert_token_to_id(self, token: str) -> int:
191
+ return self.encoder.get(token, self.unk_token_id)
192
+
193
+ def _convert_id_to_token(self, index: int) -> str:
194
+ return self.decoder.get(index, self.unk_token)
195
+
196
+ def convert_tokens_to_string(self, tokens: List[str]) -> str:
197
+ return "".join(tokens).replace("▁", " ").strip()
198
+
199
+ def _src_tokenize(self, text: str) -> List[str]:
200
+ src_lang, tgt_lang, text = text.split(" ", 2)
201
+ assert src_lang in LANGUAGE_TAGS, f"Invalid source language tag: {src_lang}"
202
+ assert tgt_lang in LANGUAGE_TAGS, f"Invalid target language tag: {tgt_lang}"
203
+ return [src_lang, tgt_lang] + self.spm.EncodeAsPieces(text)
204
+
205
+ def _tgt_tokenize(self, text: str) -> List[str]:
206
+ return self.spm.EncodeAsPieces(text)
207
+
208
+ def _decode(
209
+ self,
210
+ token_ids: Union[int, List[int]],
211
+ skip_special_tokens: bool = False,
212
+ clean_up_tokenization_spaces: bool = None,
213
+ spaces_between_special_tokens: bool = True,
214
+ **kwargs,
215
+ ) -> str:
216
+ self._switch_to_target_mode()
217
+ decoded_token_ids = super()._decode(
218
+ token_ids=token_ids,
219
+ skip_special_tokens=skip_special_tokens,
220
+ clean_up_tokenization_spaces=clean_up_tokenization_spaces,
221
+ spaces_between_special_tokens=spaces_between_special_tokens,
222
+ **kwargs,
223
+ )
224
+ self._switch_to_input_mode()
225
+ return decoded_token_ids
226
+
227
+ def build_inputs_with_special_tokens(
228
+ self, token_ids_0: List[int], token_ids_1: Optional[List[int]] = None
229
+ ) -> List[int]:
230
+ return token_ids_0 + [self.eos_token_id]
231
+
232
+ def save_vocabulary(
233
+ self, save_directory: str, filename_prefix: Optional[str] = None
234
+ ) -> Tuple[str, ...]:
235
+ if not os.path.isdir(save_directory):
236
+ logger.error(f"Vocabulary path ({save_directory}) should be a directory")
237
+ return ()
238
+
239
+ src_spm_fp = os.path.join(save_directory, "model.SRC")
240
+ tgt_spm_fp = os.path.join(save_directory, "model.TGT")
241
+ src_vocab_fp = os.path.join(save_directory, "dict.SRC.json")
242
+ tgt_vocab_fp = os.path.join(save_directory, "dict.TGT.json")
243
+
244
+ self._save_json(self.src_encoder, src_vocab_fp)
245
+ self._save_json(self.tgt_encoder, tgt_vocab_fp)
246
+
247
+ for fp, spm in [(src_spm_fp, self.src_spm), (tgt_spm_fp, self.tgt_spm)]:
248
+ with open(fp, "wb") as f:
249
+ f.write(spm.serialized_model_proto())
250
+
251
+ return src_vocab_fp, tgt_vocab_fp, src_spm_fp, tgt_spm_fp
tokenizer_config.json ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "added_tokens_decoder": {
3
+ "0": {
4
+ "content": "<s>",
5
+ "lstrip": false,
6
+ "normalized": false,
7
+ "rstrip": false,
8
+ "single_word": false,
9
+ "special": true
10
+ },
11
+ "1": {
12
+ "content": "<pad>",
13
+ "lstrip": false,
14
+ "normalized": false,
15
+ "rstrip": false,
16
+ "single_word": false,
17
+ "special": true
18
+ },
19
+ "2": {
20
+ "content": "</s>",
21
+ "lstrip": false,
22
+ "normalized": false,
23
+ "rstrip": false,
24
+ "single_word": false,
25
+ "special": true
26
+ },
27
+ "3": {
28
+ "content": "<unk>",
29
+ "lstrip": false,
30
+ "normalized": false,
31
+ "rstrip": false,
32
+ "single_word": false,
33
+ "special": true
34
+ }
35
+ },
36
+ "bos_token": "<s>",
37
+ "clean_up_tokenization_spaces": true,
38
+ "do_lower_case": false,
39
+ "eos_token": "</s>",
40
+ "model_max_length": 256,
41
+ "pad_token": "<pad>",
42
+ "name_or_path": "ai4bharat/indictrans2-indic-en-1B",
43
+ "tokenizer_class": "IndicTransTokenizer",
44
+ "auto_map": {
45
+ "AutoTokenizer": [
46
+ "tokenization_indictrans.IndicTransTokenizer",
47
+ null
48
+ ]
49
+ },
50
+ "unk_token": "<unk>"
51
+ }
tokenizer_meta.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "src_dict_size": 122706,
3
+ "tgt_dict_size": 32296,
4
+ "unk_id": 3
5
+ }
tokenizer_src.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d15ba1265a61b3b66122aab74e888442093a2c711f51b0f9896108661e1d346c
3
+ size 23876630
tokenizer_tgt.json ADDED
The diff for this file is too large to render. See raw diff
 
translate.py ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Self-contained IndicTrans2 ONNX inference helper.
3
+
4
+ This file is the single entry-point for running IndicTrans2 ONNX models from
5
+ Python. It is intentionally kept self-contained so it can be:
6
+
7
+ - Copied from the **Hugging Face repo** (where it is uploaded alongside the
8
+ ONNX bundle), or
9
+ - Found at the **GitHub source repo**:
10
+ https://github.com/Hari31416/indictrans2-onnx-export/blob/main/src/translate.py
11
+
12
+ Usage
13
+ -----
14
+
15
+ from translate import IndicTransONNX
16
+
17
+ # Pass a HF repo ID or a local directory path
18
+ model = IndicTransONNX("hari31416/indictrans2-en-indic-dist-200M-ONNX")
19
+ print(model.translate("Who will win the election?", src_lang="eng_Latn", tgt_lang="hin_Deva"))
20
+
21
+ Dependencies
22
+ ------------
23
+ pip install onnxruntime tokenizers huggingface-hub
24
+
25
+ CLI
26
+ ---
27
+ python translate.py hari31416/indictrans2-en-indic-dist-200M-ONNX \\
28
+ "Who will win the election?" --src-lang eng_Latn --tgt-lang hin_Deva
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import json
34
+ import logging
35
+ from pathlib import Path
36
+ from typing import Union
37
+
38
+ import numpy as np
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # Internal helpers
45
+ # ---------------------------------------------------------------------------
46
+
47
+ def _past_feed(past_outputs: list[np.ndarray], num_layers: int) -> dict[str, np.ndarray]:
48
+ """Build the ``past_key_values.*`` input dict for decoder_with_past."""
49
+ feed: dict[str, np.ndarray] = {}
50
+ for i in range(num_layers):
51
+ base = i * 4
52
+ feed[f"past_key_values.{i}.decoder.key"] = past_outputs[base]
53
+ feed[f"past_key_values.{i}.decoder.value"] = past_outputs[base + 1]
54
+ feed[f"past_key_values.{i}.encoder.key"] = past_outputs[base + 2]
55
+ feed[f"past_key_values.{i}.encoder.value"] = past_outputs[base + 3]
56
+ return feed
57
+
58
+
59
+ # ---------------------------------------------------------------------------
60
+ # Public API
61
+ # ---------------------------------------------------------------------------
62
+
63
+ class IndicTransONNX:
64
+ """Load an IndicTrans2 ONNX bundle and run greedy translation.
65
+
66
+ Args:
67
+ model_path: HF repo ID (e.g. ``"hari31416/indictrans2-en-indic-dist-200M-ONNX"``)
68
+ or a local directory path that contains the ONNX bundle.
69
+ providers: ONNX Runtime execution providers.
70
+ Defaults to ``["CPUExecutionProvider"]``.
71
+ Pass ``["CUDAExecutionProvider", "CPUExecutionProvider"]``
72
+ for GPU, or ``["CoreMLExecutionProvider", "CPUExecutionProvider"]``
73
+ on Apple Silicon.
74
+ """
75
+
76
+ def __init__(
77
+ self,
78
+ model_path: Union[str, Path],
79
+ providers: list[str] | None = None,
80
+ ) -> None:
81
+ import onnxruntime as ort
82
+ from tokenizers import Tokenizer
83
+
84
+ model_path = str(model_path)
85
+
86
+ # If the path looks like a HF repo ID (contains '/' but isn't a real
87
+ # local path), download via huggingface_hub.
88
+ if "/" in model_path and not Path(model_path).exists():
89
+ from huggingface_hub import snapshot_download
90
+
91
+ logger.info("Downloading snapshot for %s ...", model_path)
92
+ model_path = snapshot_download(repo_id=model_path)
93
+
94
+ snap = Path(model_path)
95
+ self._providers = providers or ["CPUExecutionProvider"]
96
+
97
+ # Tokenizers
98
+ self._src_tok = Tokenizer.from_file(str(snap / "tokenizer_src.json"))
99
+ self._tgt_tok = Tokenizer.from_file(str(snap / "tokenizer_tgt.json"))
100
+ self._meta: dict = json.loads((snap / "tokenizer_meta.json").read_text(encoding="utf-8"))
101
+
102
+ # Generation config (decoder start / eos IDs)
103
+ gen_cfg: dict = {}
104
+ gen_config_path = snap / "generation_config.json"
105
+ if gen_config_path.exists():
106
+ gen_cfg = json.loads(gen_config_path.read_text(encoding="utf-8"))
107
+ self._decoder_start_id: int = int(gen_cfg.get("decoder_start_token_id", 2))
108
+ self._eos_id: int = int(gen_cfg.get("eos_token_id", 2))
109
+
110
+ # ONNX sessions
111
+ logger.info("Loading ONNX sessions from %s ...", snap)
112
+ self._enc = ort.InferenceSession(
113
+ str(snap / "encoder_model.onnx"), providers=self._providers
114
+ )
115
+ self._dec = ort.InferenceSession(
116
+ str(snap / "decoder_model.onnx"), providers=self._providers
117
+ )
118
+ self._dec_past = ort.InferenceSession(
119
+ str(snap / "decoder_with_past_model.onnx"), providers=self._providers
120
+ )
121
+ # Number of transformer layers inferred from decoder outputs
122
+ # (1 logits tensor + 4 KV tensors per layer)
123
+ self._num_layers: int = (len(self._dec.get_outputs()) - 1) // 4
124
+
125
+ # ------------------------------------------------------------------
126
+
127
+ def translate(
128
+ self,
129
+ text: str,
130
+ src_lang: str,
131
+ tgt_lang: str,
132
+ max_new_tokens: int = 128,
133
+ ) -> str:
134
+ """Translate *text* from *src_lang* to *tgt_lang*.
135
+
136
+ Args:
137
+ text: Input sentence (plain text, no language tags needed).
138
+ src_lang: BCP-47 style lang code, e.g. ``"eng_Latn"``.
139
+ tgt_lang: BCP-47 style lang code, e.g. ``"hin_Deva"``.
140
+ max_new_tokens: Maximum tokens to generate (default: 128).
141
+
142
+ Returns:
143
+ Translated string.
144
+ """
145
+ # Prepend language tags — the format the model was trained with
146
+ prefixed = f"{src_lang} {tgt_lang} {text}"
147
+
148
+ # Tokenize source
149
+ encoded = self._src_tok.encode(prefixed)
150
+ # Clamp out-of-vocab IDs to <unk>
151
+ input_ids = np.array(
152
+ [
153
+ [
154
+ i if i < self._meta["src_dict_size"] else self._meta["unk_id"]
155
+ for i in encoded.ids
156
+ ]
157
+ ],
158
+ dtype=np.int64,
159
+ )
160
+ attn_mask = np.array([encoded.attention_mask], dtype=np.int64)
161
+
162
+ # Encoder
163
+ enc_out: np.ndarray = self._enc.run(
164
+ ["last_hidden_state"],
165
+ {"input_ids": input_ids, "attention_mask": attn_mask},
166
+ )[0]
167
+
168
+ # Greedy decoder loop
169
+ decoder_input_ids = np.array([[self._decoder_start_id]], dtype=np.int64)
170
+ output_ids: list[int] = [self._decoder_start_id]
171
+ past_outputs: list[np.ndarray] | None = None
172
+
173
+ for step in range(max_new_tokens):
174
+ if step == 0:
175
+ # First step: full encoder hidden states, no cached KV
176
+ dec_out = self._dec.run(
177
+ None,
178
+ {
179
+ "input_ids": decoder_input_ids,
180
+ "encoder_hidden_states": enc_out,
181
+ "encoder_attention_mask": attn_mask,
182
+ },
183
+ )
184
+ else:
185
+ # Subsequent steps: use cached KV from previous step
186
+ dec_out = self._dec_past.run(
187
+ None,
188
+ {
189
+ "input_ids": decoder_input_ids,
190
+ "encoder_attention_mask": attn_mask,
191
+ **_past_feed(past_outputs, self._num_layers), # type: ignore[arg-type]
192
+ },
193
+ )
194
+
195
+ logits: np.ndarray = dec_out[0]
196
+ past_outputs = list(dec_out[1:])
197
+ next_id = int(np.argmax(logits[0, -1, :]))
198
+ output_ids.append(next_id)
199
+
200
+ if next_id == self._eos_id:
201
+ break
202
+
203
+ decoder_input_ids = np.array([[next_id]], dtype=np.int64)
204
+
205
+ # Decode tokens → text
206
+ # Clamp out-of-vocab target IDs before decoding
207
+ safe_ids = [
208
+ i if i < self._meta["tgt_dict_size"] else self._meta["unk_id"]
209
+ for i in output_ids
210
+ ]
211
+ return self._tgt_tok.decode(safe_ids, skip_special_tokens=True)
212
+
213
+
214
+ # ---------------------------------------------------------------------------
215
+ # CLI entry-point
216
+ # ---------------------------------------------------------------------------
217
+
218
+ def _main() -> None:
219
+ import argparse
220
+
221
+ logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
222
+
223
+ parser = argparse.ArgumentParser(
224
+ description="Run IndicTrans2 ONNX greedy translation.",
225
+ formatter_class=argparse.RawDescriptionHelpFormatter,
226
+ epilog=__doc__,
227
+ )
228
+ parser.add_argument(
229
+ "model",
230
+ help=(
231
+ "HF repo ID (e.g. hari31416/indictrans2-en-indic-dist-200M-ONNX) "
232
+ "or local ONNX bundle directory"
233
+ ),
234
+ )
235
+ parser.add_argument("text", help="Text to translate")
236
+ parser.add_argument(
237
+ "--src-lang",
238
+ default="eng_Latn",
239
+ help="Source BCP-47 language code (default: eng_Latn)",
240
+ )
241
+ parser.add_argument(
242
+ "--tgt-lang",
243
+ default="hin_Deva",
244
+ help="Target BCP-47 language code (default: hin_Deva)",
245
+ )
246
+ parser.add_argument(
247
+ "--max-new-tokens",
248
+ type=int,
249
+ default=128,
250
+ help="Maximum tokens to generate (default: 128)",
251
+ )
252
+ args = parser.parse_args()
253
+
254
+ model = IndicTransONNX(args.model)
255
+ result = model.translate(
256
+ args.text,
257
+ src_lang=args.src_lang,
258
+ tgt_lang=args.tgt_lang,
259
+ max_new_tokens=args.max_new_tokens,
260
+ )
261
+ print(result)
262
+
263
+
264
+ if __name__ == "__main__":
265
+ _main()