ASTERIZER Tokenizer

One byte-level BPE tokenizer, frozen once and reused for ~10 years across every model from 100M → 1T params. South-Indian-first (Kannada / Tamil / Telugu / Malayalam), plus code, math, and broad multilingual coverage. Built only from open, license-audited data.

Everything lives in a single file: asterizer.py (config + all stages + CLI).

Quick start

python3 -m pip install tokenizers datasets "huggingface_hub[hf_transfer]" tiktoken transformers sentencepiece
# interactive (asks vocab size + GB):
python3 asterizer.py
export HF_TOKEN=...  # gated permissive code (starcoderdata) - accept the dataset terms on HF first
# -- STEP 1 — instant config/plan check, NO download (seconds) --
python3 asterizer.py --vocab 8K --gb 1 --dry-run --yes --allow-license-review
# -- STEP 2 — tiny END-TO-END test (~1 GB, a few min): proves download -> clean -> corpus -> train ->
#             eval -> smoke all work and write a real tokenizer dir, with NO errors --
python3 asterizer.py --vocab 8K --gb 1 --yes --allow-license-review
#  → if a bucket under-fills on the tiny sample, just bump --gb (e.g. --gb 3) and re-run.
# -- STEP 3 — pin source versions, then the PRODUCTION FREEZE (rented high-RAM CPU box): 32K+64K+128K
#             from ONE shared corpus, all gates on (~14–24 h, ≥256–512 GB RAM, no GPU) --
python3 asterizer.py --lock
python3 asterizer.py --vocab 32K,64K,128K --gb 240 --yes --require-lock --allow-license-review
# validate / inspect an existing tokenizer:
python3 asterizer.py --smoke runs/128K_240gb/tokenizer/asterizer_128K_v1.json
# FULL held-out fertility + compression vs cl100k / o200k / Llama-3 / Qwen / Gemma / Sarvam:
python3 asterizer.py --eval runs/128K_240gb/tokenizer/asterizer_128K_v1.json
# post-freeze edits:
python3 asterizer.py --promote tok.json --names "<|new_ctrl|>,<|tool2|>" --out tok2.json # size FIXED, sidecars auto-synced
python3 asterizer.py --extend tok.json --new-data new.jsonl --num-new 2000 --out tok2.json # GROWS vocab

Each build writes a complete HuggingFace directory: tokenizer.json + asterizer_<v>_v1.json + tokenizer_config.json + special_tokens_map.json

spec_hash.txt + fertility_report.json , with the shared corpus provenance in manifest.json .

Frozen design (current spec)

Aspect Decision Why
Algorithm byte-level BPE, unk_token=None , all 256 bytes seeded never an UNK in 10 years — any script/emoji/code round-trips
Normalizer NFC (never NFKC) canonical Indic forms; NFKC would destroy x² , full-width, code
Pre-tokenizer GPT-4o (o200k) regex via Split(Regex)ByteLevel(use_regex=False) its letter classes include \p{M} → Indic vowel-signs/viramas stay glued to the consonant (GPT-2/cl100k/Llama-3 split them)
Digits individual ( \p{N} ) stable arithmetic; CamelCase also splits (good for code)
Post-processor ByteLevel(trim_offsets=True) correct offsets for training pipelines
Specials 32 named + reserved (power-of-two budget) learned-merge count stays exact; reserved slots are promotable
max_token_length 32 bytes (~10 Indic chars) real Dravidian words form, no giant overfit tokens
min_frequency 2 don't prune rare South-Indian syllable merges

Vocab profiles ( vocab_size = 256 bytes + 32 named + reserved + learned ):

profile vocab_size reserved learned use
64K 65,536 992 64,256 1B–4B models (embedding tax acceptable)
128K 131,072 2,016 128,768 7B+ models (flagship; multilingual/code headroom)

Freeze policy (per the review): use 64K for 1B–4B, 128K for 7B+. At d_model=2048 a 131,072 vocab is ~268M tied-embedding params vs ~134M for 64K — a heavy tax on a 1B model.

Corpus (40% South-Indian, scales to --gb ): South-Indian 40% · code 17% (StarCoderData, 15 languages, license review-tagged) · english 10% · other-Indic 8% (hi/bn/gu/mr/pa/or) · european 5% · more-european 3.5% (el/tr/pl/nl/sv/ka/hy) · SEA 3.5% (th/vi/id/km/my/lo) · cjk 4.5% · semitic 3.8% (ar/fa/ur/he) · cyrillic 2.5% · african 1.5% (sw/am/yo/ha) · coverage 0.5% (bo/si) · math 1.5%. 26 scripts, ~40 languages. Every bucket is coverage-gated. --gb N scales to exactly N GB.

Professor review (2026-06-26) — resolution

Blocker Status
Stale 64K artifact, not from current spec ✅ quarantined ( STALE_DO_NOT_FREEZE.txt ); rebuild required
Not HF/vLLM/TGI directory-ready; pad/eos None ✅ full sidecars + bos/eos/pad now emitted
Model configs vocab_size 64000/128000 ≠ 65536/131072 ✅ fixed in all 8 FORGE configs
Packer uses uint32 at vocab 65536 (fits uint16) ✅ condition < 65536 → <= 65536 (3 sites in forge.py)
Corpus not universal ✅ expanded 12 → 26 scripts, ~25 languages
Cleaning strips ZWJ/ZWNJ ✅ now preserved (Indic conjuncts, Persian/Urdu, emoji)
Dataset revisions not pinned revision plumbing + manifest.json corpus sha256 anchor
License safety asserted, not enforced ✅ per-source license tags + manifest audit
stage_extend breaks reserved-slot promise ✅ new stage_promote (size FIXED); extend clearly labeled vocab-growth
Smoke gate masked by ... or True _smoke_gate aborts the build on failure
No per-language/domain fertility report fertility_report.json (23 language + domain probes)
64K vs 128K guidance ✅ documented freeze policy (64K ≤ 4B, 128K ≥ 7B)
Rebuild both on one pinned corpus; emit reports ⌛ run the lock-gated freeze on the rented box ( --vocab 64K,128K --gb 240 --require-lock )

