teaching_llm_applications

Week 2 — Tokenisation

Lecture Overview

Before a language model can process text, that text must be converted into a sequence of integer IDs. This week we explore tokenisation: how text is segmented into tokens, why the choice of tokeniser matters, and how to inspect and use tokenisers in practice.


🎮 Practical

from importlib.metadata import version

import os
import requests

if not os.path.exists("the-verdict.txt"):
    url = (
        "https://raw.githubusercontent.com/rasbt/"
        "LLMs-from-scratch/main/ch02/01_main-chapter-code/"
        "the-verdict.txt"
    )
    file_path = "the-verdict.txt"

    response = requests.get(url, timeout=30)
    response.raise_for_status()
    with open(file_path, "wb") as f:
        f.write(response.content)

with open("the-verdict.txt", "r", encoding="utf-8") as f:
    raw_text = f.read()
    
print("Total number of character:", len(raw_text))
print(raw_text[:99])


import re

text = "Hello, world. This, is a test."
result = re.split(r'(\s)', text)

print(result)


result = re.split(r'([,.]|\s)', text)

print(result)

# Strip whitespace from each item and then filter out any empty strings.
result = [item for item in result if item.strip()]
print(result)



text = "Hello, world. Is this-- a test?"

result = re.split(r'([,.:;?_!"()\']|--|\s)', text)
result = [item.strip() for item in result if item.strip()]
print(result)


preprocessed = re.split(r'([,.:;?_!"()\']|--|\s)', raw_text)
preprocessed = [item.strip() for item in preprocessed if item.strip()]
print(preprocessed[:30])


print(len(preprocessed))

1. Why Tokenisation?

Neural networks require numerical inputs. Text is a sequence of characters. The simplest approach would be character-level modelling — but:

Word-level tokenisation is the other extreme:

Subword tokenisation is the practical solution used by all modern LLMs:


2. Byte-Pair Encoding (BPE)

BPE is the most widely used subword algorithm (GPT-2, GPT-3, GPT-4, LLaMA).

Algorithm

  1. Start with a vocabulary of individual characters (plus a special end-of-word symbol).
  2. Count all adjacent symbol pairs in the training corpus.
  3. Merge the most frequent pair into a new symbol.
  4. Repeat until the vocabulary reaches the target size.

Example

Training text: low low low lower lower newest newest

Initial tokens: l o w </w> l o w e r </w> n e w e s t </w>

Most frequent pair: l o → merge to lo Next: lo wlow …and so on until lowest might become low est </w> etc.

Properties


3. WordPiece and SentencePiece

WordPiece (BERT, DistilBERT):

SentencePiece (LLaMA, T5, Mistral):

Tiktoken (OpenAI):


4. Special Tokens

Every tokeniser defines special tokens with reserved IDs:

Token Purpose
<|endoftext|> Separator between documents (GPT family)
[CLS] Classification token (BERT)
[SEP] Separator between sentences (BERT)
[PAD] Padding to uniform length in batches
<s> / </s> Start / end of sequence (LLaMA, Mistral)
<unk> Unknown token (rare in modern tokenisers)

5. Vocabulary Size and Its Effects

Model Tokeniser Vocabulary Size
GPT-2 BPE 50,257
GPT-3 / 4 Tiktoken (cl100k) 100,277
BERT WordPiece 30,522
LLaMA-2 SentencePiece 32,000
LLaMA-3 Tiktoken 128,256

Larger vocabulary → fewer tokens per sentence → shorter sequences → faster training and inference, but larger embedding matrix.


6. Tokenisation Artefacts and Gotchas


7. Counting Tokens (Cost and Context)

API usage is billed per token. Context windows are limited in tokens. Use tiktoken to count tokens before sending to a model:

import tiktoken
enc = tiktoken.encoding_for_model("gpt-4o")
tokens = enc.encode("The quick brown fox jumps over the lazy dog.")
print(len(tokens))   # 9

8. Practical This Week

See practicals/week02_practical.py:


9. Further Reading


Discussion Questions

  1. Why is character-level modelling not used in practice for large-scale LLMs?
  2. A user’s message in Hindi becomes 3× more tokens than the same message in English. What are the consequences?
  3. Sketch the BPE merge process on the toy string aababab. What are the first two merges?