Skip to content

Pretraining Data: Mixtures, Curation, and What the Model Sees

Scroll horizontally to read the full diagram.

Two models with the same architecture and training budget can learn different capabilities from different corpora. A coding assistant needs enough useful code; a multilingual assistant needs coverage of its languages. Repeated or contaminated examples can make evaluation look better without making either model more useful.

The question is how to turn a collection of documents into a training distribution. That means choosing what to collect, what to remove, and how often the remaining material should be sampled.

Prerequisites

This post assumes you've read Training View vs. Inference View for why "how many tokens" is the resource you're spending, and BPE from Scratch for how raw text turns into the tokens we're counting.


What the model actually sees

For a concrete scale reference, the 2024 FineWeb release contained roughly 15 trillion tokens from 96 Common Crawl snapshots. A web-based pretraining pipeline might look like this:

  1. Download a giant snapshot of the public web, for example Common Crawl.
  2. Strip the HTML boilerplate, throw away pages that aren't the right language, throw away pages that look low-quality.
  3. Detect and remove near-duplicate documents. An article may appear on several domains, giving the same text extra weight in training.
  4. Scrub personally identifiable information and the worst of the toxic content.
  5. Mix the result with carefully chosen smaller corpora: code from GitHub, books, scientific papers, high-quality Q&A threads, wikis, whatever you decide should round out the diet.
  6. Tokenize and shuffle the selected text for training.

These choices affect coverage, memorization, and privacy. Evaluation contamination needs its own check: general deduplication does not automatically remove benchmark material.

FineWeb documents filtering and deduplication decisions through controlled training experiments. The original 15T-token corpus and 1.3T-token FineWeb-Edu subset are useful examples of curation at scale. Comparing raw HTML bytes with final tokens does not tell us a retention percentage: those are different units.

From crawl to training corpus

Extract
Turn HTML into text; preserve useful code and mathematics.
Filter
Choose languages and quality criteria. Audit what each filter removes.
Deduplicate
Find repeated documents and passages; verify candidate matches.
Scrub
Apply privacy and content filters, acknowledging their false positives and misses.
Mix and tokenize
Choose sampling weights and count tokens with the training tokenizer.

Measure retention in a consistent unit at each stage. Raw HTML bytes and final token counts do not define a survival percentage.

Each stage changes the corpus. Track document, byte, and token counts separately rather than treating them as a single survival fraction.

Mixtures: what ratio of what

After cleanup, choose how often to sample each source. A corpus can contain code, books, and web pages in one ratio while the training run samples them in another.

A reasonable taxonomy of domains:

  • Web text. The long tail of the Internet. Forums, blogs, news, Q&A, Wikipedia, everything. A varied but selected sample of how people use written language.
  • Code. GitHub, package registries, Stack Overflow answers. Provides programming examples.
  • Books. Long-form prose. Fiction, non-fiction, textbooks. Teaches the model what sustained arguments and narrative structure look like. Quality varies within this category too.
  • Scientific papers. arXiv, PubMed. Teaches formal writing, math, citations, the vocabulary of technical fields.
  • Multilingual text. Non-English web and book text. Coverage, quality, tokenization, and sampling frequency all matter for language performance.

Meta reports an approximate Llama 3 mixture of 50% general knowledge, 25% math and reasoning, 17% code, and 8% multilingual tokens. These are the report’s categories. They are not interchangeable with source categories such as books or web pages, which can contain several of those kinds of content.

Llama 3 report: approximate final token mixture

General knowledge
50%
Math and reasoning
25%
Code
17%
Multilingual
8%

Categories and percentages are those reported by Meta, not a universal taxonomy. Source: The Llama 3 Herd of Models, §3.1.2.

The approximate final token mixture from the Llama 3 report, §3.1.2. These are sampling shares, not percentages of all available documents.

Curation: the pipeline inside the pipeline

Cleanup combines several filters, each with its own criteria and failure modes:

Language filtering. A small classifier (often fastText) labels each page’s language. An English-primary run might retain pages above a chosen confidence threshold. Audit false rejections, including code-switched text and underrepresented languages or dialects; the source corpus and classifier determine which material is missed.

Deduplication. Repeated pages and passages give duplicated material extra weight in training. Deduplication can reduce repetition and memorization risk, but the effect depends on what is removed and what replaces it.

Quality scoring. The scoring rule determines what counts as quality. Some teams train a classifier on "is this page like Wikipedia or not" and keep pages that score high. FineWeb-Edu trained a classifier on whether a page is "educational" and found that its selected corpus improved performance in the reported controlled benchmark comparisons. That result depends on the filtering threshold, training budget, and evaluation tasks. Other teams use per-page perplexity from a small reference model as the quality signal, keeping the low-perplexity pages. Both approaches are lossy in their own ways.