Professor review #3 (2026-06-26) — final hardening

Blocker Status
No built artifact to validate ( runs/ absent) ⌛ run the lock-gated freeze on the rented box
45 source revisions unpinned (resolved only post-build) --lock -> sources.lock.json (60/60 pinned) before download; --require-lock gates the build
Coverage gate only on South-Indian @ 50% ✅ EVERY bucket gated (default 60%, required 85%, per-bucket min_frac overrides)
Code license mixed-github(review) ✅ swapped to permissive bigcode/starcoderdata (gated, HF_TOKEN); codeparrot kept as a commented dev fallback
stage_promote leaves sidecars to manual edit ✅ auto-syncs tokenizer_config.json + special_tokens_map.json + canonical tokenizer.json
Eval too small (23 one-sentence probes) --eval : held-out streamed corpora, per-language bytes/token + tokens/word vs cl100k/o200k/Llama-3/Qwen/Gemma/Sarvam
added: Urdu absent from corpus urd_Arab in semitic bucket + eval
added: ZWJ-emoji only tested non-ZWJ ✅ ZWJ probe in smoke + eval domains

Professor review #4 (2026-06-26) — resume safety, exact sizing, honest license

Finding Status
Reference corpus 60 GB but sources sum to 60.5 GB ( --gb 240 → 242) REFERENCE_TOTAL_GB auto-computed from BUCKETS → --gb N = exactly N GB
.done marker written before the coverage gate → resume can skip a bad bucket forever ✅ floor checked before the marker; final gate clears markers of under-filled buckets so resume re-downloads (validated with a stubbed streamer)
Code tagged permissive without a stronger filter ✅ relabeled permissive(bigcode-filtered; review) ; comment documents per-file-license filtering for a hard freeze
No built artifacts to smoke-test ⌛ unchanged — run the build on the rented box

CHANGELOG (newest first — always appended)

2026-06-26 — Professor-loop pre-freeze review (gpt-5.5 reviewer, auto-logged)

A two-brain refinement loop hardens asterizer.py round by round: worker = the build author, reviewer = gpt-5.5 via test1/auto_refine_loop.py (ARM proxy). Each round sends the summary + full source, gets a JSON verdict, and applies fixes. The reviewer's #1 blocker every round is "no built artifacts exist", so APPROVED requires actually running the 240 GB build — the code/gate hardening below is what the loop produced. test1/professor_review.py appends each round to the table below automatically (logged here in the CHANGELOG, no separate log files).

Round UTC Verdict Issues Pros
5 2026-06-26T18:18:2Z NEEDS_WORK 7 6
4 2026-06-26T22:42Z NEEDS_WORK 8 8
3 2026-06-26T22:34Z NEEDS_WORK 11 8
2 2026-06-26T22:20Z NEEDS_WORK 8 8
1 2026-06-26T21:59Z NEEDS_WORK 9 6

Fixes applied across rounds (all in asterizer.py , validated offline):

2026-06-26 (round 4) — Resume safety + exact sizing + honest license (review #4)

1. Coverage gate no longer trappable on resume

2. Exact corpus sizing

3. Honest code license tag

2026-06-26 (round 3) — Final hardening (professor review #3)

1. Pre-resolved source lockfile ( --locksources.lock.json )

2. Coverage floors on EVERY bucket

3. Permissive-only code (white-label clean)

4. stage_promote keeps the whole HF dir in lockstep

5. Real eval harness ( --eval )

6. Urdu + ZWJ coverage

2026-06-26 (round 2) — Freeze-gate hardening (professor review #2)

1. Smoke gate now refuses stale / incomplete artifacts

2. Required-bucket coverage gate

3. Manifest records actual bytes + resolved dataset commit SHAs

4. Code license (open decision, not a hard blocker)

2026-06-26 — Freeze-readiness (professor review)

1. Indic ZWJ/ZWNJ preservation

2. Smoke test is a HARD freeze gate

3. Reserved-slot promotion vs vocab growth (split)

4. Dataset revision pinning + license tags + manifest

5. Corpus universality (12 → 26 scripts)

6. Per-language/script/domain fertility report

7. FORGE (cross-repo) exactness

8. Stale artifacts quarantined

2026-06-26 — SOTA audit (pre-freeze quality)

A. o200k Indic-aware pre-tokenizer (the critical fix)

B. Special tokens 24 → 32 (append-only IDs 24–31)

C. Complete HF directory + ByteLevel post-processor

2026-06-25 — Shared-corpus refactor + UX

Build it (rented high-RAM CPU box, no GPU)

The full 240 GB build needs ~150–200 GB RAM peak (BPE holds word counts in RAM). Rent a CPU box with ≥ 256–386 GB RAM, ~1 TB disk. Then:

export HF_TOKEN=...                  # gated permissive code (starcoderdata)
python3 asterizer.py --lock          # pin source SHAs -> sources.lock.json (commit it)
python3 asterizer.py --vocab 64K,128K --gb 240 --yes --require-lock

Freeze checklist before shipping: sources.lock.json fully pinned · every bucket passed its coverage floor · smoke PASS (gate) · fertility_report.json reviewed per language and vs baselines · manifest.json licenses audited (all permissive/open) · spec_hash.txt recorded · FORGE vocab_size matches tokenizer.get_vocab_size().