GPT-2 · RoBERTa Lab · Part I — The foundation
01

Tokenization
and the BPE algorithm

How a text becomes a list of numbers — and why the model sees neither letters nor words, but pieces.

Chapter 01 of 15 Before: the course map and the glossary

1 Intuition: the model does not read what you write

A neural network is, at bottom, a matrix-multiplying machine. Matrices are made of numbers. So the first thing that must happen to your text is a translation: letters become numbers. This step is called tokenization, and it happens before the model — it is not learned along with it; it is a fixed table, decided once and frozen forever.

Which "token"

The word token is older than language models, and has other meanings in IT: the credential a server issues after login, such as a JWT; the unit a compiler's lexical analyser splits off, such as a keyword; the asset recorded in a blockchain contract. In this course, a token is always a piece of text — a word, part of a word or a byte — with an integer attached. The course glossary makes this distinction for every term that has more than one meaning.

The question is: numbers for what? There are three historical answers, and understanding why the first two failed is the only way to understand why the third won.

Attempt 1 — one number per word

Simple: build a dictionary. "casa"→41, "cachorro"→892, and so on. It works until the day a word shows up that is not in the dictionary — a proper name, a typo, a new slang word, a word in another language. Then the model writes <UNK> ("unknown") and loses the information forever. And the dictionary of real English, with names and variants, goes past a million entries — which would make the last layer of the model (which needs one neuron per possible word) bigger on its own than the entire model.

Attempt 2 — one number per character

This solves the unknown: there are only ~100 characters, and any text fits. But it creates a worse problem. The word "internacionalização" becomes 19 positions instead of 1. Since the cost of attention grows with the square of the number of positions (you will see this in chapter 03), texts become much more expensive. And the model has to spend several layers just to rediscover that c-a-s-a put together means something.

Attempt 3 — one number per frequent piece

Here is the insight. What if the vocabulary were made of pieces of words, chosen automatically by the statistics of the text? Common words ("the", "casa") get a single token. Rare words decompose into pieces that exist ("Rafhael" → R + af + ha + el). Nothing is ever unknown, and the text stays short.

That is BPE — Byte Pair Encoding. It is what GPT-2 uses, it is what RoBERTa uses, and variants of it are still used in many current models.

Analogy

Think of a LEGO parts store that needs to sell the smallest possible number of part types, but still let you build anything. It will not sell only 1×1 bricks (you could build everything, but you would spend 10 thousand pieces per model). It will not sell a single "assembled Millennium Falcon" piece either (it only works for one model). It sells medium-sized pieces that show up a lot: walls, windows, wheels. BPE is the algorithm that discovers, by looking at thousands of already-built models, which medium-sized pieces are worth existing.

2 How BPE builds the vocabulary

BPE is a tokenizer training algorithm. It runs once, over a giant corpus, and produces two things: a list of tokens (vocab.json) and an ordered list of merges (merges.txt). After that, it never changes again.

The procedure

  1. Start with the alphabet. Each isolated symbol is a token. In GPT-2, that alphabet is the 256 possible bytes (we will come back to this in section 3).
  2. Count all neighboring pairs across the whole corpus. How many times does t appear followed by h? And h followed by e?
  3. Merge the most frequent pair into a new token. If t+h is the champion, the token th is created, and every t h in the corpus becomes th.
  4. Repeat — now th+e may become the champion, creating the.
  5. Stop when the vocabulary reaches the desired size. In GPT-2: 50,257 tokens.

A tiny example, done by hand

Corpus: the word banana repeated 5 times, and bandana 2 times. Let's follow three merges.

Each row is one BPE iteration. The winning pair becomes a new token, and the text gets shorter.
StepState of the textMost frequent pairCountNew token
0b a n a n a ×5   b a n d a n a ×2——alphabet: a,b,d,n
1count pairs: an=10+4=14, na=10+2=12, ba=7…a+n14an
2b an an a ×5   b an d an a ×2an+a7ana
3b an ana ×5   b an d ana ×2b+an7ban
endban ana ×5   ban d ana ×2"banana" costs 2 tokens; "bandana" costs 3.
What this reveals

