The whole design, in writing
Learn AI system design by building a synthetic data generation pipeline that uses LLMs to create training and eval data step by step. An interactive guide covering why naive generation is low-diversity and error-prone, generating with a strong teacher model, steering diversity to avoid mode collapse, quality filtering, verifying correctness, using the data for distillation/augmentation/evals, and the unhappy paths (model collapse, bias, contamination, licensing).
Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.
The big idea
What is a synthetic data pipeline?
Good models need lots of good data — but human labeling is slow, expensive, and often unavailable for a new task or a rare case. LLMs can generate training and eval data far faster and cheaper. The catch: a naive "generate 10,000 examples" gives you a repetitive, error-riddled pile that makes models worse. The value isn't generating data — it's generating diverse, correct, useful data.
A synthetic data pipeline manufactures quality datasets with a strong teacher model, then earns their usefulness through diversity steering, quality filtering, and verification. The output feeds distillation (train a smaller model), augmentation (fill data gaps), and eval sets. Generate is easy; diversity + validity are the system.
What the new pieces do
- Task Specclient
- What data you need — the task, an output schema, a few seed examples, and constraints. Steers the whole pipeline.
Step 1 · Why "just generate" fails
Repetitive and wrong
The obvious approach: prompt an LLM "generate 10,000 training examples for task X" and use the output. Why is that dataset bad?
Prompt an LLM to "generate 10,000 examples" and use them directly. What's wrong?
Fluency isn't the issue. Unsteered generation is low-diversity (the model repeats a few patterns — mode collapse) and error-prone (hallucinated facts, wrong labels, malformed outputs), so training on it teaches narrowness and mistakes.
Generation is fast and cheap; that's the appeal. The problem is quality: without diversity control and filtering you get a repetitive, error-laden dataset that degrades models.
Unsteered, the model clusters around a few templates (low diversity) and confidently produces some wrong or malformed examples (errors). A model trained on that learns a narrow slice with baked-in mistakes. The pipeline must add diversity and filter/verify quality.
Unsteered generation fails two ways. Low diversity: the model clusters around a few patterns (mode collapse), so the dataset covers a narrow slice of the real distribution. Errors: it confidently produces some hallucinated, wrong-label, or malformed examples. Train on that and the model learns narrowness and mistakes. The generation is the easy 10%; making the data diverse and correct is the system.
Why this piece earns its place
Both failures share a property that makes them dangerous in practice: neither one is visible in a sample. Read a handful of generations and every one looks fine — fluent, correctly formatted, plausible — because the diversity problem only exists at the level of the whole corpus, and the error rate is a fraction rather than a majority. Spot-checking, which is what everyone actually does, will happily certify a set that is neither varied nor correct. So the measurements have to be corpus-shaped, and they have to exist before you generate at scale: pairwise similarity across the set, counts of repeated openings and templates, a histogram over whatever axes you claim to cover. The other reason to build them early is the feedback loop. A bad dataset never announces itself as a bad dataset; it announces itself as a training run that came back slightly worse, days later, with no way to attribute the loss to anything in particular. That is the most expensive debugging loop in this design, and every check you can run before training is one you are not paying for in GPU hours and ambiguity.
Step 2 · Spec in, dataset out
The pipeline shape
Frame it as a pipeline. What does it take in, what stages does it run, and what does it guarantee about the output?
The pipeline takes a task spec — the task, an output schema, a few seed examples, and constraints — and runs stages: generate (teacher model) → diversify (steer variety) → filter (reject bad) → verify (check correctness) → store. The guarantee isn't "lots of data" but "diverse, filtered, validated data fit for training/eval." The spec (especially the seeds and schema) steers everything; each stage removes a failure mode of raw generation.
Why this piece earns its place
Read pipeline as a batch job rather than a service, because that inverts most of the usual instincts. Nothing here has a latency budget or an uptime target. What matters is that a long, rate-limited run survives being interrupted, and that any one stage can be re-run without paying for the stages before it. The cheap build is a single script that generates, filters and verifies in memory and writes out the survivors. The expensive consequence arrives the first time someone asks what the dataset would look like at a stricter gate: with nothing materialized between the stages, answering costs a full regeneration — the one stage that costs real money — so in practice nobody asks twice. Write each stage's output to its own place and the same question becomes a re-filter you can run over lunch. Give generation a deterministic unit of work too, one attribute cell or one template at a time, so a resumed run knows what it already produced instead of sweeping the space again. And treat the schema in the spec as a contract: the rule checks, the dedup key and the training format all read it, so changing it mid-run leaves you with two halves that do not belong to the same dataset.
- spectask + schema + seeds
- stagesgenerate→diversify→filter→verify
- outputdiverse + validated
What the new pieces do
- Pipelinegateway
- Runs generate → diversify → filter → verify → store, producing a clean, diverse, validated dataset from the spec.
Step 3 · The teacher
Generation with a strong model
Candidates have to come from somewhere. What generates them, and how do you steer what gets generated?
Use a strong teacher model prompted with templates, the seed examples, and often personas or roles to shape style and content. You can generate inputs, outputs, or both (e.g. a question and its answer), and use techniques like self-instruct (bootstrap new tasks from seeds) or having the teacher produce step-by-step reasoning. The teacher is usually a bigger/better model than the one you'll train — you're transferring its capability into data.
Why this piece earns its place
Two consequences of the teacher choice that only surface later. The first is correlation. If the judge downstream is the same model, or the same family, then its blind spots are the generator's blind spots — it approves exactly the errors it was most likely to make, and the filter measures nothing while reporting a healthy acceptance rate. Score with a different family where you can, and prefer a check that is not a model at all. The second is where the teacher's weakness actually lands. On anything checkable, selection does the work, and a corpus assembled by keeping only what passes can be better than the teacher's average output — that is the whole reason over-generating pays. On the part of the task you cannot check there is no selection pressure at all: a judge that shares the teacher's understanding cannot lift the data above it, so the corpus inherits those weak spots at roughly the rate the teacher has them, confidently and in the right format. That flips the selection criterion. Choose the teacher on the slice you have no checker for, because that is the only slice where its quality passes straight through to the student.
- teacherstrong model
- seeds + templatessteer content
- personasstyle + variety
What the new pieces do
- Teacher Modelservice
- A strong "teacher" LLM that produces candidate examples from prompts/templates seeded for the task.
Step 4 · Cover the space
Steering diversity
Left alone, the teacher repeats itself — the number-one failure of synthetic data. How do you force it to cover the breadth of the real distribution instead of echoing a few templates?
The teacher keeps producing similar examples (mode collapse). How do you get real diversity?
Instead of one prompt repeated, you sweep a space of attributes — topics, difficulty, style, personas, edge cases — so each generation targets a different region; raise sampling temperature for variety; and deduplicate near-identical outputs (by embedding similarity). This forces coverage of the distribution rather than clustering around a few modes.
More of the same prompt gives more of the same output — it deepens mode collapse rather than fixing it. You must vary the generation systematically and dedup.
Lower temperature makes generations MORE similar, worsening collapse. You want controlled diversity: attribute sampling + higher temperature + dedup, not more determinism.
Steer diversity explicitly. Sweep a space of attributes — topics, difficulty, style, personas, edge cases — so each generation targets a different region of the distribution (attribute/topic sampling); raise sampling temperature for variety; and de-duplicate near-identical outputs (e.g. by embedding similarity). This turns "the model's favorite ten patterns" into broad coverage. Diversity is engineered, not hoped for — it's the difference between data that generalizes and data that collapses.
Why this piece earns its place
The attribute grid is authored content, and that has a consequence people skip past: you can only cover the axes you thought to name. Whatever you left off is absent from the dataset and absent from your report of what the dataset covers, so the blind spot gets frozen in and looks like coverage. Where any real data exists — production logs, support tickets, a handful of genuine requests — derive the axes from it rather than brainstorming them at a desk. The subtler point is that a grid only buys diversity between cells. Inside one cell the teacher still has a favorite shape, and that is where collapse hides once the sweep is in place: same skeleton, same argument order, different nouns. Embedding dedup will not catch those, because they are not near-identical text — they are near-identical structure. Varying temperature, seeds and persona within a cell is the separate lever for that, and it needs its own decision. Last, weight the cells deliberately. A flat sweep asserts a flat world, and produces as many exotic edge cases as ordinary ones, which teaches a prior that is wrong in a quieter way than collapse is.
- attribute samplingsweep the space
- temperaturemore variety
- dedupdrop near-duplicates
What the new pieces do
- Diversityservice
- Steers variety: samples topics/attributes/personas and de-duplicates so generations cover the space, not one template.
Step 5 · Reject the bad ones
Quality filtering
Even from a good teacher, some generations are malformed, off-task, low-quality, or mislabeled. Letting those into the dataset poisons training. How do you keep only the good ones?
Filter every candidate before it enters the dataset. Cheap rule/format checks first (schema valid? right length? on-task?), then a model-as-judge to score quality/correctness and reject low-scoring examples — and often generate extra so you can afford to throw many away. This is generate-and-select: over-produce, then keep only the high-quality subset. Filtering is what makes synthetic data trustworthy — without it, the teacher's confident mistakes become the student's learned mistakes.
Why this piece earns its place
Filtering is a second, unannounced diversity filter, and that is the failure to watch for. Rejection is not uniform across the space you just worked to cover: the hard attribute cells — the rare topics, the difficult examples, the awkward personas — are exactly where the teacher is weakest, so they fail the checks at a higher rate and quietly drain out. An overall acceptance rate tells you nothing about this. Track survival per attribute cell, and when a cell empties, the fix is usually to generate more into it rather than to loosen the gate. There is a related bias in the judge itself: it tends to prefer long, fluent, confident answers, so hard filtering shifts the style of the corpus toward the judge's taste and the student inherits it. And do not throw the rejected candidates away. A pile of rejects is the cheapest diagnostic here: it shows which templates are broken rather than merely unlucky, it is the negative half of any set you use to check the judge against a human, and it is gone the moment the run ends, because getting it back means paying for the whole generation a second time.
- rulesschema/format/on-task
- judgescore + reject
- over-generateafford to discard
What the new pieces do
- Quality Filterservice
- Rejects bad candidates — malformed, off-task, low-quality, wrong-label — via rules and a model-as-judge.
- Datasetdata
- The curated output — diverse, filtered, verified examples ready for training, augmentation or evals.
Step 6 · Prove it where you can
Verification & grounding
Filtering catches obvious junk, but for factual or code data you can often do better than a judge's opinion — you can check correctness. How, and why does it matter so much?
You generated 1,000 synthetic coding examples with solutions. A judge model rates each one 1-10 for correctness. Is that enough?
A judge is another model guessing whether code is right by reading it, which is fallible even for a strong model — subtle bugs read as plausible. For code specifically, you have a much stronger signal available: does it actually run and pass tests?
Execution is objective evidence, not an opinion: code either passes the tests or it doesn't. Reserve judge-scoring for the genuinely uncheckable cases (subjective quality, style) and verify anything with a ground truth directly.
Human review of 1,000 examples defeats the economics that make synthetic data worth generating in the first place. Execution-based verification gets you objective correctness without that cost.
Verify correctness whenever the task allows a ground truth. Generated code? Execute it against tests and keep only what passes. Factual claims? Check against a source (retrieval/grounding) or require citations. Math? Verify the answer. This is far stronger than a judge guessing, and it's how you get reliable synthetic data for domains where wrong labels are poison. Verification turns "the teacher says it's right" into "we proved it's right."
Why this piece earns its place
Verification is the one stage that needs infrastructure the rest of the pipeline does not. Executing model-generated code means a sandbox, a wall-clock timeout, a memory cap and no network, because you are running untrusted output at volume and some fraction of it will loop forever or try to reach the internet. Budget for that before promising execution-based filtering. The subtler problem is where the tests come from. If the teacher writes the solution and its tests in the same breath, a wrong solution with agreeable tests sails through — a check is only as independent as the thing doing the checking, and self-marked homework is weak evidence. Derive the tests from the spec separately, or from a different model, and treat passing your own tests as a lower grade of proof than passing tests you did not write. It also changes the economics wherever it applies: a test run costs a fraction of a judge call, so on checkable tasks you can afford to over-produce far harder than elsewhere. The finished dataset then holds two tiers of evidence — rows a machine proved and rows a model merely liked — and those should never be reported as one quality number.
- executerun code vs tests
- groundcheck facts vs source
- provennot just plausible
What the new pieces do
- Verifyservice
- Checks correctness where it can be checked: run generated code, validate against a source, confirm labels.
Step 7 · What it's for
Distillation, augmentation & evals
You have a clean, diverse, verified dataset. What do you actually do with synthetic data?
Three main uses. Distillation: train a smaller, cheaper student model on the strong teacher's outputs — capturing much of its capability at a fraction of the cost. Augmentation: fill gaps in real data (rare classes, edge cases, new tasks) or expand scarce datasets. Eval sets: generate test cases to measure a model (with care about contamination). Done well, this is a flywheel: a strong model generates data that trains/improves models, cheaper than human labeling.
Why this piece earns its place
Augmentation is the use that goes wrong quietly. Rows generated to fill a gap sit beside real rows in the same training set, so they have to be indistinguishable from their neighbors — same length distribution, same formatting habits, same messiness — because if they are not, the student learns the tell instead of the task. Contamination is the second trap, and it is not a duplicated row. Generate training data and eval data from the same seeds and the same templates and you get near-duplicates across the split with no literal overlap, so a text-match check finds nothing and the score comes back better than it should. Split by lineage — seed, template, source document — before either set is generated, rather than by shuffling finished rows. And one inheritance worth naming out loud: a distilled student copies the teacher's manner along with its ability, the hedging, the refusals, the formatting habits and the length it likes to write at. Nobody decides that, it arrives with the data, and it is a product decision somebody should have made on purpose.
- distillteacher → smaller student
- augmentfill data gaps
- evalgenerate test sets
Step 8 · The sharp edges
Collapse, bias, contamination & rights
Synthetic data has serious traps: model collapse (training on synthetic degrades over generations), bias amplification, test-set contamination, and licensing of teacher outputs.
Avoid model collapse — recursively training models on their own synthetic output degrades quality/diversity over generations — by mixing in real data, enforcing diversity, and verifying quality (don't train blindly on unfiltered self-generated data). Watch bias: the teacher's biases get amplified in the data, so measure and rebalance. Prevent contamination: never let synthetic eval data leak into training, and don't generate evals from the same distribution you'll test on naively. Respect licensing/terms of the teacher model's outputs. And always evaluate the downstream model on real held-out data — synthetic data is a means, real-world performance is the judge.
Why this piece earns its place
The word real in "mix in real data" is carrying more weight than it looks. A corpus scraped or bought today is not cleanly human any more — a growing share of any open pool was itself written by a model — so an anchor chosen for convenience can be partly the thing it is supposed to anchor against, and the protection weakens without anyone touching the ratio. Define what counts as real for this dataset, prefer sources you collected yourself and can date, and treat a bought corpus as unknown until you can say where it came from. The ratio needs an owner rather than a principle: synthetic rows are cheap and real ones are not, so the mix drifts toward synthetic release by release unless it is a number somebody is accountable for and reports. And the licensing question belongs at teacher selection, on day one, because it is the only decision here that is genuinely expensive to reverse. A threshold is a config change that takes effect on the next run; discovering that the teacher's terms forbid your use taints every row already generated, and the remedy is regenerating the corpus and retraining whatever learned from it.
You did it
You just designed a synthetic data pipeline.
Everything you assembled, in order
- Naive "generate N examples" gives low-diversity, error-prone data that degrades models.
- The value is diverse + correct data; frame it as generate → diversify → filter → verify → store.
- A strong teacher model generates candidates, steered by seeds, templates and personas.
- Engineer diversity: attribute/topic/persona sampling + temperature + dedup to avoid mode collapse.
- Over-generate and aggressively filter (rules + model-as-judge) so only good examples enter.
- Verify correctness where checkable — execute code against tests, ground facts — beats judge-opinion.
- Use it for distillation, augmentation and evals; guard against collapse, bias, contamination and licensing.