glassllmDocumentation
DocumentationLibrary reference

Library reference

Tokenizer: complete reference

Character BPE, normalization, merges, IDs, decoding and persistence.

glassllm 0.1.04 min read · estimate

Construction and vocabulary

Import Tokenizer from the root; pre_tokenize, SPACE, SPECIALS and UNK_TEXT come from glassllm.tokenizer. Tokenizer(alphabet: list[str], merges: list[tuple[str,str]]) constructs a vocabulary containing special tokens, the alphabet and merge results, in that order. It copies the supplied lists, learns no merges and does not validate every possible manually supplied vocabulary combination.

Attribute or constant Contents
SPECIALS ["<pad>", "<unk>", "<bos>", "<eos>"], IDs 0–3
SPACE "▁", represents an ASCII space
UNK_TEXT "⁇", decoded text for <unk>
alphabet, merges Characters and learned pairs, in order
ranks Pair → merge priority dictionary
vocab Complete list of token strings indexed by ID
token_to_id, unk_id Reverse dictionary and unknown ID, normally 1
vocab_size Integer property, len(vocab)

Use tok.vocab_size without parentheses. Read token strings with tok.tokens(ids) or tok.vocab[id]; there is no id_to_token method.

Normalization and learning

pre_tokenize(text: str) -> list[str] normalizes Unicode to NFC, replaces each ASCII space with ▁ and splits chunks. Newlines are separate chunks; spaces before a word belong to its chunk. Merges never cross chunk boundaries. Other whitespace, such as tabs, is not universally converted to ▁. A literal ▁ is treated as the marker, so arbitrary Unicode strings are not fully reversible.

Tokenizer.train(text: str, vocab_size: int) -> Tokenizer counts chunks, starts with the observed alphabet and repeatedly chooses the most frequent adjacent pair. Pair ties are resolved alphabetically; each merge is applied without overlap. The requested size must accommodate four specials plus the alphabet, or ValueError is raised. It is an upper bound: a small corpus may run out of pairs first.

from glassllm import Tokenizer
from glassllm.tokenizer import pre_tokenize

assert pre_tokenize("Olá mundo\n") == ["Olá", "▁mundo", "\n"]
tok = Tokenizer.train("banana banana\n", vocab_size=32)
ids = tok.encode("banana banana\n")
assert tok.decode(ids) == "banana banana\n"
assert tok.vocab_size <= 32
assert len(tok.tokens(ids)) == len(ids)
assert tok.encode("🧪") == [tok.unk_id]
assert tok.decode([tok.unk_id]) == "⁇"

Encode and explain merges

encode(text: str) -> list[int] processes each chunk, applying the available pair with the lowest rank first. A piece absent from the vocabulary becomes unk_id. It adds no padding, BOS or EOS and imposes no length limit. Empty text produces [], which is not a valid prompt for GlassLM.generate.

encode_verbose(text: str) -> list[dict] returns one entry per chunk: {"chunk": str, "steps": list[list[str]]}. The first state contains characters; later states contain pieces after each applied merge. These are neither IDs nor probabilities. Even a chunk without merges has an initial state. This method records states for explanation even when ordinary encoding could use the internal cache.

from glassllm import Tokenizer

tok = Tokenizer.train("banana banana\n", 32)
detail = tok.encode_verbose("banana")
assert detail[0]["chunk"] == "banana"
assert detail[0]["steps"][0] == list("banana")
assert detail[0]["steps"][-1] == tok.tokens(tok.encode("banana"))

Tokens and text

tokens(ids: list[int]) -> list[str] uses Python list indexing into vocab. IDs above the range raise IndexError; negative indices follow list semantics rather than being rejected, so validate external IDs. decode(ids: list[int]) -> str joins pieces, converts ▁ to spaces, renders <unk> as ⁇ and omits the other three specials. It cannot recover text lost to normalization or unknown characters.

Save and load

save(path: str | Path) -> None writes UTF-8 JSON with version: 1, alphabet and merges. It overwrites the destination and does not create parent directories. Tokenizer.load(path: str | Path) -> Tokenizer reads the lists and reconstructs ranks and vocabulary. The current reader neither negotiates versions nor validates a full schema: missing keys raise KeyError, invalid JSON raises a reader error and filesystem failures raise OSError.

from pathlib import Path
from tempfile import TemporaryDirectory
from glassllm import Tokenizer

tok = Tokenizer.train("banana banana\n", 32)
with TemporaryDirectory() as directory:
    path = Path(directory) / "tokenizer.json"
    tok.save(path)
    restored = Tokenizer.load(path)
    assert restored.encode("banana") == tok.encode("banana")
    assert restored.vocab == tok.vocab

Relationship to the papers

The project uses the subword BPE merging idea described by Sennrich, Haddow and Birch. Its implementation starts with Unicode characters, not bytes, and provides no byte fallback. The visible space marker resembles SentencePiece, but this is a custom tokenizer: it neither loads SentencePiece models nor implements all its algorithms. Follow the calculations in Tokens and BPE.

See it in the interface

Text, controls and BPE tokens: Editor, continuation and token IDs
Editor, continuation and token IDs

Prompt, Generate, Step once, Pause, Reset, output and IDs.

Actual screenshot of the English interface.
Open original size

Project sources

src/glassllm/tokenizer.py

Related papers

Primary sources for concepts used in the project. The year identifies the first arXiv preprint version; the publication note names the venue or journal when recorded by the source.