The model's vocabulary is a statistical portrait of the training corpus. GPT-2 was trained on WebText — 8 million English-language web pages, collected from links upvoted on Reddit. That is why English is efficient (1 token ≈ 0.75 words) and Portuguese is expensive (1 token ≈ 0.4 words): BPE never had a statistical reason to create the token ção (a very common Portuguese suffix). You will measure this in the lab, and it will sting a little.

3 GPT-2's move: BPE at the byte level

Here is GPT-2's specific contribution, and it is elegant. The original BPE (used in machine translation, 2016) started from Unicode characters. Problem: there are more than 149 thousand Unicode characters. If the initial alphabet is already huge, little room is left in the vocabulary for the useful merges — and emojis or Chinese ideograms could still fall out and become <UNK>.

GPT-2's solution: don't start from characters, start from bytes. All digital text, in any language, is a sequence of UTF-8 bytes. And bytes only come in 256 possible values. So:

The odd detail: why "Ġ" and "Ċ" show up

When you inspect GPT-2's tokens, you will see things like Ġthe and ĠcasaĊ. It looks strange, but it is a simple convention.

Some bytes are control characters or whitespace — printing them directly would break the vocabulary files. So GPT-2 remaps these problematic bytes to visible, harmless Unicode characters:

Real byteValueBecomesMeans
space32 (0x20)Ġ"a new word started here"
line break10 (0x0A)Ċend of line
tab9 (0x09)ĉtab

It is purely cosmetic — Ġ is literally the space character wearing a hat. But it carries real information: the space is part of the token. Ġcasa (" casa", preceded by a space) and casa (glued to the previous word, as in "minhacasa") are different tokens, with different IDs and different embeddings.

A practical consequence that often surprises

If you write a prompt ending in a space — "O Brasil é " — the model has already received the Ġ on its own and now has to complete a token that begins without a space. That throws it into a strange region of token space and quality drops visibly. Never end a prompt with a space. Write "O Brasil é" and let the model produce the Ġ itself.

4 The math (there is little, but it is what matters)

BPE training is a sequence of greedy choices. At each step t, with C the current corpus and P the set of neighboring token pairs:

(a*, b*) = argmax(a,b) ∈ P   countC(a, b) Pick the neighboring pair (a,b) that appears the greatest number of times in the corpus. "argmax" = "the argument that maximizes", that is, which pair wins, not how many times it appeared.

That pair is merged into a new token ab, the corpus is rewritten, and the pair enters the merges.txt list at position t. Repeat until |V| = 50,257.

Application (what runs when you type)

Tokenizing a new text does not redo the counts. It only replays the merges, in the same order in which they were learned:

x0 = bytes(text)   →   xt = apply(xt−1, merget) The order is sacred: merge no. 1 is tried before no. 2, always. Swapping the order produces a different tokenization — that is why merges.txt is an ordered file, not a set.

And GPT-2's final vocabulary breaks down like this:

|V| = 256 + 50,000 + 1 = 50,257 256 base bytes  +  50,000 learned merges  +  1 special token <|endoftext|> (ID 50256), used to separate documents in training and to signal the end of generation.

5 GPT-2 vs RoBERTa vs GPT-1: three different tokenizers

This is a point that confuses many people: RoBERTa uses exactly the same algorithm as GPT-2 — byte-level BPE, 50,000 merges. It was an explicit decision by the RoBERTa authors, who swapped BERT's WordPiece for GPT-2's BPE. But the special tokens and the corpus are different, so the IDs do not match.

Comparison of the three tokenizers in the course. Note that the same text becomes completely different numbers in each one.
GPT-1 (openai-gpt)GPT-2 (whole family)RoBERTa (detectors)
AlgorithmBPE over characters + spaCy/ftfyBPE over bytesBPE over bytes
Vocabulary40,47850,25750,265
Unknown token<unk> existsdoes not existdoes not exist
Upper caseeverything lowercasedpreservedpreserved
Special tokens<unk><|endoftext|><s> </s> <pad> <unk> <mask>
Space marker</w> at the end of the wordĠ at the startĠ at the start
Wraps the sentencenono<s> … </s>

RoBERTa's 8 extra tokens (50,265 vs 50,257) are precisely the special ones it needs and GPT-2 does not: <pad> to fill batches of different lengths, <mask> for its training objective (chapter 10), and <s>/</s> to mark start and end — and <s> later becomes the vector that carries the summary of the whole sentence for classification (chapter 12).

