VKVlad KulikovPublic / lectures
Modern LLMs2 / 20

Module 2. Tokenization and Embeddings

Try each exercise first. Expand worked answers or project guidance when needed; their open state is shared between RU/EN.

Download course files (.zip) 2588.1 KB

The archive preserves the source folder structure, both languages, and supporting files.

E1. UTF-8 accounting

Before merges, how many byte-level tokens do Привет, мир!, Hello, world!, and наивный 🚀 use? Explain every byte.

Show answer and solution

Solution. Before BPE merges, each UTF-8 byte is one base token. Привет, мир! has nine Cyrillic letters (Привет has 6 and мир has 3), hence 9⋅2=189\cdot2=18 bytes, plus a comma, a space, and !: 21. Hello, world! contains 13 ASCII characters and therefore uses 13 bytes. In наивный 🚀, seven Cyrillic letters use 14 bytes, the space uses 1, and the emoji uses 4, for 19 total. A tokenizer-free check is len(text.encode("utf-8")).

E2. Ranked encoding

Implement an encoder that repeatedly merges the pair with the lowest available rank:

Show answer and solution

Solution.

Python
def encode_by_rank(text, merges):
    chunks = re.findall(PAT, text)
    output = []
    for chunk in chunks:
        ids = list(chunk.encode("utf-8"))
        while len(ids) > 1:
            pairs = set(zip(ids[:-1], ids[1:]))
            best = min(pairs, key=lambda p: merges.get(p, float("inf")))
            if best not in merges:
                break
            ids = merge([ids], best, merges[best])[0]
        output.extend(ids)
    return output

The regex first splits the text into pre-token chunks, which must be encoded independently. Within each chunk, enumerate adjacent pairs, choose the present pair with the smallest numeric rank, and merge all valid occurrences. Stop when no present pair belongs to merges. Corpus frequency is not recomputed at inference: training has already encoded its effect in the learned ranks.

E3. A reproducible fertility protocol

Fix the corpus, word segmentation rule, tokenizer revision, and aggregation method. Report total tokens divided by total words, plus bytes/token. Explain why averaging per-sentence fertility assigns disproportionate weight to short sentences.

Show answer and solution

Solution. Freeze the exact corpus snapshot and strata, normalization and word rule, tokenizer name and revision, and whether special tokens are added. For each line record UTF-8 bytes bib_i, words wiw_i, and tokenizer IDs tit_i (normally excluding beginning-of-sequence and end-of-sequence markers, BOS and EOS), then compute

fertility=∑iti∑iwi,bytes/token=∑ibi∑iti.\text{fertility}=\frac{\sum_i t_i}{\sum_i w_i},\qquad \text{bytes/token}=\frac{\sum_i b_i}{\sum_i t_i}.

This micro-average weights every word equally. The alternative 1N∑iti/wi\frac1N\sum_i t_i/w_i weights every line equally, so a one-word line affects the result as much as a long sentence; zero-word lines also need a separate policy. Publish ∑wi\sum w_i, ∑ti\sum t_i, ∑bi\sum b_i, and excluded-line counts so another reader can reproduce the result.

E4 [project]. Multilingual map

Use FLORES-200 with published tokenizers for o200k_base, Llama 4, Qwen3, Gemma 3, DeepSeek V4, and Kimi K3. Record exact revisions and let the measurements determine the ranking rather than predicting a winner.

Project demos and guidance

Demo for this project exercise: English project-demo notebook for Module 2, section “1. What does the denominator count?”. It shows a compact reproducible example on small inputs, not a completed solution to the full-scale project.

Specify suitable word segmentation for each language, or compare tokens per aligned sentence. A regex matching letter runs treats a Chinese string as one run, not one linguistic word. The demo exposes denominators and units per sentence; its wordrun_proxy is not WordPiece.

E5 [project]. Harmony audit

In the main notebook’s optional network cell, load o200k_harmony, count names and unique IDs, identify the alias at 200018, and classify protocol versus reserved spellings.

Project demos and guidance

Demo for this project exercise: English project-demo notebook for Module 2, section “2. Why can names outnumber IDs?”. It shows a compact reproducible example on small inputs, not a completed solution to the full-scale project.

Expected for the 11 October 2026 snapshot: 1,091 names, 1,090 unique special IDs, 10 protocol names, 1,081 reserved-form names, and n_vocab=201088 after adding 199,998 mergeable IDs.

E6. Lookup and one learning step

Using EE from §11, compute E[[0,2,0]], E[0] @ E.T, and the tied BF16 weight payload for V=16384,d=512V=16384,d=512. Then minimize L=12∑t∥Eit−Yt∥2L=\frac12\sum_t\|E_{i_t}-Y_t\|^2 with zero targets YY and IDs [0,2,0]: what gradient does each row receive?

Show answer and solution

Solution. Lookup gives [[1,0],[0,1],[1,0]]. The logits are [1,2,0], so ID 1 wins. There are 16384⋅512=838860816384\cdot512=8388608 parameters occupying 1677721616777216 BF16 bytes, or 16 MiB. Repeated ID 0 accumulates two contributions: the gradient is [[2,0],[0,0],[0,1]]. A step of 0.1 gives [[0.8,0],[2,0],[0,0.9]], lowering LL from 1.5 to 1.045. The notebook checks the gradient with finite differences; this is a lookup exercise rather than semantic model training.