---
license: mit
library_name: transformers.js
pipeline_tag: translation
base_model: alirezamsh/small100
tags:
- translation
- multilingual
- onnx
- int8
- small100
- m2m100
language:
- multilingual
- af
- am
- ar
- ast
- az
- ba
- be
- bg
- bn
- br
- bs
- ca
- ceb
- cs
- cy
- da
- de
- el
- en
- es
- et
- fa
- ff
- fi
- fr
- fy
- ga
- gd
- gl
- gu
- ha
- he
- hi
- hr
- ht
- hu
- hy
- id
- ig
- ilo
- is
- it
- ja
- jv
- ka
- kk
- km
- kn
- ko
- lb
- lg
- ln
- lo
- lt
- lv
- mg
- mk
- ml
- mn
- mr
- ms
- my
- ne
- nl
- no
- ns
- oc
- or
- pa
- pl
- ps
- pt
- ro
- ru
- sd
- si
- sk
- sl
- so
- sq
- sr
- ss
- su
- sv
- sw
- ta
- th
- tl
- tn
- tr
- uk
- ur
- uz
- vi
- wo
- xh
- yi
- yo
- zh
- zu
---
# SMaLL-100 · ONNX (int8)
**[English](#english) | [中文](#中文)**
ONNX export of [**SMaLL-100**](https://huggingface.co/alirezamsh/small100) — a
shallow, distilled multilingual machine-translation model (distilled from
M2M-100), covering **100 languages with direct any-to-any translation** (no
English pivot). This repo packages an **int8-quantized, ~609 MB** deployment set
for on-device / offline use. There is no official SMaLL-100 ONNX on the Hub; this
fills that gap.
## Platforms · 跨平台
One model + one `tokenizer.json` + one `lang_tokens.json`, four runtimes. All follow
the same recipe in **[USAGE.md](USAGE.md)**; runnable/reference code in **[examples/](examples/)**.
| Platform | Runtime + tokenizer | Example |
|---|---|---|
| Python | optimum / onnxruntime + `tokenizers` | [examples/python](examples/python/translate.py) ✅ |
| transformers.js | `@huggingface/transformers` v3 | [examples/transformers-js](examples/transformers-js/translate.mjs) ✅ |
| Android | onnxruntime-android + DJL `tokenizers` | [examples/android](examples/android/Small100Translator.kt) 📝 |
| iOS | onnxruntime-swift + swift-transformers | [examples/ios](examples/ios/Small100Translator.swift) 📝 |
`tokenizer.json` is a validated HuggingFace fast tokenizer (encoding matches the
official SMaLL-100 tokenizer exactly); `lang_tokens.json` maps 100 language codes →
token ids. The target-language token is prepended to the **source** (not
`forced_bos_token_id`). 语言 token 加在源句前,各平台一致。
---
## English
### Why SMaLL-100
- **Direct** translation between any of 100 languages (no pivot → single hop).
- **Shallow decoder (3 layers)** → fast: ~4× faster than M2M-100 at similar
quality; on a laptop CPU greedy decoding is ~150–250 ms/sentence.
- Small enough for mobile: **609 MB int8** vs ~850 MB for NLLB-200-distilled-600M.
### Files
```
onnx/
encoder_model.onnx 274 MB (int8)
decoder_model_merged.onnx 307 MB (int8, merged decoder w/ KV cache)
tokenizer.json validated HF fast tokenizer (all platforms)
lang_tokens.json {lang_to_id, eos, pad, unk, decoder_start}
sentencepiece.bpe.model, vocab.json upstream SentencePiece tokenizer (Python path)
config.json, generation_config.json, tokenizer_config.json,
special_tokens_map.json, added_tokens.json
tokenization_small100.py SMaLL-100 tokenizer (upstream Python)
USAGE.md platform-agnostic algorithm
examples/ python · transformers-js · android · ios
scripts/export.py reproduce the export + int8 quantization
scripts/translate_demo.py end-to-end demo (upstream tokenizer path)
```
The two `.onnx` files are **int8-quantized** but keep the standard optimum names
(`encoder_model.onnx` / `decoder_model_merged.onnx`) so `from_pretrained` loads
them without extra arguments; this repo ships the quantized weights only.
Model constants: `d_model=1024`, `encoder_layers=12`, `decoder_layers=3`,
`heads=16` (head_dim=64), `vocab=128112`, `decoder_start=eos=2`, `pad=1`, `unk=3`.
The decoder ONNX I/O is the standard optimum merged-decoder signature
(`input_ids`, `encoder_hidden_states`, `encoder_attention_mask`,
`past_key_values.{i}.{decoder,encoder}.{key,value}`, `use_cache_branch` → `logits`,
`present.{i}...`).
### Usage (optimum / onnxruntime, Python)
```bash
pip install optimum[onnxruntime] transformers sentencepiece
python scripts/translate_demo.py
```
```python
from optimum.onnxruntime import ORTModelForSeq2SeqLM
from tokenization_small100 import SMALL100Tokenizer
tok = SMALL100Tokenizer.from_pretrained(".")
model = ORTModelForSeq2SeqLM.from_pretrained(
".", subfolder="onnx", use_merged=True, use_io_binding=False)
tok.tgt_lang = "en" # target token is prepended to the SOURCE
enc = tok("你好,请问最近的地铁站怎么走?", return_tensors="pt")
out = model.generate(**enc, num_beams=1, max_length=128)
print(tok.batch_decode(out, skip_special_tokens=True)[0])
# -> "Hello, what is the nearest metro station?"
```
**Note (M2M vs SMaLL-100):** SMaLL-100 does **not** use `forced_bos_token_id`.
The target language is selected by setting `tok.tgt_lang`, which prepends the
language token (e.g. `en`→128022, `zh`→128102, `ja`→128046, `ko`→128052) to the
**source** input. Decode is greedy from `decoder_start_token_id=2`.
### Supported languages (100)
Any-to-any: pass any of these ISO-639 codes as the target. Full code→token-id map
in [`lang_tokens.json`](lang_tokens.json).
100 languages · click to expand
| | | | |
|---|---|---|---|
| `af` Afrikaans | `am` Amharic | `ar` Arabic | `ast` Asturian |
| `az` Azerbaijani | `ba` Bashkir | `be` Belarusian | `bg` Bulgarian |
| `bn` Bengali | `br` Breton | `bs` Bosnian | `ca` Catalan |
| `ceb` Cebuano | `cs` Czech | `cy` Welsh | `da` Danish |
| `de` German | `el` Greek | `en` English | `es` Spanish |
| `et` Estonian | `fa` Persian | `ff` Fula | `fi` Finnish |
| `fr` French | `fy` Western Frisian | `ga` Irish | `gd` Scottish Gaelic |
| `gl` Galician | `gu` Gujarati | `ha` Hausa | `he` Hebrew |
| `hi` Hindi | `hr` Croatian | `ht` Haitian Creole | `hu` Hungarian |
| `hy` Armenian | `id` Indonesian | `ig` Igbo | `ilo` Iloko |
| `is` Icelandic | `it` Italian | `ja` Japanese | `jv` Javanese |
| `ka` Georgian | `kk` Kazakh | `km` Central Khmer | `kn` Kannada |
| `ko` Korean | `lb` Luxembourgish | `lg` Ganda | `ln` Lingala |
| `lo` Lao | `lt` Lithuanian | `lv` Latvian | `mg` Malagasy |
| `mk` Macedonian | `ml` Malayalam | `mn` Mongolian | `mr` Marathi |
| `ms` Malay | `my` Burmese | `ne` Nepali | `nl` Dutch |
| `no` Norwegian | `ns` Northern Sotho | `oc` Occitan | `or` Oriya |
| `pa` Panjabi | `pl` Polish | `ps` Pashto | `pt` Portuguese |
| `ro` Romanian | `ru` Russian | `sd` Sindhi | `si` Sinhala |
| `sk` Slovak | `sl` Slovenian | `so` Somali | `sq` Albanian |
| `sr` Serbian | `ss` Swati | `su` Sundanese | `sv` Swedish |
| `sw` Swahili | `ta` Tamil | `th` Thai | `tl` Tagalog |
| `tn` Tswana | `tr` Turkish | `uk` Ukrainian | `ur` Urdu |
| `uz` Uzbek | `vi` Vietnamese | `wo` Wolof | `xh` Xhosa |
| `yi` Yiddish | `yo` Yoruba | `zh` Chinese | `zu` Zulu |
### Benchmarks (int8, laptop CPU arm64, greedy)
| direction | latency |
|---|---|
| zh→en | ~160 ms |
| en→zh | ~130 ms |
| ja→zh | ~170 ms |
| ko→zh | ~140 ms |
Quality is decent for a 330M distilled model; high-resource pairs are good,
some place-name / register slips on harder pairs.
### How it was quantized (the merged-decoder trick)
The merged decoder wraps its cached / non-cached paths in an ONNX `If` node.
`onnxruntime.quantization.quantize_dynamic` skips subgraphs by default, leaving
the decoder unquantized (~1.26 GB). The fix:
```python
quantize_dynamic(src, dst, weight_type=QuantType.QInt8,
extra_options={"EnableSubgraph": True})
```
This quantizes the MatMuls inside the `If` branches → merged decoder ~322 MB,
keeping the KV cache (so generation stays fast). See `scripts/export.py`.
### Tokenizer
`tokenizer.json` is a validated HuggingFace **fast** tokenizer whose encoding
matches the official SMaLL-100 tokenizer **exactly** (verified across zh/en/ja/ko).
It loads in `tokenizers` (Rust), transformers.js, DJL (Android) and
swift-transformers (iOS) — one tokenizer for every platform. It was rebuilt from
`Xenova/m2m100_418M` (same 128112 vocab) by dropping 1053 merges that referenced
out-of-vocab pieces (which made newer `tokenizers` reject the file), and by
setting the post-processor to append `` only. The language token is **not**
baked in — prepend `lang_tokens.json[tgt]` in app code (see USAGE.md).
`sentencepiece.bpe.model` + `tokenization_small100.py` remain for the upstream
Python path.
### License
SMaLL-100 is released under the **MIT license** (see the upstream model card).
This repo redistributes the ONNX-converted weights under the same terms.
Upstream: SMaLL-100 (Mohammadshahi et al., EMNLP 2022),
[paper](https://arxiv.org/abs/2210.11621) ·
[model](https://huggingface.co/alirezamsh/small100).
---
## 中文
[**SMaLL-100**](https://huggingface.co/alirezamsh/small100) 的 ONNX 导出版——一个
浅层、蒸馏的多语言机器翻译模型(从 M2M-100 蒸馏而来),覆盖 **100 种语言、任意语向
直译**(无需经英语中转)。本仓库打包了 **int8 量化、约 609 MB** 的部署集,供端上 /
离线使用。目前 Hub 上没有官方的 SMaLL-100 ONNX,本仓库填补这一空缺。
### 为什么选 SMaLL-100
- **任意 100 种语言之间直译**(无中转 → 单跳)。
- **浅层解码器(3 层)** → 快:质量相近下比 M2M-100 快约 4×;笔记本 CPU 上贪心解码
约 150–250 ms/句。
- 体积适合移动端:**int8 609 MB**,相比之下 NLLB-200-distilled-600M 约 850 MB。
### 文件说明
```
onnx/
encoder_model.onnx 274 MB (int8)
decoder_model_merged.onnx 307 MB (int8, 合并 decoder,带 KV 缓存)
tokenizer.json 经验证的 HF fast 分词器(四端通用)
lang_tokens.json {lang_to_id, eos, pad, unk, decoder_start}
sentencepiece.bpe.model、vocab.json 上游 SentencePiece 分词器(Python 路径)
config.json、generation_config.json、tokenizer_config.json、
special_tokens_map.json、added_tokens.json
tokenization_small100.py SMaLL-100 分词器(上游 Python)
USAGE.md 与平台无关的算法说明
examples/ python · transformers-js · android · ios
scripts/export.py 复现导出 + int8 量化
scripts/translate_demo.py 端到端示例(上游分词器路径)
```
两个 `.onnx` 文件是 **int8 量化**的,但保留 optimum 标准命名
(`encoder_model.onnx` / `decoder_model_merged.onnx`),以便 `from_pretrained`
无需额外参数即可加载;本仓库只提供量化后的权重。
模型常量:`d_model=1024`、`encoder_layers=12`、`decoder_layers=3`、`heads=16`
(head_dim=64)、`vocab=128112`、`decoder_start=eos=2`、`pad=1`、`unk=3`。decoder
的 ONNX 输入输出是 optimum 标准合并解码器签名(`input_ids`、
`encoder_hidden_states`、`encoder_attention_mask`、
`past_key_values.{i}.{decoder,encoder}.{key,value}`、`use_cache_branch` →
`logits`、`present.{i}...`)。
### 用法(optimum / onnxruntime,Python)
```bash
pip install optimum[onnxruntime] transformers sentencepiece
python scripts/translate_demo.py
```
```python
from optimum.onnxruntime import ORTModelForSeq2SeqLM
from tokenization_small100 import SMALL100Tokenizer
tok = SMALL100Tokenizer.from_pretrained(".")
model = ORTModelForSeq2SeqLM.from_pretrained(
".", subfolder="onnx", use_merged=True, use_io_binding=False)
tok.tgt_lang = "en" # 目标语言 token 会被加到「源句」前
enc = tok("你好,请问最近的地铁站怎么走?", return_tensors="pt")
out = model.generate(**enc, num_beams=1, max_length=128)
print(tok.batch_decode(out, skip_special_tokens=True)[0])
# -> "Hello, what is the nearest metro station?"
```
**注意(M2M 与 SMaLL-100 的区别)**:SMaLL-100 **不使用** `forced_bos_token_id`。
目标语言通过设置 `tok.tgt_lang` 选择,它会把语言 token(如 `en`→128022、
`zh`→128102、`ja`→128046、`ko`→128052)加到**源句**前面。解码从
`decoder_start_token_id=2` 开始贪心生成。
### 支持的语言(100 种)
任意语向互译:把下列任一 ISO-639 code 作为目标语言传入。完整的 code→token id 映射见
[`lang_tokens.json`](lang_tokens.json)。
100 种语言 · 点击展开
| | | | |
|---|---|---|---|
| `af` 南非荷兰语 | `am` 阿姆哈拉语 | `ar` 阿拉伯语 | `ast` 阿斯图里亚斯语 |
| `az` 阿塞拜疆语 | `ba` 巴什基尔语 | `be` 白俄罗斯语 | `bg` 保加利亚语 |
| `bn` 孟加拉语 | `br` 布列塔尼语 | `bs` 波斯尼亚语 | `ca` 加泰罗尼亚语 |
| `ceb` 宿务语 | `cs` 捷克语 | `cy` 威尔士语 | `da` 丹麦语 |
| `de` 德语 | `el` 希腊语 | `en` 英语 | `es` 西班牙语 |
| `et` 爱沙尼亚语 | `fa` 波斯语 | `ff` 富拉语 | `fi` 芬兰语 |
| `fr` 法语 | `fy` 西弗里斯语 | `ga` 爱尔兰语 | `gd` 苏格兰盖尔语 |
| `gl` 加利西亚语 | `gu` 古吉拉特语 | `ha` 豪萨语 | `he` 希伯来语 |
| `hi` 印地语 | `hr` 克罗地亚语 | `ht` 海地克里奥尔语 | `hu` 匈牙利语 |
| `hy` 亚美尼亚语 | `id` 印度尼西亚语 | `ig` 伊博语 | `ilo` 伊洛卡诺语 |
| `is` 冰岛语 | `it` 意大利语 | `ja` 日语 | `jv` 爪哇语 |
| `ka` 格鲁吉亚语 | `kk` 哈萨克语 | `km` 高棉语 | `kn` 卡纳达语 |
| `ko` 韩语 | `lb` 卢森堡语 | `lg` 卢干达语 | `ln` 林加拉语 |
| `lo` 老挝语 | `lt` 立陶宛语 | `lv` 拉脱维亚语 | `mg` 马尔加什语 |
| `mk` 马其顿语 | `ml` 马拉雅拉姆语 | `mn` 蒙古语 | `mr` 马拉地语 |
| `ms` 马来语 | `my` 缅甸语 | `ne` 尼泊尔语 | `nl` 荷兰语 |
| `no` 挪威语 | `ns` 北索托语 | `oc` 奥克语 | `or` 奥里亚语 |
| `pa` 旁遮普语 | `pl` 波兰语 | `ps` 普什图语 | `pt` 葡萄牙语 |
| `ro` 罗马尼亚语 | `ru` 俄语 | `sd` 信德语 | `si` 僧伽罗语 |
| `sk` 斯洛伐克语 | `sl` 斯洛文尼亚语 | `so` 索马里语 | `sq` 阿尔巴尼亚语 |
| `sr` 塞尔维亚语 | `ss` 斯瓦蒂语 | `su` 巽他语 | `sv` 瑞典语 |
| `sw` 斯瓦希里语 | `ta` 泰米尔语 | `th` 泰语 | `tl` 他加禄语 |
| `tn` 茨瓦纳语 | `tr` 土耳其语 | `uk` 乌克兰语 | `ur` 乌尔都语 |
| `uz` 乌兹别克语 | `vi` 越南语 | `wo` 沃洛夫语 | `xh` 科萨语 |
| `yi` 意第绪语 | `yo` 约鲁巴语 | `zh` 中文 | `zu` 祖鲁语 |
### 基准(int8,笔记本 CPU arm64,贪心解码)
| 语向 | 延迟 |
|---|---|
| 中→英 | ~160 ms |
| 英→中 | ~130 ms |
| 日→中 | ~170 ms |
| 韩→中 | ~140 ms |
作为 330M 蒸馏模型质量尚可;高资源语对不错,较难的语对偶有地名 / 语气偏差。
### 量化方法(合并 decoder 的关键技巧)
合并 decoder 把「带缓存 / 不带缓存」两条路径包在一个 ONNX `If` 节点里。
`onnxruntime.quantization.quantize_dynamic` 默认不进子图,会导致 decoder 未被量化
(约 1.26 GB)。解决办法:
```python
quantize_dynamic(src, dst, weight_type=QuantType.QInt8,
extra_options={"EnableSubgraph": True})
```
这会量化 `If` 分支内部的 MatMul → 合并 decoder 约 322 MB,且保留 KV 缓存(生成依旧
很快)。见 `scripts/export.py`。
### 分词器
`tokenizer.json` 是一份经过验证的 HuggingFace **fast** 分词器,编码结果与官方
SMaLL-100 分词器**完全一致**(中英日韩均已核对)。它能在 `tokenizers`(Rust)、
transformers.js、DJL(Android)、swift-transformers(iOS) 中加载——**一份分词器通吃四端**。
它基于 `Xenova/m2m100_418M`(同 128112 词表)重建:剔除了 1053 个引用越界 piece 的
merge(正是它们导致新版 `tokenizers` 拒绝加载),并把 post-processor 改为只追加
``。语言 token **不**写死在分词器里——由应用层前缀 `lang_tokens.json[tgt]`
(见 USAGE.md)。`sentencepiece.bpe.model` + `tokenization_small100.py` 保留作为
上游 Python 路径。
### 许可协议
SMaLL-100 以 **MIT 协议**发布(见上游模型卡)。本仓库以相同条款再分发 ONNX 转换后的
权重。
上游:SMaLL-100(Mohammadshahi 等,EMNLP 2022),
[论文](https://arxiv.org/abs/2210.11621) ·
[模型](https://huggingface.co/alirezamsh/small100)。