6 The code, line by line

# tokenizar_na_mao.py — GPT-2's BPE without magic
from transformers import AutoTokenizer

tok = AutoTokenizer.from_pretrained("openai-community/gpt2")

texto = "O Brasil é gigante pela própria natureza."

# 1) text -> list of IDs (what the model actually receives)
ids = tok.encode(texto)
print(ids)
#    [46, 8290, 2634, 20692, 12427, 27638, 355, 1050, 16864, 3450, 46844, 13]

# 2) each ID back to the piece of text it represents
for i in ids:
    print(f"{i:>6}  {tok.decode([i])!r:<12}  {tok.convert_ids_to_tokens([i])[0]}")
#    the 3rd field shows the raw form, with the Ġ for the space

# 3) the metric that matters: how many tokens per word?
palavras = len(texto.split())
print(f"{len(ids)} tokens para {palavras} palavras = {len(ids)/palavras:.2f} tokens/palavra")

The output of line 3 is the moment it clicks. In English, that number sits near 1.3. In Portuguese, near 2.4. In other words: the same text costs almost twice as many tokens in Portuguese — twice the memory, twice the time, half the useful context, and (in paid APIs) twice the price. It is not that the model "dislikes" Portuguese; it is that merges.txt never saw enough Portuguese to create good pieces.

7 In the lab

cd GPT-2
.venv\Scripts\python.exe lab\01_tokenizer.py

This script does four things. Watch each one:

  1. Breaks a Portuguese sentence down token by token, showing ID, text and the raw form with Ġ. Look for where the words split — almost always at the suffix.
  2. Compares the same idea in English and Portuguese and prints the tokens/word ratio for both. That is the measure of the corpus bias.
  3. Passes the same text through the three tokenizers (GPT-1, GPT-2, RoBERTa) and shows that the IDs bear no relation to one another. This proves, in practice, that you cannot mix one model's tokenizer with another model's weights.
  4. Tests pathological cases: emoji, large number, proper name, double space. The most revealing result is the numbers: " 2024" costs 1 token (it was a frequent year on the 2018 internet), but " 1234567" becomes 3 tokens, split into 123+45+67 — pieces with no arithmetic meaning whatsoever. The model never sees the whole number. That is literally why models of this generation got simple sums wrong: they do not do arithmetic on numbers, but on arbitrary fragments of digits chosen by text frequency.
What you should be able to explain after running it

Why "Rafhael" costs more tokens than "the"; why Ġcasa and casa are different things; and why a model that sees 1+000 instead of 1000 has structural difficulty with arithmetic.

Exercise 1 — predict before running

  1. How many tokens do you think GPT-2 spends on "antidisestablishmentarianism" (28 letters, English)? And on "pneumoultramicroscopicossilicovulcanoconiótico" (46 letters, Portuguese)? Write down both guesses, then run. The difference will be bigger than you expect.
  2. Tokenize "casa", " casa" and "Casa". How many distinct IDs show up? Why?
  3. Find an English word of 6+ letters that GPT-2 represents with a single token. Hint: think of words that were very common on the 2018 internet.
  4. Hard: the token with ID 50256 is <|endoftext|>. Run tok.decode([50256]). Now explain why it is the last ID, and not the first.
References

The primary sources for the concepts in this chapter. The numbers in the text were measured in the lab; the ideas come from here.

Technical terms here carry their machine-learning meaning. The course glossary shows that meaning next to the others the same words have in IT.

  1. Gage, P. (1994). A New Algorithm for Data Compression. The C Users Journal, 12(2), 23–38.
  2. Sennrich, R., Haddow, B. and Birch, A. (2016). Neural Machine Translation of Rare Words with Subword Units. ACL 2016. arXiv:1508.07909
  3. Radford, A., Wu, J., Child, R., Luan, D., Amodei, D. and Sutskever, I. (2019). Language Models are Unsupervised Multitask Learners. Technical report, OpenAI. cdn.openai.com

Now the text is a list of integers. But an integer has no meaning — ID 41 is not "more" or "less" than 40.
In chapter 02 these integers become vectors, and that is where meaning is born.