Skip to main content

Primoji

A compositional semantic tokenizer for language models, built from Natural Semantic Metalanguage (NSM) primitives, Unicode emoji, common word tokens, and a UTF-8 byte fallback.

from primoji import Tokenizer

tok = Tokenizer()
ids = tok.encode("The teacher explained photosynthesis")
tok.decode(ids)
# 'teacher say photosynthesis'

tok.vocab_size
# 10195

Install

pip install primoji

Requires Python 3.10+. Only runtime dependency is emoji. Optional extras for the dictionary build pipeline (spacy, wordfreq), training scripts (torch, tokenizers, datasets), or fuzzy matching (symspellpy):

pip install "primoji[build-dict]"   # to rebuild the dictionary from sources
pip install "primoji[train]"        # to run the training scripts
pip install "primoji[spelling]"     # to enable conservative SymSpell fuzzy matching
pip install "primoji[all]"          # everything (also includes dev tools)

What it is

Primoji is a tokenizer that constructs token meanings compositionally. Instead of discovering subword units from corpus statistics like BPE, it ships with a fixed compositional vocabulary of about 10,000 tokens: 1,175 Unicode emoji, 140 NSM semantic primitives, ~7,700 common word tokens, ~830 structural and geographic tokens (flags, anchors, punctuation, math), and 258 byte-fallback tokens. Rare and technical words decompose into short sequences of primitives ([PLANT, HAVE, LIGHT] for photosynthesis, [WATER, CAUSE, AIR] for evaporation). The encode pipeline never produces an UNK token.

When you might want it: research on tokenization, controlled experiments that need a vocabulary 3x smaller than BPE at near-parity compression, multilingual experiments where the primitive set transfers across languages by design, small-vocab architectures whose cost scales with vocabulary size, or educational and scientific text where compositional technical vocabulary is dense. It is not a drop-in BPE replacement for production English chatbots: generation is lossy at the word level (decoded text reconstructs concepts but not always exact surface forms), and creative or stylistic writing loses distinctions like "melancholy" vs "sadness" that BPE preserves as distinct tokens.

Quick start

from primoji import Tokenizer

tok = Tokenizer()

# Encode and decode
ids = tok.encode("Water evaporates when heated")
print(ids)
# [1266, ..., ...]
print(tok.decode(ids))

# Inspect a token
print(tok.describe(ids[0]))
# '💧 (ID 1266) — Tier 2 primitive: WATER — Liquid, fluid'

# Classify a word by which tier handles it
print(tok.classify_word("photosynthesis"))   # 'dict_composed'
print(tok.classify_word("water"))            # 'tier2_primitive'
print(tok.classify_word("the"))              # 'dict_dropped'
print(tok.classify_word("Mediterranean"))    # 'tier3_anchor' or 'byte_fallback'

# Vocabulary size
print(tok.vocab_size)                        # 10195

Vocabulary structure

Four tiers with strict encoding precedence. The model only ever sees integer IDs; the tier labels exist for diagnostics and the dictionary build.

Tier Count Examples
1a Direct emoji ~1,175 🐕 (dog), 🏠 (house), ☀️ (sun)
1b Common word tokens ~7,700 government, important, however
2 Compositional primitives 140 PLANT, HAVE, LIGHT, MOVE, KNOW
3 Structural and geographic ~830 🇩🇪, 🇫🇷, punctuation, digits, math operators, NER anchors
4 UTF-8 byte fallback 258 256 byte tokens + 2 boundary markers

The 140 primitives are 65 canonical NSM primes from Wierzbicka's Natural Semantic Metalanguage plus 75 domain expansions (perceptual, grammatical, scientific, social). Country flags act as geographic modifiers in compositions. Anchors are the top ~500 proper nouns extracted from FineWeb-Edu via spaCy NER.

A note on emoji: the model never sees glyphs, only integer IDs. Tier 1a is just a convenient source of ~1,200 visually distinct token slots for concrete nouns. In generated output, emoji tokens decode to their English words ("dog", "house", "sun"), not to Unicode emoji.

Configuration

