Index Ai Academy
Phase 5 · Transformers · Scale & inference

Pretraining at scale

Data, compute and what models learn from the internet.

About 35 minutes · A free account opens the whole lesson

Create a free account Try part of it here

Already have an account? Sign in to open this lesson

Why it matters

Your tiny GPT trained on 9,000 tokens in seconds. A frontier model trains on tens of trillions of tokens. The run lasts months, on thousands of GPUs. Yet the goal is the same: guess the next token. Knowing what goes into that run (which data, how much computing, what it costs) explains a lot about the models you use. It explains what they know and why they have a cutoff date. It also explains why they cost what they do, and why only a few companies train them.

What you'll be able to do

  • Describe the pretraining pipeline: collect, filter, deduplicate, mix and tokenize text
  • Estimate training compute with the 6·N·D rule and turn it into GPU time
  • Explain what a pretrained (base) model knows, and where its knowledge cutoff comes from

What it covers

  1. Same goal, far more text
  2. Filtering by rules
  3. Finding near-duplicates
  4. Worked example: Jaccard by hand
  5. How much compute?
  6. Engineering at scale

Same goal, far more text

Pretraining is the first training stage, and by far the biggest. The model plays the same game as in the last few lessons: read some text, guess the next token. Nothing about that goal changes. What changes is the amount. Instead of one small catalog, the model reads a huge, broad collection of text.

Think of a cook who learns by tasting every dish in a city. What they taste shapes what they can cook. So a lot of the work goes into choosing the text. A typical pipeline has five steps.

  1. Collect. Most text comes from the web. Common Crawl, a nonprofit, has crawled billions of pages a month since 2008. Labs add code, books, scientific papers and math. Some also add licensed text, or synthetic text written by other models.
  2. Extract and filter. Strip out the web page code (the HTML) and detect the language. Then drop low-quality pages: menus, spam, near-empty pages, and pages stuffed with search keywords. Many labs also train a small classifier, a model that gives each page a quality score. Hugging Face's open FineWeb dataset (2024) used this kind of filtering. From 96 Common Crawl snapshots, it kept 15 trillion tokens.
  3. Deduplicate. The web repeats itself. Think of copied page footers, mirrored sites, and one news story posted on many sites. Duplicates waste computing time. They also push the model to memorize that text word for word.
  4. Mix. Decide the share of web pages, code, math and so on. Like a recipe, the mixture strongly shapes the result. It decides what the model ends up good at.
  5. Tokenize. Cut the text into tokens with a BPE tokenizer, as in the tokenization lesson. Then pack the tokens into training windows.
Quick check
A lab wants its next model to be better at writing code. Which pipeline step changes that most directly?
  1. Collect: crawl the same websites more often
  2. Mix: raise the share of code in the training data
  3. Deduplicate: remove more copies
  4. Tokenize: pack the windows more tightly
Show the answer

Mix: raise the share of code in the training data

The mixture decides how much of each kind of text the model sees. More good code in the mix makes it better at code. The other steps matter, but less directly.

Filtering by rules

