Model collapse is the loss of quality that happens when AI models are trained, round after round, on AI-generated data instead of human-written data. Rare data, such as uncommon words and facts, disappears first. Part of the web is now written by AI, so any model trained on web data can take some of it in.
We trained GPT-2 only on its own output for four rounds. It got 52% worse at predicting Wikipedia text that it never trained on, even though its own text looked more fluent by one measure. With 50% new real data in every round, it got only 6% worse.
This guide shows how to collect real data and keep it in every training round. It uses Website Content Crawler, an Apify Actor (a ready-to-run cloud tool), and the Internet Archive.
What is model collapse? #
Model collapse is a loss of quality that grows when each new model trains on data generated by earlier models instead of real data. A 2024 Nature paper trained each new language model on text generated by the previous model. The models lost the less common parts of the original data first.
The paper describes two stages:
- Early model collapse. The model loses the tails of the data distribution, such as rare words, rare facts, and rare writing styles.
- Late model collapse. The output converges to a narrow distribution that is very different from the original data.
Three other terms are often confused with model collapse, but each one describes a different problem:
| Term | What goes wrong | Cause |
|---|---|---|
| Model collapse | The model loses rare data, and its output loses diversity | Repeated training on data generated by earlier models instead of real data |
| Mode collapse | A generative model produces only a few kinds of output | Unstable training of a generative adversarial network (GAN). The term now also describes low-diversity output from large language models (LLMs) |
| Data poisoning | The model learns a hidden behavior (a backdoor) or a false fact | An attacker adds malicious examples to the training data |
| Model drift | The model's accuracy drops after deployment | The world changes after the training data was collected |
How much of the web is written by AI? #
Pew Research Center used an AI-text detector from Pangram Labs to check a sample of pages from Common Crawl's July 2026 snapshot. About 10% of the sampled pages showed signs of being written by AI. Among the pages dated after ChatGPT's release in November 2022, the share was more than a third.
A detector can be wrong about a single page. Filtering crawled pages with it removes some human text (false positives) and keeps some AI text (false negatives). Act before the crawl instead: choose your sources, and keep proof of when each text existed.
Is model collapse real? What the research shows #
Model collapse is real in controlled experiments, but we found no published study that shows it in a deployed frontier model. The labs that train these models deliberately mix synthetic (model-generated) data with web data. Microsoft's Phi-4 report shows why mixing matters. A 13B test model that was trained only on synthetic data scored 14.8 points lower than Phi-3-medium on TriviaQA, a benchmark of factual questions.
Collapse comes mainly from replacing real data
A common claim is that any synthetic data in training causes collapse. In the Nature experiments, collapse came from replacing the real training data with synthetic data in each round.
Another 2024 study instead kept all real data and added each round's synthetic data to it, which the paper calls accumulating data. The test error then stayed below a fixed level.
Collapse is avoidable, but diversity can still drop
Another claim is that collapse can't be avoided. A third study showed when repeated training stays stable. The first model must fit the data well, and each round needs a large enough share of real data.
In the Nature run, 10% of the original data in each round, about 240k tokens, kept the degradation minor. Together, these papers cover three ways to build each round's training data:
Diversity can still drop while the test error stays stable. A 2024 study of diversity measured lower lexical diversity (word choice) and syntactic diversity (sentence structure) over rounds of training on synthetic data.
Quality filtering can make collapse worse
Many teams expect data cleaning to remove AI text, but AI text can pass a quality filter more easily than human text. A 2026 study ran two open-source filtering pipelines on 10k Common Crawl documents from 2026. Pangram's detector labeled each document as human, AI, or mixed. Some of the authors work at Pangram Labs, which sells an AI-text detector.
The pipeline used to build FineWeb, Hugging Face's open web dataset, kept AI-labeled documents 2.3 times as often as human-labeled ones. The DataComp for Language Models (DCLM) pipeline kept them 9.8 times as often.
We tested it: GPT-2 trained on its own output #
We repeated the Nature paper's setup on a smaller scale, with GPT-2 small instead of Meta's OPT-125m, so that it runs on a laptop. For the real data, Website Content Crawler (apify/website-content-crawler) collected 480 random Wikipedia featured articles in 3 minutes and 10 seconds, for $0.09 of platform usage.
Round 0 fine-tuned GPT-2 on real data only. Each later round fine-tuned the original GPT-2 on 32k tokens that mixed real data with the previous round's output in four ratios. The output was generated with nucleus (top-p) sampling at 0.9, which drops the least likely tokens.
"Original" real data reuses the round-0 articles, and "new" real data adds unseen articles each round. Each 10% share was only about 3.2k tokens.
The table shows the mean of three runs with different random seeds after four rounds, with the range of the change in parentheses. Perplexity measures prediction error on held-out text that no round trained on, the same text for every mix, so lower is better:
| Training mix | Perplexity after round 4 | Perplexity change since round 0 | Distinct words in the output, change since round 0 | Outputs that repeat a 4-word phrase |
|---|---|---|---|---|
| Round 0 (real data only) | 38.0 | 5.3k words (baseline) | 4% | |
| 100% model output | 57.9 | +52% (+45% to +58%) | -39% | 36% |
| 90% output, 10% original real data | 49.7 | +31% (+30% to +31%) | -33% | 32% |
| 90% output, 10% new real data | 50.0 | +31% (+30% to +33%) | -27% | 28% |
| 50% output, 50% new real data | 40.3 | +6% (+5% to +7%) | +2% | 6% |
The dashed line shows GPT-2 before any fine-tuning, at 42.6. Round by round, the 100% line rises the most, and only the 50% line ends below the dashed line:
In every seed, the model that trained only on its own output ended worse than GPT-2 before any fine-tuning. Yet its own text got easier to predict. Measured with the round-0 model, the perplexity of its output fell from 22.6 to 10.3, so the collapsing model looked more fluent.
Rare words were lost fastest. Of the words that appeared at most twice in the round-0 data, the output used about 1.6k at first and about 750 after four rounds.
With 50% new real data, the model stayed below GPT-2 before any fine-tuning. Its perplexity still rose a little in every round, and its output used 28% fewer rare words. With 10%, perplexity rose 31%, compared with a minor loss in the Nature run, so check both the share and the amount of real data.
The size of the effect can differ for large models. The test code and each seed's results are in the GitHub repository.
How to improve AI models with web data without causing collapse #
Web data can improve AI models without causing collapse when every training round keeps enough real data. Accumulate data instead of replacing it, track the provenance of each row (where it came from), and verify and limit synthetic rows. The seven steps below apply these rules in a pipeline with Website Content Crawler and the Internet Archive. If you fine-tune a model or run continued pre-training on your own web data, the pipeline looks like this:
Before you start
You need Python 3.11 or later, an Apify account, and its API token. You can follow every step on the Apify free plan, which includes monthly platform usage, and each demo crawl below cost less than $0.01. Step 5 also needs a model server with an OpenAI-compatible API, such as Ollama.
All seven scripts are in a GitHub Gist, and they use the Apify API client for Python. Clone the Gist, create a virtual environment, install the packages, and set the token:
git clone https://gist.github.com/60706e0f72e345117e94f3051d3db68e.git model-collapse-pipeline
cd model-collapse-pipeline
python3 -m venv .venv && source .venv/bin/activate
pip install "apify-client>=3.2.1,<4" "httpx>=0.28,<1" "datasketch>=2,<3"
export APIFY_TOKEN="your-apify-token"
Step 1: Keep all real data, and never replace it
Collapse comes mainly from synthetic data that replaces real data, so start with storage. Keep every row that you collect in a named dataset, which Apify stores until you delete it, and add new data to the old data.
Keep a validation set of human-written text, such as archive copies from Step 3, and a separate validation set of synthetic rows. Report the loss on each set separately, because a mixed set can hide the harm. In the 2026 filtering study, AI text made some of the 800 trained models worse. A validation set that was 22.3% AI text missed that harm in 95.5% of those runs.
Step 2: Check AI preferences, then collect with provenance
Before you crawl a site for AI training data, check what the site allows. Cloudflare's Content Signals policy adds a robots.txt line such as Content-Signal: search=yes, ai-train=no. The Internet Engineering Task Force (IETF) AI Preferences working group drafts a standard Content-Usage line for robots.txt and HTTP headers. Python's urllib.robotparser ignores both, so you have to read them yourself.
In the EU, the AI Act has required general-purpose AI model providers to respect machine-readable opt-outs from text and data mining since August 2, 2025. So record what you checked. The guide to whether web scraping is legal covers the legal questions in more detail.
signals.py reads these lines and any robots.txt Disallow rules for AI crawlers such as GPTBot, and python signals.py checks four common cases:
en.wikipedia.org {'robots': 200, 'signals': {}, 'blocks_ai_bots': [], 'no_train': False}
www.pewresearch.org {'robots': 200, 'signals': {'ai-train': 'yes', 'search': 'yes', 'ai-input': 'yes'}, 'blocks_ai_bots': [], 'no_train': False}
www.bbcgoodfood.com {'robots': 200, 'signals': {'search': 'yes', 'ai-train': 'no'}, 'blocks_ai_bots': ['GPTBot', 'CCBot', 'meta-externalagent', 'Bytespider'], 'no_train': True}
stackoverflow.com {'robots': 403, 'signals': {}, 'blocks_ai_bots': [], 'no_train': None}
Wikipedia sets no AI preference, Pew Research Center allows training, and BBC Good Food blocks it. Stack Overflow's robots.txt couldn't be read, so no_train is None, and the collection script skips every site where no_train isn't False.
collect.py crawls the allowed URLs with Website Content Crawler. It uses the Cheerio crawler type, the fastest one for static pages such as Wikipedia:
def crawl(urls: list[str]) -> tuple[str, list[dict]]:
run = client.actor("apify/website-content-crawler").call(
run_input={
"startUrls": [{"url": url} for url in urls],
"crawlerType": "cheerio",
"maxCrawlDepth": 0,
"maxCrawlPages": len(urls),
"respectRobotsTxtFile": True,
},
memory_mbytes=2048,
run_timeout=timedelta(minutes=30),
logger=None,
)
if run is None or run.status != "SUCCEEDED":
raise RuntimeError(f"Crawl ended as {run and run.status}, nothing saved")
return run.id, list(client.dataset(run.default_dataset_id).iterate_items(clean=True))
For sites that render with JavaScript, set crawlerType to playwright:adaptive.
Each page becomes a row with provenance fields: id, source (live or archive), the Actor run's crawl_id, the page's publish and last-edit dates, and ai_signals. If you plan to share the dataset, also record each source's license, such as CC BY-SA for Wikipedia text. python collect.py skipped the site that blocks training and the site whose robots.txt it couldn't read:
3 of 5 URLs allowed for training
https://en.wikipedia.org/wiki/Laika 2002-01-25T16:51:21Z 2026-08-20T15:28:08Z
https://en.wikipedia.org/wiki/Sourdough 2003-02-28T03:28:13Z 2026-08-29T09:14:58Z
https://www.pewresearch.org/data-labs/2026/08/20/how-much-of-the-internet-is-written-with-ai/ 2026-08-20T11:55:49-04:00 2026-09-17T12:09:54-04:00
The Laika article's publish date is January 2002, but its last edit is from August 2026.
Step 3: Add a pre-ChatGPT baseline from the Internet Archive
A publish date shows when a page was created, not when its text was written. Of the 480 featured articles from the GPT-2 test, 460 were created before ChatGPT. All 474 articles with a last-edit date were edited after its release. In the median article, 11% of the sentences were new or edited since the last revision before ChatGPT.
An archive copy from before November 30, 2022, proves that a text existed before ChatGPT. baseline.py asks the Internet Archive's capture index (CDX) API for the last capture (archive copy) before that date:
def last_capture(url: str) -> str | None:
params = {"url": url, "to": CUTOFF, "output": "json", "fl": "timestamp",
"filter": ["statuscode:200", "mimetype:text/html"], "limit": "-1"}
Then it crawls each capture with Website Content Crawler. The archive can serve a newer capture than the one you asked for, as it did for 24 of 40 Wikipedia articles in our test. So the script checks the capture time in the memento-datetime header:
captured = parsedate_to_datetime(item["metadata"]["headers"]["memento-datetime"])
if captured.strftime("%Y%m%d") > CUTOFF:
continue # the archive served a newer capture than the one requested
python baseline.py found a copy from before ChatGPT for both Wikipedia articles:
https://en.wikipedia.org/wiki/Laika existed by 2022-11-23T17:27:55+00:00
https://en.wikipedia.org/wiki/Sourdough existed by 2022-11-20T01:56:38+00:00
Apify Console shows the same capture time for the Laika archive row:
A capture proves when the text existed, not that a human wrote it. Archive copies don't change what a site allows, so check its live robots.txt before you use them, and follow the Internet Archive's terms of use. To keep the load low, baseline.py checks one URL at a time.
For a large, general baseline, stream a Common Crawl snapshot from before ChatGPT, such as CC-MAIN-2022-40, from the FineWeb dataset.
Step 4: Deduplicate without a perplexity filter
Duplicates give common text extra weight, and collapse tends to start with the loss of uncommon text. dedup.py finds near-duplicates across runs and URLs with MinHash from the datasketch library. It keeps archive rows first, then the newest live copy, and drops rows that reach a Jaccard similarity of 0.7 to a kept row. Both Wikipedia articles had changed enough since 2022 to stay below that, so it kept both versions:
keep archive https://en.wikipedia.org/wiki/Sourdough (closest kept row 0.00)
keep archive https://en.wikipedia.org/wiki/Laika (closest kept row 0.00)
keep live https://www.pewresearch.org/data-labs/2026/08/20/how-much-of-the-internet-is-written-with-ai/ (closest kept row 0.00)
keep live https://en.wikipedia.org/wiki/Sourdough (closest kept row 0.59)
keep live https://en.wikipedia.org/wiki/Laika (closest kept row 0.52)
Be careful with perplexity filters, because low perplexity doesn't mean good text, as the GPT-2 test showed. Instead, remove boilerplate (menus, footers, cookie notices), broken pages, pages in the wrong language, and personal data such as email addresses. Before you add a quality classifier or an AI-text detector, test it on your archive rows and on AI-generated text about the same topics.
Step 5: Augment real data, then verify and tag every synthetic row
Synthetic data is often most useful when you derive it from real data. One tested method is Web Rephrase Augmented Pre-training (WRAP). In its tests, a 1:1 mix of real and rephrased web text made pre-training about three times faster. The guide to synthetic data generation vs. real data for AI covers more uses for both.
rephrase.py rephrases passages of the archive rows with any model server that has an OpenAI-compatible API. If you use a hosted model, check that its license or terms let you train on its output. The script tags each kept row with its source and the sampling seed, which local servers such as Ollama use to make a sample reproducible. A numeric check rejects any rephrased passage with a number that the source passage doesn't contain:
seed = random.SystemRandom().randrange(2**31) # a new seed for every request
text = rephrase(passage, seed)
new_numbers = numbers(text) - numbers(passage)
if not text or new_numbers:
print(f"reject {row['url']}: {sorted(new_numbers) or 'empty reply'}")
continue
The demo kept 10 passages that Qwen3 4B rephrased, and at least three of them changed a fact without adding a number:
| What changed | Source passage | Rephrased passage |
|---|---|---|
| A possible cause became a likely one | "possibly caused by a failure of the central R-7 sustainer" | "likely resulting from a failure of the central R-7 sustainer" |
| Twelve dogs became twelve flights | "they had lofted twelve dogs into sub-orbital space" | "they had conducted twelve sub-orbital flights using dogs" |
| Experienced settlers became anyone with a starter | "Old hands came to be called 'sourdoughs'" | "Individuals who possessed such starters became known as 'sourdoughs'" |
The numeric check can't catch these changes, because no numbers changed. Verify the kept passages with a second model, or check a sample manually.
Step 6: Build each training round with a minimum share of real data
A training round applies the rules from Step 1. For the validation sets, hold out whole pages, not single rows. Of the 13-grams (sequences of 13 words) in the Laika archive row, 53% also appear in its live row.
build_round.py holds out whole pages with an archive copy and saves them in validation_ids.json for later rounds. It drops every training row that shares a 13-gram with them, a decontamination step that the GPT-3 team also used. Then it keeps one version of every other page, the archive copy if there is one. It adds synthetic rows only while the share of words from real rows stays at or above MIN_REAL_SHARE, which is 0.5 by default:
real_words = sum(words(row) for row in clean)
budget = real_words * (1 - MIN_REAL_SHARE) / MIN_REAL_SHARE
added, used = [], 0
for row in usable:
if used + words(row) <= budget:
added.append(row)
used += words(row)
python build_round.py built a round from the demo rows:
training: 2 real rows (8,586 words) and 4 synthetic rows (875 words), 91% real words
validation: 1 real and 1 synthetic rows
dropped: 0 real and 5 synthetic rows from or overlapping the validation pages, 0 over the limit
MIN_REAL_SHARE counts every crawled row as real. For sources with a lot of AI text, use only their archive copies, or exclude them.
The two validation sets from Step 1 are in validation_real.jsonl and validation_synthetic.jsonl, so report the loss on each. In the GPT-2 test, held-out perplexity rose in every mix, even at 50%. The clearer warning sign was the perplexity of the model's own output. It fell about 50% in the 100% and 10% mixes and barely changed in the 50% mix.
Step 7: Monitor diversity, and refresh the data on a schedule
Diversity metrics can show how a new crawl differs from the baseline before you train on it. monitor.py takes the first 300 words of each text that has that many. It compares groups of rows by vocabulary size (distinct words), repeated 4-word phrases, and gzip compression ratio:
def diversity(texts: list[list[str]]) -> dict:
vocab = {w for ws in texts for w in ws}
repeats = sum(len(set(zip(ws, ws[1:], ws[2:], ws[3:]))) < len(ws) - 3 for ws in texts)
raw = "\n".join(" ".join(ws) for ws in texts).encode()
return {
"texts": len(texts),
"vocabulary": len(vocab),
"repeated_phrase_share": round(repeats / len(texts), 3),
"compression_ratio": round(len(raw) / len(gzip.compress(raw)), 3),
}
A higher compression ratio means lower diversity. Run python monitor.py rows.jsonl crawl_id on each new crawl, or python monitor.py rows.jsonl source to compare archive rows with live rows. Use groups of at least a few hundred pages, because results from small groups are unreliable.
New data also limits model drift. In a 2021 study, language models had up to 16% higher perplexity on text up to two years newer than their training data. To refresh the data, run collect.py on a schedule, then run the other scripts again. baseline.py and rephrase.py skip rows that they already processed, and the ROUND environment variable sets the round number.
Retrain or retrieve?
Fine-tuning is not always the best way to add new facts to a model. A 2024 study found that retrieval-augmented generation (RAG) did better than fine-tuning for new knowledge. For AI chatbots whose source documents change every week, retrieval usually works better than retraining.
uses Apify to supply web data to its AI chatbot. The
live RAG pipeline with n8nkeeps a RAG index updated with Website Content Crawler.
RAG Web Browser returns the top search results for each question as clean Markdown. For retrieval, check ai-input in Content Signals, or ai-use in the IETF draft, for the host of each result.
Next steps #
The scripts will change, but the rule stays the same: never let synthetic data replace your real data, and know the provenance of every row. To check your own data, put the pages of one important source in START_URLS in collect.py. Then run collect.py and baseline.py, and count the rows with an archive copy from before ChatGPT.
For more sources, Apify Store has thousands of Actors. For example, Google Maps Reviews Scraper returns reviews with their publish dates, so you can keep reviews from before ChatGPT.
The guide to collecting data for machine learning covers how to choose sources. You can also see how Apify can collect web data for generative AI.
FAQ #
Is model collapse already happening?
We found no published study that shows model collapse in a deployed frontier model, and labs mix synthetic and web data on purpose. Collapse has been shown in experiments where synthetic data replaces real data in repeated rounds. In these experiments, rare data and output diversity are lost first.
What is an example of model collapse?
When we trained GPT-2 only on its own output for four rounds, it got 52% worse at predicting Wikipedia text that it never trained on. Rare words were lost fastest, while its own text got easier to predict. The 2024 Nature paper found the same pattern with Meta's OPT-125m.
Do AI models degrade over time?
Yes, in two ways. Deployed models get worse as the world changes, which is model drift. In one study, models had up to 16% higher perplexity (prediction error) on text up to two years newer than their training data. Models retrained on AI-generated data can also lose quality, which is model collapse.
Why does mode collapse happen?
Mode collapse happens when the generator of a generative adversarial network (GAN) finds a few outputs that fool the discriminator. It then rarely produces other outputs. For LLMs, the term also describes low-diversity output. Model collapse is different: it develops over several rounds of training on synthetic data.