Skip to main content

Mon Tokenizer

PyPI

Mon mixes with Burmese constantly and English routinely, so all three are trained on and measured separately. Anything else on the page (Thai, emoji, IPA, CJK) round-trips through byte fallback rather than being lost.

pip install mon-tokenizer
from mon_tokenizer import MonTokenizer

tokenizer = MonTokenizer()
result = tokenizer.encode("ဂွံအခေါင်အရာမွဲသ္ဂောံဒုင်စသိုင်ကၠာကၠာရ။")

result["pieces"]  # ['▁ဂွံ', 'အခေါင်အရာ', 'မွဲ', 'သ္ဂောံ', 'ဒုင်စသိုင်', 'ကၠာ', 'ကၠာရ။']
result["ids"]  # token ids
result["text"]  # the normalized string that was encoded

assert tokenizer.decode_ids(result["ids"]) == result["text"]

Also on the Hub, producing identical ids:

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("janakhpon/mon_tokenizer")

Measured

Vocabulary 64,256 · trained on the train split of 893,936 lines / 85.8M characters · scored on the whole validation split.

stratum chars/token syllable violations round-trip byte fallback
Mon 4.686 1.07% (n=492,469) 100% 0.00%
Burmese 4.117 0.93% (n=25,546) 100% 0.00%
English 4.112 — (n=0) 100% 0.02%
mixed script 3.804 0.81% (n=28,133) 100% 0.19%

98.74% of the distinct characters in the Mon validation split are single tokens — 392 of 397, over all 29,600 lines. The remaining five (a combining diaeresis below, a Greek capital pi, two Sinhala letters and one emoji; seven occurrences in 2.28M characters) round-trip through byte fallback at one to four tokens each.

Compression describes a corpus, not a tokenizer — quote it with the corpus. Violations count token boundaries inside a Myanmar syllable; the denominator is given because English legitimately has none. Round-trip compares after normalization, which is a deliberate transform.

model_card() returns these machine-readably, and a test fails if they drift from what the artifact does.

Round-trip, and its two known limits

for text in ["🙏 emoji", "ภาษาไทย", "漢字", "Ωπ√∫", "ကျော် page 42 — “quoted” ၏"]:
    r = tokenizer.encode(text)
    assert tokenizer.decode_ids(r["ids"]) == r["text"]

Without byte fallback, characters outside the vocabulary aren't flagged — they're deleted, leaving fluent-looking output with content missing. That matters if you feed OCR output back into a corpus.

Two inputs are known not to round-trip. The loss happens at encode, so no decoder can recover it:

tokenizer.encode(" abc")["ids"] == tokenizer.encode("abc")["ids"]  # True
tokenizer.encode("a b")["ids"] == tokenizer.encode("a▁b")["ids"]  # True

A single leading space is dropped, and a literal U+2581 is read as a space. The table above still holds: the corpus it was measured on is stripped per line and contains no U+2581, so it excludes both cases by construction rather than disproving them. Both are recorded as xfail tests.

Fixing this needs prepend_scheme="never", which changes every token id and so requires a retrain and a new Hub artifact. Deferred on purpose, because id stability is worth more than these two cases. Interior and trailing whitespace, tabs, newlines, NUL and control characters all round-trip.

Normalization

Applied automatically, and stored inside the artifact so it cannot drift from the model: invisible characters stripped, Unicode space separators folded to U+0020, then NFC. Runs of spaces are preserved.

API

encode(text) {"pieces": [...], "ids": [...], "text": normalized}
encode_ids(text) / encode_batch(texts) list[int] / list[list[int]]
decode(pieces) / decode_ids(ids) str
normalize(text) the artifact's own normalizer
get_vocab_size() / get_vocab() int / dict[str, int]
id_to_piece(id) / piece_to_id(piece) str / int
unk_id bos_id eos_id pad_id 0 1 2 3
model_card() artifact_version, corpus digest, config, measured metrics

The artifact is cached by path, so constructing many instances is cheap.

CLI

pip install 'mon-tokenizer[cli]'

mon-tokenizer "ဂွံအခေါင်အရာမွဲ"          # tokenize
mon-tokenizer -v "ဂွံအခေါင်အရာမွဲ"       # per-token table
mon-tokenizer -d --ids "316,12644,294"   # decode ids

Exit codes: 0 ok · 1 usage · 2 artifact failed to load · 130 interrupted.

Upgrading from 0.2.x

Every token id has changed. Rebuild any embedding matrix built against 0.2.x, or pin mon-tokenizer<1.0 — it stays on PyPI and works.

Requirements

Python 3.11+. Two direct dependencies: tokenizers and regex. The install is 17 packages / ~30MB, because tokenizers requires huggingface-hub.

Development

uv sync --all-extras
uv run ruff format --check . && uv run ruff check . && uv run mypy && uv run pytest

Retraining, releasing and the house rules for tests: docs/how_to_contribute.md.

Design

docs/architecture.md — every decision with the measurement behind it and the condition that would reverse it: algorithm, vocabulary size, why grapheme clusters are the wrong unit for Myanmar, and what was rejected.

Links

MIT.

Metadata

Release files for mon-tokenizer 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mon-tokenizer 1.0.1
File Size Uploaded
mon_tokenizer-1.0.1.tar.gz 1.1 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for mon-tokenizer 1.0.1
File Interpreter ABI Platform
mon_tokenizer-1.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 2.2 MB

Release files / mon_tokenizer-1.0.1.tar.gz

Download URL mon_tokenizer-1.0.1.tar.gz
Size 1.1 MB
Tags Source
SHA-256 checksum
How to use checksums
10c2b7e75edb624713c285dec681e0e9755b30bc59b92962b8d2630eb95de2de
BLAKE2b-256 checksum
How to use checksums
3c1547333dfd8f0284c3367dba697c39aa1edf8f1965c45968944af85e1b1ce9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / mon_tokenizer-1.0.1-py3-none-any.whl

Download URL mon_tokenizer-1.0.1-py3-none-any.whl
Size 1.1 MB
Tags Python 3
SHA-256 checksum
How to use checksums
6e8d2d78662478571991f5de44a68ae946e2038bdc7f8e010cbcfdbff78d9a45
BLAKE2b-256 checksum
How to use checksums
975b9a3e69b23bc9866b9f410bfd42a3a7f353530e6ed7d4cae3f8c77201d812
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 release files

1.0.0

2 release files

0.2.3

2 release files

0.2.1

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page