Early pipelines used simple rules to spot junk pages. DeepMind's Gopher paper published a set much like the one below. The code tests five pages against four rules:

  • enough words: the page has at least 20 words;
  • mostly words: at least 80% of its words contain a letter;
  • few symbols: fewer than one symbol (#, $, |, <, > or !) per 10 words;
  • not repetitive: count the different sentences. They must be more than half of all sentences.

Predict which pages survive, then run it.

import re

pages = {
    "product page": "The Ridgeline tent sleeps two and weighs 1.4 kg. Its recycled nylon fly sheds rain, and the two doors mean nobody climbs over anybody at 3 a.m. Pitch it in under five minutes, even in wind.",
    "menu": "Home | Shop | Sale | About | Contact | Login | Cart (0) | Help | Stores | Gift cards",
    "spam": "BEST DEALS!!! $$$ cheap tents cheap tents cheap tents $$$ CLICK HERE >>> www.example-deals.biz <<< BUY NOW!!!",
    "repeated": "Free shipping on all orders. Free shipping on all orders. Free shipping on all orders. Free shipping on all orders.",
    "how-to": "To waterproof a jacket, wash it with a technical cleaner, rinse twice, then tumble dry on low for twenty minutes. The heat reactivates the coating so water beads up again.",
}

def quality_checks(text):
    words = text.split()
    alpha = [w for w in words if re.search(r"[A-Za-z]", w)]
    symbols = sum(text.count(c) for c in "#$|<>!")
    sentences = [s.strip() for s in re.split(r"[.!?]+", text) if s.strip()]
    return {
        "enough words": len(words) >= 20,
        "mostly words": len(alpha) / len(words) >= 0.8,
        "few symbols": symbols / len(words) < 0.1,
        "not repetitive": len(set(sentences)) / len(sentences) > 0.5,
    }

for name, text in pages.items():
    failed = [rule for rule, ok in quality_checks(text).items() if not ok]
    print(f"{name:<13} {'keep' if not failed else 'drop'}  {', '.join(failed)}")
It runs right here in your browser, with nothing to install.

The product page and the how-to guide pass every rule. The menu is mostly bars and single words, so it fails two rules. The spam is short and full of symbols, so it fails three. The repeated page says one sentence four times. Only one of its four sentences is different, so it fails the last rule.

Real pipelines apply dozens of rules like these to billions of pages. A learned quality classifier then runs on top. Every rule has a cost, though. Strict filters also throw away unusual but valuable text, such as poetry, tables or non-standard dialects.

Try it now

Most of the engineering effort here goes into data, not into the model.

Quick check
A page has 40 real words and no symbols. It is one sentence copied four times. Which rule drops it?
  1. mostly words
  2. not repetitive
  3. enough words
  4. None of them: the page is kept
Show the answer

not repetitive

Only 1 of its 4 sentences is different. 1 ÷ 4 = 0.25, which is not more than 0.5. It passes the other three rules.

Finding near-duplicates

Exact copies are easy to catch. You hash each document, which turns it into a short fingerprint code. Identical documents get identical fingerprints, so a quick lookup finds them.

Near-copies are harder. Two product pages might differ by a single word. To catch them, you need a score for how alike two documents are. A standard one works in two steps.

  1. Break each document into overlapping three-word phrases, called shingles. "The tent is light" gives two shingles: "the tent is" and "tent is light".
  2. Compute the Jaccard similarity. Count the shingles the two documents share. Divide by the number of different shingles they have between them.

Think of two friends' playlists. Count the songs on both lists, then divide by all the songs on either list. That share is how similar their taste is.

In plain words: Jaccard similarity is the share of phrases two documents have in common. A score of 1 means they have exactly the same phrases. A score of 0 means they share none.

Go deeper Jaccard as a formula
J(A,B)=∣A∩B∣∣A∪B∣J(A, B) = \frac{|A \cap B|}{|A \cup B|}

Read it as: "the Jaccard similarity of A and B is the size of their intersection over the size of their union." Here AA and BB are the two sets of shingles. The intersection, written A∩BA \cap B, holds the shingles found in both. The union, written A∪BA \cup B, holds the shingles found in either. The vertical bars around a set mean "how many items". In Python, a & b is the intersection and a | b is the union. That's why the code says len(a & b) / len(a | b).

Here it is on the practice catalog you trained your tiny GPT on. The code scores every pair of descriptions, then counts the pairs above three thresholds.

import re
import requests

docs = requests.get("https://api.gradient.test/v1/corpus").json()["text"].strip().split("\n")

def shingles(text, n=3):
    words = re.findall(r"[a-z0-9]+", text.lower())
    return {tuple(words[i:i + n]) for i in range(len(words) - n + 1)}

def jaccard(a, b):
    return len(a & b) / len(a | b)

sets = [shingles(d) for d in docs]
pairs = sorted(((jaccard(sets[i], sets[j]), i, j)
                for i in range(len(docs)) for j in range(i + 1, len(docs))), reverse=True)
print(f"{len(docs)} documents, {len(set(docs))} distinct as exact strings")
for threshold in (0.8, 0.6, 0.5):
    print(f"pairs sharing at least {threshold:.0%} of their phrases: {sum(p[0] >= threshold for p in pairs)}")
score, i, j = pairs[0]
print(f"\nclosest pair ({score:.2f}):\n  {docs[i]}\n  {docs[j]}")

No two descriptions are identical. Yet the closest pair differs only in the product's name. An exact-match check would keep both.

Comparing every pair works for a few hundred documents. At web scale it's impossible. Four documents make 6 pairs, but a billion documents make about 5×10175 \times 10^{17} pairs. That's a 5 followed by 17 zeros. So pipelines use MinHash, a hashing trick. It estimates Jaccard similarity and puts likely duplicates in the same bucket, without comparing everything.

Worked example

Worked example: Jaccard by hand

Two product lines differ by one word:

  • A: "the tent is light and warm"
  • B: "the tent is light and dry"

A has four shingles: "the tent is", "tent is light", "is light and" and "light and warm". B has the same first three, then "light and dry". Count the shingles they share. Then count all the different shingles across both. Predict the Jaccard similarity, then run the code.

import re

def shingles(text, n=3):
    words = re.findall(r"[a-z0-9]+", text.lower())
    return {tuple(words[i:i + n]) for i in range(len(words) - n + 1)}

a = shingles("The tent is light and warm.")
b = shingles("The tent is light and dry.")
print(len(a), len(b))
print("shared:", len(a & b))
print("all different:", len(a | b))
print("Jaccard:", len(a & b) / len(a | b))
In the lesson you can change this and run it.

Each line has 4 shingles, and they share 3. Between them there are 5 different shingles: the 3 shared ones, plus "light and warm" and "light and dry". So the similarity is 3 ÷ 5 = 0.6. Changing one word at the end changed only one shingle in each line. A threshold of 0.6 would flag this pair as near-duplicates.

Quick check
Why do pretraining pipelines remove near-duplicate documents?
  1. Duplicates make the tokenizer slower
  2. The model can't read the same sentence twice
  3. Duplicates waste compute and encourage the model to memorize repeated text
  4. To make the dataset fit in one file
Show the answer

Duplicates waste compute and encourage the model to memorize repeated text

Repeated text gets seen many times in each pass over the data. That spends compute on it, and it raises the risk that the model memorizes it word for word.

How much compute?

Training is mostly arithmetic. Engineers count it in FLOPs, short for floating-point operations. One FLOP is one multiply or one add on decimal numbers. A rule of thumb estimates the total from two numbers:

  • NN, the number of parameters (the model's adjustable weights);
  • DD, the number of training tokens the model reads.
FLOPs≈6×N×D\text{FLOPs} \approx 6 \times N \times D

In plain words: training costs about 6 FLOPs for every parameter, for every token. Double the model or double the data, and the cost doubles.

Where does the 6 come from? For each token, the forward pass (making a guess) costs about 2 FLOPs per parameter: one multiply and one add. The backward pass (working out how to fix each weight) costs about twice that, so 4 more. People write the rule as 6ND6ND for short.

Try it with small numbers. A toy model with 1,000 parameters reads 50 tokens. That's 6 × 1,000 × 50 = 300,000 FLOPs. A laptop does that in well under a millisecond.

params = 1_000      # N: parameters
tokens = 50         # D: training tokens
flops = 6 * params * tokens
print(f"{flops:,} FLOPs")

To turn FLOPs into time, divide by how many FLOPs a chip does per second. An Nvidia H100 GPU peaks at about 989 trillion FLOPs per second with 16-bit numbers. Real training never reaches that peak. Chips spend much of their time waiting for data to move between memory and chips. The share of peak speed you actually get is called utilization. Large runs typically reach 30–50%, so the code below uses 40%.

def training_flops(params, tokens):
    return 6 * params * tokens

def readable(seconds):
    for unit, size in [("years", 365 * 86400), ("days", 86400), ("hours", 3600), ("minutes", 60)]:
        if seconds >= size:
            return f"{seconds / size:,.1f} {unit}"
    return "under a second" if seconds < 1 else f"{seconds:.0f} seconds"

runs = [
    ("our tiny GPT", 38_135, 300 * 16 * 24),       # 300 steps of 16 windows of 24 tokens
    ("GPT-3 (2020)", 175e9, 300e9),
    ("Chinchilla (2022)", 70e9, 1.4e12),
    ("Llama 3.1 405B (2024)", 405e9, 15.6e12),
]
per_gpu = 989e12 * 0.40      # an H100 at 40% of its peak 16-bit speed, in FLOPs per second
for name, n, d in runs:
    c = training_flops(n, d)
    print(f"{name:<22} {c:8.1e} FLOPs   one GPU: {readable(c / per_gpu):>14}   16,000 GPUs: {readable(c / per_gpu / 16_000)}")

Python shows huge numbers in e-notation. 2.6e+10 means 2.6 with the decimal point moved 10 places right: 26 billion. That's your tiny GPT, done in under a second. Llama 3.1 405B needs about 3.8e+25 FLOPs. On one GPU that would take over 3,000 years. On 16,000 GPUs it takes about 70 days.

Meta reported training Llama 3.1 405B on up to 16,000 H100 GPUs. Our estimate of about 70 days of pure compute is in line with its published figures. (GPT-3 originally trained on older V100 GPUs. The table uses today's hardware so you can compare.) Cloud GPUs rent for a few dollars per hour. So a run like that costs tens of millions of dollars. That's before counting failed experiments, staff and data.

Quick check
Using the rule of thumb, what does this print?
params = 2e9
tokens = 1e12
print(6 * params * tokens)
  1. 1.2e+21
  2. 1.2e+22
  3. 6e+21
  4. 2e+21
Show the answer

1.2e+22

6 × 2 × 10⁹ × 1 × 10¹² = 12 × 10²¹, which Python writes as 1.2e+22.

Engineering at scale

At this size, the engineering matters as much as the model. Training is split across thousands of GPUs. Each GPU holds a slice of the model and a slice of each batch of data. Numbers are stored in 16 bits, or even 8, to save memory and time.

Hardware also fails, often. So the run saves checkpoints regularly. A checkpoint is a saved copy of the model, like a save point in a video game. After a crash, training restarts from the last one. Meta's Llama 3 report describes hundreds of interruptions during a single run.

What comes out: a base model

The result is a base model: a very good document completer. It works like your phone's autocomplete, at a far higher level. Give it the start of a product page, and it writes a believable rest of the page. Ask it to write a tagline and it doesn't obey: it continues the text as if it were a document, which may or may not contain a tagline.

It has picked up grammar, facts, coding patterns and reasoning habits from its data. But it doesn't follow instructions reliably, and it doesn't act as an assistant. Those skills come from post-training, in Phase 6.

Two practical results follow.

  • Knowledge cutoff. The model knows the world only as it was when its data was collected. Anything later must come from the prompt, from tools or from retrieval.
  • The data is the model. Gaps, biases and errors in the training text show up in the model. What the web says most often, the model says most confidently.

For marketers: if your brand, product type or market is rarely discussed online, a model knows little about it. That's one reason to ground answers in your own documents. This is called retrieval-augmented generation (RAG): the system looks up your documents and adds them to the prompt.

Quick check
A model's training data was collected up to March 2024. Later that year you ask it about a product launched in June 2024. What should you expect?
  1. It has no reliable knowledge of the product, and it may guess
  2. It refuses every question about 2024
  3. It knows, because models update themselves every day
  4. It knows, as long as the product is popular
Show the answer

It has no reliable knowledge of the product, and it may guess

Pretraining is a snapshot of its data. Anything newer has to come from the prompt, tools or retrieval.

Practice what you just read

  • 5 code examples you can edit and run
  • a practice exercise that checks your code
  • a 5-question quiz
  • 5 review cards for spaced repetition

Create a free account

Try a quiz question

Why does a pretrained model have a 'knowledge cutoff'?

  1. It only knows what was in its training data, which was collected up to a certain date
  2. Its context window is too small to hold recent news, so it can only keep what it read most often
  3. Models are deliberately blocked from learning new facts, in case the new facts turn out to be unsafe
  4. The tokenizer can't encode new words, so anything written after training is unreadable to the model

Answer it at the end of the lesson to see if you're right, and why.

Before this lesson