A model multiplies numbers, not letters. This section builds the first two links of the chain that turns a raw string into something a GPT can consume: text → tokens → token IDs. (The next sections add sub-word tokenization and, finally, the embedding vectors themselves.)
2.1 Understanding word embeddings
An embedding embedding A mapping from discrete data (tokens) to points in a continuous vector space, arranged so that related items sit near each other. defined in ch. 2 — open in glossary maps discrete things — here, words — to points in a continuous vector space, arranged so that related items land near each other. A word embedding word embedding An embedding of individual words or tokens — the most common form of text embedding. defined in ch. 2 — open in glossary is the per-word case (you can also embed sentences or whole documents). Embeddings can have anywhere from one to thousands of dimensions; below we squash them to 2-D just to look at them.
2.2 Tokenizing text
Tokenization tokenization Splitting raw text into tokens (words, sub-words, or characters) as the first step of turning text into numbers. defined in ch. 2 — open in glossary is the first concrete step: splitting the raw string into tokens. A simple word/punctuation tokenizer is just a regular expression that splits on whitespace and keeps punctuation as its own tokens.
import re
text = "Hello, world. This, is a test."
result = re.split(r'([,.:;?_!"()\']|--|\s)', text) #A
result = [item.strip() for item in result if item.strip()] #B
print(result)
# ['Hello', ',', 'world', '.', 'This', ',', 'is', 'a', 'test', '.'] - #A Split on punctuation, em-dashes (--), and whitespace. Wrapping the pattern in ( ) keeps the delimiters as tokens instead of discarding them.
- #B Drop pure-whitespace fragments and strip stray spaces, leaving clean word/punctuation tokens.
2.3 Converting tokens into token IDs
Tokens are still strings. To get numbers, collect every unique token into a vocabulary vocabulary The set of all unique tokens, each mapped to a unique integer ID. defined in ch. 2 — open in glossary that maps each token to a unique integer — its token ID token id The integer that a vocabulary assigns to a token. defined in ch. 2 — open in glossary . The vocabulary is usually sorted, so IDs are assigned alphabetically.
1 · Tokenize the training text
Break the complete training text into individual tokens with the regex tokenizer.
Building a vocabulary and encoding text to token IDs (book Figure, p. 10).
The book wraps this into a SimpleTokenizerV1 class with two methods: encode
(text → IDs) and decode (IDs → text, via the inverse vocabulary).
class SimpleTokenizerV1:
def __init__(self, vocab):
self.str_to_int = vocab #A
self.int_to_str = {i: s for s, i in vocab.items()} #B
def encode(self, text): #C
preprocessed = re.split(r'([,.:;?_!"()\']|--|\s)', text)
preprocessed = [item.strip() for item in preprocessed if item.strip()]
ids = [self.str_to_int[s] for s in preprocessed]
return ids
def decode(self, ids): #D
text = " ".join([self.int_to_str[i] for i in ids])
text = re.sub(r'\s+([,.?!"()\'])', r'\1', text) #E
return text - #A str_to_int is the vocabulary: token → ID (used by encode).
- #B int_to_str is the inverse vocabulary: ID → token (used by decode).
- #C encode: tokenize the text the same way, then map each token to its ID.
- #D decode: map each ID back to its token and join with spaces.
- #E Tidy up: remove the space the join() inserted before punctuation.
Encoding a sentence gives a plain list of integers — for example "the quick brown" might become:
7 | 6 | 0 |
| the | quick | brown |
2.4 Adding special context tokens
SimpleTokenizerV1 has a fatal flaw: feed it a word that isn’t in the
vocabulary and it throws a KeyError. Real text also needs markers for
structure — where one document ends and another begins. Both are solved with
special tokens special token A reserved token marking structure or unknown words, e.g. <|endoftext|>, <|unk|>, [BOS], [EOS], [PAD].
defined in ch. 2 — open in glossary
.
| Token⇅ | Purpose⇅ | |
|---|---|---|
| <|endoftext|> | Marks the boundary between independent documents concatenated for training. | + |
| <|unk|> | Stands in for any token missing from the vocabulary. | + |
| [BOS] | Beginning of sequence — marks where a text starts. | |
| [EOS] | End of sequence — marks where a text ends (e.g. between concatenated examples). | |
| [PAD] | Padding — fills short sequences so every sequence in a batch has equal length. |
SimpleTokenizerV2 adds <|unk|> and <|endoftext|> to the vocabulary and, in
encode, replaces any out-of-vocabulary token with <|unk|>:
all_tokens = sorted(set(preprocessed))
all_tokens.extend(["<|endoftext|>", "<|unk|>"]) #A
vocab = {token: integer for integer, token in enumerate(all_tokens)}
class SimpleTokenizerV2:
def __init__(self, vocab):
self.str_to_int = vocab
self.int_to_str = {i: s for s, i in vocab.items()}
def encode(self, text):
preprocessed = re.split(r'([,.:;?_!"()\']|--|\s)', text)
preprocessed = [item.strip() for item in preprocessed if item.strip()]
preprocessed = [
item if item in self.str_to_int else "<|unk|>" #B
for item in preprocessed
]
ids = [self.str_to_int[s] for s in preprocessed]
return ids
def decode(self, ids):
text = " ".join([self.int_to_str[i] for i in ids])
text = re.sub(r'\s+([,.:;?!"()\'])', r'\1', text)
return text - #A Append the two special tokens to the end of the vocabulary, giving them the two highest IDs.
- #B The one substantive change vs. V1: any token not in the vocabulary is replaced by <|unk|> before ID lookup, so encode() never crashes.
Joining two texts with " <|endoftext|> ".join((text1, text2)) and encoding
them shows both special tokens in action — unknown words map to the <|unk|> ID,
and the <|endoftext|> ID marks the seam between the two texts.
Key idea
A tokenizer is just two lookup tables —str→int for encode, its inverse
int→str for decode. Everything hard about real tokenizers (unknown words,
document boundaries) is handled by adding a few reserved special tokens to
that vocabulary. Next, §2.5 replaces this brittle word-level
scheme with byte pair encoding, which never meets a word it can’t encode.📝 Check yourself: text → token IDs
0 / 51.Why must text be turned into embeddings before a neural network can use it?
2.In the tokenize → vocabulary → token-ID pipeline, what exactly is a token ID?
3.SimpleTokenizerV1 crashes when it meets a word that isn't in its vocabulary. How does SimpleTokenizerV2 fix this?
4.What is the <|endoftext|> special token used for?
5.The tokenizer's decode(ids) method is described as using the 'inverse vocabulary'. What is that?