Pretraining at scale
Data, compute and what models learn from the internet.
Create a free account Try part of it here
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
- Same goal, far more text
- Filtering by rules
- Finding near-duplicates
- Worked example: Jaccard by hand
- How much compute?
- 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.
- 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.
- 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.
- 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.
- 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.
- Tokenize. Cut the text into tokens with a BPE tokenizer, as in the tokenization lesson. Then pack the tokens into training windows.
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)}")
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.
Most of the engineering effort here goes into data, not into the model.
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.
- Break each document into overlapping three-word phrases, called shingles. "The tent is light" gives two shingles: "the tent is" and "tent is light".
- 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
Read it as: "the Jaccard similarity of A and B is the size of their intersection over the size of their union." Here and are the two sets of shingles. The intersection, written , holds the shingles found in both. The union, written , 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 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: 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))
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.
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:
- , the number of parameters (the model's adjustable weights);
- , the number of training tokens the model reads.
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 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.
params = 2e9 tokens = 1e12 print(6 * params * tokens)
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.
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
Try a quiz question
Why does a pretrained model have a 'knowledge cutoff'?