PII and toxicity scrubbing. Strip phone numbers, email addresses, social security numbers. Reject pages that score above some threshold on a toxicity classifier. This is difficult, because the line between "useful toxicity discussion" and "harmful content" is not a line a classifier can draw cleanly, and over-aggressive scrubbing kills content you wanted.

Filtering trades quantity and diversity against the properties a filter rewards. Aggressive filtering can remove valuable dialects, specialist writing, or difficult examples. Compare candidate filters with controlled training runs and inspect what they exclude.


Dedup, with a little bit of math

Deduplication needs a way to find likely matches without comparing every pair of documents.

The naive version of dedup is: for every pair of documents, compute some similarity score, and drop one of each pair that's above a threshold. Comparing all pairs is O(N2)O(N^2), which becomes prohibitively expensive for tens of billions of documents.

MinHash and locality-sensitive hashing (LSH) make candidate search cheaper. Represent each document by a set of overlapping character n-grams, or shingles. Under a uniformly random ordering, two sets have the same minimum element with probability equal to their Jaccard similarity. Repeating that construction gives a compact similarity estimate.

Quick reminder of Jaccard similarity for two sets AA and BB:

J(A,B)=∣A∩B∣∣A∪B∣J(A, B) = \frac{|A \cap B|}{|A \cup B|}

If two documents share most of their shingles, JJ is close to 1. If they share almost none, JJ is close to 0. The MinHash trick is: pick a uniformly random ordering of the shingle universe, represented by hh, compute min⁡x∈Ah(x)\min_{x \in A} h(x) and min⁡x∈Bh(x)\min_{x \in B} h(x), and then:

P[min⁡x∈Ah(x)=min⁡x∈Bh(x)]=J(A,B)P[\min_{x \in A} h(x) = \min_{x \in B} h(x)] = J(A, B)

For nonempty sets, the equality holds for a uniformly random permutation (or an ideal minwise-independent hash). Practical seeded hashes approximate this condition. Repeating the construction estimates Jaccard from signature agreement.

Split a K=brK=br signature into bb bands of rr rows. Two documents become a candidate if any band matches. Under independent hashes, a pair with similarity ss becomes a candidate with probability 1−(1−sr)b1-(1-s^r)^b. For example, 128 hashes can form 16 bands of 8 rows. This is a soft probability curve, not a hard similarity threshold; verify candidates before dropping documents.

MinHash and LSH: candidate search is not duplicate verification

Shingles
A = {a,b,c,d}; B = {a,b,c,e}. Jaccard = 3/5 = 0.60.
One random ordering
For a < e < b < d < c, both sets have minimum a: one signature match.
Repeat
Independent random orderings give a signature agreement rate that estimates Jaccard.
LSH bands
With b bands of r rows, candidate probability is 1 − (1 − sʳ)ᵇ under independent hashes.
Verify
Compare candidate shingles against the chosen threshold before removing a document.

A tiny constructed example. A band match alone is not proof of similarity.

A small shingle-set example separates signature estimation, candidate search, and duplicate verification.

Worked example: spending a fixed token budget

Suppose two training runs each process 2T tokens. At 10% code, a run sees 0.2T code tokens and 1.8T other tokens. At 50%, it sees 1T of each. Increasing code exposure by 0.8T means removing 0.8T from another part of the mixture.

2T total = 0.20T code + 1.80T other data

Changing the mix reallocates tokens. Whether coding or reasoning improves must be measured in controlled runs; it cannot be calculated from this share.

Move the code share to see the budget arithmetic. The display predicts token exposure, not benchmark performance.

To measure the effect, keep architecture, tokenizer, total tokens, and optimization settings fixed. Evaluate coding, reasoning, language coverage, and the tasks served by the displaced data. Repeat runs where practical.


Chinchilla, Kaplan, and the scaling law underneath all this

For a fixed compute budget CC (FLOPs), how should you split your effort between making the model bigger (more parameters PP) and training it for longer (more tokens DD)?

Kaplan et al. (2020) and Hoffmann et al. (2022) reached different fitted allocations of training compute between parameter count and token count. GPT-3’s observed 300B/175B ratio is about 1.7 tokens per parameter; that number is not itself a Kaplan scaling law.

