Building Fine-Tuning Datasets
A fine-tune is its dataset. Hyperparameters decide whether training converges; the dataset decides what
the model becomes. Most disappointing fine-tunes are correctly-configured runs over data that encoded
the wrong thing.
Two failures cause most of the damage, and both are settled before a single example is generated:
teaching facts that belong in retrieval, and having no way to detect that the model got worse at
everything else.
Gate 1: Is this a knowledge problem?
If the goal contains "so it knows our X" — product names, runbook facts, current inventory, policy
details — that part is a retrieval problem, and fine-tuning is the wrong tool for it.
Models acquire facts in pretraining; fine-tuning teaches them to use what they have. Examples carrying
genuinely new facts are learned much more slowly than ones consistent with existing knowledge, and as
they finally are learned they linearly increase the model's tendency to hallucinate (Gekhman et al.,
EMNLP 2024, measured on closed-book QA). The rate is measured over the whole evaluation, not just the
newly taught items — so the cost lands on factuality generally, and it grows the longer you train to
make the new facts stick.
Split the request explicitly before proceeding:
| Part of the goal |
Where it goes |
| Facts, documents, entities, anything that changes |
RAG |
| Format, structure, house style, tone |
Fine-tuning |
| Task procedure, tool-call syntax, reasoning style |
Fine-tuning |
| Domain vocabulary and question shapes over retrieved context |
Fine-tuning, with facts still retrieved at inference |
Say which parts you routed where. A user asking for one thing usually wants both halves solved, not the
retrieval half silently folded into the training set.
Gate 2: Which technique
Stage and parameter budget are independent choices. Read references/choosing-technique.md before
committing — it covers prompt/RAG/decoding alternatives, continued pretraining vs SFT vs preference
optimization vs RL, DPO/KTO/ORPO/SimPO selection, full FT vs LoRA vs QLoRA, and required stage ordering.
Short version: if you can write the correct output, SFT. If you can only say which of two outputs is
better, preference optimization. If a script can verify correctness and SFT has plateaued, RL.
Default parameter budget is LoRA, or QLoRA when VRAM-bound.
Gate 3: Does the base model already do it?
Before generating data for any capability or behavior, probe the base model on it — a couple dozen
samples across the cases you care about — and only keep the slices it measurably fails. This is Gate 1's
knowledge rule generalized: fine-tuning is for what the model gets wrong, and training a slice it
already handles is not neutral.
The two costs are real and documented:
- Wasted effort. A team building a behavior dataset probed their base model and found it already
passed one entire behavior at ~94% with no reproducible failure pattern — they dropped 150 rows that
would have taught nothing.
- Active regression. The same narrow behavioral fine-tune, ~750 rows, caused a measurable
general-capability regression on four reasoning benchmarks (grade-school math down ~10–16pp) that
nobody saw until the benchmark panel ran, because the run had no eval split. Every row you add can
cost capability elsewhere; rows that teach nothing pay that cost for no gain.
So the probe is not optional diligence — it decides what goes in the dataset. Keep the confirmed-failing
cases, resample the ambiguous ones (a 1-of-3 refusal is sampling noise, not a failure — single-shot
refusal evaluation is only ~92% accurate, Larsen et al. 2025), and drop what the base model already
does. Report confirmed categories as a rate ("2/3", "3/3"), not a binary: a 2/3 confirmation is a
watch-item, not a solved one, since under pure noise it still confirms ~26% of the time, and it is
exactly the category to re-probe after the next training run.
The deliverable
A fine-tuning dataset is not a file of examples. It ships as eight parts, and a handoff missing any of
them cannot be evaluated or reproduced. Deliver them in this order — the order is the method:
eval/ — the held-out suite, built first. Real examples only, reserved before generation. Three
subsets: task (held-out real examples of the target behavior), retention (general-capability
probes the base model already passes), behavior (refusals, safety, tone invariants to preserve).
baseline.json — the base model scored on all three subsets before any training. Without this
there is no denominator and "it looks good" is not a result.
taxonomy.md — the axes the data must cover, with a target count per cell. Diversity comes from
varying what you condition on, so the axes have to exist on paper before generation and be re-counted
after filtering. Empty cells are the next generation round's target, not an acceptable outcome.
train.jsonl / val.jsonl / test.jsonl in messages format, split by source document so
synthetic siblings never straddle the boundary, deduplicated at all three levels (exact,
near-duplicate, semantic), and decontaminated against every eval set you intend to report.
- The replay mix — task data blended with general instruction data at a stated ratio, so the model
does not lose what it already had.
- A verified format contract — which loss-masking flag matches your dataset format
(
assistant_only_loss for messages, completion_only_loss for prompt/completion), which EOS token
the chat template actually emits, and confirmation of both by decoding one training batch. A wrong
flag trains on the user's turns and a wrong EOS produces a model that never stops; neither shows up
as a bad loss curve, which is why this is a checked artifact rather than a habit.
card.md — provenance. Seed source, generator model and version, generation prompts, filters and
thresholds, dedup and decontamination method, counts per taxonomy cell, license and terms.
config.yaml — the training config, with the eval curve enabled so overfitting is visible while
it happens.
Building the eval set first is what stops the dataset from being optimized toward whatever the generator
happened to produce.
Write every prose artifact skeleton-first, one section per edit. A single tool call cannot emit more
than roughly a thousand tokens, and the plan, taxonomy, and card all run longer, so a one-shot write
truncates mid-string and the call fails. Write the file with its headings and a one-line stub under
each, then replace one stub per edit. Start this way rather than falling back to it — the skeleton
costs nothing and the sections land in the same number of edits either way.
Generate with a script, not by hand
You write the pipeline; the pipeline writes the data. Emitting training examples yourself, one at a
time into a JSONL file, is the wrong shape for this work no matter how few examples you need:
- It does not scale. Three hundred examples at ~800 tokens each is far past what any agent can emit,
and you will produce a truncated file while believing you produced a dataset.
- It is not reproducible. A dataset you cannot regenerate is one you cannot fix. A script plus a
seed, a pinned generator model, and a versioned prompt can be re-run when you find a defect.
- The generation loop is genuinely a loop. Generate into taxonomy cells → verify → filter → dedup →
re-count coverage → generate into the cells that came up short. That cycle runs several times and
cannot be done by hand.
- Filtering needs code anyway. MinHash near-duplicate detection, embedding nearest-neighbor
thresholds, and n-gram decontamination are not eyeball operations.
So the artifacts you hand-author are the inputs: the taxonomy, the generation prompts, the seed
examples, the filter thresholds. Everything downstream is produced by running something.
scripts/generate.py and scripts/curate.py in this skill are a working reference pipeline —
taxonomy-driven generation with resume, then dedup, decontamination, and a coverage report. Read them,
adapt the prompts and taxonomy to the task, and run them; they are a starting point, not a framework.
Prefer extending them over writing a pipeline from scratch.
Order of work
digraph finetune_data {
rankdir=TB;
"Split knowledge from behavior" [shape=box];
"Behavior part non-empty?" [shape=diamond];
"Route to RAG; stop" [shape=doublecircle];
"Choose stage + parameter budget" [shape=box];
"Write eval suite from real data" [shape=box];
"Score base model -> baseline.json" [shape=box];
"Define taxonomy of axes to cover" [shape=box];
"Generate wide" [shape=box];
"Verify, filter, dedup, decontaminate" [shape=box];
"Coverage gaps remain?" [shape=diamond];
"Blend replay data" [shape=box];
"Train with eval curve" [shape=box];
"Gates pass vs baseline?" [shape=diamond];
"Ship with card.md" [shape=doublecircle];
"Diagnose: data or config?" [shape=box];
"Split knowledge from behavior" -> "Behavior part non-empty?";
"Behavior part non-empty?" -> "Route to RAG; stop" [label="no"];
"Behavior part non-empty?" -> "Choose stage + parameter budget" [label="yes"];
"Choose stage + parameter budget" -> "Write eval suite from real data";
"Write eval suite from real data" -> "Score base model -> baseline.json";
"Score base model -> baseline.json" -> "Define taxonomy of axes to cover";
"Define taxonomy of axes to cover" -> "Generate wide";
"Generate wide" -> "Verify, filter, dedup, decontaminate";
"Verify, filter, dedup, decontaminate" -> "Coverage gaps remain?";
"Coverage gaps remain?" -> "Generate wide" [label="yes, target the empty cells"];
"Coverage gaps remain?" -> "Blend replay data" [label="no"];
"Blend replay data" -> "Train with eval curve";
"Train with eval curve" -> "Gates pass vs baseline?";
"Gates pass vs baseline?" -> "Ship with card.md" [label="yes"];
"Gates pass vs baseline?" -> "Diagnose: data or config?" [label="no"];
"Diagnose: data or config?" -> "Generate wide";
}
The loop back from coverage gaps is the step most pipelines skip. Generation is cheap and filtering is
destructive, so generate wide and filter down — then look at which taxonomy cells came out empty and
generate specifically into those, rather than running the same unconditioned loop again and getting
the same modes back.
Quality over quantity, with numbers
Within a fixed budget, curated hundreds beat unfiltered tens of thousands for general instruction
and style — the consistent result across LIMA (1,000 curated competitive against 50k), AlpaGasus, and
LIMO (817 reasoning traces). That is what those papers measured, and it does not transfer to
overriding a base prior (a safety refusal, a strong default): there the behavior needs both a minimum
count and a minimum share of the mix, and a small set of rephrased variants of a handful of scenarios
is the least-favorable regime for "less is more." Judge which regime you are in before reaching for the
quality-over-quantity conclusion. Typical ranges:
| Goal |
Examples |
| Format / structure conversion |
100–1,000 |
| Style, tone, voice |
500–2,000 |
| Classification / extraction |
50–500 per class |
| General instruction following |
1,000–10,000 |
| Reasoning distillation |
800–10,000 verified traces |
When a run underperforms, doubling the data is usually the wrong reflex — and specifically wrong when
the extra rows are rephrasings of scenarios you already have: at a fixed update budget, repeating a small
set causes the same world-knowledge forgetting as scaling, and a narrow, repetitive dataset is the
mode-collapse setup. The data move that does help retention is adding new general replay, not more
of the target behavior. Check in this order: LR (retention-side, first), then target modules, then
diversity, then mix.
Degradation gates
Run these against baseline.json before calling a fine-tune successful. Each maps to a documented
failure mode, and passing the task metric while failing these is the most common way a bad model ships.
| Gate |
Check |
Action if it fails |
| Task |
Target metric improved on held-out real examples |
The fine-tune did nothing — check LR and target modules |
| Retention |
Above run-to-run noise (≥1 SE at your n) on capabilities the base already passed |
A single-digit drop on a few benches is a common LoRA-SFT outcome, not "catastrophic" (the literature's catastrophe is a bench near 0, e.g. SLIM's MMLU→0.00). Fix in this order: lower LR (retention-side, see lora-configuration.md) and add a replay mix — the evidence-backed levers — then a val split + small lora_dropout as cheap overfitting control, then fewer epochs |
| Factuality |
Hallucination rate not above base |
You taught unknown facts — move them to RAG |
| Safety |
Refusal behavior preserved |
Safety alignment degrades even from purely benign data (Qi et al., ICLR 2024) — add safety examples to the mix |
| Format |
Outputs terminate and parse |
EOS or chat-template bug, not a data problem |
| Diversity |
Outputs not collapsed to one phrasing |
Overfitting — fewer epochs, lower LR, or scale alpha by 0.5 |
Measuring only the task metric is how a model that got 10 points better at one thing and 15 worse at
everything else gets shipped.
References and scripts
Read the reference you need; do not load all five.
| File |
Covers |
references/choosing-technique.md |
RAG/prompting alternatives, CPT vs SFT vs DPO vs RL, DPO/KTO/ORPO/SimPO, full FT vs LoRA vs QLoRA, stage ordering |
references/synthetic-generation.md |
Quality/diversity/complexity tradeoff, Self-Instruct, Evol-Instruct, Magpie, personas, doc-grounded QA, self-chat, trajectories, distillation, verification and judge bias, model collapse, licensing |
references/data-quality.md |
Eval set construction, sizing, dedup levels, decontamination, balance, mix-share to override a base prior, messages format, loss masking, EOS, chat templates, split leakage, dataset cards |
references/lora-configuration.md |
rank, alpha, target modules, LR, schedule, packing, dropout/val as regression levers, rsLoRA/DoRA/LoRA+/PiSSA, worked configs, symptom-to-knob table, merging |
references/avoiding-degradation.md |
Forgetting mechanisms, replay ratios and sources, unknown-knowledge and unknown-capability detection, safety regression, overfitting and collapse symptoms, gate thresholds |
The scripts/ are a runnable reference pipeline — adapt, don't rewrite:
| Script |
Does |
scripts/generate.py |
Taxonomy-driven generation against any OpenAI-compatible endpoint, resume-aware, records provenance per row |
scripts/curate.py |
Malformed-drop → exact/near-dup dedup → decontaminate against eval files → per-cell coverage report (stdlib only) |
Common mistakes
| Mistake |
Why it bites |
| Fine-tuning to inject facts |
Slow to learn, and linearly increases hallucination as it does |
alpha = 0.5r |
Backwards. Use alpha = 2r (or r); keep alpha/r ≥ 1 |
| Targeting attention only |
MLP layers carry the higher-rank updates — target all linear layers |
| Splitting after augmentation |
Synthetic siblings straddle train/test and the score is fiction |
| No base-model measurement |
No denominator; regressions are invisible |
| No replay data in the mix |
The model gets the task and loses everything else |
| Raising temperature for diversity |
Diversity comes from varying conditioning — personas, taxonomy cells — not sampling noise |
| Trusting an unvalidated LLM judge |
Position, verbosity, and self/familiarity-preference bias measure the wrong thing. For code quality, deterministic checks (syntax, execution, schema) are the validated layer — the judge is an advisory ranking, not a pass/fail gate |
| Deduplicating exact matches only |
Synthetic pipelines overproduce semantic near-duplicates that exact hashing misses |
| Packing a small dataset |
No throughput to gain on hundreds of rows, and cross-contamination risk on multi-turn data — leave it off |
| Training a slice the base model already passes |
Wasted rows at best; every added row can cost capability elsewhere |
| No validation split |
No eval-loss curve means regressions stay invisible until the model ships |
| Ignoring the teacher's terms of service |
Major API providers restrict training on their outputs; this blocks release, not development |
1---2name: building-finetuning-datasets3description: Use when preparing to fine-tune, post-train, distill, or LoRA/QLoRA-tune an LLM, when generating synthetic or instruction training data, building SFT/DPO/preference sets, reasoning traces or tool-call trajectories, when choosing rank/alpha/learning rate, when deciding fine-tuning vs RAG, or when a tuned model regressed — hallucinates, lost general ability, repeats, or ignores its format.4---56# Building Fine-Tuning Datasets78A fine-tune is its dataset. Hyperparameters decide whether training converges; the dataset decides what9the model becomes. Most disappointing fine-tunes are correctly-configured runs over data that encoded10the wrong thing.1112Two failures cause most of the damage, and both are settled before a single example is generated:13teaching facts that belong in retrieval, and having no way to detect that the model got worse at14everything else.1516## Gate 1: Is this a knowledge problem?1718If the goal contains "so it knows our X" — product names, runbook facts, current inventory, policy19details — that part is a retrieval problem, and fine-tuning is the wrong tool for it.2021Models acquire facts in pretraining; fine-tuning teaches them to *use* what they have. Examples carrying22genuinely new facts are learned much more slowly than ones consistent with existing knowledge, and as23they finally are learned they **linearly increase the model's tendency to hallucinate** (Gekhman et al.,24EMNLP 2024, measured on closed-book QA). The rate is measured over the whole evaluation, not just the25newly taught items — so the cost lands on factuality generally, and it grows the longer you train to26make the new facts stick.2728Split the request explicitly before proceeding:2930| Part of the goal | Where it goes |31|---|---|32| Facts, documents, entities, anything that changes | RAG |33| Format, structure, house style, tone | Fine-tuning |34| Task procedure, tool-call syntax, reasoning style | Fine-tuning |35| Domain vocabulary and question shapes over retrieved context | Fine-tuning, with facts still retrieved at inference |3637Say which parts you routed where. A user asking for one thing usually wants both halves solved, not the38retrieval half silently folded into the training set.3940## Gate 2: Which technique4142Stage and parameter budget are independent choices. Read `references/choosing-technique.md` before43committing — it covers prompt/RAG/decoding alternatives, continued pretraining vs SFT vs preference44optimization vs RL, DPO/KTO/ORPO/SimPO selection, full FT vs LoRA vs QLoRA, and required stage ordering.4546Short version: if you can write the correct output, **SFT**. If you can only say which of two outputs is47better, **preference optimization**. If a script can verify correctness and SFT has plateaued, **RL**.48Default parameter budget is **LoRA**, or QLoRA when VRAM-bound.4950## Gate 3: Does the base model already do it?5152Before generating data for any capability or behavior, **probe the base model on it** — a couple dozen53samples across the cases you care about — and only keep the slices it measurably fails. This is Gate 1's54knowledge rule generalized: fine-tuning is for what the model gets *wrong*, and training a slice it55already handles is not neutral.5657The two costs are real and documented:5859- **Wasted effort.** A team building a behavior dataset probed their base model and found it already60 passed one entire behavior at ~94% with no reproducible failure pattern — they dropped 150 rows that61 would have taught nothing.62- **Active regression.** The same narrow behavioral fine-tune, ~750 rows, caused a measurable63 general-capability regression on four reasoning benchmarks (grade-school math down ~10–16pp) that64 nobody saw until the benchmark panel ran, because the run had no eval split. Every row you add can65 cost capability elsewhere; rows that teach nothing pay that cost for no gain.6667So the probe is not optional diligence — it decides what goes in the dataset. Keep the confirmed-failing68cases, resample the ambiguous ones (a 1-of-3 refusal is sampling noise, not a failure — single-shot69refusal evaluation is only ~92% accurate, Larsen et al. 2025), and drop what the base model already70does. Report confirmed categories as a rate ("2/3", "3/3"), not a binary: a 2/3 confirmation is a71watch-item, not a solved one, since under pure noise it still confirms ~26% of the time, and it is72exactly the category to re-probe after the next training run.7374## The deliverable7576A fine-tuning dataset is not a file of examples. It ships as eight parts, and a handoff missing any of77them cannot be evaluated or reproduced. Deliver them in this order — the order is the method:78791. **`eval/` — the held-out suite, built first.** Real examples only, reserved before generation. Three80 subsets: *task* (held-out real examples of the target behavior), *retention* (general-capability81 probes the base model already passes), *behavior* (refusals, safety, tone invariants to preserve).822. **`baseline.json` — the base model scored on all three subsets** before any training. Without this83 there is no denominator and "it looks good" is not a result.843. **`taxonomy.md` — the axes the data must cover**, with a target count per cell. Diversity comes from85 varying what you condition on, so the axes have to exist on paper before generation and be re-counted86 after filtering. Empty cells are the next generation round's target, not an acceptable outcome.874. **`train.jsonl` / `val.jsonl` / `test.jsonl`** in messages format, split by *source document* so88 synthetic siblings never straddle the boundary, deduplicated at all three levels (exact,89 near-duplicate, semantic), and decontaminated against every eval set you intend to report.905. **The replay mix** — task data blended with general instruction data at a stated ratio, so the model91 does not lose what it already had.926. **A verified format contract** — which loss-masking flag matches your dataset format93 (`assistant_only_loss` for messages, `completion_only_loss` for prompt/completion), which EOS token94 the chat template actually emits, and confirmation of both by decoding one training batch. A wrong95 flag trains on the user's turns and a wrong EOS produces a model that never stops; neither shows up96 as a bad loss curve, which is why this is a checked artifact rather than a habit.977. **`card.md` — provenance.** Seed source, generator model and version, generation prompts, filters and98 thresholds, dedup and decontamination method, counts per taxonomy cell, license and terms.998. **`config.yaml` — the training config**, with the eval curve enabled so overfitting is visible while100 it happens.101102Building the eval set first is what stops the dataset from being optimized toward whatever the generator103happened to produce.104105**Write every prose artifact skeleton-first, one section per edit.** A single tool call cannot emit more106than roughly a thousand tokens, and the plan, taxonomy, and card all run longer, so a one-shot write107truncates mid-string and the call fails. Write the file with its headings and a one-line stub under108each, then replace one stub per edit. Start this way rather than falling back to it — the skeleton109costs nothing and the sections land in the same number of edits either way.110111## Generate with a script, not by hand112113**You write the pipeline; the pipeline writes the data.** Emitting training examples yourself, one at a114time into a JSONL file, is the wrong shape for this work no matter how few examples you need:115116- **It does not scale.** Three hundred examples at ~800 tokens each is far past what any agent can emit,117 and you will produce a truncated file while believing you produced a dataset.118- **It is not reproducible.** A dataset you cannot regenerate is one you cannot fix. A script plus a119 seed, a pinned generator model, and a versioned prompt can be re-run when you find a defect.120- **The generation loop is genuinely a loop.** Generate into taxonomy cells → verify → filter → dedup →121 re-count coverage → generate into the cells that came up short. That cycle runs several times and122 cannot be done by hand.123- **Filtering needs code anyway.** MinHash near-duplicate detection, embedding nearest-neighbor124 thresholds, and n-gram decontamination are not eyeball operations.125126So the artifacts you hand-author are the *inputs*: the taxonomy, the generation prompts, the seed127examples, the filter thresholds. Everything downstream is produced by running something.128129`scripts/generate.py` and `scripts/curate.py` in this skill are a working reference pipeline —130taxonomy-driven generation with resume, then dedup, decontamination, and a coverage report. Read them,131adapt the prompts and taxonomy to the task, and run them; they are a starting point, not a framework.132Prefer extending them over writing a pipeline from scratch.133134## Order of work135136```dot137digraph finetune_data {138 rankdir=TB;139 "Split knowledge from behavior" [shape=box];140 "Behavior part non-empty?" [shape=diamond];141 "Route to RAG; stop" [shape=doublecircle];142 "Choose stage + parameter budget" [shape=box];143 "Write eval suite from real data" [shape=box];144 "Score base model -> baseline.json" [shape=box];145 "Define taxonomy of axes to cover" [shape=box];146 "Generate wide" [shape=box];147 "Verify, filter, dedup, decontaminate" [shape=box];148 "Coverage gaps remain?" [shape=diamond];149 "Blend replay data" [shape=box];150 "Train with eval curve" [shape=box];151 "Gates pass vs baseline?" [shape=diamond];152 "Ship with card.md" [shape=doublecircle];153 "Diagnose: data or config?" [shape=box];154155 "Split knowledge from behavior" -> "Behavior part non-empty?";156 "Behavior part non-empty?" -> "Route to RAG; stop" [label="no"];157 "Behavior part non-empty?" -> "Choose stage + parameter budget" [label="yes"];158 "Choose stage + parameter budget" -> "Write eval suite from real data";159 "Write eval suite from real data" -> "Score base model -> baseline.json";160 "Score base model -> baseline.json" -> "Define taxonomy of axes to cover";161 "Define taxonomy of axes to cover" -> "Generate wide";162 "Generate wide" -> "Verify, filter, dedup, decontaminate";163 "Verify, filter, dedup, decontaminate" -> "Coverage gaps remain?";164 "Coverage gaps remain?" -> "Generate wide" [label="yes, target the empty cells"];165 "Coverage gaps remain?" -> "Blend replay data" [label="no"];166 "Blend replay data" -> "Train with eval curve";167 "Train with eval curve" -> "Gates pass vs baseline?";168 "Gates pass vs baseline?" -> "Ship with card.md" [label="yes"];169 "Gates pass vs baseline?" -> "Diagnose: data or config?" [label="no"];170 "Diagnose: data or config?" -> "Generate wide";171}172```173174The loop back from coverage gaps is the step most pipelines skip. Generation is cheap and filtering is175destructive, so generate wide and filter down — then look at which taxonomy cells came out empty and176generate *specifically* into those, rather than running the same unconditioned loop again and getting177the same modes back.178179## Quality over quantity, with numbers180181Within a *fixed budget*, curated hundreds beat unfiltered tens of thousands for **general instruction182and style** — the consistent result across LIMA (1,000 curated competitive against 50k), AlpaGasus, and183LIMO (817 reasoning traces). That is what those papers measured, and it does not transfer to184*overriding a base prior* (a safety refusal, a strong default): there the behavior needs both a minimum185*count* and a minimum *share of the mix*, and a small set of rephrased variants of a handful of scenarios186is the least-favorable regime for "less is more." Judge which regime you are in before reaching for the187quality-over-quantity conclusion. Typical ranges:188189| Goal | Examples |190|---|---|191| Format / structure conversion | 100–1,000 |192| Style, tone, voice | 500–2,000 |193| Classification / extraction | 50–500 per class |194| General instruction following | 1,000–10,000 |195| Reasoning distillation | 800–10,000 verified traces |196197When a run underperforms, **doubling the data is usually the wrong reflex** — and specifically wrong when198the extra rows are rephrasings of scenarios you already have: at a fixed update budget, repeating a small199set causes the same world-knowledge forgetting as scaling, and a narrow, repetitive dataset is the200mode-collapse setup. The data move that *does* help retention is adding **new** general replay, not more201of the target behavior. Check in this order: **LR** (retention-side, first), then target modules, then202diversity, then mix.203204## Degradation gates205206Run these against `baseline.json` before calling a fine-tune successful. Each maps to a documented207failure mode, and passing the task metric while failing these is the most common way a bad model ships.208209| Gate | Check | Action if it fails |210|---|---|---|211| Task | Target metric improved on held-out real examples | The fine-tune did nothing — check LR and target modules |212| Retention | Above run-to-run noise (≥1 SE at your n) on capabilities the base already passed | A single-digit drop on a few benches is a *common* LoRA-SFT outcome, not "catastrophic" (the literature's catastrophe is a bench near 0, e.g. SLIM's MMLU→0.00). Fix in this order: **lower LR** (retention-side, see `lora-configuration.md`) and **add a replay mix** — the evidence-backed levers — then a val split + small `lora_dropout` as cheap overfitting control, then fewer epochs |213| Factuality | Hallucination rate not above base | You taught unknown facts — move them to RAG |214| Safety | Refusal behavior preserved | Safety alignment degrades even from purely benign data (Qi et al., ICLR 2024) — add safety examples to the mix |215| Format | Outputs terminate and parse | EOS or chat-template bug, not a data problem |216| Diversity | Outputs not collapsed to one phrasing | Overfitting — fewer epochs, lower LR, or scale alpha by 0.5 |217218Measuring only the task metric is how a model that got 10 points better at one thing and 15 worse at219everything else gets shipped.220221## References and scripts222223Read the reference you need; do not load all five.224225| File | Covers |226|---|---|227| `references/choosing-technique.md` | RAG/prompting alternatives, CPT vs SFT vs DPO vs RL, DPO/KTO/ORPO/SimPO, full FT vs LoRA vs QLoRA, stage ordering |228| `references/synthetic-generation.md` | Quality/diversity/complexity tradeoff, Self-Instruct, Evol-Instruct, Magpie, personas, doc-grounded QA, self-chat, trajectories, distillation, verification and judge bias, model collapse, licensing |229| `references/data-quality.md` | Eval set construction, sizing, dedup levels, decontamination, balance, mix-share to override a base prior, messages format, loss masking, EOS, chat templates, split leakage, dataset cards |230| `references/lora-configuration.md` | rank, alpha, target modules, LR, schedule, packing, dropout/val as regression levers, rsLoRA/DoRA/LoRA+/PiSSA, worked configs, symptom-to-knob table, merging |231| `references/avoiding-degradation.md` | Forgetting mechanisms, replay ratios and sources, unknown-knowledge and unknown-capability detection, safety regression, overfitting and collapse symptoms, gate thresholds |232233The `scripts/` are a runnable reference pipeline — adapt, don't rewrite:234235| Script | Does |236|---|---|237| `scripts/generate.py` | Taxonomy-driven generation against any OpenAI-compatible endpoint, resume-aware, records provenance per row |238| `scripts/curate.py` | Malformed-drop → exact/near-dup dedup → decontaminate against eval files → per-cell coverage report (stdlib only) |239240## Common mistakes241242| Mistake | Why it bites |243|---|---|244| Fine-tuning to inject facts | Slow to learn, and linearly increases hallucination as it does |245| `alpha = 0.5r` | Backwards. Use `alpha = 2r` (or `r`); keep `alpha/r` ≥ 1 |246| Targeting attention only | MLP layers carry the higher-rank updates — target all linear layers |247| Splitting after augmentation | Synthetic siblings straddle train/test and the score is fiction |248| No base-model measurement | No denominator; regressions are invisible |249| No replay data in the mix | The model gets the task and loses everything else |250| Raising temperature for diversity | Diversity comes from varying conditioning — personas, taxonomy cells — not sampling noise |251| Trusting an unvalidated LLM judge | Position, verbosity, and self/familiarity-preference bias measure the wrong thing. For *code* quality, deterministic checks (syntax, execution, schema) are the validated layer — the judge is an advisory ranking, not a pass/fail gate |252| Deduplicating exact matches only | Synthetic pipelines overproduce semantic near-duplicates that exact hashing misses |253| Packing a small dataset | No throughput to gain on hundreds of rows, and cross-contamination risk on multi-turn data — leave it off |254| Training a slice the base model already passes | Wasted rows at best; every added row can cost capability elsewhere |255| No validation split | No eval-loss curve means regressions stay invisible until the model ships |256| Ignoring the teacher's terms of service | Major API providers restrict training on their outputs; this blocks release, not development |