Two kinds, and only one is yours to fix
The kind you can check: your training data contains items from the evaluation you are quoting. This happens more easily than people expect, particularly when your training data is synthetic: a teacher model that memorised a public benchmark during pretraining will happily reproduce its items when asked to generate examples of that kind of task.
The kind you cannot check: the base model’s pretraining corpus contained the benchmark. You have no access to that corpus, no way to search it, and no way to rule it out.
The second is why public benchmark numbers on a fine-tuned model are weak evidence about anything, and why your own held-out set (built from your own data, after your own cutoff) is the strong evidence.
Checking the kind you can
The conventional test is n-gram overlap, and the convention from the large pretraining papers is 13-grams: if a 13-word sequence from an evaluation item appears anywhere in the training data, treat the item as contaminated.
def ngrams(text, n=13):
w = text.lower().split()
return {" ".join(w[i:i+n]) for i in range(max(0, len(w)-n+1))}
train_grams = set().union(*(ngrams(t) for t in train_texts))
hits = [i for i, t in enumerate(eval_texts) if ngrams(t) & train_grams]
print(f"{len(hits)}/{len(eval_texts)} eval items share a 13-gram with training")Thirteen is a reasonable default: long enough that ordinary phrases do not match, short enough to catch a paraphrase that kept one clause. Run it in both directions: evaluation against training, and training against any public benchmark you intend to quote.
Some benchmarks embed a canary string specifically so that their presence in a corpus can be detected. Grep for it. If it is in your training data, something upstream scraped the benchmark.
The ordering test
A cleverer check for multiple-choice benchmarks, and it needs no access to anyone’s corpus.
If a model has memorised a question as it appears in the benchmark, it will assign higher probability to the canonical ordering of its answer options than to a shuffled ordering of the same options. A model that is reasoning about the question has no reason to prefer one arrangement.
p_original = seq_logprob(model, render(q, q.options))
p_shuffled = mean(seq_logprob(model, render(q, shuffle(q.options)))
for _ in range(5))A consistent preference for the original ordering across many questions is evidence the model saw the benchmark in that form. This is a real and usable diagnostic, and it is one of the few tools available for the contamination you cannot otherwise inspect.
What contamination does to your decisions
It inflates the number you are using to make a decision, and it inflates it unevenly. The items that leaked are the ones the model scores well on, so the headline rises while the model’s actual generalisation has not moved. You then ship, and production disagrees.
The specific damage in a fine-tuning project: you compare a tuned model against a baseline, and the tuned model’s training data overlaps the evaluation while the baseline’s does not. The comparison is structurally unfair in the tuned model’s favour, and the gap you measure is partly recall.
The defences
- Build the evaluation set from data the training set cannot contain: a later time period, a held-out group, or items written specifically for evaluation and never released into the training pipeline.
- Run the n-gram check across your split, and report the number in your evaluation write-up even when it is zero. A stated zero is information; an unstated one is an assumption.
- Do not quote public benchmarks as evidence that your fine-tune worked. They cannot be cleaned of base-model contamination, and they are not measuring your task anyway.
- Keep a locked set created after the training data’s cutoff date. Temporal separation is the one defence that also protects against the contamination you cannot inspect, because a benchmark that did not exist when the base model was trained cannot be in its corpus.
The summary
You can rule out contamination between your training set and your evaluation set. You cannot rule it out between the base model’s pretraining and any public benchmark. Say which of the two you have checked, and do not let a reader assume you checked both. That sentence, written into an evaluation report, is worth more than an additional decimal place on any of its numbers.
Every lesson here is open. Your progress is recorded once you pass the Running Models Yourself exam.
Check your understanding
A tuned model scores markedly higher on a public multiple-choice benchmark when the answer options are in their canonical order than when the same options are shuffled. What does this indicate?
Pick the one you would defend. Your answer stays private.
Stuck on something?Ask Rafael, your AI tutor
