This is an ONNX version of nlp-chula/phayathaibert-thai-pos-tagger,
a PhayaThaiBERT model fine-tuned for Universal POS tagging on UD Thai-TUD. It runs with
onnxruntime and tokenizers, without PyTorch or Transformers.
This repository only converts the model. All credit for training goes to the original authors
at nlp-chula and to the
PhayaThaiBERT authors.
Made for the PyThaiNLP POS tagging engine
(issue #1536).
Files
| File |
Contents |
model.onnx |
The model, opset 17, with dynamic batch and sequence axes. Embedding tables are int8; all other weights are float32. |
tokenizer.json |
Tokenizer, unchanged from the original model. |
config.json |
Original config, including the 15 id2label UPOS tags. |
scripts/ |
Scripts for export, inference and the comparison against PyTorch. |
Usage
Input is a list of pre-segmented Thai words. Each word is tagged with the prediction for its
first subword, the same alignment the model was trained with.
pip install onnxruntime tokenizers numpy huggingface_hub
import json
import numpy as np
import onnxruntime as ort
from huggingface_hub import snapshot_download
from tokenizers import Tokenizer
model_dir = snapshot_download("wiriyabot/phayathaibert-thai-pos-tagger-onnx")
session = ort.InferenceSession(f"{model_dir}/model.onnx", providers=["CPUExecutionProvider"])
tokenizer = Tokenizer.from_file(f"{model_dir}/tokenizer.json")
id2label = {int(k): v for k, v in json.load(open(f"{model_dir}/config.json"))["id2label"].items()}
words = ["ฉัน", "กิน", "ข้าว", "ที่", "ร้านอาหาร"]
enc = tokenizer.encode(words, is_pretokenized=True)
logits = session.run(None, {
"input_ids": np.array([enc.ids], dtype=np.int64),
"attention_mask": np.array([enc.attention_mask], dtype=np.int64),
})[0][0]
tags, prev = [], None
for i, word_id in enumerate(enc.word_ids):
if word_id is not None and word_id != prev:
tags.append(id2label[int(logits[i].argmax())])
prev = word_id
print(list(zip(words, tags)))
# [('ฉัน', 'PRON'), ('กิน', 'VERB'), ('ข้าว', 'NOUN'), ('ที่', 'ADP'), ('ร้านอาหาร', 'NOUN')]
Input length
Keep each model call at 415 subwords or fewer, counting <s> and </s>. Split longer
input into sentences, or into chunks of whole words, and tag each one separately.
- The model runs on up to 510 tokens (longer input fails), but only the first 415 are
reliable. From position 415 on, accuracy drops from about 89% to about 34%. The
original PyTorch model does the same, so the drop is not caused by the ONNX export. It
suggests that the model was pretrained on sequences of about 416 tokens.
tokenizer.json truncates at 510 tokens without a warning. This setting comes from
the original model. Words after the cut get no subword, so the loop above returns fewer
tags than words. Call tokenizer.no_truncation() and check the length yourself.
Measured on the UD Thai-TUD test set, tagged as one long text in chunks of whole words:
| Maximum chunk length |
UPOS accuracy |
| 510 subwords |
80.26% |
| 415 subwords |
90.08% |
| One sentence at a time (longest sentence: 164 subwords) |
91.21% |
The PyThaiNLP engine pos_tag(words, engine="phayathaibert")
(issue #1536) chunks long input this way.
Evaluation
All results below are on the UD Thai-TUD test set: 363 sentences, 7,683 words, gold
segmentation. Each run was on CPU (Intel Core i5-1340P) with onnxruntime 1.30, using
scripts/compare_with_pytorch.py.
| Version |
Size |
UPOS accuracy |
Same tag as PyTorch |
| PyTorch original |
1,108 MB |
91.16% |
— |
| ONNX float32 |
1,108 MB |
91.16% |
100% |
| ONNX, int8 embeddings (this repo) |
532 MB |
91.21% |
99.64% |
| ONNX, int8 everywhere (not used) |
278 MB |
87.91% |
92.05% |
- Speed. ONNX averaged roughly 35–45 ms per sentence on this CPU; PyTorch averaged
about 120 ms.
- Batching. Outputs are identical whether a sentence is run alone or inside a padded
batch.
- Tokenizer. The
tokenizers encoding matches the Transformers tokenizer on every test
sentence.
- Which weights are int8. Only the embedding tables (
Gather: word, position and token
type embeddings) are quantized. They hold
69% of the weights and tolerate int8 well. Quantizing the attention and feed-forward
MatMul layers too costs 3.3 accuracy points. It also makes the results depend on the
batch, so those layers stay float32.
- Comparison with the model card. The original model card reports 90.64%. The higher
figure here was measured with the same alignment, by a different evaluation script.
Reproduce
pip install torch transformers onnx onnxruntime
hf download nlp-chula/phayathaibert-thai-pos-tagger --local-dir source
python scripts/export_onnx.py source out # export + embedding quantization
python scripts/pos_tag_onnx.py out th_tud-ud-test.conllu
Citation
Please cite PhayaThaiBERT:
@article{Sriwirote_2025,
title={PhayaThaiBERT: Enhancing a Pretrained Thai Language Model with Unassimilated Loanwords},
volume={24},
ISSN={2375-4702},
url={http://dx.doi.org/10.1145/3765962},
DOI={10.1145/3765962},
number={11},
journal={ACM Transactions on Asian and Low-Resource Language Information Processing},
publisher={Association for Computing Machinery (ACM)},
author={Sriwirote, Panyut and Rutherford, Attapol T. and Thapiang, Jalinee and Timtong, Vasan},
year={2025},
month=Oct,
pages={1–17}
}
License
MIT, the same as the original model. Training data: the
UD Thai-TUD treebank (CC BY-SA 4.0).