The dictionary, primitive set, and word list are all swappable. Five tunable dimensions:

  1. Vocabulary size. From ~5,300 tokens (~6% composed text) to ~18,000 tokens (~2% composed). Controls the trade-off between compression and semantic-structure signal.
  2. Composition depth. Maximum number of primitives per concept. Default 5.
  3. Primitive count. Add domain-specific primitives (e.g.\ HEALTH for medical text) without touching the rest of the system.
  4. Composition rate. A consequence of the above three.
  5. Domain specialization. A medical Primoji can have 3,000 medical compositions on top of 10,000 general words. The dictionary build pipeline accepts a target corpus and produces a corresponding word list.

Rebuild the dictionary from sources:

pip install "primoji[build-dict]"
python -m scripts.build_dictionary

This regenerates data/dictionary_seed.json from layered sources: Unicode CLDR annotations, NSM primitive synonyms, NER anchors, WordNet auto-compositions. Layer precedence is fixed: primitive synonyms override emoji catalog entries, single-primitive word tokens override compositions to preserve reverse lookup, and ELCo and emoji2vec mappings supplement CLDR base entries.

Training your own model

Reference training scripts for 125M and 1B GPT-style models live in scripts/:

# Tokenize FineWeb-Edu shards (or any HuggingFace dataset)
python -m scripts.prepare_training_data --n-docs 500000

# Train 125M
python -m scripts.train --tokenizer primoji --v2 --byte-weight 0.7

# Train 1B with gradient accumulation
python -m scripts.train --tokenizer primoji --model-size 1b \
  --batch-size 4 --grad-accum 8 --v2 --byte-weight 0.7

# Train BPE baseline on the same data
python -m scripts.train --tokenizer mistral

Bits-per-byte (BPB) is computed correctly across vocabulary sizes for fair cross-tokenizer comparison. See scripts/train.py for the full hyperparameter list.

Limitations

  • English only. The NSM primes are verified across 30+ language families, but the dictionary, word list, and evaluation are all English. Multilingual evaluation is future work.
  • Lossy at the word level. Known compositions decode exactly; novel compositions decode to their primitive names, which is sufficient for training-efficiency studies (BPB) but not for verbatim text generation without an auxiliary surface-form mechanism.
  • Designed for educational and scientific text. Compression and learning benefits are strongest where vocabulary is compositional and concept-dense. Conversational, creative, or code-heavy text will not see the same gains.
  • Not yet tested at scale beyond 1B parameters.

Tests

pip install "primoji[dev]"
pytest

616 tests covering encode/decode round-trips, byte-fallback coverage, compositional embedding correctness, ID-range invariants, and frozen vocabulary boundaries.

Paper

The full paper is included in this repository as paper.pdf. It is not yet on arXiv. If you reference this work, please cite as:

@unpublished{bandov2026primoji,
  title  = {Primoji: Compositional Semantic Tokenization for Language Model Training},
  author = {Bandov, Frane},
  year   = {2026},
  note   = {Manuscript. \url{https://github.com/frane/primoji/blob/master/paper.pdf}},
}

License

Apache 2.0. See LICENSE.

Acknowledgments

Anna Wierzbicka and the wider Natural Semantic Metalanguage research community for the linguistic foundation. The FineWeb-Edu team at HuggingFace for the training corpus. The Unicode CLDR project for emoji annotations.

Metadata

Release files for primoji 0.1.0

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

Source distribution (sdist)

Source distribution for primoji 0.1.0
File Size Uploaded
primoji-0.1.0.tar.gz 480.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for primoji 0.1.0
File Interpreter ABI Platform
primoji-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 956.9 kB

Release files / primoji-0.1.0.tar.gz

Download URL primoji-0.1.0.tar.gz
Size 480.1 kB
Tags Source
SHA-256 checksum
How to use checksums
f2b53db254405461dbd63252a84236bce016bef9ea4fabeadef45b6467813c59
BLAKE2b-256 checksum
How to use checksums
ac13b9c299cfcd3ae4df3ba6fcec21a68707e69a26d9344945859ba8e4f8aa13
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 7, 2026.

Transparency log

Release files / primoji-0.1.0-py3-none-any.whl

Download URL primoji-0.1.0-py3-none-any.whl
Size 476.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f82dc85834181d222218454697fa7b13063cca2c47a5d3f9cabf5efa9286a170
BLAKE2b-256 checksum
How to use checksums
184dfef3eee7318d49325fc1d7bff473ce4d3ffecdc90c8ba46c806932373d6b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

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