Hoffmann et al. trained Chinchilla, a 70B-parameter model on 1.4T tokens, and reported stronger performance than the larger Gopher under a similar training-compute budget. The resulting 20-token-per-parameter heuristic is useful context, but its best value depends on the fitted regime and objective.

A training-compute optimum does not include the cost of serving the model. Spending more training compute on a smaller model can be worthwhile if it reduces inference cost at the required quality.

Llama 3’s smaller models were deliberately trained beyond the training-compute-optimal allocation. Meta describes this as improving quality within an inference budget. That objective includes serving cost, unlike a fixed pretraining-compute budget.

Tokens vs parameters — scaling budgets
log-log
1B10B100B1.0T10B100B1.0T10.0Tparameters Ptraining tokens DGPT-3 ratio ≈ 1.7 tok/paramChinchilla = 20 tok/paramGPT-3 · 2020P = 175B · D = 300BD / P = 1.7 tok/param

Scroll horizontally to read the full diagram.

Selected dense models on log axes. The 20:1 line is a Chinchilla-style heuristic; 1.7:1 is GPT-3’s observed ratio, not a Kaplan law.


The data wall

If a run consumes more tokens than a corpus contains, it must repeat data, expand the corpus, or change its training plan. The number of training tokens is therefore not the same as the number of distinct tokens available.

Distinct data and training exposure

1T distinct tokens × 1 pass
1T training tokens
1T distinct tokens × 4 passes
4T training tokens, still 1T distinct tokens
Add generated text
More training material; quality and diversity depend on generation and filtering.

Arithmetic examples, not estimates of the size of the web. More exposures do not provide the same information as equally many new tokens.

Four passes over a corpus quadruple training exposure without quadrupling distinct data.

Estimates of available text depend on language, access, filtering, deduplication, and tokenizer. Treat them as estimates under a specified definition, not a fixed line every model will cross.

In the data-constrained regime studied by Muennighoff et al., repeating data for up to four passes incurred little loss penalty, with diminishing returns after that. The useful number of passes depends on the corpus and training setup. Synthetic data and broader language coverage are other options, each requiring its own quality checks.


Implementation sketch: a toy MinHash filter

This standard-library example finds candidate matches with MinHash and verifies their shingle similarity.

import hashlib
from collections import defaultdict
 
def shingles(text: str, k: int = 5) -> set[str]:
    if k < 1:
        raise ValueError("k must be positive")
    text = text.lower()
    # A short or empty document still has a nonempty representation.
    return {text[i:i+k] for i in range(len(text)-k+1)} or {text}
 
def signature(sh: set[str], count: int) -> tuple[int, ...]:
    # Seeded SHA-1 approximates independent orderings for this toy example.
    return tuple(min(int(hashlib.sha1(f"{seed}:{s}".encode()).hexdigest(), 16)
                     for s in sh) for seed in range(count))
 
def dedup(docs, num_hashes=128, bands=16, threshold=0.8):
    if bands < 1 or num_hashes < 1 or num_hashes % bands:
        raise ValueError("positive hash count must be divisible by bands")
    if not 0 <= threshold <= 1:
        raise ValueError("threshold must lie in [0, 1]")
    rows = num_hashes // bands
    buckets = defaultdict(list)
    sets = [shingles(d) for d in docs]
    kept = []
    for i, sh in enumerate(sets):
        sig = signature(sh, num_hashes)
        keys = [(b,) + sig[b*rows:(b+1)*rows] for b in range(bands)]
        candidates = {j for key in keys for j in buckets[key]}
        duplicate = any(len(sh & sets[j]) / len(sh | sets[j]) >= threshold
                        for j in candidates)
        if not duplicate:
            kept.append(i)
            for key in keys:
                buckets[key].append(i)
    return [docs[i] for i in kept]
 
if __name__ == "__main__":
    docs = ["The quick brown fox.", "The quick brown fox.",
            "Training data shapes a model.", "", ""]
    assert dedup(docs) == [docs[0], docs[2], ""]
    print(dedup(docs))
 
 

Signature construction is independent across documents and can be parallelized. The number of hashes sets signature cost; bands and rows trade candidate recall against verification work. This toy keeps the earliest verified representative. Approximate candidate search can still miss near-duplicates, so a production pipeline needs a measured recall/precision tradeoff.


What the counts leave out

A token count alone does not describe source quality, repetition, domain coverage, or benchmark contamination. A parameter count alone does not say whether a model received enough useful training. Compare both, and keep evaluation separate from curation decisions.

What's next

The next post covers Distributed Training: FSDP, DeepSpeed, and Megatron: how to distribute model state and computation across GPUs, and what communication each approach requires.


Additional reading (and watching)