← lmlab

research notebook / 01_tokenizer

tokenization

from characters to bytes and learned merges

naive, byte-level, and byte-pair tokenization in one inspectable notebook.

6 saved output blockssource 7e31866static export · never re-executed
reading this record

code is folded so the argument and saved outputs come first. expand any cell to inspect the implementation. saved outputs are historical evidence from the source notebook, not a new run or independent verification.

inspect code · cell 01
In [1]:
import sys
from pathlib import Path

repo_root = Path.cwd()
if not (repo_root / "data").exists():
    repo_root = repo_root.parent
sys.path.insert(0, str(repo_root))

from src.tokenizer import (
    bpe_decode,
    bpe_encode,
    decode_unicode_tokens,
    encode_unicode_string,
    perform_merges,
    save_tokenizer,
)

# Initial text
text = "Hello, world! This is a sample text for testing."
torture_text = "Hello, world! café پاکستان 日本語 🤖 ∇²ψ"  # Cursed unicode string as a stress test

Implementing a naive tokenizer

inspect code · cell 02
In [2]:
chars = sorted(set(text))

char_to_index = {char: index for index, char in enumerate(chars)}
index_to_char = {index: char for index, char in enumerate(chars)}


def naive_encode(text):
    # This function encodes the input text into a list of tokens.
    return [char_to_index[char] for char in text]


def naive_decode(tokens):
    # This function decodes the list of tokens back into the original text.
    return "".join(index_to_char[token] for token in tokens)


encoded_text = naive_encode("Hello world! This is a test.")
print("Encoded:", encoded_text)
decoded_text = naive_decode(encoded_text)
print("Decoded:", decoded_text)
Encoded: [4, 8, 13, 13, 16, 0, 21, 16, 18, 13, 7, 1, 0, 5, 11, 12, 19, 0, 12, 19, 0, 6, 0, 20, 8, 19, 20, 3]
Decoded: Hello world! This is a test.

Byte-level tokenizer

inspect code · cell 03
In [3]:
# Unicode string → UTF-8 bytes → integer tokens

encoded_torture_text = encode_unicode_string(torture_text)
print("Encoded torture text:", encoded_torture_text)
decoded_torture_text = decode_unicode_tokens(encoded_torture_text)
print("Decoded torture text:", decoded_torture_text)
Encoded torture text: [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 32, 99, 97, 102, 195, 169, 32, 217, 190, 216, 167, 218, 169, 216, 179, 216, 170, 216, 167, 217, 134, 32, 230, 151, 165, 230, 156, 172, 232, 170, 158, 32, 240, 159, 164, 150, 32, 226, 136, 135, 194, 178, 207, 136]
Decoded torture text: Hello, world! café پاکستان 日本語 🤖 ∇²ψ

BPE (byte-pair encoding)

inspect code · cell 04
In [4]:
target_vocab_size = 512

tokens = encode_unicode_string(torture_text)


tokens, vocab, merges = perform_merges(tokens, target_vocab_size)
print("Final tokens:", tokens)
Final tokens: [310]
inspect code · cell 05
In [5]:
# Tiny Shakespeare corpus
corpus = (repo_root / "data" / "tiny_shakespeare.txt").read_text(encoding="utf-8")

tokens = encode_unicode_string(
    corpus[:100_000]
)  # Limit to first 100,000 characters for demonstration

fin_tokens, vocab, merges = perform_merges(tokens, target_vocab_size)
save_tokenizer(repo_root / "data" / "tiny_shakespeare_bpe.json", vocab, merges)

print(f"Encoded corpus into {len(fin_tokens):,} tokens")
print("Saved tokenizer to data/tiny_shakespeare_bpe.json")
Encoded corpus into 47,586 tokens
Saved tokenizer to data/tiny_shakespeare_bpe.json
inspect code · cell 06
In [6]:
test = "To be, or not to be 🤖"

encoded = bpe_encode(test, vocab, merges)
decoded = bpe_decode(encoded, vocab)
print("Original:", test)
print("Encoded:", encoded)
print("Decoded:", decoded)

assert test == decoded, "Decoded text does not match the original!"
Original: To be, or not to be 🤖
Encoded: [84, 274, 317, 261, 470, 331, 294, 353, 240, 159, 164, 150]
Decoded: To be, or not to be 🤖
inspect code · cell 07
In [7]:
tests = [
    "hello world",
    "To be, or not to be",
    "aaaaaaaaaaaa",
    "hello\nworld",
    "🐸",
    "پاکستان",
    "hello 🐸 پاکستان",
    "",
]

for test in tests:
    encoded = bpe_encode(test, vocab, merges)
    decoded = bpe_decode(encoded, vocab)
    print("Original:", test)
    print("Encoded:", encoded)
    print("Decoded:", decoded)
    assert test == decoded, "Decoded text does not match the original!"
Original: hello world
Encoded: [323, 276, 274, 414, 108, 100]
Decoded: hello world
Original: To be, or not to be
Encoded: [84, 274, 317, 261, 470, 331, 294, 317]
Decoded: To be, or not to be
Original: aaaaaaaaaaaa
Encoded: [97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97]
Decoded: aaaaaaaaaaaa
Original: hello
world
Encoded: [323, 276, 111, 10, 414, 108, 100]
Decoded: hello
world
Original: 🐸
Encoded: [240, 159, 144, 184]
Decoded: 🐸
Original: پاکستان
Encoded: [217, 190, 216, 167, 218, 169, 216, 179, 216, 170, 216, 167, 217, 134]
Decoded: پاکستان
Original: hello 🐸 پاکستان
Encoded: [323, 276, 274, 240, 159, 144, 184, 32, 217, 190, 216, 167, 218, 169, 216, 179, 216, 170, 216, 167, 217, 134]
Decoded: hello 🐸 پاکستان
Original: 
Encoded: []
Decoded: